2017-10-18 3 views
1

Ich bin auf der Suche nach dem besten Weg, um eine Plakatwand in Qt3D zu erstellen. Ich möchte ein Flugzeug, das der Kamera zugewandt ist, egal wo es ist, und nicht die Größe ändert, wenn die Kamera nach vorne oder hinten fährt. Ich habe gelesen, wie man das mit GLSL-Vertex- und Geometrieshadern macht, aber ich suche nach der Qt3D-Methode, es sei denn, Kunden-Shader sind die effizienteste und beste Art des Billboarding.Billboarding mit Qt3D 2.0

Ich habe gesucht, und es scheint, ich kann die Matrix auf eine QTransform über Eigenschaften festlegen, aber es ist mir nicht klar, wie ich die Matrix manipulieren würde, oder vielleicht gibt es einen besseren Weg? Ich benutze die C++ API, aber eine QML Antwort würde tun. Ich könnte es nach C++ portieren.

+0

Was haben Sie schon gemacht? Welches Problem hatten Sie beim Schreiben des Codes? – folibis

Antwort

4

Wenn Sie nur eine Werbetafel zeichnen möchten, können Sie eine Ebene hinzufügen und sie drehen, wenn sich die Kamera bewegt. Wenn Sie dies effizient mit Tausenden oder Millionen von Billboards durchführen möchten, sollten Sie benutzerdefinierte Shader verwenden. Wir haben dies getan, um Betrüger-Sphären in Qt3D zu zeichnen.

Wir haben jedoch keinen Geometrieshader verwendet, da wir auf Systeme ausgerichtet waren, die keine Geometrieshader unterstützten. Stattdessen haben wir nur den Vertex-Shader verwendet, indem wir vier Vertices im Ursprung platziert und diese auf dem Shader verschoben haben. Um viele Kopien zu erstellen, haben wir instanziertes Zeichnen verwendet. Wir haben jede Gruppe von vier Ecken entsprechend den Positionen der Kugeln bewegt. Schließlich haben wir jeden der vier Eckpunkte jeder Kugel so verschoben, dass sie eine Werbetafel ergeben, die immer der Kamera zugewandt ist.

Beginnen Sie mit der Unterklassenbildung von QGeometry und erstellen Sie einen Pufferfunktor, der vier Punkte im Ursprung erstellt (siehe spherespointgeometry.cpp). Geben Sie jedem Punkt eine ID, die wir später verwenden können. Wenn Sie Geometrieshader verwenden, wird die ID nicht benötigt und Sie können nur einen Stützpunkt erstellen.

class SpheresPointVertexDataFunctor : public Qt3DRender::QBufferDataGenerator 
{ 
public: 
    SpheresPointVertexDataFunctor() 
    { 
    } 

    QByteArray operator()() Q_DECL_OVERRIDE 
    { 
     const int verticesCount = 4; 
     // vec3 pos 
     const quint32 vertexSize = (3+1) * sizeof(float); 

     QByteArray verticesData; 
     verticesData.resize(vertexSize*verticesCount); 
     float *verticesPtr = reinterpret_cast<float*>(verticesData.data()); 

     // Vertex 1 
     *verticesPtr++ = 0.0; 
     *verticesPtr++ = 0.0; 
     *verticesPtr++ = 0.0; 
     // VertexID 1 
     *verticesPtr++ = 0.0; 

     // Vertex 2 
     *verticesPtr++ = 0.0; 
     *verticesPtr++ = 0.0; 
     *verticesPtr++ = 0.0; 
     // VertexID 2 
     *verticesPtr++ = 1.0; 

     // Vertex 3 
     *verticesPtr++ = 0.0; 
     *verticesPtr++ = 0.0; 
     *verticesPtr++ = 0.0; 
     // VertexID3 
     *verticesPtr++ = 2.0; 

     // Vertex 4 
     *verticesPtr++ = 0.0; 
     *verticesPtr++ = 0.0; 
     *verticesPtr++ = 0.0; 
     // VertexID 4 
     *verticesPtr++ = 3.0; 

     return verticesData; 
    } 

    bool operator ==(const QBufferDataGenerator &other) const Q_DECL_OVERRIDE 
    { 
     Q_UNUSED(other); 
     return true; 
    } 

    QT3D_FUNCTOR(SpheresPointVertexDataFunctor) 
}; 

Für die realen Positionen verwendeten wir einen separaten QBuffer. Wir haben auch Farbe und Maßstab gesetzt, aber ich habe die hier (siehe spheredata.cpp) weggelassen:

void SphereData::setPositions(QVector<QVector3D> positions, QVector3D color, float scale) 
{ 
    QByteArray ba; 
    ba.resize(positions.size() * sizeof(QVector3D)); 
    SphereVBOData *vboData = reinterpret_cast<QVector3D *>(ba.data()); 
    for(int i=0; i<positions.size(); i++) { 
     QVector3D &position = vboData[i]; 
     position = positions[i]; 
    } 
    m_buffer->setData(ba); 
    m_count = positions.count(); 
} 

Dann in QML, wir die Geometrie mit dem Puffer in einem QGeometryRenderer verbunden. Dies kann auch in C++ durchgeführt werden, wenn Sie es vorziehen (siehe Spheres.qml):

GeometryRenderer { 
    id: spheresMeshInstanced 
    primitiveType: GeometryRenderer.TriangleStrip 
    enabled: instanceCount != 0 
    instanceCount: sphereData.count 

    geometry: SpheresPointGeometry { 
     attributes: [ 
      Attribute { 
       name: "pos" 
       attributeType: Attribute.VertexAttribute 
       vertexBaseType: Attribute.Float 
       vertexSize: 3 
       byteOffset: 0 
       byteStride: (3 + 3 + 1) * 4 
       divisor: 1 
       buffer: sphereData ? sphereData.buffer : null 
      } 
     ] 
    } 
} 

Schließlich haben wir individuelle Shadern geschaffen, um die Plakatwände zu zeichnen. Beachten Sie, dass, weil wir impostor Kugeln gezeichnet haben, die Billboard-Größe erhöht wurde, um Raytracing im Fragment-Shader aus schwierigen Winkeln zu behandeln. Sie brauchen den Faktor 2.0*0.6 im Allgemeinen nicht.

Vertex-Shader:

#version 330 

in vec3 vertexPosition; 
in float vertexId; 
in vec3 pos; 
in vec3 col; 
in float scale; 

uniform vec3 eyePosition = vec3(0.0, 0.0, 0.0); 

uniform mat4 modelMatrix; 
uniform mat4 mvp; 

out vec3 modelSpherePosition; 
out vec3 modelPosition; 
out vec3 color; 
out vec2 planePosition; 
out float radius; 
vec3 makePerpendicular(vec3 v) { 
    if(v.x == 0.0 && v.y == 0.0) { 
     if(v.z == 0.0) { 
      return vec3(0.0, 0.0, 0.0); 
     } 
     return vec3(0.0, 1.0, 0.0); 
    } 
    return vec3(-v.y, v.x, 0.0); 
} 

void main() { 
    vec3 position = vertexPosition + pos; 
    color = col; 
    radius = scale; 
    modelSpherePosition = (modelMatrix * vec4(position, 1.0)).xyz; 

    vec3 view = normalize(position - eyePosition); 
    vec3 right = normalize(makePerpendicular(view)); 
    vec3 up = cross(right, view); 

    float texCoordX = 1.0 - 2.0*(float(vertexId==0.0) + float(vertexId==2.0)); 
    float texCoordY = 1.0 - 2.0*(float(vertexId==0.0) + float(vertexId==1.0)); 
    planePosition = vec2(texCoordX, texCoordY); 

    position += 2*0.6*(-up - right)*(scale*float(vertexId==0.0)); 
    position += 2*0.6*(-up + right)*(scale*float(vertexId==1.0)); 
    position += 2*0.6*(up - right)*(scale*float(vertexId==2.0)); 
    position += 2*0.6*(up + right)*(scale*float(vertexId==3.0)); 

    vec4 modelPositionTmp = modelMatrix * vec4(position, 1.0); 
    modelPosition = modelPositionTmp.xyz; 

    gl_Position = mvp*vec4(position, 1.0); 
} 

Fragment-Shader:

#version 330 

in vec3 modelPosition; 
in vec3 modelSpherePosition; 
in vec3 color; 
in vec2 planePosition; 
in float radius; 

out vec4 fragColor; 

uniform mat4 modelView; 
uniform mat4 inverseModelView; 
uniform mat4 inverseViewMatrix; 
uniform vec3 eyePosition; 
uniform vec3 viewVector; 

void main(void) { 
    vec3 rayDirection = eyePosition - modelPosition; 
    vec3 rayOrigin = modelPosition - modelSpherePosition; 

    vec3 E = rayOrigin; 
    vec3 D = rayDirection; 

    // Sphere equation 
    //  x^2 + y^2 + z^2 = r^2 
    // Ray equation is 
    //  P(t) = E + t*D 
    // We substitute ray into sphere equation to get 
    //  (Ex + Dx * t)^2 + (Ey + Dy * t)^2 + (Ez + Dz * t)^2 = r^2 
    float r2 = radius*radius; 
    float a = D.x*D.x + D.y*D.y + D.z*D.z; 
    float b = 2.0*E.x*D.x + 2.0*E.y*D.y + 2.0*E.z*D.z; 
    float c = E.x*E.x + E.y*E.y + E.z*E.z - r2; 

    // discriminant of sphere equation 
    float d = b*b - 4.0*a*c; 
    if(d < 0.0) { 
     discard; 
    } 

    float t = (-b + sqrt(d))/(2.0*a); 
    vec3 sphereIntersection = rayOrigin + t * rayDirection; 

    vec3 normal = normalize(sphereIntersection); 
    vec3 normalDotCamera = color*dot(normal, normalize(rayDirection)); 

    float pi = 3.1415926535897932384626433832795; 

    vec3 position = modelSpherePosition + sphereIntersection; 

    // flat red 
    fragColor = vec4(1.0, 0.0, 0.0, 1.0); 
} 

Es ist schon einige Zeit her, dass wir das erste umgesetzt, und es könnte einfacher Möglichkeiten, es jetzt zu tun, aber dies sollte geben Sie eine Idee von den Stücken, die Sie brauchen.

+0

Vielen Dank für diese großartige Antwort! Ich bin während der Recherche tatsächlich über Ihr GitHub-Repo gestolpert. Schön, eine Erklärung dazu zu haben. –

+0

Gern geschehen! Ich habe neulich über dieses Repository nachgedacht und es hätte ein bisschen benutzerfreundlicher und dokumentierter sein können. Freut mich, dass du die Frage gestellt und mich daran erinnert hast, es noch einmal zu sehen. Lass es mich wissen, wenn du weitere Fragen hast. – dragly