Qt3d. Нарисуйте прозрачный QSphereMesh над треугольниками

У меня есть функция, которая рисует треугольники через OpenGL

Я рисую два треугольника нажатием кнопки (функция on_drawMapPushButton_clicked()).

Затем я рисую сферу, расположенную над этими треугольниками. И теперь я вижу, что сфера правильно нарисована над первым треугольником, а второй треугольник - над сферой, а не наоборот.

Если я нажму кнопку во второй раз, то там, где первый и второй треугольники правильно нарисованы.

Когда я нажимаю кнопку в третий раз, второй треугольник снова рисуется над сферой.

Когда я нажимаю кнопку в четвертый раз, тогда сфер правильно рисуется над первым и вторым треугольниками и так далее.

Если я использую в SphereMesh QPhongMaterial вместо QPhongAlphaMaterial, то сфера всегда правильно рисуется над первым и вторым треугольниками. Как должно быть.

Я не могу понять, что я делаю неправильно, чтобы моя сфера всегда рисовалась поверх треугольников.

Код, рисующий прозрачную сферу:

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 ответа

Решение

Моя ошибка заключалась в том, что я сделал неправильный порядок создания и удаления сущностей Треугольники и Сфера.

В псевдокоде правильный порядок выглядит следующим образом:

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

Хорошо, вот вам подробный ответ.

Причина, почему это иногда случается, а иногда и не зависит от порядка ваших сущностей. Если вы экспериментируете с двумя простыми сферами, одна прозрачная, а другая нет, вы увидите, что когда прозрачная сфера будет добавлена ​​позже, она будет нарисована над непрозрачным объектом - так, как вы этого хотите.

Это происходит потому, что непрозрачный объект будет нарисован первым (он появляется первым в графе сцены), а затем прозрачный объект, который даст вам желаемый результат. В другом случае, когда прозрачный объект рисуется первым, непрозрачный объект рисуется выше, потому что QPhongAlphaMaterial имеет QNoDepthMask состояние рендера, которое говорит не записывать в буфер глубины. Таким образом, непрозрачный объект всегда проходит тест глубины, на котором прозрачный объект фактически уже нарисован. Вы должны проделать еще некоторую работу, чтобы правильно нарисовать прозрачные объекты для произвольных графиков сцены и положения камеры.

График рендеринга Qt3D

Чтобы понять, что вам нужно сделать, вы должны понять, как устроен график рендеринга Qt3D. Если вы уже знаете это, вы можете пропустить эту часть.

Слова, выделенные курсивом, указывают на графическое изображение в следующем тексте

Если вы используете Qt3DWindow, вы не можете получить доступ к корневому узлу графика рендеринга. Поддерживается окном. Вы можете получить доступ к QRenderSettings и корневому узлу вашего фреймграфа через функции activeFramegraph() а также renderSettings() который вы оба можете вызвать в окно. Вы также можете установить корневой узел графа сцены через setRootEntity() функция Qt3DWindow, Окно внутри имеет QAspectEngine где он устанавливает корневой узел всего графа, который является корневым узлом графа рендеринга на изображении графа выше.

Если вы хотите вставить узел фрейма графа в существующий фрейм графа трехмерного окна, вы должны добавить его в качестве родителя активного фрейма графа, который я объясню в следующем разделе. Если у вас есть свой собственный framegraph, который вы устанавливаете в окне через setActiveFramegraph() затем просто добавьте его до конца, этого должно быть достаточно.

С помощью QSortPolicy

Как вы уже выяснили по другим вопросам, вы можете использовать QSortPolicy в вашем framegraph, чтобы отсортировать объекты по расстоянию до камеры. Вы можете добавить политику сортировки следующим образом (при условии, что view твой Qt3DWindow а также scene является вашей корневой сущностью графа сцены, хотя я не понимаю, почему это так):

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. Тот, который рисует все непрозрачные объекты, и тот, который рисует все прозрачные. Чтобы достичь этого, вы должны использовать 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 теперь это ваш брат в QClearBuffers, Вы можете увидеть иллюстрацию примера framegraphs здесь.

Теперь вам нужно создать два конечных узла, которые содержат фильтры слоев. Движок 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 сначала нарисует непрозрачные объекты, а затем, поскольку он использует тот же видовой экран и все, нарисует прозрачные объекты выше. Он будет рисовать их правильно (т. Е. Не перед частями непрозрачных объектов, за которыми фактически скрывается прозрачный объект, потому что мы не очистили буферы глубины снова -> Разделение фрейма графа происходит только на узле камеры).

Теперь установите новый framegaph на вашем Qt3DWindow:

view.setActiveFrameGraph(renderSurfaceSelector);

Результат:

Если вы используете 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]
    }

Тогда все, что вы добавите во второй слой, будет нарисовано поверх первого слоя. Я использовал это, чтобы текст всегда отображался перед фигурами, но его можно использовать аналогично с прозрачными пленками.

Другие вопросы по тегам