2017-10-18 22 views
1

Je cherche la meilleure façon de créer un panneau d'affichage dans Qt3D. Je voudrais un avion qui fait face à la caméra où que ce soit et ne change pas de taille lorsque la caméra se déplace en avant ou en arrière. J'ai lu comment faire cela en utilisant le vertex de GLSL et les shaders de géométrie, mais je suis à la recherche de la méthode Qt3D, à moins que les shaders de client soient le moyen le plus efficace et le meilleur de billboarding.Billboard utilisant Qt3D 2.0

J'ai regardé, et il semble que je peux définir la matrice sur un QTransform via les propriétés, mais je ne suis pas clair comment je voudrais manipuler la matrice, ou peut-être il y a une meilleure façon? J'utilise l'API C++, mais une réponse QML ferait l'affaire. Je pourrais le porter en C++.

+0

Qu'avez-vous déjà fait? Quel problème avez-vous rencontré lors de l'écriture du code? – folibis

Répondre

4

Si vous souhaitez dessiner un seul panneau d'affichage, vous pouvez ajouter un plan et le faire pivoter à chaque fois que la caméra se déplace. Cependant, si vous voulez le faire efficacement avec des milliers ou des millions de panneaux d'affichage, je recommande d'utiliser des shaders personnalisés. Nous l'avons fait pour dessiner des sphères imposantes dans Qt3D. Cependant, nous n'avons pas utilisé de shaders de géométrie car nous ciblions des systèmes qui ne supportaient pas les shaders de géométrie. Au lieu de cela, nous avons utilisé seulement le vertex shader en plaçant quatre sommets dans l'origine et les avons déplacés sur le shader. Pour créer de nombreuses copies, nous avons utilisé le dessin instancié. Nous avons déplacé chaque ensemble de quatre sommets en fonction des positions des sphères. Finalement, nous avons déplacé chacun des quatre sommets de chaque sphère de sorte qu'ils résultent en un panneau d'affichage qui fait toujours face à la caméra. Commencez par sous-classer QGeometry et créez un foncteur tampon qui crée quatre points, tous dans l'origine (voir spherespointgeometry.cpp). Donnez à chaque point un identifiant que nous pourrons utiliser plus tard. Si vous utilisez des shaders de géométrie, l'ID n'est pas nécessaire et vous pouvez vous en sortir en créant un seul sommet.

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) 
}; 

Pour les positions réelles, nous avons utilisé un QBuffer distinct. Nous avons également mis la couleur et l'échelle, mais j'ai omis ceux qui sont ici (voir spheredata.cpp):

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(); 
} 

Puis, en QML, nous avons connecté la géométrie avec le tampon dans un QGeometryRenderer. Cela peut aussi être fait en C++, si vous préférez (voir 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 
      } 
     ] 
    } 
} 

Enfin, nous avons créé des shaders personnalisés pour dessiner les panneaux d'affichage. Notez que parce que nous étions en train de dessiner des sphères d'imposteur, la taille du panneau d'affichage a été augmentée pour gérer le raytracing dans le fragment shader à partir d'angles inconfortables. Vous n'avez probablement pas besoin du facteur 2.0*0.6 en général.

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); 
} 

shader Fragment:

#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); 
} 

Il a été un certain temps depuis que nous avons mis en œuvre cela, et il pourrait y avoir des moyens plus faciles de le faire maintenant, mais cela devrait donner vous une idée des pièces dont vous avez besoin.

+0

Merci pour cette superbe réponse! En fait, j'ai couru sur votre repo GitHub pour cette recherche. C'est bien d'avoir une explication pour l'accompagner. –

+0

De rien! Je pensais à ce référentiel l'autre jour et que ça aurait pu être un peu plus convivial et documenté. Je suis content que tu aies posté la question et que tu me rappelles d'avoir un autre regard. Faîtes moi savoir si vous avez d'autres questions. – dragly