c++ - 使用 Qt3D 2.0 的广告牌

标签 c++ 3d qml qt5 qt3d

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

我看过了,看来我可以通过属性在 QTransform 上设置矩阵,但我不清楚我将如何操作矩阵,或者也许有更好的方法?我正在使用 C++ api,但 QML 答案就可以了。我可以将它移植到 C++。

最佳答案

如果只想绘制一个广告牌,可以添加一个平面并在相机移动时旋转它。但是,如果您想对数千或数百万个广告牌高效地执行此操作,我建议使用自定义着色器。我们这样做是为了在 Qt3D 中绘制冒充球体。

但是,我们没有使用几何着色器,因为我们的目标系统不支持几何着色器。相反,我们通过在原点放置四个顶点并将它们移动到着色器上来仅使用顶点着色器。为了创建多个拷贝,我们使用了实例化绘图。我们根据球体的位置移动了每组四个顶点。最后,我们移动了每个球体的四个顶点中的每一个,这样它们就形成了一个始终面向相机的广告牌。

首先对 QGeometry 进行子类化并创建一个缓冲区仿函数,该仿函数创建四个点,全部位于原点(参见 spherespointgeometry.cpp )。给每个点一个我们可以稍后使用的 ID。如果您使用几何着色器,则不需要 ID,您可以只创建一个顶点。

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):

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 ):

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 因子。

顶点着色器:

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

自从我们第一次实现它已经有一段时间了,现在可能有更简单的方法来实现它,但这应该让您了解您需要的部分。

关于c++ - 使用 Qt3D 2.0 的广告牌,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46820903/

相关文章:

qt - 在 ScrollView 中编辑 TextInput

C++ 信号不会在 QML 端被捕获

c++ - 当窗口管理器在 qt QML 中完成调整窗口大小时如何获得信号?

c++ - sizeof 如何知道数组的大小?

c++ - 为什么必须在何处以及为什么要放置"template"和"typename"关键字?

python - 如何在 OpenCV 中检测这是谁的脸?

c++ - 到无穷远并返回

css - 精确测量的解释 css 3d 立方体

3d - Three.js中ObjLoader和ObjLoader2的区别

python - PyMEL 中矩阵和四元数以及 EulerRotation 之间转换的最简单方法