【问题标题】:Qt3d. Draw transparent QSphereMesh over trianglesQt3d。在三角形上绘制透明 QSphereMesh
【发布时间】:2019-07-26 19:51:36
【问题描述】:

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

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

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

如果我第二次按下按钮,那么球体会正确地绘制在第一个和第二个三角形上。

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

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

如果我在 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_);
    }
}

【问题讨论】:

    标签: c++ qt opengl qt3d


    【解决方案1】:

    好的,扩展答案来了。

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

    发生这种情况是因为不透明对象将首先被绘制(它首先出现在场景图中),而透明对象稍后会为您提供所需的结果。在另一个首先绘制透明对象的情况下,不透明对象被绘制在上面,因为QPhongAlphaMaterial 具有QNoDepthMask 渲染状态,告诉它不要写入深度缓冲区。因此,不透明对象总是通过深度测试,透明对象实际上已经被绘制到。您必须做更多工作才能为任意场景图和相机位置正确绘制透明对象。

    Qt3D 渲染图

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

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

    如果使用Qt3DWindow,则无法访问渲染图的根节点。它由窗口维护。您可以通过函数activeFramegraph()renderSettings() 访问QRenderSettings框架图的根节点,这两个函数都可以在窗口上调用。您也可以通过Qt3DWindowsetRootEntity()函数设置场景图的根节点。窗口内部有一个QAspectEngine,这里设置了整个图的根节点,也就是上图中的渲染图的根节点

    如果你想在 3D 窗口的现有框架图中插入一个框架图节点,你必须将它添加为活动框架图的父节点,我将在下一节中解释。如果您有自己的自定义框架图,通过setActiveFramegraph() 在窗口上设置,那么只需将其附加到末尾,这就足够了。

    使用QSortPolicy

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

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

    此代码的问题在于,此排序策略根据实体中心到相机的距离对实体进行排序。如果其中一个不透明对象比透明对象更靠近相机,则无论如何都会稍后绘制它并遮挡透明对象。有关图形说明,请参见下图。

    红色和黑色球体比圆环更远离相机,这就是为什么它们首先被绘制并且它们不会遮挡圆环。

    没有红色球体的中心比圆环中心更靠近相机。它比圆环晚渲染并遮挡它。

    使用两个 Framegraph 分支

    如果您使用两个框架图分支,则可以解决上述问题。一个绘制所有不透明实体,一个绘制所有透明实体。为此,您必须使用QLayerQLayerFilter。您可以将图层附加到实体,然后将图层过滤器添加到您的框架图中。通过这种方式,您可以排除实体进入框架图的某个分支。

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

    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 的子代,它现在在您的框架图中相对于QClearBuffers 是同级的。您可以查看示例框架图的插图here

    现在您必须创建两个包含层过滤器的叶节点。 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);
    

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

    现在在 Qt3DWindow 上设置新的帧间隙:

    view.setActiveFrameGraph(renderSurfaceSelector);
    

    结果:


    编辑 (26.03.21):正如Patrick B. 正确指出的那样,使用建议的具有两个图层的解决方案,您必须将两个图层作为组件添加到场景中的任何灯光。您可以通过将QLayerFilters 上的过滤器模式设置为QLayerFilter::FilterMode::DiscardAnyMatching 来解决此问题,然后颠倒过滤器的顺序。这样,transparentFilter 会丢弃任何附有transparentLayer 的实体,但不会丢弃灯,因为它们没有transparentLayeropaqueFilter 反之亦然。

    【讨论】:

    • 当我使用第一种方式(使用 QSort)时,它可以部分工作。当我从不同的角度看时,我并不总是能看到一个透明的身影。我想用两层(不透明和透明)尝试第二种方式,但我不明白如何使用它。我想,我需要使用 transparentLayeropaqueLayer 作为我的地图或 QMesh 的 QEntity 的父级,但在这种情况下,我会使用我在地图或 QMesh 中使用的材质为所有背景着色。你从哪里得到所有这些知识?来自书籍或网站?
    • 您不需要将transparentLayeropaqueLayer 设置为任何对象的父级,因为将其作为组件添加到QEntity 会将其设置为该实体的子级。将opaqueLayer 添加到您的三角形中,如下所示:trianglesEntity_-&gt;addComponent(opaqueLayer) 并为球体添加selectEntity_-&gt;addComponent(transparentLayer)。这应该可以解决问题。
    • 你当然是对的。我从糟糕/不完整的文档和示例中获得了所有这些知识。去年我在一个大学项目中使用了它,这就是为什么我花了两倍时间才完成它的原因。我最终切换到了原生 OpenGL,但如果可以的话,尽量避免任何人的麻烦。您可以查看我的GitHub 以获取更多示例。 This 也是一个很好的例子。也看看manual official Qt tests。它们提供了大量信息。
    • 感谢您的大力支持。我弄错了绘制和删除的顺序。我发布了答案。
    • @FlorianBlume:很好地解释了这里发生的事情。我刚刚对Text2DEntity 遇到了同样的问题,它也是透明的。
    【解决方案2】:

    我的错误是我创建和删除三角形和球体实体的顺序错误。

    在伪代码中的正确顺序如下:

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

    【讨论】:

      【解决方案3】:

      如果您将 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]
          }
      

      然后你添加到第二层的任何东西都会被绘制在第一层的顶部。我使用它来确保文本始终显示在形状前面,但它也可以类似地用于透明胶片。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-12-04
        • 1970-01-01
        • 2011-07-11
        相关资源
        最近更新 更多