飞道的博客

Qml 中用 Shader 实现圣诞树旋转灯

392人阅读  评论(0)

一、前言

        2022年圣诞节到来啦,很高兴这次我们又能一起度过~

        这次给大家带来一个简单漂亮圣诞树灯。

        当然了,本篇文章主要是讲解一下如何在 Qml 中使用 GLSL 来实现自己的特效。

        至于代码嘛,我比较喜欢在 Shaderjoy 上寻找,那里有很多超级炫酷的着色器实现的特效,并且可以很轻松的集成到 Qml 中。


二、纯 GLSL 实现的圣诞树旋转灯

        首先,想要在 Qml 中使用 GLSL,我们需要借助 ShaderEffect

ShaderEffect 类型将自定义顶点和片段(像素)着色器应用于矩形。它允许在 QML 场景中添加阴影、模糊、着色和页面卷曲等效果。
注意:根据使用的 Qt Quick Scenegraph 后端,可能不支持 ShaderEffect 类型。例如,使用软件后端,则根本不会渲染效果。

        ShaderEffect 有两个重要属性:

        vertexShader: 此属性保存顶点着色器源码字符串 ( 实际上也可以是文件 )。

        fragmentShader: 此属性保存片元着色器源码字符串 ( 实际上也可以是文件 )。

        我这里就比较简单了,直接将着色器代码文件传入即可:


  
  1. import QtQuick 2.15
  2. import QtQuick. Window 2.15
  3. Window {
  4. id: root
  5. width: 1280
  6. height: 900
  7. visible: true
  8. title: qsTr( "Christmas tree lights")
  9. ShaderEffect {
  10. anchors. fill: parent
  11. vertexShader: "file:./glsl/christmas_tree_lights.vert"
  12. fragmentShader: "file:./glsl/christmas_tree_lights.frag"
  13. property vector3d iResolution: Qt. vector3d(root. width, root. height, 0)
  14. property real iTime: 0
  15. Text {
  16. text: "Time: " + parent. iTime. toFixed( 2)
  17. color: "white"
  18. }
  19. Timer {
  20. running: true
  21. repeat: true
  22. interval: 10
  23. onTriggered: parent. iTime += 0.01;
  24. }
  25. }
  26. }

        当然,还要向着色器中传入一些 uniform 变量,直接在 Qml 中声明即可,Qt 会自动帮我们传入。

        着色器的代码来自 Shadertoy:https://www.shadertoy.com

        顶点着色器很简单,直接传递变量,计算顶点即可:


  
  1. #version 450
  2. uniform mat4 qt_Matrix;
  3. in vec4 qt_Vertex;
  4. out vec4 fragCoord;
  5. void main() {
  6. fragCoord = qt_Vertex;
  7. gl_Position = qt_Matrix * qt_Vertex;
  8. }

        然后是片元着色器( 比较复杂 ) :


  
  1. #version 450
  2. in vec4 fragCoord;
  3. uniform vec3 iResolution; // viewport resolution (in pixels)
  4. uniform float iTime; // shader playback time (in seconds)
  5. const float pi = 3.1415927;
  6. const float dotsnbt = 90.0; // Number of dots for the tree
  7. const float dotsnbs = 20.0; // Number of dots for the star (per circle)
  8. vec3 hsv2rgb (vec3 hsv) { // from HSV to RGB color vector
  9. hsv.yz = clamp (hsv.yz, 0.0, 1.0);
  10. 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));
  11. }
  12. void mainImage(out vec4 fragColor, in vec2 fragCoord)
  13. {
  14. float time = iTime;
  15. float mx = max(iResolution.x, iResolution.y);
  16. vec2 scrs = iResolution.xy / mx;
  17. vec2 uv = vec2(fragCoord.x, iResolution.y - fragCoord.y) / mx;
  18. //vec2 m = vec2(mouse.x / scrs.x, mouse.y * (scrs.y / scrs.x));
  19. vec2 pos = vec2( 0.0); // Position of the dots
  20. vec3 col = vec3( 0.0); // Color of the dots
  21. float intensitys = 1.0 / 4000.0; // Light intensity for the star
  22. float intensityt = 1.0 / 2000.0; // Light intensity for the tree
  23. float scale = 0.2; // Size of the star
  24. /*** Star ***/
  25. for( float i = 0.0 ; i < dotsnbs; i++){
  26. pos = vec2( cos(time * 0.2) / 20.0 * cos( 2.0 * pi * i / dotsnbs),
  27. 0.15 * sin( 2.0 * pi * i / dotsnbs)) * scale;
  28. pos += vec2(scrs.x / 2.0, scrs.y * 0.11);
  29. col += hsv2rgb( vec3(i / dotsnbs, distance(uv, pos) * ( 1.0 / intensitys), intensitys / distance(uv, pos)));
  30. pos = vec2( 0.12 * cos( 2.0 * pi * i / dotsnbs + time * 0.2),
  31. 0.08 * sin( 2.0 * pi * i / dotsnbs)) * scale;
  32. pos += vec2(scrs.x / 2.0, scrs.y * 0.11);
  33. col += hsv2rgb( vec3( 1.0 - i / dotsnbs, distance(uv, pos) * ( 1.0 / intensitys), intensitys / distance(uv, pos)));
  34. pos = vec2( 0.12 * cos( 2.0 * pi * i / dotsnbs + time * 0.2),
  35. -0.08 * sin( 2.0 * pi * i / dotsnbs)) * scale;
  36. pos += vec2(scrs.x / 2.0, scrs.y * 0.11);
  37. col += hsv2rgb( vec3(i / dotsnbs, distance(uv, pos) * ( 1.0 / intensitys), intensitys / distance(uv, pos)));
  38. }
  39. /*** Tree ***/
  40. float angle = dotsnbt * 1.8; // Angle of the cone
  41. for( float i = 0.0 ; i < dotsnbt ; i++){
  42. pos = vec2(scrs.x / 2.0 + sin(i / 2.0 - time * 0.2) / ( 3.0 / (i + 1.0) * angle),
  43. scrs.y * ((i) / dotsnbt + 0.21) * 0.80);
  44. col += hsv2rgb( vec3( 1.5 * i / dotsnbt + fract(time / 4.0), distance(uv, pos) * ( 1.0 / intensityt), intensityt / distance(uv, pos)));
  45. }
  46. fragColor = vec4( col, 1.0 );
  47. }
  48. void main(void)
  49. {
  50. mainImage(gl_FragColor, vec2(fragCoord.x, iResolution.y - fragCoord.y));
  51. }

三、效果展示

        _(: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/ShadertoyExampleshttps://github.com/mengps/ShadertoyExamples        CSDN的:

https://download.csdn.net/download/u011283226/87342581https://download.csdn.net/download/u011283226/87342581


转载:https://blog.csdn.net/u011283226/article/details/128429822
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场