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

一、前言

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

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

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

        至于代码嘛,我比较喜欢在 Shaderjoy 上寻找,那里有很多超级炫酷的着色器实现的特效,并且可以很轻松的集成到 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/ShadertoyExampleshttps://github.com/mengps/ShadertoyExamples        CSDN的:

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

你可能感兴趣的:(Qt,Quick(Qml)开发之旅,GLSL,GLSL,着色器,Qml)