Qml 中用 Shader 实现圣诞树旋转灯
一、前言
2022年圣诞节到来啦,很高兴这次我们又能一起度过~
这次给大家带来一个简单漂亮圣诞树灯。
当然了,本篇文章主要是讲解一下如何在 Qml 中使用 GLSL 来实现自己的特效。
至于代码嘛,我比较喜欢在 Shadertoy 上寻找,那里有很多超级炫酷的着色器实现的特效,并且可以很轻松的集成到 Qml 中。
二、纯 GLSL 实现的圣诞树旋转灯
首先,想要在 Qml 中使用 GLSL,我们需要借助 ShaderEffect。
ShaderEffect 类型将自定义顶点和片段(像素)着色器应用于矩形。它允许在 QML 场景中添加阴影、模糊、着色和页面卷曲等效果。
注意:根据使用的 Qt Quick Scenegraph 后端,可能不支持 ShaderEffect 类型。例如,使用软件后端,则根本不会渲染效果。
ShaderEffect 有两个重要属性:
vertexShader: 此属性保存顶点着色器源码字符串 ( 实际上也可以是文件 )。
fragmentShader: 此属性保存片元着色器源码字符串 ( 实际上也可以是文件 )。
我这里就比较简单了,直接将着色器代码文件传入即可:
import QtQuick 2.15
import QtQuick.Window 2.15
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"
fragmentShader: "file:./glsl/christmas_tree_lights.frag"
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;
}
}
}
当然,还要向着色器中传入一些 uniform 变量,直接在 Qml 中声明即可,Qt 会自动帮我们传入。
着色器的代码来自 Shadertoy:https://www.shadertoy.com
顶点着色器很简单,直接传递变量,计算顶点即可:
#version 450
uniform mat4 qt_Matrix;
in vec4 qt_Vertex;
out vec4 fragCoord;
void main() {
fragCoord = qt_Vertex;
gl_Position = qt_Matrix * qt_Vertex;
}
然后是片元着色器( 比较复杂 ) :
#version 450
in vec4 fragCoord;
uniform vec3 iResolution; // viewport resolution (in pixels)
uniform 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(void)
{
mainImage(gl_FragColor, vec2(fragCoord.x, iResolution.y - fragCoord.y));
}
三、效果展示
_(:3 」∠)_ 动图质量不太行,大家凑合看。
四、结语
另外,要提一点:
在Qt 5中,效果以GLSL(OpenGL着色语言)源代码的形式提供,通常作为字符串嵌入QML中。从Qt 5.8开始,可以引用本地文件或Qt资源系统中的文件。
在Qt 6中,Qt Quick支持图形API,如Vulkan、Metal和Direct3D 11。因此,使用GLSL源字符串不再可行。相反,新的着色器管道基于将Vulkan兼容的GLSL代码编译成SPIR-V,然后收集反射信息并翻译成其他着色语言,如HLSL、金属着色语言和各种GLSL版本。生成的资产被打包到一个单独的包中,通常存储在扩展名为.qsb的文件中。此过程最迟在应用程序构建时离线完成。在运行时,场景图和底层图形抽象使用这些.qsb文件。因此,ShaderEffect需要Qt 6中的文件(本地或qrc)引用来代替内联着色器代码。
例如,vertexShader和fragmentShader属性是Qt 6中的URL,其工作方式与Image.source非常相似。然而,ShaderEffect仅支持文件和qrc方案。也可以省略文件方案,以便以方便的方式指定相对路径。这样的路径是相对于组件(.qml文件)位置解析的。
因此,在 Qt 6 中,我们可以使用更加广泛的 qsb,这样只需要写一种着色器即可支持所有后端。
不过关于 qsb 具体如何使用,我后面会写一下相关的博客~
五、源码下载
Github的:
https://github.com/mengps/ShadertoyExamples
https://github.com/mengps/ShadertoyExamples
CSDN的:
https://download.csdn.net/download/u011283226/87342581
https://download.csdn.net/download/u011283226/87342581
————————————————
原文链接:https://blog.csdn.net/u011283226/article/details/128429822