【写在前面】
Qt 5 的图形体系结构非常依赖 OpenGL 作为底层 3D 图形 API。但过去 8 年来随着 Metal 和 Vulkan 的推出,市场发生了巨大变化。现在,Qt 6 加入了大量不同平台的图形 API,以确保用户可以在所有平台上以最高性能运行 Qt。
在 Qt Quick 中的所有 3D 图形现在都基于称为 RHI(渲染硬件接口)的 3D 图形新抽象层之上。RHI 使 Qt 可以使用基础 OS /平台的本机 3D 图形 API。因此,默认情况下,Qt Quick 现在将在 Windows 上使用Direct3D,在 macOS 上使用 Metal。有关详细信息,请参阅有关 RHI 的博客文章系列。
Qt 中的 OpenGL 特定类仍然存在,但现在已从 QtOpenGL 模块中的 QtGui 中移出。此外,Qt 6 还添加了一个名为 QtShaderTools ( Qt 着色器工具 ) 的新模块,以跨平台的方式处理这些 API 的不同着色语言。
【qsb详细说明】
qsb 是 Qt 着色器工具模块提供的命令行工具。
它集成了第三方库,如 glsland 和 SPIRV-Cross,可以选择调用外部工具,如 fxc 或 SPIRV-opt,并生成 .qsb 文件。此外,它还可用于检查 .qsb 包的内容。
简单提一下:
glsland 是用于将 GLSL 转换为 SPIR-V 的工具。
SPIRV-Cross 是用于将 SPIR-V 解析和转换为其他着色器语言的工具。
Qt 着色器工具模块构建在 Khronos SPIR-V 网站上描述的 SPIR-V 开源生态系统上。为了编译成 SPIR-V,使用 glsland,而翻译和反射是通过 SPIRV-Cross 完成的。
为了允许在 Qt 应用程序和库中编写一次着色器代码,所有着色器都应该用一种语言编写,然后编译成SPIR-V。目前,这种 shanding 语言是与 Vulkan 兼容的 GLSL。这与 OpenGL 风格 GLSL Qt 5.x 预期的不同。有关差异的概述,请参阅 GL_KHR_vulkan_glsl 规范。
然后通过翻译 SPIR-V 字节码以及反射信息(输入、输出、着色器资源)生成其他着色语言(如 GLSL、HLSL 和金属着色语言)的源代码。
特别是对于 GLSL,这还涉及生成多个变体,这意味着源代码适用于不同的 GLSL 版本,例如 GLSL ES 100、GLSL ES 300、GLSL 120、150等。然后将所有这些都打包到可序列化的 QShader 容器对象中,通常以扩展名为 .qsb 的文件形式存储在磁盘中。
Qt 渲染硬件接口( RHI )直接使用 QShader 实例,选择最适合运行时使用的图形 API 的着色器源或字节码。QShader 对象通常从应用程序附带的 .qsb 文件或 Qt 资源系统中的Qt本身反序列化。
一些平台提供了将着色器源代码编译为类似于 SPIR-V 的中间格式的选项。这涉及运行特定于平台的工具。使用 Direct 3D,qsb 工具提供了在生成 HLSL 源代码后从 Windows SDK 调用 fxc 工具的选项。
然后,它用 fxc 生成的 DXBC 二进制文件替换 HLSL 源代码。这可以对应用程序的运行时性能产生积极影响,因为它们不再需要自己进行第一阶段的编译( HLSL 源代码到 DXBC )。对于 macOS 和 iOS,XCode SDK 提供了类似的工具。这种方法的缺点是,只能在各自的平台上运行这些工具。
因此,这最适合与 qsb 的 CMake 集成结合使用,因为在应用程序构建时进行着色器调节隐含地提供了有关目标平台以及可以调用哪些平台特定工具的知识。
下图描述了调用 qsb 工具期间发生的步骤:
Qt 着色器调节系统的主要组件包括:
- qsb命令行工具
- CMake集成qsb工具
- QShader(QtGui模块的一部分)
- QShaderBaker(本模块的一部分,与 qsb 工具等效的库)
注意:从 Qt 6.2 开始,C++API 被认为是私有的。不建议从应用程序直接使用它们。相反,直接或间接从 CMake 项目使用 qsb 命令行工具。
【正文开始】
上面巴拉巴拉讲了一大坨,我们不需要过于关心,只需要知道:Qt 6 以后,对于 Qt Quick 不再直接使用各个图形 API 的着色器语言,而是使用一种 .qsb 的文件,这个文件由 qsb.exe 工具生成,它包含了各个图形 API 使用的字节码。
通过使用 qsb,我们便可以自由切换硬件渲染后端而无需写多个平台的着色器代码。
另一方面,.qsb 文件主要由 Qt Scene Graph 和 Qt3D 使用,当然,Qml 中相关的组件也同样使用,例如:ShaderEffect。
因此,本篇使用前一篇的圣诞树作为例子来教大家如何使用。
首先,因为现在是 Qt6,所以需要使用 qsb.exe 将 glsl 文件生成 .qsb 文件:
qsb.exe 工具的位置位于编译套件目录下的 bin 文件夹中,比如 Qt\6.2.0\msvc2019_64\bin,接着,我们敲入命令:
qsb.exe --glsl "450 es,120,150" --hlsl 50 --msl 12 -o christmas_tree_lights.vert.qsb .\christmas_tree_lights.vert
qsb.exe --glsl "450 es,120,150" --hlsl 50 --msl 12 -o christmas_tree_lights.frag.qsb .\christmas_tree_lights.frag
将分别生成 christmas_tree_lights.vert.qsb 和 christmas_tree_lights.frag.qsb,这是顶点&片段着色器对应的各个平台着色器的字节码。
命令具体的意义很简单,也可以使用 qsb --help 来获取帮助、
接着,上篇 ShaderEffect 的 vertexShader 和 fragmentShader 文件名需要更改:
import QtQuick
import QtQuick.Window
Window {
id: root
width: 1280
height: 900
visible: true
title: qsTr("Christmas tree lights")
ShaderEffect {
anchors.fill: parent
vertexShader: "file:./glsl/christmas_tree_lights.vert.qsb"
fragmentShader: "file:./glsl/christmas_tree_lights.frag.qsb"
property vector3d iResolution: Qt.vector3d(root.width, root.height, 0)
property real iTime: 0
Text {
text: "Time: " + parent.iTime.toFixed(2)
color: "white"
}
Timer {
running: true
repeat: true
interval: 10
onTriggered: parent.iTime += 0.01;
}
}
}
导入 qsb 即可。
当然,要生成 qsb,我们的 glsl 需要改为 GL_KHR_vulkan_glsl,其规范链接:GLSL/GL_KHR_vulkan_glsl.txt at master · KhronosGroup/GLSL · GitHubGLSL Shading Language Issue Tracker. Contribute to KhronosGroup/GLSL development by creating an account on GitHub.https://github.com/KhronosGroup/GLSL/blob/master/extensions/khr/GL_KHR_vulkan_glsl.txt 顶点着色器 christmas_tree_lights.vert 改为:
#version 450
layout(location = 0) in vec4 qt_Vertex;
layout(location = 1) in vec2 qt_MultiTexCoord0; //未使用
layout(location = 0) out vec4 fragCoord;
layout(std140, binding = 0) uniform buf {
mat4 qt_Matrix;
};
void main()
{
fragCoord = qt_Vertex;
gl_Position = qt_Matrix * qt_Vertex;
}
因为 ShaderEffect 有一些默认的输入,即便没有使用也需要写全。
片段着色器 christmas_tree_lights.frag 改为:
#version 450
layout(location = 0) in vec2 fragCoord;
layout(location = 0) out vec4 fragColor;
layout(std140, binding = 0) uniform buf {
mat4 qt_Matrix;
float qt_Opacity;
vec3 iResolution; // viewport resolution (in pixels)
float iTime; // shader playback time (in seconds)
};
const float pi = 3.1415927;
const float dotsnbt = 90.0; // Number of dots for the tree
const float dotsnbs = 20.0; // Number of dots for the star (per circle)
vec3 hsv2rgb (vec3 hsv) { // from HSV to RGB color vector
hsv.yz = clamp (hsv.yz, 0.0, 1.0);
return hsv.z * (1.0 + 0.63 * hsv.y * (cos (2.0 * 3.14159 * (hsv.x + vec3 (0.0, 2.0 / 3.0, 1.0 / 3.0))) - 1.0));
}
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
float time = iTime;
float mx = max(iResolution.x, iResolution.y);
vec2 scrs = iResolution.xy / mx;
vec2 uv = vec2(fragCoord.x, iResolution.y - fragCoord.y) / mx;
//vec2 m = vec2(mouse.x / scrs.x, mouse.y * (scrs.y / scrs.x));
vec2 pos = vec2(0.0); // Position of the dots
vec3 col = vec3(0.0); // Color of the dots
float intensitys = 1.0 / 4000.0; // Light intensity for the star
float intensityt = 1.0 / 2000.0; // Light intensity for the tree
float scale = 0.2; // Size of the star
/*** Star ***/
for(float i = 0.0 ; i < dotsnbs; i++){
pos = vec2(cos(time * 0.2) / 20.0 * cos(2.0 * pi * i / dotsnbs),
0.15 * sin(2.0 * pi * i / dotsnbs)) * scale;
pos += vec2(scrs.x / 2.0, scrs.y * 0.11);
col += hsv2rgb(vec3(i / dotsnbs, distance(uv, pos) * (1.0 / intensitys), intensitys / distance(uv, pos)));
pos = vec2(0.12 * cos(2.0 * pi * i / dotsnbs + time * 0.2),
0.08 * sin(2.0 * pi * i / dotsnbs)) * scale;
pos += vec2(scrs.x / 2.0, scrs.y * 0.11);
col += hsv2rgb(vec3(1.0 - i / dotsnbs, distance(uv, pos) * (1.0 / intensitys), intensitys / distance(uv, pos)));
pos = vec2(0.12 * cos(2.0 * pi * i / dotsnbs + time * 0.2),
-0.08 * sin(2.0 * pi * i / dotsnbs)) * scale;
pos += vec2(scrs.x / 2.0, scrs.y * 0.11);
col += hsv2rgb(vec3(i / dotsnbs, distance(uv, pos) * (1.0 / intensitys), intensitys / distance(uv, pos)));
}
/*** Tree ***/
float angle = dotsnbt * 1.8; // Angle of the cone
for(float i = 0.0 ; i < dotsnbt ; i++){
pos = vec2(scrs.x / 2.0 + sin(i / 2.0 - time * 0.2) / (3.0 / (i + 1.0) * angle),
scrs.y * ((i) / dotsnbt + 0.21) * 0.80);
col += hsv2rgb(vec3(1.5 * i / dotsnbt + fract(time / 4.0), distance(uv, pos) * (1.0 / intensityt), intensityt / distance(uv, pos)));
}
fragColor = vec4( col, 1.0 );
}
void main() {
mainImage(fragColor, vec2(fragCoord.x, iResolution.y - fragCoord.y));
}
运行的效果与前篇一致:
【结语】
最后,如果需要完整代码或者了解 ShaderEffect 的用法,可以看看前篇:
Qml 中用 Shader 实现圣诞树旋转灯_梦起丶的博客-CSDN博客2022年圣诞节到来啦,很高兴这次我们又能一起度过~这次给大家带来一个简单漂亮圣诞树灯。当然了,本篇文章主要是讲解一下如何在 Qml 中使用GLSL来实现自己的特效。至于代码嘛,我比较喜欢在Shaderjoy上寻找,那里有很多超级炫酷的着色器实现的特效,并且可以很轻松的集成到 Qml 中。https://blog.csdn.net/u011283226/article/details/128429822?spm=1001.2014.3001.5501