Qt3D 几何着色器在 QML 中工作但在 C++ 中不工作

Qt3D geometry shader working in QML but not in C++

更新

从下面的代码看OpenGL版本好像至少是4.3

QSurfaceFormat format = view.format();
int major = format.majorVersion();
int minor = format.minorVersion();

所以几何着色器应该可以工作,但问题似乎出在其他地方。



原问题

在尝试回答有关如何在 Qt3D 中创建广告牌的 时,我遇到了一个问题,但我找不到解决方案。

我使用了 this GitHub repository 中的代码,其中包含 C++ 和 QML。它完美地工作并展示了如何在 Qt3D 中实现广告牌——至少在使用 QML 时是这样。这是代码的屏幕截图:


现在,问我提到的问题的人需要用 C++ 编写,所以我尝试翻译它,因为每个 QML class 都有相应的 C++ class。我只是在一定程度上成功了。原始代码使用几何着色器来创建广告牌。当我 不包括着色器的几何部分 时,我设法在屏幕上使用预定义的颜色 绘制各个点,例如这个(我把点圈起来了,这样你可以看得更清楚):

但是一旦我包含了几何着色器,所有的点都消失了。但是这个确切的着色器在 QML 下工作。

我把它归结为几何着色器是问题,因为当我注释掉它时我得到了白点但是当我添加它时这些点不再显示(显然广告牌也不是):

billboardShaderProgram->setVertexShaderCode(Qt3DRender::QShaderProgram::loadSource(QUrl("qrc:/shaders/billboards.vert")));
//billboardShaderProgram->setGeometryShaderCode(Qt3DRender::QShaderProgram::loadSource(QUrl("qrc:/shaders/billboards.geom")));
billboardShaderProgram->setFragmentShaderCode(Qt3DRender::QShaderProgram::loadSource(QUrl("qrc:/shaders/billboards.frag")));

在检查了 OpenGL 版本(似乎是 4.3)之后,问题一定出在我如何在 C++ 中创建对象上。



代码


您可以找到项目here on GitHub


或者,我将在此处添加相关的 class 并尽量减少它们。

main.cpp:

// Includes for framegraph
#include <Qt3DExtras/Qt3DWindow>
#include <Qt3DRender/QFrameGraphNode>
#include <Qt3DRender/QRenderSurfaceSelector>
#include <Qt3DRender/QViewport>
#include <Qt3DRender/QCameraSelector>
#include <Qt3DRender/QCamera>
#include <Qt3DRender/QCameraLens>
#include <Qt3DRender/QClearBuffers>
#include <Qt3DExtras/QForwardRenderer>

#include <Qt3DExtras/QFirstPersonCameraController>
#include <Qt3DInput/QInputSettings>

#include <Qt3DCore/QEntity>
#include <Qt3DExtras/QPlaneMesh>
#include <Qt3DExtras/QPhongMaterial>
#include <Qt3DRender/QGeometryRenderer>
#include <Qt3DCore/QTransform>
#include <Qt3DRender/QMaterial>
#include <Qt3DRender/QParameter>
#include <Qt3DRender/QTexture>
#include <Qt3DRender/QTextureImage>
#include <Qt3DRender/QEffect>
#include <Qt3DRender/QTechnique>
#include <Qt3DRender/QRenderPass>
#include <Qt3DRender/QShaderProgram>
#include <Qt3DExtras/QSphereMesh>
#include <Qt3DExtras/QCuboidMesh>
#include <Qt3DRender/QGraphicsApiFilter>
#include <Qt3DExtras/QTextureMaterial>

#include <QSurfaceFormat>

#include <QVector3D>
#include <QColor>

#include <QGuiApplication>

#include "billboardgeometry.h"

#include <QOpenGLContext>

Qt3DExtras::QFirstPersonCameraController * cameraController;

int windowWidth = 1600;
int windowHeight = 800;

Qt3DCore::QEntity *createScene() {
    Qt3DCore::QEntity *root = new Qt3DCore::QEntity();

    cameraController = new Qt3DExtras::QFirstPersonCameraController(root);

    // Add plane
    Qt3DCore::QEntity *planeEntity = new Qt3DCore::QEntity(root);
    Qt3DExtras::QPlaneMesh *planeMesh = new Qt3DExtras::QPlaneMesh(planeEntity);
    planeMesh->setWidth(20);
    planeMesh->setHeight(20);
    Qt3DExtras::QPhongMaterial *planeMaterial = new Qt3DExtras::QPhongMaterial(planeEntity);
    planeMaterial->setAmbient(QColor(0, 0, 0.7 * 255, 0.1 * 255));
    planeEntity->addComponent(planeMesh);
    planeEntity->addComponent(planeMaterial);

    // Add sphere
    Qt3DCore::QEntity *sphereEntity = new Qt3DCore::QEntity(root);
    Qt3DExtras::QSphereMesh *sphereMesh = new Qt3DExtras::QSphereMesh(sphereEntity);
    Qt3DExtras::QPhongMaterial *sphereMaterial = new Qt3DExtras::QPhongMaterial(sphereEntity);
    sphereMaterial->setAmbient(Qt::red);
    Qt3DCore::QTransform *sphereTransform = new Qt3DCore::QTransform(sphereEntity);
    sphereTransform->setTranslation(QVector3D(0., 5., 0.));
    sphereEntity->addComponent(sphereMesh);
    sphereEntity->addComponent(sphereMaterial);
    sphereEntity->addComponent(sphereTransform);

    // Add cube
    Qt3DCore::QEntity *cubeEntity = new Qt3DCore::QEntity(root);
    Qt3DExtras::QCuboidMesh *cubeMesh = new Qt3DExtras::QCuboidMesh(cubeEntity);
    Qt3DExtras::QPhongMaterial *cubeMaterial = new Qt3DExtras::QPhongMaterial(cubeEntity);
    cubeMaterial->setAmbient(Qt::gray);
    Qt3DCore::QTransform *cubeTransform = new Qt3DCore::QTransform();
    cubeTransform->setTranslation(QVector3D(2., 2., 5.));
    cubeEntity->addComponent(cubeMesh);
    cubeEntity->addComponent(cubeMaterial);
    cubeEntity->addComponent(cubeTransform);

    // Add Billboard
    Qt3DCore::QEntity *billboardEntity = new Qt3DCore::QEntity(root);

    // Create billboard geometry
    QVector<QVector3D> pos;
    pos << QVector3D(1, 1, 0);
    pos << QVector3D(-1, 2, 8);
    pos << QVector3D(1, 1, 7);
    pos << QVector3D(0, 0, 4);
    BillboardGeometry *billboardGeometry = new BillboardGeometry(billboardEntity);
    billboardGeometry->setPoints(pos);
    Qt3DRender::QGeometryRenderer *billboardRenderer = new Qt3DRender::QGeometryRenderer(billboardEntity);
    billboardRenderer->setPrimitiveType(Qt3DRender::QGeometryRenderer::Points);
    billboardRenderer->setGeometry(billboardGeometry);
    billboardRenderer->setVertexCount(billboardGeometry->count());

    Qt3DCore::QTransform *billboardTransform = new Qt3DCore::QTransform(billboardEntity);
    billboardTransform->setTranslation(QVector3D(0., 1.5, 0.));

    // Billboard material

    // Image of billboard material
    Qt3DRender::QMaterial *billboardMaterial = new Qt3DRender::QMaterial(billboardEntity);
    Qt3DRender::QTexture2D* texture = new Qt3DRender::QTexture2D();
    Qt3DRender::QTextureImage* textureImage = new Qt3DRender::QTextureImage(texture);
    textureImage->setSource(QUrl(QStringLiteral("qrc:/success-kid.png")));
    texture->addTextureImage(textureImage);

    // Parameters of billboard material
    Qt3DRender::QParameter* billboardParam1 = new Qt3DRender::QParameter(QStringLiteral("tex0"), texture);
    Qt3DRender::QParameter* billboardParam2 = new Qt3DRender::QParameter(QStringLiteral("WIN_SCALE"), QSize(1600, 800));
    Qt3DRender::QParameter* billboardParam3 = new Qt3DRender::QParameter(QStringLiteral("BB_SIZE"), QSize(100, 100));
    billboardMaterial->addParameter(billboardParam1);
    billboardMaterial->addParameter(billboardParam2);
    billboardMaterial->addParameter(billboardParam3);

    // Effect of material
    Qt3DRender::QEffect* billboardEffect = new Qt3DRender::QEffect();
    Qt3DRender::QTechnique* billboardTechnique = new Qt3DRender::QTechnique();
    billboardTechnique->graphicsApiFilter()->setApi(Qt3DRender::QGraphicsApiFilter::OpenGL);
    billboardTechnique->graphicsApiFilter()->setProfile(Qt3DRender::QGraphicsApiFilter::CoreProfile);
    billboardTechnique->graphicsApiFilter()->setMajorVersion(3);
    billboardTechnique->graphicsApiFilter()->setMinorVersion(1);
    // You need the filter key because the QForwardRenderer employed as the default framegraph by the Qt3DWindow
    // extends QTechniqueFilter and filters for this key exactly. Without it, the material gets discarded.
    Qt3DRender::QFilterKey* filterKey = new Qt3DRender::QFilterKey(billboardMaterial);
    filterKey->setName(QStringLiteral("renderingStyle"));
    filterKey->setValue(QStringLiteral("forward"));
    billboardTechnique->addFilterKey(filterKey);
    Qt3DRender::QRenderPass* billboardRenderPass = new Qt3DRender::QRenderPass();
    Qt3DRender::QShaderProgram* billboardShaderProgram = new Qt3DRender::QShaderProgram();
    billboardShaderProgram->setVertexShaderCode(Qt3DRender::QShaderProgram::loadSource(QUrl("qrc:/shaders/billboards.vert")));
    //billboardShaderProgram->setGeometryShaderCode(Qt3DRender::QShaderProgram::loadSource(QUrl("qrc:/shaders/billboards.geom")));
    billboardShaderProgram->setFragmentShaderCode(Qt3DRender::QShaderProgram::loadSource(QUrl("qrc:/shaders/billboards.frag")));
    billboardRenderPass->setShaderProgram(billboardShaderProgram);
    billboardTechnique->addRenderPass(billboardRenderPass);
    billboardEffect->addTechnique(billboardTechnique);
    billboardMaterial->setEffect(billboardEffect);

    billboardEntity->addComponent(billboardRenderer);
    billboardEntity->addComponent(billboardMaterial);
    billboardEntity->addComponent(billboardTransform);

    return root;
}

int main(int argc, char* argv[])
{
    QGuiApplication app(argc, argv);

    Qt3DExtras::Qt3DWindow view;
    view.resize(windowWidth, windowHeight);
    Qt3DExtras::QForwardRenderer *renderer = (Qt3DExtras::QForwardRenderer *)view.activeFrameGraph();
    renderer->setClearColor("black");

    Qt3DRender::QCamera *camera = view.camera();
    camera->setProjectionType(Qt3DRender::QCameraLens::PerspectiveProjection);
    camera->setFieldOfView(45);
    // Cast to float to ensure float division
    camera->setAspectRatio(windowWidth / (float) windowHeight);
    camera->setNearPlane(0.1f);
    camera->setFarPlane(100.f);
    camera->setPosition(QVector3D(0., 10., 20.));
    camera->setViewCenter(QVector3D(0., 0., 0.));
    camera->setUpVector(QVector3D(0., 1., 0.));

    Qt3DCore::QEntity *root = createScene();
    view.setRootEntity(root);
    cameraController->setCamera(camera);

    view.setTitle("Billboards");
    view.show();

    return app.exec();
}

billboardgeometry.h:

#ifndef BILLBOARDGEOMETRY_H
#define BILLBOARDGEOMETRY_H

#include <Qt3DRender/QGeometry>
#include <Qt3DRender/QBuffer>

#include <QVector3D>


class BillboardGeometry : public Qt3DRender::QGeometry
{
  Q_OBJECT

  Q_PROPERTY(int count READ count NOTIFY countChanged)

public:
  BillboardGeometry( Qt3DCore::QNode *parent = nullptr );

  void setPoints( const QVector<QVector3D> &vertices );

  int count();

signals:
    void countChanged(int count);

private:
  Qt3DRender::QAttribute *mPositionAttribute = nullptr;
  Qt3DRender::QBuffer *mVertexBuffer = nullptr;
  int mVertexCount = 0;
};

#endif // BILLBOARDGEOMETRY_H

billboardgeometry.cpp:

#include "billboardgeometry.h"

#include <Qt3DRender/QAttribute>


BillboardGeometry::BillboardGeometry( Qt3DCore::QNode *parent )
  : Qt3DRender::QGeometry( parent )
  , mPositionAttribute( new Qt3DRender::QAttribute( this ) )
  , mVertexBuffer( new Qt3DRender::QBuffer( Qt3DRender::QBuffer::VertexBuffer, this ) )
{

  mPositionAttribute->setAttributeType( Qt3DRender::QAttribute::VertexAttribute );
  mPositionAttribute->setBuffer( mVertexBuffer );
  mPositionAttribute->setVertexBaseType( Qt3DRender::QAttribute::Float );
  mPositionAttribute->setVertexSize( 3 );
  mPositionAttribute->setName( Qt3DRender::QAttribute::defaultPositionAttributeName() );

  addAttribute( mPositionAttribute );

}

int BillboardGeometry::count()
{
  return mVertexCount;
}

void BillboardGeometry::setPoints(const QVector<QVector3D> &vertices)
{
  QByteArray vertexBufferData;
  vertexBufferData.resize( vertices.size() * 3 * sizeof( float ) );
  float *rawVertexArray = reinterpret_cast<float *>( vertexBufferData.data() );
  int idx = 0;
  for ( const auto &v : vertices )
  {
    rawVertexArray[idx++] = v.x();
    rawVertexArray[idx++] = v.y();
    rawVertexArray[idx++] = v.z();
  }

  mVertexCount = vertices.count();
  mVertexBuffer->setData( vertexBufferData );

  emit countChanged(mVertexCount);
}

billboard.vert:

#version 150

uniform mat4 modelViewProjection;

in vec3 vertexPosition;

void main(void)
{
    gl_Position = modelViewProjection * vec4(vertexPosition, 1);
}

billboard.geom:

#version 150

layout (points) in;
layout (triangle_strip, max_vertices = 4) out;

uniform mat4 modelViewProjection;

uniform vec2 BB_SIZE;    // billboard size in pixels
uniform vec2 WIN_SCALE;  // the size of the viewport in pixels

out vec2 UV;

void main (void)
{

    vec4 P = gl_in[0].gl_Position;
    P /= P.w;
    
    //vec2 size = vec2(0.5,0.5);
    vec2 size = BB_SIZE / WIN_SCALE;
    
    gl_Position = P;
    gl_Position.xy += vec2(-0.5,-0.5) * size;
    UV = vec2(0,0);
    EmitVertex();
    
    gl_Position = P;
    gl_Position.xy += vec2(0.5,-0.5) * size;
    UV = vec2(1,0);
    EmitVertex();
    
    gl_Position = P;
    gl_Position.xy += vec2(-0.5,+0.5) * size;
    UV = vec2(0,1);
    EmitVertex();
    
    gl_Position = P;
    gl_Position.xy += vec2(+0.5,+0.5) * size;
    UV = vec2(1,1);
    EmitVertex();
    
    EndPrimitive();
}

billboard.frag:

#version 150

uniform sampler2D tex0;

in vec2 UV;

void main(void)
{
    //gl_FragColor = texture(tex0, UV);
    gl_FragColor = vec4(1, 1, 1, 1);
}

shaders.qrc:

<RCC>
    <qresource prefix="/shaders">
        <file>billboards.frag</file>
        <file>billboards.vert</file>
        <file>billboards.geom</file>
    </qresource>
</RCC>

billboards.pro:

TEMPLATE = app
QT += 3dcore 3drender 3dinput 3dquick qml quick 3dquickextras 3dextras

SOURCES += \
        main.cpp \
    billboardgeometry.cpp

RESOURCES += qml.qrc \
    shaders.qrc


# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target

HEADERS += \
    billboardgeometry.h

success-kid.jpg

GitHub 用户 wonder-sk and ismailsunni 通过指出代码中的错误为我解决了这个问题:

Qt3DRender::QParameter* billboardParam2 = new Qt3DRender::QParameter(QStringLiteral("WIN_SCALE"), QSize(1600, 800));
Qt3DRender::QParameter* billboardParam3 = new Qt3DRender::QParameter(QStringLiteral("BB_SIZE"), QSize(100, 100));

在这两行中,它需要是 QSizeF 而不是 QSize - 瞧,着色器正在工作!

或者,已经存在一个 C++ 端口 here