9.5 顶点着色器
顶点着色器可用于操纵着色器效果提供的顶点。在正常情况下,着色效果有 4 个顶点(左上角 top-left,右上角 top-right,左下角 bottom-left 和右下角 bottom-righ)。报告的每个顶点都是来自 vec4 的类型。为了可视化顶点着色器,我们将编写一个缩放效果。这种效果通常用于使矩形窗口区域缩放到一个点。
** 设置场景 **
首先我们将再次设置我们的场景。
import QtQuick 2.5
Rectangle {
width: 480; height: 240
color: '#1e1e1e'
Image {
id: sourceImage
width: 160; height: width
source: "assets/lighthouse.jpg"
visible: false
}
Rectangle {
width: 160; height: width
anchors.centerIn: parent
color: '#333333'
}
ShaderEffect {
id: genieEffect
width: 160; height: width
anchors.centerIn: parent
property variant source: sourceImage
property bool minimized: false
MouseArea {
anchors.fill: parent
onClicked: genieEffect.minimized = !genieEffect.minimized
}
}
}
这提供了一个具有深色背景和使用图像作为源纹理的着色器效果的场景。原始图像在我们的缩放效果产生的图像上不可见。另外,我们在与着色器效果相同的几何体上添加了一个黑色矩形,因此我们可以更好地感知我们需要点击以实现还原效果的位置。
缩放效果是通过点击图像触发的,这是在覆盖缩放效果的鼠标区域中定义的。在 onClicked 处理方法中,我们将自定义布尔属性 minimized。稍后我们将使用此属性实现切换缩放的效果。
** 最小化和恢复原样 **
在我们设置场景之后,我们定义一个类型为 real 的属性称为 minimize,该属性将包含我们最小化的当前值。该值将从 0.0 到 1.0 不等,并由顺序动画进行控制。
property real minimize: 0.0
SequentialAnimation on minimize {
id: animMinimize
running: genieEffect.minimized
PauseAnimation { duration: 300 }
NumberAnimation { to: 1; duration: 700; easing.type: Easing.InOutSine }
PauseAnimation { duration: 1000 }
}
SequentialAnimation on minimize {
id: animNormalize
running: !genieEffect.minimized
NumberAnimation { to: 0; duration: 700; easing.type: Easing.InOutSine }
PauseAnimation { duration: 1300 }
}
动画由最小化(minimized)属性的值引发。现在我们已经设置了所有要准备的环境,我们终于可以看看我们的顶点着色器了。
vertexShader: "
uniform highp mat4 qt_Matrix;
attribute highp vec4 qt_Vertex;
attribute highp vec2 qt_MultiTexCoord0;
varying highp vec2 qt_TexCoord0;
uniform highp float minimize;
uniform highp float width;
uniform highp float height;
void main() {
qt_TexCoord0 = qt_MultiTexCoord0;
highp vec4 pos = qt_Vertex;
pos.y = mix(qt_Vertex.y, height, minimize);
pos.x = mix(qt_Vertex.x, width, minimize);
gl_Position = qt_Matrix * pos;
}"
在我们的例子中,为每个顶点调用顶点着色器四次。提供默认的 qt 定义参数,如qt_Matrix,qt_Vertex,qt_MultiTexCoord0,qt_TexCoord0。我们早已讨论过的变量。 另外,我们将着色器效果的最小化,宽度和高度变量链接到我们的顶点着色器代码中。在主函数中,我们将当前纹理坐标存储在我们的 qt_TexCoord0 中,使其可用于片段着色器。现在我们复制当前位置并修改顶点的 x 和 y 位置:
highp vec4 pos = qt_Vertex;
pos.y = mix(qt_Vertex.y, height, minimize);
pos.x = mix(qt_Vertex.x, width, minimize);
mix(...) 功能在第 3 个参数提供的点(0.0-1.0)上的前 2 个参数之间提供线性插值。所以在我们的例子中,我们根据当前最小化值,在当前 y 位置和高度之间插入 y,与 x 类似。请记住,最小值是由我们的连续动画控制,并从 0.0 到 1.0(反之亦然)。
上面所产生的效果不是真正的缩放效果,但已经迈出了我们的第一步。
** 简单的弯曲 **
至此我们完成了顶点的 x 和 y 分量的最小化。现在我们要稍微修改 x 操作,并根据当前的 y 值进行修改。 所需的变化相当小。 y 位置如前所述计算。x 位置的插值现在取决于顶点 y 位置:
highp float t = pos.y / height;
pos.x = mix(qt_Vertex.x, width, t * minimize);
这导致当 y 位置较大时向 x 方向趋向于宽度。换句话说,上面的 2 个顶点根本不受影响,因为它们的 y 位置为 0,而较低的两个顶点 x 位置都朝向宽度弯曲,因此它们朝向相同的 x 位置弯曲。
import QtQuick 2.5
Rectangle {
width: 480; height: 240
color: '#1e1e1e'
Image {
id: sourceImage
width: 160; height: width
source: "assets/lighthouse.jpg"
visible: false
}
Rectangle {
width: 160; height: width
anchors.centerIn: parent
color: '#333333'
}
ShaderEffect {
id: genieEffect
width: 160; height: width
anchors.centerIn: parent
property variant source: sourceImage
property real minimize: 0.0
property bool minimized: false
SequentialAnimation on minimize {
id: animMinimize
running: genieEffect.minimized
PauseAnimation { duration: 300 }
NumberAnimation { to: 1; duration: 700; easing.type: Easing.InOutSine }
PauseAnimation { duration: 1000 }
}
SequentialAnimation on minimize {
id: animNormalize
running: !genieEffect.minimized
NumberAnimation { to: 0; duration: 700; easing.type: Easing.InOutSine }
PauseAnimation { duration: 1300 }
}
vertexShader: "
uniform highp mat4 qt_Matrix;
uniform highp float minimize;
uniform highp float height;
uniform highp float width;
attribute highp vec4 qt_Vertex;
attribute highp vec2 qt_MultiTexCoord0;
varying highp vec2 qt_TexCoord0;
void main() {
qt_TexCoord0 = qt_MultiTexCoord0;
// M1>>
highp vec4 pos = qt_Vertex;
pos.y = mix(qt_Vertex.y, height, minimize);
highp float t = pos.y / height;
pos.x = mix(qt_Vertex.x, width, t * minimize);
gl_Position = qt_Matrix * pos;
** 更好的弯曲效果 **
由于目前的弯曲情况并不令人满意,我们会增加几个部分来改善情况。首先我们增强我们的动画来支持自己的弯曲属性。这是必要的,因为弯曲应该立即发生,并且 y 最小化应该被延迟。两个动画的总和相同(300 + 700 + 1000 和 700 + 1300)。
property real bend: 0.0
property bool minimized: false
// change to parallel animation
ParallelAnimation {
id: animMinimize
running: genieEffect.minimized
SequentialAnimation {
PauseAnimation { duration: 300 }
NumberAnimation {
target: genieEffect; property: 'minimize';
to: 1; duration: 700;
easing.type: Easing.InOutSine
}
PauseAnimation { duration: 1000 }
}
// adding bend animation
SequentialAnimation {
NumberAnimation {
target: genieEffect; property: 'bend'
to: 1; duration: 700;
easing.type: Easing.InOutSine }
PauseAnimation { duration: 1300 }
}
}
另外,为了使弯曲成为平滑曲线,x 位置上的 y 效应不会由 0..1 的弯曲函数修改,pos.x 现在取决于新的弯曲属性动画:
highp float t = pos.y / height;
t = (3.0 - 2.0 * t) * t * t;
pos.x = mix(qt_Vertex.x, width, t * bend);
曲线以 0.0 值开始平滑曲线,然后平稳地向 1.0 值增长并停止。以下是指定范围内的功能图。我们只关心 0..1 的范围区间。
最直观的变化是增加我们的顶点数量。可以使用网格来增加使用的顶点:
mesh: GridMesh { resolution: Qt.size(16, 16) }
着色器效果现在具有 16x16 顶点的相等分布式网格,而不是之前使用的 2×2 个顶点。这使得顶点之间的插值看起来更加平滑。
我们可以看到正在使用的曲线的影响,因为弯曲结束时很好地平滑。这是弯曲效果最强的地方。
** 选择一边 **
作为最终的增强,我们希望能够切换侧面。一方面,缩放效果消失了。到目前为止,它总是朝着 width 方向消失。通过添加一个 side 属性,我们可以将其修改成 0 和 width 之间的点。
ShaderEffect {
...
property real side: 0.5
vertexShader: "
...
uniform highp float side;
...
pos.x = mix(qt_Vertex.x, side * width, t * bend);
"
}
** 打包缩放效果 **
最后要做的是很好地打包我们的效果。为此,我们将缩放效果代码提取到一个名为 GenieEffect 的组件中。它具有着色器作为根元素。我们删除鼠标区域,因为这不应该在组件内,因为效果的触发可以被最小化(minimized)属性代替。
import QtQuick 2.5
ShaderEffect {
id: genieEffect
width: 160; height: width
anchors.centerIn: parent
property variant source
mesh: GridMesh { resolution: Qt.size(10, 10) }
property real minimize: 0.0
property real bend: 0.0
property bool minimized: false
property real side: 1.0
ParallelAnimation {
id: animMinimize
running: genieEffect.minimized
SequentialAnimation {
PauseAnimation { duration: 300 }
NumberAnimation {
target: genieEffect; property: 'minimize';
to: 1; duration: 700;
easing.type: Easing.InOutSine
}
PauseAnimation { duration: 1000 }
}
SequentialAnimation {
NumberAnimation {
target: genieEffect; property: 'bend'
to: 1; duration: 700;
easing.type: Easing.InOutSine }
PauseAnimation { duration: 1300 }
}
}
ParallelAnimation {
id: animNormalize
running: !genieEffect.minimized
SequentialAnimation {
NumberAnimation {
target: genieEffect; property: 'minimize';
to: 0; duration: 700;
easing.type: Easing.InOutSine
}
PauseAnimation { duration: 1300 }
}
SequentialAnimation {
PauseAnimation { duration: 300 }
NumberAnimation {
target: genieEffect; property: 'bend'
to: 0; duration: 700;
easing.type: Easing.InOutSine }
PauseAnimation { duration: 1000 }
}
}
vertexShader: "
uniform highp mat4 qt_Matrix;
attribute highp vec4 qt_Vertex;
attribute highp vec2 qt_MultiTexCoord0;
uniform highp float height;
uniform highp float width;
uniform highp float minimize;
uniform highp float bend;
uniform highp float side;
varying highp vec2 qt_TexCoord0;
void main() {
qt_TexCoord0 = qt_MultiTexCoord0;
highp vec4 pos = qt_Vertex;
pos.y = mix(qt_Vertex.y, height, minimize);
highp float t = pos.y / height;
t = (3.0 - 2.0 * t) * t * t;
pos.x = mix(qt_Vertex.x, side * width, t * bend);
gl_Position = qt_Matrix * pos;
}"
}
我们现在可以这样使用该效果:
import QtQuick 2.5
Rectangle {
width: 480; height: 240
color: '#1e1e1e'
GenieEffect {
source: Image { source: 'assets/lighthouse.jpg' }
MouseArea {
anchors.fill: parent
onClicked: parent.minimized = !parent.minimized
}
}
}
我们通过删除我们的背景矩形来简化代码,我们将图像直接分配给效果,而不是将其加载到独立的图像元素中。
9.6 帷幕效果
在自定义着色效果的最后一个例子中,我们想给你带来帷幕效果。2011年5月首次发布此效果作为 Qt 着色器效果实验室 的一部分。
当时我(原作者)真的很喜欢这些效果,帷幕效果是我最喜欢的。我特别帷幕怎样打开和怎样隐藏背景物体。
只是一个机器人的背景,窗帘实际上是一个名为fabric.jpg的图像,它是着色器效果的来源。效果使用顶点着色器摆动窗帘,并使用片段着色器提供一些阴影。下面是一个简单的图表,让我们更好地了解代码。
窗帘的波浪色调通过帘幕宽度上的 7 个上/下(7 * PI = 21.99 ...)的 sin 曲线计算。另一个重要的部分是秋千。 当窗帘打开或关闭时,窗帘的顶部宽度是动画的。bottomWidth 遵循 topWidth 与 SpringAnimation。通过这一点,我们创造了窗帘底部摆动的效果。计算的摆动提供了在顶点的 y 分量上内插的这种摆动的强度。
窗帘效果位于 CurtainEffect.qml 组件中,其中织物图像用作纹理源。在这里使用着色器没有任何新意义,只是在片段着色器中处理顶点着色器中的 gl_Position 和 gl_FragColor 的不同方法。
import QtQuick 2.5
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: "
attribute highp vec4 qt_Vertex;
attribute highp vec2 qt_MultiTexCoord0;
uniform highp mat4 qt_Matrix;
varying highp vec2 qt_TexCoord0;
varying lowp float shade;
uniform highp float topWidth;
uniform highp float bottomWidth;
uniform highp float width;
uniform highp float height;
uniform highp float amplitude;
void main() {
qt_TexCoord0 = qt_MultiTexCoord0;
highp vec4 shift = vec4(0.0, 0.0, 0.0, 0.0);
highp float swing = (topWidth - bottomWidth) * (qt_Vertex.y / height);
shift.x = qt_Vertex.x * (width - topWidth + swing) / width;
shade = sin(21.9911486 * qt_Vertex.x / width);
shift.y = amplitude * (width - topWidth + swing) * shade;
gl_Position = qt_Matrix * (qt_Vertex - shift);
shade = 0.2 * (2.0 - shade ) * ((width - topWidth + swing) / width);
}"
fragmentShader: "
uniform sampler2D source;
varying highp vec2 qt_TexCoord0;
varying lowp float shade;
void main() {
highp vec4 color = texture2D(source, qt_TexCoord0);
color.rgb *= 1.0 - shade;
gl_FragColor = color;
}"
}
该效果用于 curtaindemo.qml 文件。
import QtQuick 2.5
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: 'Qt5 Cadaques'
}
CurtainEffect {
id: curtain
anchors.fill: parent
}
MouseArea {
anchors.fill: parent
onClicked: curtain.open = !curtain.open
}
}
窗帘通过窗帘效果的定制 open 属性打开。我们使用 MouseArea 来触发窗帘的打开和关闭。
9.7 Qt GraphicsEffect 库
图形效果库是着色器效果的集合。准备由 Qt 开发商制作。这是一个很好的工具集,可用于我们的应用程序,但也是学习如何构建着色器的重要来源。
图形效果库带有一个所谓的手动测试平台,这是一个交互式发现不同效果的好工具。
测试程序位于 $QTDIR/qtgraphicaleffects/tests/manual/testbed 目录下。
效果库包含约 20 种效果。效果列表和简短描述可以在下面找到。
** 图形效果列表 **
以下是使用 Blur 类别中的 FastBlur 效果的示例:
import QtQuick 2.5
import QtGraphicalEffects 1.0
Rectangle {
width: 480; height: 240
color: '#1e1e1e'
Row {
anchors.centerIn: parent
spacing: 16
Image {
id: sourceImage
source: "assets/tulips.jpg"
width: 200; height: width
sourceSize: Qt.size(parent.width, parent.height)
smooth: true
}
FastBlur {
width: 200; height: width
source: sourceImage
radius: blurred?32:0
property bool blurred: false
Behavior on radius {
NumberAnimation { duration: 1000 }
}
MouseArea {
id: area
anchors.fill: parent
onClicked: parent.blurred = !parent.blurred
}
}
}
}
左边的图像是原始图像。单击右侧的图像会切换模糊属性,并在 1 秒内将模糊半径从 0 到 32 的动画效果。
本章使用的图片资源:
本章完,欢迎提出建议和指正翻译问题。