# 窗帘特效

在最后一个自定义着色器效果的示例中,将学习窗帘特效。 此特效于2011年5月首次发布,它是用于着色器特效的Qt实验室(Qt labs for shader effects) (opens new window)的一部分。

image

当时,作者很喜欢这些特效,其中最喜欢窗帘特效。 作者仅是喜欢窗帘打开和隐藏背景物体的方式。

本章中,特效已针对Qt 6进行了调整。它进行了轻微的简化,以便更好地展示。

表示窗帘图像的名称为fabric.png。 该效果使用顶点着色器来回摆动窗帘,并使用片段着色器应用阴影以显示织物如何折叠。

下图显示了此着色器的工作原理。 这些顶部的波纹是通过具有7个周期的sin函数(7*PI=21.99…)曲线计算得到的;另一部分是下面的摆动。 当拉开或拉上窗帘时,窗帘的topWidht执行动画; bottomWidth使用SpringAnimation跟随topWidth,这产生了窗帘底部自由摆动的效果。 计算的swing分量基于顶点y分量的摆动强度。

image

CurtainEffect.qml文件中实现了窗帘效果,其中织物图像充当纹理源。 在QML代码中,调整了mesh属性以确保增加顶点的数量来提供更平滑的结果。

import QtQuick

ShaderEffect {
    anchors.fill: parent

    mesh: GridMesh {
        resolution: Qt.size(50, 50)
    }

    property real topWidth: open?width:20
    property real bottomWidth: topWidth
    property real amplitude: 0.1
    property bool open: false
    property variant source: effectSource

    Behavior on bottomWidth {
        SpringAnimation {
            easing.type: Easing.OutElastic;
            velocity: 250; mass: 1.5;
            spring: 0.5; damping: 0.05
        }
    }

    Behavior on topWidth {
        NumberAnimation { duration: 1000 }
    }


    ShaderEffectSource {
        id: effectSource
        sourceItem: effectImage;
        hideSource: true
    }

    Image {
        id: effectImage
        anchors.fill: parent
        source: "../assets/fabric.png"
        fillMode: Image.Tile
    }

    vertexShader: "curtain.vert.qsb"

    fragmentShader: "curtain.frag.qsb"
}

如下所示,顶点着色器根据topWidthbottomWidth属性重塑窗帘,并根据y坐标推断移位。 它还计算在片段着色器中使用的shade值。 shade属性通过location1中的附加输出传递。

#version 440

layout(location=0) in vec4 qt_Vertex;
layout(location=1) in vec2 qt_MultiTexCoord0;

layout(location=0) out vec2 qt_TexCoord0;
layout(location=1) out float shade;

layout(std140, binding=0) uniform buf {
    mat4 qt_Matrix;
    float qt_Opacity;
    
    float topWidth;
    float bottomWidth;
    float width;
    float height;
    float amplitude;
} ubuf;

out gl_PerVertex { 
    vec4 gl_Position;
};
 
void main() {
    qt_TexCoord0 = qt_MultiTexCoord0;

    vec4 shift = vec4(0.0, 0.0, 0.0, 0.0);
    float swing = (ubuf.topWidth - ubuf.bottomWidth) * (qt_Vertex.y / ubuf.height);
    shift.x = qt_Vertex.x * (ubuf.width - ubuf.topWidth + swing) / ubuf.width;

    shade = sin(21.9911486 * qt_Vertex.x / ubuf.width);
    shift.y = ubuf.amplitude * (ubuf.width - ubuf.topWidth + swing) * shade;

    gl_Position = ubuf.qt_Matrix * (qt_Vertex - shift);

    shade = 0.2 * (2.0 - shade) * ((ubuf.width - ubuf.topWidth + swing) / ubuf.width);
}

在下面的片段着色器中,shade作为location1中的输入被拾取,然后用于计算fragColorfragColor用于绘制相关像素。

#version 440

layout(location=0) in vec2 qt_TexCoord0;
layout(location=1) in float shade;

layout(location=0) out vec4 fragColor;

layout(std140, binding=0) uniform buf {
    mat4 qt_Matrix;
    float qt_Opacity;
    
    float topWidth;
    float bottomWidth;
    float width;
    float height;
    float amplitude;
} ubuf;

layout(binding=1) uniform sampler2D source;

void main() {
    highp vec4 color = texture(source, qt_TexCoord0);
    color.rgb *= 1.0 - shade;
    fragColor = color;
}

QML动画和将变量从顶点着色器传递到片段着色器的组合演示了如何将QML和着色器一起使用来构建复杂的动画效果。

curtaindemo.qml文件本身使用的特效展示如下。

import QtQuick

Item {
    id: root
    width: background.width; height: background.height


    Image {
        id: background
        anchors.centerIn: parent
        source: '../assets/background.png'
    }

    Text {
        anchors.centerIn: parent
        font.pixelSize: 48
        color: '#efefef'
        text: 'Qt 6 Book'
    }

    CurtainEffect {
        id: curtain
        anchors.fill: parent
    }

    MouseArea {
        anchors.fill: parent
        onClicked: curtain.open = !curtain.open
    }
}

窗帘是通过窗帘特效上的自定义open属性打开的。 当用户单击或触摸该区域时,使用MouseArea来触发窗帘的打开和关闭。

最后更新: 1/15/2022, 11:25:48 PM