How can I draw the outline of an object on top of any other object in Qt3D? For instance to highlight a selected object in a 3D editor?
问题:
回答1:
If you want to draw the outline of an entity at all times, even if the entity is behind other entities, one solution is to do it in two steps:
- Draw everything as normal.
- Draw only the outline of the selected object.
When drawing the outline, you need to use an outline effect, which can be implemented in two render passes:
- Render the geometry to a texture using a simple color shader.
- Render to screen using a shader that takes each pixel in the texture and compares the surrounding pixels. If they are equal, we are inside the object and the fragment can be discarded. If they differ, we are on the edge of the object and we should draw the color.
Here is a simple implementation of the above-mentioned shader:
#version 150
uniform sampler2D color;
uniform vec2 winSize;
out vec4 fragColor;
void main()
{
int lineWidth = 5;
vec2 texCoord = gl_FragCoord.xy / winSize;
vec2 texCoordUp = (gl_FragCoord.xy + vec2(0, lineWidth)) / winSize;
vec2 texCoordDown = (gl_FragCoord.xy + vec2(0, -lineWidth)) / winSize;
vec2 texCoordRight = (gl_FragCoord.xy + vec2(lineWidth, 0)) / winSize;
vec2 texCoordLeft = (gl_FragCoord.xy + vec2(-lineWidth, 0)) / winSize;
vec4 col = texture(color, texCoord);
vec4 colUp = texture(color, texCoordUp);
vec4 colDown = texture(color, texCoordDown);
vec4 colRight = texture(color, texCoordRight);
vec4 colLeft = texture(color, texCoordLeft);
if ((colUp == colDown && colRight == colLeft) || col.a == 0.0)
discard;
fragColor = col;
}
Note: It might be a better idea to take the difference between the values instead of using an equality.
With this method, you don't have to worry about depth testing and the order in which the objects are drawn: The second time you draw, you will always draw on top of everything else.
You could do this by adding a single effect with two techniques with different filter keys. Alternatively, if you want to use materials from Qt3D.Extras, you can add another entity with the same transform and mesh and a material that uses the outline technique.
Here is an example that draws the outline on top of everything else using two render passes:
import QtQuick 2.2 as QQ2
import Qt3D.Core 2.0
import Qt3D.Render 2.0
import Qt3D.Input 2.0
import Qt3D.Extras 2.0
Entity {
Camera {
id: camera
projectionType: CameraLens.PerspectiveProjection
fieldOfView: 45
aspectRatio: 16/9
nearPlane : 0.1
farPlane : 1000.0
position: Qt.vector3d( 0.0, 0.0, -40.0 )
upVector: Qt.vector3d( 0.0, 1.0, 0.0 )
viewCenter: Qt.vector3d( 0.0, 0.0, 0.0 )
}
OrbitCameraController {
camera: camera
}
components: [
RenderSettings {
activeFrameGraph: RenderSurfaceSelector {
id: surfaceSelector
Viewport {
CameraSelector {
camera: camera
FrustumCulling {
TechniqueFilter {
matchAll: [
FilterKey { name: "renderingStyle"; value: "forward" }
]
ClearBuffers {
clearColor: Qt.rgba(0.1, 0.2, 0.3)
buffers: ClearBuffers.ColorDepthStencilBuffer
}
}
TechniqueFilter {
matchAll: [
FilterKey { name: "renderingStyle"; value: "outline" }
]
RenderPassFilter {
matchAny: [
FilterKey {
name: "pass"; value: "geometry"
}
]
ClearBuffers {
buffers: ClearBuffers.ColorDepthStencilBuffer
RenderTargetSelector {
target: RenderTarget {
attachments : [
RenderTargetOutput {
objectName : "color"
attachmentPoint : RenderTargetOutput.Color0
texture : Texture2D {
id : colorAttachment
width : surfaceSelector.surface.width
height : surfaceSelector.surface.height
format : Texture.RGBA32F
}
}
]
}
}
}
}
RenderPassFilter {
parameters: [
Parameter { name: "color"; value: colorAttachment },
Parameter { name: "winSize"; value : Qt.size(surfaceSelector.surface.width, surfaceSelector.surface.height) }
]
matchAny: [
FilterKey {
name: "pass"; value: "outline"
}
]
}
}
}
}
}
}
},
InputSettings { }
]
PhongMaterial {
id: material
}
Material {
id: outlineMaterial
effect: Effect {
techniques: [
Technique {
graphicsApiFilter {
api: GraphicsApiFilter.OpenGL
majorVersion: 3
minorVersion: 1
profile: GraphicsApiFilter.CoreProfile
}
filterKeys: [
FilterKey { name: "renderingStyle"; value: "outline" }
]
renderPasses: [
RenderPass {
filterKeys: [
FilterKey { name: "pass"; value: "geometry" }
]
shaderProgram: ShaderProgram {
vertexShaderCode: "
#version 150 core
in vec3 vertexPosition;
uniform mat4 modelViewProjection;
void main()
{
gl_Position = modelViewProjection * vec4( vertexPosition, 1.0 );
}
"
fragmentShaderCode: "
#version 150 core
out vec4 fragColor;
void main()
{
fragColor = vec4( 1.0, 0.0, 0.0, 1.0 );
}
"
}
}
]
}
]
}
}
SphereMesh {
id: sphereMesh
radius: 3
}
Transform {
id: sphereTransform
}
Transform {
id: sphereTransform2
// TODO workaround because the transform cannot be shared
matrix: sphereTransform.matrix
}
Entity {
id: sphereEntity
components: [ sphereMesh, material, sphereTransform ]
}
Entity {
id: sphereOutlineEntity
components: [ sphereMesh, outlineMaterial, sphereTransform2 ]
}
Entity {
id: outlineQuad
components: [
PlaneMesh {
width: 2.0
height: 2.0
meshResolution: Qt.size(2, 2)
},
Transform {
rotation: fromAxisAndAngle(Qt.vector3d(1, 0, 0), 90)
},
Material {
effect: Effect {
techniques: [
Technique {
filterKeys: [
FilterKey { name: "renderingStyle"; value: "outline" }
]
graphicsApiFilter {
api: GraphicsApiFilter.OpenGL
profile: GraphicsApiFilter.CoreProfile
majorVersion: 3
minorVersion: 1
}
renderPasses : RenderPass {
filterKeys : FilterKey { name : "pass"; value : "outline" }
shaderProgram : ShaderProgram {
vertexShaderCode: "
#version 150
in vec4 vertexPosition;
uniform mat4 modelMatrix;
void main()
{
gl_Position = modelMatrix * vertexPosition;
}
"
fragmentShaderCode: "
#version 150
uniform sampler2D color;
uniform vec2 winSize;
out vec4 fragColor;
void main()
{
int lineWidth = 5;
vec2 texCoord = gl_FragCoord.xy / winSize;
vec2 texCoordUp = (gl_FragCoord.xy + vec2(0, lineWidth)) / winSize;
vec2 texCoordDown = (gl_FragCoord.xy + vec2(0, -lineWidth)) / winSize;
vec2 texCoordRight = (gl_FragCoord.xy + vec2(lineWidth, 0)) / winSize;
vec2 texCoordLeft = (gl_FragCoord.xy + vec2(-lineWidth, 0)) / winSize;
vec4 col = texture(color, texCoord);
vec4 colUp = texture(color, texCoordUp);
vec4 colDown = texture(color, texCoordDown);
vec4 colRight = texture(color, texCoordRight);
vec4 colLeft = texture(color, texCoordLeft);
if ((colUp == colDown && colRight == colLeft) || col.a == 0.0)
discard;
fragColor = col;
}
"
}
}
}]
}
}
]
}
}
The result: