使用Qt3D 2.0的广告牌 [英] Billboarding using Qt3D 2.0

查看:123
本文介绍了使用Qt3D 2.0的广告牌的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在寻找在Qt3D中创建广告牌的最佳方法。我希望飞机无论面向何处都面向相机,并且在向前或向后移动时不会改变尺寸。我已经阅读了如何使用GLSL顶点和几何着色器来执行此操作,但是我正在寻找Qt3D方式,除非客户着色器是最有效,最好的广告牌方式。

I am looking for the best way to create a billboard in Qt3D. I would like a plane which faces the camera wherever it is and does not change sized when the camera dollies forward or back. I have read how to do this using GLSL vertex and geometry shaders, but I am looking for the Qt3D way, unless customer shaders is the most efficient and best way of billboarding.

我已经看过了,看来我可以通过属性在 QTransform 上设置矩阵,但是我不清楚如何处理矩阵,或者可能在那里是更好的方法?我正在使用C ++ API,但是可以使用QML答案。我可以将其移植到C ++。

I have looked, and it appears I can set the Matrix on a QTransform via properties, but it isn't clear to me how I would manipulate the matrix, or perhaps there is a better way? I am using the C++ api, but a QML answer would do. I could port it to C++.

推荐答案

如果您只想绘制一个广告牌,则可以添加平面并在任何时候旋转它相机移动。但是,如果要对成千上万的广告牌高效地执行此操作,建议您使用自定义着色器。我们这样做是在Qt3D中绘制冒名顶替者球。

If you want to draw just one billboard, you can add a plane and rotate it whenever the camera moves. However, if you want to do this efficiently with thousands or millions of billboards, I recommend using custom shaders. We did this to draw impostor spheres in Qt3D.

但是,我们没有使用几何着色器,因为我们的目标系统是不支持几何着色器的系统。取而代之的是,我们仅通过在原点放置四个顶点并将其移动到着色器上来使用顶点着色器。要创建许多副本,我们使用了实例图。我们根据球体的位置移动了每组四个顶点。最后,我们移动了每个球体的四个顶点中的每个顶点,以使它们生成始终与相机相对的广告牌。

However, we didn't use a geometry shader because we were targeting systems that didn't support geometry shaders. Instead, we used only the vertex shader by placing four vertices in the origin and moved these on the shader. To create many copies, we used instanced drawing. We moved each set of four vertices according to the positions of the spheres. Finally, we moved each of the four vertices of each sphere such that they result in a billboard that is always facing the camera.

首先对QGeometry进行子类化,并创建了一个缓冲区函子,该函子在原点中创建了四个点(请参见 spherespointgeometry.cpp )。给每个点分配一个ID,以便以后使用。如果使用几何着色器,则不需要ID,并且仅创建一个顶点就可以逃脱。

Start out by subclassing QGeometry and created a buffer functor that creates four points, all in the origin (see spherespointgeometry.cpp). Give each point an ID that we can use later. If you use geometry shaders, the ID is not needed and you can get away with creating only one vertex.

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

对于真实头寸,我们使用了单独的QBuffer。我们还设置了颜色和比例,但是我在这里省略了(请参见 spheredata.cpp ):

For the real positions, we used a separate QBuffer. We also set color and scale, but I have omitted those here (see 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();
}

然后,在QML中,我们将几何与QGeometryRenderer中的缓冲区相连。如果愿意,也可以在C ++中完成(请参阅
Spheres.qml ):

Then, in QML, we connected the geometry with the buffer in a QGeometryRenderer. This can also be done in C++, if you prefer (see 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
            }
        ]
    }
}

最后,我们创建了自定义着色器以绘制广告牌。请注意,由于我们正在绘制冒名顶替者球体,因此增加了广告牌的大小,以处理来自尴尬角度的片段着色器中的光线跟踪。通常,您可能不需要 2.0 * 0.6 系数。

Finally, we created custom shaders to draw the billboards. Note that because we were drawing impostor spheres, the billboard size was increased to handle raytracing in the fragment shader from awkward angles. You likely do not need the 2.0*0.6 factor in general.

顶点着色器:

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

片段着色器:

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

自我们首次实现此功能以来已有一段时间,而且可能会更容易现在可以使用的方法,但这应该使您对所需的零件有所了解。

It has been some time since we first implemented this, and there might be easier ways to do it now, but this should give you an idea of the pieces you need.

这篇关于使用Qt3D 2.0的广告牌的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆