问题
I draw some 2d text entities in a Qt3D QML scene but some of the texts always render opaque, i.e hide the contents behind them. When looking at the scene from behind ( changing the position of the camera to Qt.vector3d(0,0,-40)
) all texts render OK.
The following image shows the wrong behaviour, I would expect the text "AAARGH" not to be rendered on a white background, but the green text shining through.
Platform is Windows 64-bit, Qt5.13.0, and Visual Studio 2019.
See the following small example that demonstrates the issue:
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();
}
I suppose the reason of this behaviour comes from the GLSL shaders (distancefieldtext.vert
and distancefieldtext.frag
) that are used in Qt3D to render the Text2dEntity.
See attached shader sources.
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;
}
Any ideas on this how to make Qt3D render the text2DEntities with the text itself opaque and the spaces between the text transparant, independent on the viewing direction? Thanks in advance.
Edit
I must have inadvertently changed something in the example, as changing the position of the camera does not show the expected behaviour anymore. I will correct that on Monday when I have access to my work environment.
Update
As I needed double sided lighting for my entities, I had an additional CullFace
component with NoCulling
added to the RenderStateSet
, that explains this behaviour. My FrameGraph was looking like this:
components: [
RenderSettings {
activeFrameGraph: RenderStateSet {
renderStates: [
CullFace { mode: CullFace.NoCulling }
]
ForwardRenderer {
camera: camera
clearColor: "transparent"
}
}
},
InputSettings { }
]
When viewing from the back side, as the entities were defined from back to front the rendering was correct. This is stated explicitly in the documentation of SortPolicy:
"If QSortPolicy is not present in the FrameGraph, entities are drawn in the order they appear in the entity hierarchy."
When adding an additional SortPolicy
component with BackToFront
to the FrameGraph the rendering was correct independent of the viewing direction. The FrameGraph then looked like this:
components: [
RenderSettings {
activeFrameGraph: SortPolicy {
sortTypes: [ SortPolicy.BackToFront ]
RenderStateSet {
renderStates: [
CullFace { mode: CullFace.NoCulling }
]
ForwardRenderer {
camera: camera
clearColor: "transparent"
}
}
}
},
InputSettings { }
]
回答1:
The issue seems to be the line
zValue = vertexPosition.z;
in the vertex shader. vertexPosition
is a coordinate in model space. If you want to calculate the the z distance to the camera then you've to transform the coordinate to view space by the modelView
matrix:
vec4 viewPosition = modelView * vec4(vertexPosition.xyz, 1.0);
zValue = -viewPosition.z;
Note, since the view space z axis points out of the viewport, the coordinate has to be inverted to get the distance to the camera.
Another possibility, which seems even be more correct to me, is to calculate the clip space coordinate and further the normalized device coordinate. The clip space coordinate is calculated by the transformation by the model view matrix (mvp
) and the normalized device coordinate, by Perspective divide:
vec4 clipPosition = modelView * vec4(vertexPosition.xyz, 1.0);
zValue = clipPosition.z / clipPosition.w;
The normalized device coordinates are in range [-1, 1]. The z component can linearly be mapped to the depth of the fragment. By default the depth range is [0, 1] (except it is changed by glDepthRange.
So in the fragment shader gl_FragDepth
can be set by:
gl_FragDepth = zValue * 0.5 + 0.5;
If you use alpha Blending, then the Depth Test has to be disabled.
The objects behind other objects may not be drawn at all, because they are discarded by the depth test.
Of course this depends on the drawing order. If the text which is covered is drawn first, then it will work. But if it is drawn last, then it is discarded. This explains the different behavior dependent on the direction of view.
Note, when blending is active, then the color doesn't affect the color buffer (if alpha is 0), but if the depth test is enable, then of course the depth is written to the depth buffer.
The only alternative is to draw the objects in sorted order form the back to the front. Of course the order depends on the direction of view, so the objects would have to be sorted per frame.
回答2:
I did some tests and to get the unwanted behaviour, I made some changes to your code by messing up the order of the entities used. As you know the order of the entities is important. In my example the text2DEntity "textFront" is put before the text2DEntity "textBack" in the hierarchy. So without changing your render environment we get something like this: I added a red sphere to test a little deeper.
I found a solution using a Forward renderer and without working with a depth buffer. Here is the result (of course, I didn't change the order of the entities):
We have to use a SortPolicy, so that the forward renderer knows which object is in front of another. This will change the order of entities compared to the distance of the camera and not on the hierarchy order of the qml file.
components: [
RenderSettings {
activeFrameGraph: SortPolicy {
sortTypes: [
SortPolicy.BackToFront
]
ForwardRenderer {
camera: camera
clearColor: "black"
}
}
},
// Event Source will be set by the Qt3DQuickWindow
InputSettings { }
]
Here is the full file content for easy testing:
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
}
}
}
来源:https://stackoverflow.com/questions/57627805/text2dentity-renders-opaque-and-hides-other-entities-behind-it