使用Qt3D 2.0的广告牌

Billboarding using Qt3D 2.0

本文关键字:广告牌 Qt3D 使用      更新时间:2023-10-16

我正在寻找在Qt3D中创建广告牌的最佳方法。我想要一个无论在哪里都能面对相机的飞机,当相机向前或向后移动时,它的大小不会改变。我已经阅读了如何使用GLSL顶点和几何体着色器来实现这一点,但我正在寻找Qt3D方式,除非客户着色器是最有效和最好的计费方式。

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

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

但是,我们没有使用几何体着色器,因为我们的目标系统不支持几何体着色器。相反,我们只使用顶点着色器,在原点中放置四个顶点,并在着色器上移动这些顶点。为了创建许多副本,我们使用了实例化绘图。我们根据球体的位置移动每组四个顶点。最后,我们移动了每个球体的四个顶点中的每一个,使它们产生一个始终面向相机的广告牌。

首先对QGeometry进行子类化,并创建一个缓冲函数,该函数创建四个点,全部位于原点(请参见spherepointgeometry.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++中完成(请参阅球体(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);
}

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