Qt3d。在三角形上绘制透明的QSphereMesh

时间:2019-03-05 10:58:15

标签: c++ qt opengl qt3d

我有一个可以通过OpenGL绘制三角形的函数

我通过按下一个按钮绘制两个三角形(函数on_drawMapPushButton_clicked())。

然后我在这些三角形上方绘制一个球体。现在我看到,该球体正确地绘制在第一个三角形上,但是第二个三角形却绘制在该球体上,反之亦然。

如果我第二次按下按钮,则会在第一个和第二个三角形上正确绘制spehere。

当我第三次按下按钮时,第二个三角形再次在球体上绘制。

当我第四次按下按钮时,会在第一个和第二个三角形上正确绘制spehere,依此类推。

如果我在SphereMesh QPhongMaterial中而不是QPhongAlphaMaterial中使用,则始终在第一个和第二个三角形上正确绘制spehere。

我不明白我做错了什么,以使我的球体总是画在三角形上。

绘制透明球体的代码:

selectModel_ = new Qt3DExtras::QSphereMesh(selectEntity_);
selectModel_->setRadius(75);
selectModel_->setSlices(150);

selectMaterial_ = new Qt3DExtras::QPhongAlphaMaterial(selectEntity_);
selectMaterial_->setAmbient(QColor(28, 61, 136));
selectMaterial_->setDiffuse(QColor(11, 56, 159));
selectMaterial_->setSpecular(QColor(10, 67, 199));
selectMaterial_->setShininess(0.8f);

selectEntity_->addComponent(selectModel_);
selectEntity_->addComponent(selectMaterial_);

函数drawTriangles:

void drawTriangles(QPolygonF triangles, QColor color){
    int numOfVertices = triangles.size();

    // Create and fill vertex buffer
    QByteArray bufferBytes;
    bufferBytes.resize(3 * numOfVertices * static_cast<int>(sizeof(float)));
    float *positions = reinterpret_cast<float*>(bufferBytes.data());

    for(auto point : triangles){
        *positions++ = static_cast<float>(point.x());
        *positions++ = 0.0f; //We need to drow only on the surface
        *positions++ = static_cast<float>(point.y());
    }

    geometry_ = new Qt3DRender::QGeometry(mapEntity_);
    auto *buf = new Qt3DRender::QBuffer(geometry_);
    buf->setData(bufferBytes);

    positionAttribute_ = new Qt3DRender::QAttribute(mapEntity_);
    positionAttribute_->setName(Qt3DRender::QAttribute::defaultPositionAttributeName());
    positionAttribute_->setVertexBaseType(Qt3DRender::QAttribute::Float); //In our buffer we will have only floats
    positionAttribute_->setVertexSize(3); // Size of a vertex
    positionAttribute_->setAttributeType(Qt3DRender::QAttribute::VertexAttribute); // Attribute type
    positionAttribute_->setByteStride(3 * sizeof(float));
    positionAttribute_->setBuffer(buf); 
    geometry_->addAttribute(positionAttribute_); // Add attribute to ours  Qt3DRender::QGeometry

    // Create and fill an index buffer
    QByteArray indexBytes;
    indexBytes.resize(numOfVertices * static_cast<int>(sizeof(unsigned int))); // start to end
    unsigned int *indices = reinterpret_cast<unsigned int*>(indexBytes.data());

    for(unsigned int i = 0; i < static_cast<unsigned int>(numOfVertices); ++i) {
        *indices++ = i;
    }

    auto *indexBuffer = new Qt3DRender::QBuffer(geometry_);
    indexBuffer->setData(indexBytes);

    indexAttribute_ = new Qt3DRender::QAttribute(geometry_);
    indexAttribute_->setVertexBaseType(Qt3DRender::QAttribute::UnsignedInt); //In our buffer we will have only unsigned ints
    indexAttribute_->setAttributeType(Qt3DRender::QAttribute::IndexAttribute); // Attribute type
    indexAttribute_->setBuffer(indexBuffer);
    indexAttribute_->setCount(static_cast<unsigned int>(numOfVertices)); // Set count of our vertices
    geometry_->addAttribute(indexAttribute_); // Add the attribute to ours Qt3DRender::QGeometry

    shape_ = new Qt3DRender::QGeometryRenderer(mapEntity_);
    shape_->setPrimitiveType(Qt3DRender::QGeometryRenderer::Triangles);
    shape_->setGeometry(geometry_); 

    //Create material
    material_ = new Qt3DExtras::QPhongMaterial(mapEntity_);
    material_->setAmbient(color);

    trianglesEntity_ = new Qt3DCore::QEntity(mapEntity_);
    trianglesEntity_->addComponent(shape_); 
    trianglesEntity_->addComponent(material_);
}

按下按钮处理程序on_drawMapPushButton_clicked():

void on_drawMapPushButton_clicked()
{
    clearMap(); //Implementation is above
    QPolygonF triangle1;
    triangle1 << QPointF( 0 ,-1000) << QPointF(0 ,1000) << QPointF(1000, -1000);
    drawTriangles(triangle1, Qt::black);

    QPolygonF triangle2;
    triangle2 << QPointF(-1000,-1000) << QPointF(-100,1000) << QPointF(-100,-1000);
    drawTriangles(triangle2, Qt::red);
}

地图清除功能clearMap():

void clearMap()
{
    if(mapEntity_){
        delete mapEntity_;
        mapEntity_ = nullptr;
        mapEntity_ = new Qt3DCore::QEntity(view3dRootEntity_);
    }
}

3 个答案:

答案 0 :(得分:3)

好的,这是扩展答案。

这种情况有时发生而有时不发生的原因取决于您的实体的顺序。如果您尝试使用两个简单的球体,一个是透明球体,另一个不是透明球体,那么您会发现,稍后添加透明球体时,它会绘制在不透明对象上方,就像您想要的那样。

之所以发生这种情况,是因为不透明的对象将首先被绘制(它在场景图中首先出现),然后透明的对象将为您提供所需的结果。在首先绘制透明对象的另一种情况下,由于QPhongAlphaMaterial具有QNoDepthMask呈现状态(告诉它不要写入深度缓冲区),因此在上面绘制了不透明对象。因此,不透明对象始终通过深度测试,而该透明对象实际上已经吸引到该位置。您需要做更多的工作才能为任意场景图和摄像机位置正确绘制透明对象。

Qt3D渲染图

要了解您必须执行的操作,您应该了解Qt3D渲染图的布局方式。如果您已经知道这一点,则可以跳过此部分。

enter image description here

斜体字在以下文本中引用了图形图像中的项目。

如果使用Qt3DWindow,则不能访问渲染图的根节点。它由窗口维护。您可以通过函数activeFramegraph()renderSettings()来访问框架图的 QRenderSettings 根图,这两个函数都可以在窗口上调用。您还可以通过setRootEntity()的{​​{1}}功能设置场景图的根节点。窗口内部有一个QAspectEngine,在其中设置整个图的根节点,即上图图像中渲染图的根节点

如果要在3D窗口的现有框架图中插入框架图节点,则必须将其添加为活动框架图的父级,我将在下一部分中进行解释。如果您通过Qt3DWindow在窗口中设置了自己的自定义框架图,则只需将其附加到末尾即可。

使用setActiveFramegraph()

根据其他问题,您已经发现,可以在框架图中使用QSortPolicy来按与相机的距离对实体进行排序。您可以如下添加排序策略(假设QSortPolicy是您的viewQt3DWindow是您的场景图的 root 实体,尽管我并不明白为什么要这样):

scene

此代码的问题在于,这种排序策略是按照实体的中心到相机的距离对它们进行排序的。如果不透明对象之一比透明对象更靠近摄影机,则无论如何它都会稍后被绘制并遮住透明对象。请参见下面的图片以获取图形说明。

红色和黑色的球体比圆环离摄像机更远,这就是为什么它们先被绘制并且不遮盖圆环的原因。 no occlusion

红色球体的中心比圆环的中心更靠近相机。它比圆环晚渲染并封闭。 occlusion

使用两个框架分支

如果使用两个框架图分支,则可以解决上述问题。一种绘制所有不透明的实体,另一种绘制所有透明的实体。为此,您必须使用Qt3DRender::QFrameGraphNode *framegraph = view.activeFrameGraph(); Qt3DRender::QSortPolicy *sortPolicy = new Qt3DRender::QSortPolicy(scene); framegraph->setParent(sortPolicy); QVector<Qt3DRender::QSortPolicy::SortType> sortTypes = QVector<Qt3DRender::QSortPolicy::SortType>() << Qt3DRender::QSortPolicy::BackToFront; sortPolicy->setSortTypes(sortTypes); view.setActiveFrameGraph(framegraph); QLayer。您可以将图层附加到实体,然后将图层过滤器添加到框架图。这样,您可以排除实体进入框架图的某个分支。

假设您创建了两层,一层用于不透明对象,一层用于透明​​对象:

QLayerFilter

您必须将透明层作为一个组件附加到每个透明对象,并将不透明层附加到每个不透明对象(使用Qt3DRender::QLayer *transparentLayer = new Qt3DRender::QLayer; Qt3DRender::QLayer *opaqueLayer = new Qt3DRender::QLayer; )。

不幸的是,您需要一棵特殊的框架图树来包括两个相应的图层过滤器(同样,假设addComponent()是您的view):

Qt3DWindow

这是第一个清除缓冲区的分支。现在,添加以下代码:

Qt3DRender::QRenderSurfaceSelector *renderSurfaceSelector
        = new Qt3DRender::QRenderSurfaceSelector();
renderSurfaceSelector->setSurface(&view);
Qt3DRender::QClearBuffers *clearBuffers 
        = new Qt3DRender::QClearBuffers(renderSurfaceSelector);
clearBuffers->setBuffers(Qt3DRender::QClearBuffers::AllBuffers);
clearBuffers->setClearColor(Qt::white);

由于您将Qt3DRender::QViewport *viewport = new Qt3DRender::QViewport(renderSurfaceSelector); Qt3DRender::QCameraSelector *cameraSelector = new Qt3DRender::QCameraSelector(viewport); Qt3DRender::QCamera *camera = new Qt3DRender::QCamera(cameraSelector); // set your camera parameters here cameraSelector->setCamera(camera); 创建为QViewport的子代,因此相对于QRenderSurfaceSelector,它现在是框架图中的同级对象。您可以看到示例框架图here的图示。

现在,您必须创建两个包含层过滤器的叶节点。 Qt3D引擎到达叶子时始终执行整个分支。这意味着首先绘制不透明的对象,然后绘制透明的对象。

QClearBuffers

两层滤镜现在是框架图分支中的叶节点,并且Qt3D将首先绘制不透明的对象,然后再绘制,因为它使用相同的视口并且所有内容都将在上面绘制透明的对象。它将正确绘制它们(即,不在透明对象实际位于的不透明对象的前面,因为我们没有再次清除深度缓冲区->分割图仅在相机节点上发生。)

现在在您的// not entirely sure why transparent filter has to go first // I would have expected the reversed order of the filters but this works... Qt3DRender::QLayerFilter *transparentFilter = new Qt3DRender::QLayerFilter(camera); transparentFilter->addLayer(transparentLayer); Qt3DRender::QLayerFilter *opaqueFilter = new Qt3DRender::QLayerFilter(camera); opaqueFilter->addLayer(opaqueLayer); 上设置新的框幅:

Qt3DWindow

结果:

enter image description here

答案 1 :(得分:0)

我的错误是我没有正确创建和删除Triangles和Sphere实体的顺序。

以伪代码正确的顺序如下:

clearTriangles();
clearSphere();       
drawTriangles();
drawSphere();

答案 2 :(得分:0)

如果将Qt3d与QML一起使用,并且想要控制绘制顺序的元素,则可以通过QML文件中的图层顺序来控制它。

类似的东西:

{
  objectName: "firstLayer"
  id : firstLayer
}
Layer {
  objectName: "secondLayer"
  id: secondLayer
}

将它们添加到图层过滤器的顺序将控制首先绘制的顺序:

RenderSurfaceSelector {
   CameraSelector {
     id : cameraSelector
     camera: mainCamera
     FrustumCulling {
       ClearBuffers {
       buffers : ClearBuffers.AllBuffers
       clearColor: "#04151c"
       NoDraw {}
    }
    LayerFilter
    {
       objectName: "firstLayerFilter"
       id: firstLayerFilter
       layers: [firstLayer]
    }

    LayerFilter
    {
      id: secondLayerFilter
      objectName: "secondLayerFilter"
      layers: [secondLayer]
    }

然后,添加到第二层的所有内容都将绘制在第一层的顶部。我用它来确保文本始终显示在图形的前面,但是可以与透明胶片类似地使用。