Text2DEntity 呈现不透明并隐藏其背后的其他实体

Text2DEntity renders opaque and hides other entities behind it

我在 Qt3D QML 场景中绘制了一些 2d 文本实体,但一些文本总是呈现 不透明,即隐藏它们后面的内容。从后面看场景时(将相机的位置更改为 Qt.vector3d(0,0,-40) )所有文本都渲染正常。

下图显示了错误的行为,我希望文本 "AAARGH" 不会呈现在白色背景上,而是绿色的文本闪耀。

平台是 Windows 64 位、Qt5.13.0 和 Visual Studio 2019。

请参阅以下演示该问题的小示例:

BrokenEntity.qml

import Qt3D.Core 2.0
import Qt3D.Render 2.0
import Qt3D.Input 2.0
import Qt3D.Extras 2.13

import QtQuick 2.0 as QQ2

Entity {
    id: sceneRoot

    Camera {
        id: camera
        projectionType: CameraLens.PerspectiveProjection
        fieldOfView: 45
        nearPlane : 0.1
        farPlane : 1000.0
        position: Qt.vector3d( 0.0, 0.0, 40 )
        upVector: Qt.vector3d( 0.0, 1.0, 0.0 )
        viewCenter: Qt.vector3d( 0.0, 0.0, 0.0 )
    }

    OrbitCameraController { camera: camera }

    components: [
        RenderSettings {
            activeFrameGraph: ForwardRenderer {
                camera: camera
                clearColor: "transparent"
            }
        },

        InputSettings { }
    ]

    Entity {
        components: [ Transform { translation: Qt.vector3d(-12.5,-5,-20) } ]
        Text2DEntity {
            font.family: "Sans Serif"
            font.pointSize: 5
            color: Qt.rgba(0, 0, 1, 0.5)
            text: "AAARGH"
            width: text.length * font.pointSize
            height: font.pointSize * 1.2
        }
    }
    Entity {
        PhongMaterial {
            id: material
            ambient: Qt.rgba(1, 1, 0, 1)
            diffuse: Qt.rgba(1, 1, 0, 1)
        }
        SphereMesh {
            id: sphereMesh
            radius: 1
            rings: 50
            slices: 50
        }
        Transform {
            id: sphereTransform
            translation: Qt.vector3d(0,0,-25)
            scale3D: Qt.vector3d(1, 1, 1)
        }
        components: [ sphereMesh, material, sphereTransform ]
    }
    Entity {
        components: [ Transform { translation: Qt.vector3d(-25,-5,-30) } ]
        Text2DEntity {
            font.family: "Sans Serif"
            font.pointSize: 10
            color: Qt.rgba(0, 1, 0, 1.0)
            text: "BBBRGH"
            width: text.length * font.pointSize
            height: font.pointSize * 1.2
        }
    }
}

main.qml

import QtQuick 2.0
import QtQuick.Scene3D 2.0

Item {
    Rectangle {
        id: scene
        anchors.fill: parent
        anchors.margins: 50
        color: "white"

        Scene3D {
            id: scene3d
            anchors.fill: parent
            anchors.margins: 10
            focus: true
            aspects: ["input", "logic"]
            cameraAspectRatioMode: Scene3D.AutomaticAspectRatio

            BrokenEntity {}
        }
    }
}

main.cpp

#include <QGuiApplication>
#include <QQuickView>

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

    QQuickView view;

    view.resize(500, 500);
    view.setResizeMode(QQuickView::SizeRootObjectToView);
    view.setSource(QUrl("qrc:/main.qml"));
    view.show();

    return app.exec();
}

我想这种行为的原因来自 Qt3D 中用于呈现 Text2dEntity 的 GLSL 着色器(distancefieldtext.vertdistancefieldtext.frag)。

查看附加的着色器源代码。

distancefieldtext.vert

#version 150 core

in vec3 vertexPosition;
in vec2 vertexTexCoord;

out vec2 texCoord;
out float zValue;

uniform mat4 modelView;
uniform mat4 mvp;

void main()
{
    texCoord = vertexTexCoord;
    zValue = vertexPosition.z;

    gl_Position = mvp * vec4(vertexPosition.xy, 0.0, 1.0);
}

distancefieldtext.frag

#version 150 core

uniform sampler2D distanceFieldTexture;
uniform float minAlpha;
uniform float maxAlpha;
uniform float textureSize;
uniform vec4 color;

in vec2 texCoord;
in float zValue;

out vec4 fragColor;

void main()
{
    // determine the scale of the glyph texture within pixel-space coordinates
    // (that is, how many pixels are drawn for each texel)
    vec2 texelDeltaX = abs(dFdx(texCoord));
    vec2 texelDeltaY = abs(dFdy(texCoord));
    float avgTexelDelta = textureSize * 0.5 * (texelDeltaX.x + texelDeltaX.y + texelDeltaY.x + texelDeltaY.y);
    float texScale = 1.0 / avgTexelDelta;

    // scaled to interval [0.0, 0.15]
    float devScaleMin = 0.00;
    float devScaleMax = 0.15;
    float scaled = (clamp(texScale, devScaleMin, devScaleMax) - devScaleMin) / (devScaleMax - devScaleMin);

    // thickness of glyphs should increase a lot for very small glyphs to make them readable
    float base = 0.5;
    float threshold = base * scaled;
    float range = 0.06 / texScale;

    float minAlpha = threshold - range;
    float maxAlpha = threshold + range;

    float distVal = texture(distanceFieldTexture, texCoord).r;
    fragColor = color * smoothstep(minAlpha, maxAlpha, distVal);
    gl_FragDepth = gl_FragCoord.z - zValue * 0.00001;
}

关于如何让 Qt3D 渲染 text2DEntities 的任何想法,文本本身不透明,文本之间的空间透明,独立于观察方向?提前致谢。

编辑

我一定是无意中更改了示例中的某些内容,因为更改相机的位置不再显示预期的行为。我将在周一访问我的工作环境时更正该错误。

更新

因为我的实体需要双面照明,所以我有一个额外的 CullFace 组件,其中 NoCulling 添加到 RenderStateSet,这解释了这种行为。我的 FrameGraph 看起来像这样:

components: [
    RenderSettings {
        activeFrameGraph: RenderStateSet {
            renderStates: [
                CullFace { mode: CullFace.NoCulling }
            ]
            ForwardRenderer {
                camera: camera
                clearColor: "transparent"
            }
        }
    },

    InputSettings { }
]

从背面查看时,由于实体是从后到前定义的,因此渲染是正确的。 SortPolicy 的文档中明确说明了这一点:

"If QSortPolicy is not present in the FrameGraph, entities are drawn in the order they appear in the entity hierarchy."

当向 FrameGraph 添加一个带有 BackToFront 的附加 SortPolicy 组件时,无论观察方向如何,渲染都是正确的。 FrameGraph 看起来像这样:

components: [
    RenderSettings {
        activeFrameGraph: SortPolicy {
            sortTypes: [ SortPolicy.BackToFront ]
            RenderStateSet {
                renderStates: [
                    CullFace { mode: CullFace.NoCulling }
                ]
                ForwardRenderer {
                    camera: camera
                    clearColor: "transparent"
                }
            }
        }
    },

    InputSettings { }
]

问题似乎是行

zValue = vertexPosition.z;

在顶点着色器中。 vertexPosition 是模型 space 中的坐标。如果你想计算到相机的 z 距离,那么你必须通过 modelView 矩阵将坐标转换为视图 space:

vec4 viewPosition = modelView * vec4(vertexPosition.xyz, 1.0);
zValue = -viewPosition.z;

注意,由于视图 space z 轴指向视口外,因此必须反转坐标才能获得到相机的距离。


另一种对我来说似乎更正确的可能性是计算剪辑 space 坐标并进一步标准化设备坐标。剪辑space坐标由模型视图矩阵(mvp)和归一化设备坐标变换计算得到,由Perspective divide:

vec4 clipPosition = modelView * vec4(vertexPosition.xyz, 1.0);
zValue = clipPosition.z / clipPosition.w;

规范化的设备坐标在 [-1, 1] 范围内。 z 分量可以线性映射到片段的深度。默认情况下,深度范围为 [0, 1](除了由 glDepthRange.
更改 所以在片段着色器中gl_FragDepth可以设置为:

gl_FragDepth = zValue * 0.5 + 0.5;

如果您使用 alpha Blending, then the Depth Test,则必须禁用。

其他对象后面的对象可能根本不会被绘制,因为它们被深度测试丢弃了。 当然这取决于绘图顺序。如果首先绘制被覆盖的文本,那么它将起作用。但如果它是最后绘制的,那么它就会被丢弃。这解释了取决于观察方向的不同行为。
请注意,当混合处于活动状态时,颜色不会影响颜色缓冲区(如果 alpha 为 0),但如果启用深度测试,那么深度当然会写入深度缓冲区。

唯一的选择是按照从后到前的排序顺序绘制对象。当然,顺序取决于观察方向,因此必须按帧对对象进行排序。

我做了一些测试,为了得到不需要的行为,我通过打乱所用实体的顺序对您的代码进行了一些更改。如您所知,实体的顺序很重要。 在我的示例中,text2DEntity "textFront" 在层次结构中放在 text2DEntity "textBack" 之前。所以在不改变你的渲染环境的情况下,我们得到这样的东西: 我添加了一个红色球体来测试更深一点。

我找到了一个使用前向渲染器而不使用深度缓冲区的解决方案。这是结果(当然,我没有改变实体的顺序):

我们必须使用 SortPolicy,以便前向渲染器知道哪个对象在另一个对象前面。这将改变实体相对于相机距离的顺序,而不是 qml 文件的层次结构顺序。

components: [
    RenderSettings {
        activeFrameGraph: SortPolicy {
            sortTypes: [
                SortPolicy.BackToFront
            ]

            ForwardRenderer {
                camera: camera
                clearColor: "black"
            }
        }
    },
    // Event Source will be set by the Qt3DQuickWindow
    InputSettings { }
]

为了便于测试,这里是完整的文件内容:

BrokenEntity.qml

import Qt3D.Core 2.12 
import Qt3D.Render 2.12
import Qt3D.Input 2.12
import Qt3D.Extras 2.13
import QtQuick 2.12 as QQ2

Entity {
    id: sceneRoot

    Camera {
        id: camera
        projectionType: CameraLens.PerspectiveProjection
        fieldOfView: 45
        nearPlane : 0.1
        farPlane : 1000.0
        position: Qt.vector3d( 0.0, 0.0, 40 )
        upVector: Qt.vector3d( 0.0, 1.0, 0.0 )
        viewCenter: Qt.vector3d( 0.0, 0.0, 0.0 )
    }
    OrbitCameraController { camera: camera }

        components: [
            RenderSettings {
                activeFrameGraph: SortPolicy {
                    sortTypes: [
                        SortPolicy.BackToFront
                    ]

                    ForwardRenderer {
                        camera: camera
                        clearColor: "black"
                    }
                }
            },
            // Event Source will be set by the Qt3DQuickWindow
            InputSettings { }
        ]

    Entity {
        id: textFront
        components: [ Transform { translation: Qt.vector3d(-12.5,-5,-20) } ] //IKKE

        Text2DEntity {
            font.family: "Sans Serif"
            font.pointSize: 3
            color: "white"
            text: "textFront"
            width: text.length * font.pointSize*2
            height: font.pointSize * 4
        }
    }

    PhongMaterial {
        id: material
        ambient: Qt.rgba(1, 1, 0, 1)
        diffuse: Qt.rgba(1, 1, 0, 1)
    }
    PhongMaterial {
        id: material2
        ambient: Qt.rgba(1, 0, 0, 1)
        diffuse: Qt.rgba(1, 0, 0, 1)
    }
    SphereMesh {
        id: sphereMesh
        radius: 5
        rings: 50
        slices: 50
    }

    Entity {
        id: mysphere
        Transform {
            id: sphereTransform
            translation: Qt.vector3d(0,0,-25)
            scale3D: Qt.vector3d(1, 1, 1)
        }
        components: [ sphereMesh, material, sphereTransform ]
    }

    Entity {
        id: mysphere2
        Transform {
            id: sphereTransform2
            translation: Qt.vector3d(0,0,-50)
            scale3D: Qt.vector3d(1, 1, 1)
        }
        components: [ sphereMesh, material2, sphereTransform2 ]
    }

    Entity {
        id: textBack
        components: [ Transform { translation: Qt.vector3d(-25,-5,-30) } ]

        Text2DEntity {
            font.family: "Sans Serif"
            font.pointSize: 10
            color: Qt.rgba(0, 1, 0, 1.0)
            text: "textBack"
            width: text.length * font.pointSize
            height: font.pointSize * 2
        }
    }
}