我正在尋找在Qt3D中創建廣告牌的最佳方式。我想要一架飛機,它面向相機,無論它在哪裏,並且在相機向前或向後移動時都不會改變尺寸。我已經閱讀了如何使用GLSL頂點和幾何着色器來做到這一點,但我正在尋找Qt3D方式,除非客戶着色器是廣告牌最有效和最好的方式。使用Qt3D 2.0的廣告牌
我看過了,看起來我可以通過屬性在QTransform
上設置矩陣,但是我不清楚如何操作矩陣,或者有更好的方法嗎?我正在使用C++ API,但QML答案可以。我可以將它移植到C++。
我正在尋找在Qt3D中創建廣告牌的最佳方式。我想要一架飛機,它面向相機,無論它在哪裏,並且在相機向前或向後移動時都不會改變尺寸。我已經閱讀了如何使用GLSL頂點和幾何着色器來做到這一點,但我正在尋找Qt3D方式,除非客戶着色器是廣告牌最有效和最好的方式。使用Qt3D 2.0的廣告牌
我看過了,看起來我可以通過屬性在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);
}
它已經有一段時間,因爲我們第一次實現這一點,有可能是更容易的方法,現在做到這一點,但是這應該給你對你需要的作品有了一個想法。
謝謝你這個極好的答案!在研究過程中,我實際上碰到了你的GitHub回購。很高興有一個解釋,以配合它。 –
不客氣!我實際上是在想着這個倉庫,並且它可能更加便於用戶使用和記錄。很高興你發佈了這個問題,並提醒我再看看它。如果您還有其他問題,請告訴我。 – dragly
你已經做了什麼?編寫代碼時你面臨什麼問題? – folibis