# 窗帘特效
在最后一个自定义着色器效果的示例中,将学习窗帘特效。 此特效于2011年5月首次发布,它是用于着色器特效的Qt实验室(Qt labs for shader effects) (opens new window)的一部分。
当时,作者很喜欢这些特效,其中最喜欢窗帘特效。 作者仅是喜欢窗帘打开和隐藏背景物体的方式。
本章中,特效已针对Qt 6进行了调整。它进行了轻微的简化,以便更好地展示。
表示窗帘图像的名称为fabric.png
。 该效果使用顶点着色器来回摆动窗帘,并使用片段着色器应用阴影以显示织物如何折叠。
下图显示了此着色器的工作原理。 这些顶部的波纹是通过具有7个周期的sin函数(7*PI=21.99…)曲线计算得到的;另一部分是下面的摆动。 当拉开或拉上窗帘时,窗帘的topWidht
执行动画; bottomWidth
使用SpringAnimation
跟随topWidth
,这产生了窗帘底部自由摆动的效果。 计算的swing
分量基于顶点y分量的摆动强度。
在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"
}
如下所示,顶点着色器根据topWidth
和bottomWidth
属性重塑窗帘,并根据y坐标推断移位。 它还计算在片段着色器中使用的shade
值。 shade
属性通过location
1中的附加输出传递。
#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
作为location
1中的输入被拾取,然后用于计算fragColor
,fragColor
用于绘制相关像素。
#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
来触发窗帘的打开和关闭。