在 Qt 3D 中使用自定义着色器可以实现高度定制化的渲染效果。以下是完整的自定义着色器开发方案。
一、基础着色器创建
1. 创建自定义材质
qml
import Qt3D.Core 2.15
import Qt3D.Render 2.15
import Qt3D.Extras 2.15
Entity {
components: [
Transform { translation: Qt.vector3d(0, 0, -5) },
CuboidMesh {},
Material {
effect: Effect {
techniques: [
// OpenGL技术
Technique {
graphicsApiFilter {
api: GraphicsApiFilter.OpenGL
profile: GraphicsApiFilter.CoreProfile
majorVersion: 3
minorVersion: 3
}
renderPasses: [
RenderPass {
shaderProgram: ShaderProgram {
vertexShaderCode: "
#version 330
in vec3 vertexPosition;
in vec3 vertexNormal;
uniform mat4 mvp;
out vec3 vNormal;
void main() {
gl_Position = mvp * vec4(vertexPosition, 1.0);
vNormal = vertexNormal;
}
"
fragmentShaderCode: "
#version 330
in vec3 vNormal;
out vec4 fragColor;
void main() {
vec3 lightDir = normalize(vec3(1,1,1));
float diff = max(dot(vNormal, lightDir), 0.0);
fragColor = vec4(diff * vec3(1.0,0.5,0.2), 1.0);
}
"
}
}
]
},
// Vulkan技术
Technique {
graphicsApiFilter {
api: GraphicsApiFilter.Vulkan
majorVersion: 1
minorVersion: 0
}
// Vulkan着色器代码...
}
]
}
}
]
}
二、高级着色器技术
1. 着色器参数传递
qml
Material {
parameters: [
// 统一变量
Parameter { name: "time"; value: timeUniform.value },
Parameter { name: "color"; value: "blue" },
// 纹理
Parameter {
name: "diffuseTexture"
value: Texture2D {
generateMipMaps: true
minificationFilter: Texture.LinearMipMapLinear
magnificationFilter: Texture.Linear
wrapMode {
x: WrapMode.Repeat
y: WrapMode.Repeat
}
TextureImage {
source: "texture.png"
}
}
}
]
}
// 动画化参数
NumberAnimation {
id: timeUniform
target: timeUniform
property: "value"
from: 0
to: 100
duration: 5000
loops: Animation.Infinite
}
2. 几何着色器示例
qml
ShaderProgram {
geometryShaderCode: "
#version 330
layout(triangles) in;
layout(triangle_strip, max_vertices=3) out;
in vec3 vNormal[];
out vec3 gNormal;
uniform float explodeAmount;
void main() {
vec3 avgPos = (gl_in[0].gl_Position.xyz +
gl_in[1].gl_Position.xyz +
gl_in[2].gl_Position.xyz) / 3.0;
for(int i=0; i<3; i++) {
vec3 dir = normalize(gl_in[i].gl_Position.xyz - avgPos);
gl_Position = gl_in[i].gl_Position + vec4(dir * explodeAmount, 0.0);
gNormal = vNormal[i];
EmitVertex();
}
EndPrimitive();
}
"
}
3. 计算着色器 (Qt 6.2+)
qml
ComputeCommand {
workGroupX: 32
workGroupY: 32
workGroupZ: 1
shaderProgram: ShaderProgram {
computeShaderCode: "
#version 430
layout(local_size_x = 32, local_size_y = 32) in;
layout(rgba32f, binding = 0) uniform image2D outputImage;
void main() {
ivec2 pixelCoords = ivec2(gl_GlobalInvocationID.xy);
vec4 color = vec4(
float(pixelCoords.x)/1024.0,
float(pixelCoords.y)/768.0,
0.5,
1.0
);
imageStore(outputImage, pixelCoords, color);
}
"
}
}
三、着色器特效实现
1. 卡通渲染 (Toon Shading)
glsl
// 顶点着色器
vertexShaderCode: "
#version 330
in vec3 vertexPosition;
in vec3 vertexNormal;
uniform mat4 mvp;
uniform mat4 modelMatrix;
out vec3 vNormal;
out vec3 vPosition;
void main() {
gl_Position = mvp * vec4(vertexPosition, 1.0);
vNormal = normalize(mat3(modelMatrix) * vertexNormal);
vPosition = vec3(modelMatrix * vec4(vertexPosition, 1.0));
}
"
// 片段着色器
fragmentShaderCode: "
#version 330
in vec3 vNormal;
in vec3 vPosition;
out vec4 fragColor;
uniform vec3 lightPos = vec3(5,5,5);
uniform vec3 lightColor = vec3(1,1,1);
uniform vec3 objectColor = vec3(0.5,0.3,0.8);
void main() {
vec3 lightDir = normalize(lightPos - vPosition);
float diff = max(dot(vNormal, lightDir), 0.0);
// 离散化光照
if(diff > 0.8) diff = 1.0;
else if(diff > 0.5) diff = 0.6;
else if(diff > 0.2) diff = 0.3;
else diff = 0.1;
vec3 diffuse = diff * lightColor;
vec3 result = (diffuse) * objectColor;
// 添加轮廓
float edge = max(dot(vNormal, vec3(0,0,1)), 0.0);
if(edge < 0.2) result = vec3(0,0,0);
fragColor = vec4(result, 1.0);
}
"
2. 水波纹效果
glsl
// 顶点着色器
vertexShaderCode: "
#version 330
in vec3 vertexPosition;
in vec2 vertexTexCoord;
uniform mat4 mvp;
uniform float time;
out vec2 vTexCoord;
void main() {
float wave = sin(time + vertexPosition.x * 5.0) * 0.1;
vec3 pos = vertexPosition + vec3(0, wave, 0);
gl_Position = mvp * vec4(pos, 1.0);
vTexCoord = vertexTexCoord;
}
"
// 片段着色器
fragmentShaderCode: "
#version 330
in vec2 vTexCoord;
uniform sampler2D waterTexture;
uniform float time;
out vec4 fragColor;
void main() {
vec2 distortedTexCoord = vTexCoord + vec2(
sin(time + vTexCoord.y * 10.0) * 0.02,
cos(time + vTexCoord.x * 10.0) * 0.02
);
vec4 color = texture(waterTexture, distortedTexCoord);
// 添加高光
float spec = pow(max(0.0, sin(vTexCoord.x * 50.0 + time * 5.0)), 10.0);
color.rgb += vec3(spec * 0.5);
fragColor = color;
}
"
四、性能优化技巧
1)着色器变体管理:
qml
Technique {
filterKeys: [
FilterKey { name: "renderingStyle"; value: "forward" },
FilterKey { name: "lightingModel"; value: "phong" }
]
// ...
}
2)着色器预编译:
qml
ShaderProgram {
id: precompiledShader
vertexShaderCode: loadShaderSource("shaders/precompiled.vert.qsb")
fragmentShaderCode: loadShaderSource("shaders/precompiled.frag.qsb")
function loadShaderSource(file) {
var request = new XMLHttpRequest()
request.open("GET", file, false)
request.send(null)
return request.responseText
}
}
3)着色器LOD:
qml
LevelOfDetail {
thresholds: [10, 50, 100]
currentIndex: calculateLodIndex()
Technique { /* 高细节着色器 */ }
Technique { /* 中细节着色器 */ }
Technique { /* 低细节着色器 */ }
}
五、调试与问题排查
1)着色器错误捕获:
qml
ShaderProgram {
onLogChanged: console.log("Shader Log:", log)
onStatusChanged: {
if (status === ShaderProgram.Error)
console.error("Shader Error:", log)
}
}
2)帧调试器集成:
qml
RenderSettings {
activeFrameGraph: ForwardRenderer {
// ...渲染设置...
DebugOverlay {
shaderInfoEnabled: true
}
}
}
3)变量可视化调试:
glsl
// 临时调试输出
fragColor = vec4(vNormal * 0.5 + 0.5, 1.0); // 可视化法线
// fragColor = vec4(vec3(gl_FragCoord.z), 1.0); // 可视化深度
六、完整示例:波浪变形+边缘高光效果
1. QML主文件 (main.qml)
import QtQuick 2.15
import Qt3D.Core 2.15
import Qt3D.Render 2.15
import Qt3D.Extras 2.15
import QtQuick.Controls 2.15
ApplicationWindow {
width: 1280
height: 720
visible: true
View3D {
anchors.fill: parent
camera: camera
Entity {
id: sceneRoot
Camera {
id: camera
projectionType: CameraLens.PerspectiveProjection
fieldOfView: 45
aspectRatio: 16/9
nearPlane: 0.1
farPlane: 1000.0
position: Qt.vector3d(0, 1, 6)
upVector: Qt.vector3d(0, 1, 0)
viewCenter: Qt.vector3d(0, 0, 0)
}
components: [
RenderSettings {
activeFrameGraph: ForwardRenderer {
clearColor: "black"
camera: camera
}
},
InputSettings {}
]
// 自定义着色器实体
Entity {
id: customShaderEntity
components: [
Transform {
id: entityTransform
translation: Qt.vector3d(0, -0.5, 0)
rotation: fromAxisAndAngle(Qt.vector3d(1, 0, 0), -45)
},
TorusMesh {
id: torusMesh
radius: 1.5
minorRadius: 0.5
rings: 50
slices: 50
},
CustomMaterial {
id: customMaterial
// 参数通过QML控制
time: timeSlider.value
waveHeight: waveHeightSlider.value
highlightColor: colorPicker.color
}
]
}
// 环境光
Entity {
components: [
DirectionalLight {
worldDirection: Qt.vector3d(0.3, -1, 0.2).normalized()
color: Qt.rgba(0.5, 0.5, 0.5, 1)
intensity: 0.5
}
]
}
}
}
// 控制面板
Pane {
anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
width: parent.width * 0.8
height: 150
Column {
spacing: 5
Row {
spacing: 10
Label { text: "时间: "; anchors.verticalCenter: parent.verticalCenter }
Slider {
id: timeSlider
from: 0
to: 100
value: 0
width: 200
}
Label { text: "波浪高度: "; anchors.verticalCenter: parent.verticalCenter }
Slider {
id: waveHeightSlider
from: 0
to: 1
value: 0.3
width: 200
}
}
Row {
spacing: 10
Label { text: "高光颜色: " }
ColorDialog {
id: colorDialog
title: "选择高光颜色"
}
Button {
text: "选择颜色"
onClicked: colorDialog.open()
}
Rectangle {
width: 30
height: 30
color: colorPicker.color
border.width: 1
}
}
}
}
// 颜色选择器
ColorDialog {
id: colorPicker
color: "cyan"
}
// 自动动画
NumberAnimation {
target: timeSlider
property: "value"
from: 0
to: 100
duration: 10000
loops: Animation.Infinite
running: true
}
}
2. 自定义材质 (CustomMaterial.qml)
import Qt3D.Core 2.15
import Qt3D.Render 2.15
Material {
id: root
// 可绑定属性
property real time: 0.0
property real waveHeight: 0.3
property color highlightColor: "cyan"
// 材质参数
parameters: [
Parameter { name: "time"; value: root.time },
Parameter { name: "waveHeight"; value: root.waveHeight },
Parameter { name: "highlightColor"; value: root.highlightColor }
]
// 材质效果
effect: Effect {
techniques: [
// OpenGL技术
Technique {
graphicsApiFilter {
api: GraphicsApiFilter.OpenGL
profile: GraphicsApiFilter.CoreProfile
majorVersion: 3
minorVersion: 3
}
renderPasses: [
RenderPass {
shaderProgram: ShaderProgram {
id: gl3ShaderProgram
// 顶点着色器
vertexShaderCode: "
#version 330 core
in vec3 vertexPosition;
in vec3 vertexNormal;
in vec2 vertexTexCoord;
uniform mat4 modelViewProjection;
uniform mat4 modelMatrix;
uniform float time;
uniform float waveHeight;
out vec3 vNormal;
out vec3 vPosition;
out vec2 vTexCoord;
void main() {
// 波浪变形
float wave = sin(time + vertexPosition.x * 5.0) *
cos(time + vertexPosition.z * 3.0) *
waveHeight;
vec3 displacedPosition = vertexPosition +
vec3(0.0, wave, 0.0);
gl_Position = modelViewProjection *
vec4(displacedPosition, 1.0);
// 计算变形后的法线
float dx = cos(time + vertexPosition.x * 5.0) *
5.0 * waveHeight;
float dz = -sin(time + vertexPosition.z * 3.0) *
3.0 * waveHeight;
vec3 tangent = normalize(vec3(1.0, dx, 0.0));
vec3 bitangent = normalize(vec3(0.0, dz, 1.0));
vNormal = normalize(cross(tangent, bitangent));
vPosition = vec3(modelMatrix *
vec4(displacedPosition, 1.0));
vTexCoord = vertexTexCoord;
}
"
// 片段着色器
fragmentShaderCode: "
#version 330 core
in vec3 vNormal;
in vec3 vPosition;
in vec2 vTexCoord;
uniform vec3 highlightColor;
uniform float time;
out vec4 fragColor;
void main() {
// 基础颜色
vec3 baseColor = vec3(0.2, 0.4, 0.8);
// 光照计算
vec3 lightPos = vec3(3.0, 5.0, 2.0);
vec3 lightDir = normalize(lightPos - vPosition);
vec3 viewDir = normalize(-vPosition);
vec3 reflectDir = reflect(-lightDir, vNormal);
// 漫反射
float diff = max(dot(vNormal, lightDir), 0.0);
vec3 diffuse = diff * vec3(1.0);
// 镜面反射 (Blinn-Phong)
vec3 halfwayDir = normalize(lightDir + viewDir);
float spec = pow(max(dot(vNormal, halfwayDir), 0.0), 32.0);
vec3 specular = spec * highlightColor;
// 边缘高光
float rim = 1.0 - max(dot(viewDir, vNormal), 0.0);
rim = smoothstep(0.7, 1.0, rim);
vec3 rimLight = rim * highlightColor * 0.8;
// 组合所有光照
vec3 result = (diffuse + specular + rimLight) * baseColor;
// 添加基于UV的图案
float pattern = sin(vTexCoord.x * 20.0 + time) *
sin(vTexCoord.y * 20.0 + time);
pattern = smoothstep(-0.3, 0.3, pattern);
result = mix(result, vec3(1.0), pattern * 0.3);
fragColor = vec4(result, 1.0);
}
"
}
// 渲染状态配置
renderStates: [
CullFace { mode: CullFace.Back },
DepthTest { depthFunction: DepthTest.Less }
]
}
]
},
// Vulkan技术 (可选)
Technique {
graphicsApiFilter {
api: GraphicsApiFilter.Vulkan
majorVersion: 1
minorVersion: 0
}
// Vulkan着色器代码...
}
]
}
}
3. 效果说明
这个完整示例实现了以下效果:
波浪变形:顶点着色器中基于时间的正弦波变形
动态法线计算:根据变形表面重新计算法线
多光源组合:包含漫反射、镜面反射和边缘高光
UV图案效果:基于纹理坐标的动画图案
QML控制:通过滑块控制波浪高度和时间动画
颜色选择:可交互选择高光颜色
4. 扩展建议
1)添加纹理支持:
qml
// 在CustomMaterial.qml中添加
property alias texture: textureParam.value
Parameter {
id: textureParam
name: "diffuseTexture"
value: Texture2D {
generateMipMaps: true
minificationFilter: Texture.LinearMipMapLinear
magnificationFilter: Texture.Linear
TextureImage {
source: "texture.png"
}
}
}
2)添加几何着色器:
glsl
// 在ShaderProgram中添加
geometryShaderCode: "
#version 330 core
layout(triangles) in;
layout(triangle_strip, max_vertices=3) out;
in vec3 vNormal[];
out vec3 gNormal;
uniform float explodeAmount;
void main() {
vec3 avgPos = (gl_in[0].gl_Position.xyz +
gl_in[1].gl_Position.xyz +
gl_in[2].gl_Position.xyz) / 3.0;
for(int i=0; i<3; i++) {
vec3 dir = normalize(gl_in[i].gl_Position.xyz - avgPos);
gl_Position = gl_in[i].gl_Position + vec4(dir * explodeAmount, 0.0);
gNormal = vNormal[i];
EmitVertex();
}
EndPrimitive();
}
"
3)性能优化:
qml
// 使用预编译的着色器二进制文件
ShaderProgram {
vertexShaderCode: loadShader("shaders/wave.vert.qsb")
fragmentShaderCode: loadShader("shaders/wave.frag.qsb")
function loadShader(file) {
var xhr = new XMLHttpRequest
xhr.open("GET", file, false)
xhr.send()
return xhr.responseText
}
}