Billboard usando o Qt3D 2.0

Eu estou procurando a melhor maneira de criar um outdoor no Qt3D. Eu gostaria de um avião que enfrenta a câmera onde quer que esteja e não mude de tamanho quando a câmera se movimenta para frente ou para trás. Eu li como fazer isso usando shaders de vértice e geometry GLSL, mas estou procurando a maneira Qt3D, a menos que os shaders de clientes sejam a maneira mais eficiente e melhor de fazer billboard.

Eu olhei, e parece que posso definir o Matrix em um QTransform via propriedades, mas não está claro para mim como eu manipularia a matriz, ou talvez haja uma maneira melhor? Eu estou usando a API C ++, mas uma resposta QML faria. Eu poderia portá-lo para C ++.

Se você quiser desenhar apenas um outdoor, poderá adicionar um plano e girá-lo sempre que a câmera se mover. No entanto, se você quiser fazer isso de maneira eficiente com milhares ou milhões de outdoors, recomendo usar shaders personalizados. Fizemos isso para desenhar esferas impostoras no Qt3D.

No entanto, não usamos um shader de geometry porque tínhamos como alvo sistemas que não suportavam shaders de geometry. Em vez disso, usamos apenas o shader de vértice colocando quatro vértices na origem e os movemos no shader. Para criar muitas cópias, usamos o desenho instanciado. Nós movemos cada conjunto de quatro vértices de acordo com as posições das esferas. Finalmente, movemos cada um dos quatro vértices de cada esfera, de modo que eles resultam em um outdoor que está sempre de frente para a câmera.

Comece por subclassificar QGeometry e crie um funtor de buffer que cria quatro pontos, todos na origem (veja spherespointgeometry.cpp ). Dê a cada ponto um ID que possamos usar depois. Se você usar shaders de geometry, o ID não será necessário e você poderá criar apenas um vértice.

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

Para as posições reais, usamos um QBuffer separado. Nós também definimos cor e escala, mas eu omiti esses aqui (veja spheredata.cpp ):

 void SphereData::setPositions(QVector positions, QVector3D color, float scale) { QByteArray ba; ba.resize(positions.size() * sizeof(QVector3D)); SphereVBOData *vboData = reinterpret_cast(ba.data()); for(int i=0; isetData(ba); m_count = positions.count(); } 

Então, no QML, conectamos a geometry com o buffer em um QGeometryRenderer. Isso também pode ser feito em C ++, se preferir (consulte 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 } ] } } 

Finalmente, criamos shaders personalizados para desenhar os outdoors. Observe que, como estávamos desenhando esferas impostoras, o tamanho do quadro de avisos aumentava para lidar com o traçado de raios no sombreamento de fragments a partir de ângulos inoportunos. Você provavelmente não precisa do fator 2.0*0.6 em geral.

Shader de vértice:

 #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(vx == 0.0 && vy == 0.0) { if(vz == 0.0) { return vec3(0.0, 0.0, 0.0); } return vec3(0.0, 1.0, 0.0); } return vec3(-vy, vx, 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 = Dx*Dx + Dy*Dy + Dz*Dz; float b = 2.0*Ex*Dx + 2.0*Ey*Dy + 2.0*Ez*Dz; float c = Ex*Ex + Ey*Ey + Ez*Ez - 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); } 

Já faz algum tempo desde que implementamos isso pela primeira vez, e pode haver maneiras mais fáceis de fazer isso agora, mas isso deve lhe dar uma ideia das peças que você precisa.