# 顶点着色器
顶点着色器可用于操纵着色器效果提供的顶点。 在正常情况下,着色器效果有4个顶点(左上、右上、左下和右下),呈现的每个顶点都使用vec4
类型。 为了将顶点着色器可视化,将编写一个精灵特效。 此特效用于让矩形窗口区域变成为一个点后消失,就像精灵消失在灯中一样。
# 设置场景
首先,使用一幅图像和一个着色器效果设置场景。
import QtQuick
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。 稍后将使用此属性来切换效果。
# 最小化和标准化
在设置好场景后,定义了一个名为minimize的real类型的属性,该属性将包含最小化程度的当前值。 该值将在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属性的切换触发。 现在已经设置好了所有的环境,终于可以观察顶点着色器了。
#version 440
layout(location=0) in vec4 qt_Vertex;
layout(location=1) in vec2 qt_MultiTexCoord0;
layout(location=0) out vec2 qt_TexCoord0;
layout(std140, binding=0) uniform buf {
mat4 qt_Matrix;
float qt_Opacity;
float minimize;
float width;
float height;
} ubuf;
out gl_PerVertex {
vec4 gl_Position;
};
void main() {
qt_TexCoord0 = qt_MultiTexCoord0;
vec4 pos = qt_Vertex;
pos.y = mix(qt_Vertex.y, ubuf.height, ubuf.minimize);
pos.x = mix(qt_Vertex.x, ubuf.width, ubuf.minimize);
gl_Position = ubuf.qt_Matrix * pos;
}
在例子中,每个顶点调用顶点着色器四次。 提供了默认的qt定义参数,如qt_Matrix、qt_Vertex、qt_MultiTexCoord0、qt_TexCoord0,这些是之前已经讨论过的变量。 此外,将着色器效果中的minimize、width和height变量链接到顶点着色器代码中。 在main函数中,将当前纹理坐标存储在qt_TexCoord0 中,以使其可用于片段着色器。 现在复制当前位置并修改顶点的x和y位置:
vec4 pos = qt_Vertex;
pos.y = mix(qt_Vertex.y, ubuf.height, ubuf.minimize);
pos.x = mix(qt_Vertex.x, ubuf.width, ubuf.minimize);
mix(…)
提供了在前两个参数之间进行线性插值的函数,第3个参数提供(0.0-1.0)之间的插值比例的点。 因此,在此例子中,根据当前的最小值(ubuf.minimize
)在当前y位置和高度之间对y进行插值;对于x也类似这样。 请记住,最小化的值由顺序动画进行动画处理,并从0.0变动到1.0;反之亦然。
由此产生的效果并不是真正的精灵效果,但已经朝着它迈出了一大步。
# 原始的弯曲
那么,最小化了顶点的x和y分量。 现在,想稍微修改x的操作并使其取决于当前的y值。 所需的更改非常小,y位置如前所述一样计算,x位置的插值现在取决于顶点y的位置:
float t = pos.y / ubuf.height;
pos.x = mix(qt_Vertex.x, ubuf.width, t * minimize);
当y位置较大时,会导致x的位置趋于宽度(译者注释:ubuf.height
是一个定值,pos.y
越大,t
则越大;在下面的式子中,t * minimize
越大,根据线性插值的原理,则计算出来的pos.x
越接近ubuf.width
)。 换句话说,上面的2个顶点根本不受影响,因为它们的y位置为0(译者注释:pos.y
等于0的时候,t
也为0,那么在第二个式子中,t * minimize
为0,则pos.x
的值为qt_Vertex.x
,所以最上面的两个顶点不受影响。);而下面的两个顶点的x位置都向宽度弯曲,因此它们向相同的x位置弯曲(译者注释:在最下面的点,存在pos.y = ubuf.height
,=> t = 1
=> t * minimize = minimize
)。
# 更好的弯曲
由于弯曲效果并不是真正令人满意,目前将增加几个部分内容,以改善效果。
首先,增强动画以支持的弯曲特性。这是必要的,因为弯曲应立即发生,y值最小化应有短暂延迟。两个动画的总持续时间相同(300+700+1000和700+1300)。
首先从QML上添加bend
并设置其动画。
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 }
}
}
然后,将bend
添加到统一缓冲区ubuf
,并在着色器中使用它来实现更平滑的弯曲。
#version 440
layout(location=0) in vec4 qt_Vertex;
layout(location=1) in vec2 qt_MultiTexCoord0;
layout(location=0) out vec2 qt_TexCoord0;
layout(std140, binding=0) uniform buf {
mat4 qt_Matrix;
float qt_Opacity;
float minimize;
float width;
float height;
float bend;
} ubuf;
out gl_PerVertex {
vec4 gl_Position;
};
void main() {
qt_TexCoord0 = qt_MultiTexCoord0;
vec4 pos = qt_Vertex;
pos.y = mix(qt_Vertex.y, ubuf.height, ubuf.minimize);
float t = pos.y / ubuf.height;
t = (3.0 - 2.0 * t) * t * t;
pos.x = mix(qt_Vertex.x, ubuf.width, t * ubuf.bend);
gl_Position = ubuf.qt_Matrix * pos;
}
曲线在0.0值处开始平滑增长,然后增长到1.0值处停止。下面是是指定范围内函数的曲线图。对于此示例而言,只关注0到1范围的情况。
还需要增加顶点的数量,使用网格可以增加使用的顶点。
mesh: GridMesh { resolution: Qt.size(16, 16) }
着色器效果现在具有由16x16个顶点组成的均匀分布的网格,而不是以前使用的2x2个顶点。这使得顶点之间的插值看起来更加平滑。
可以看到所使用的曲线的影响,弯曲会在末端平滑。这是弯曲效果最强的地方。
# 选择边缘
最后,继续增强效果,希望能够改变精灵效果时两侧的边缘。 边缘朝着精灵效应消失的地方。 直到现在,它总是朝着宽度方向(译者注释:最下面的最右点,也就是在最下面线的长度方向)消失。 通过添加side
属性,可以将消失位置修改为0到宽度之间的点。
ShaderEffect {
...
property real side: 0.5
...
}
#version 440
layout(location=0) in vec4 qt_Vertex;
layout(location=1) in vec2 qt_MultiTexCoord0;
layout(location=0) out vec2 qt_TexCoord0;
layout(std140, binding=0) uniform buf {
mat4 qt_Matrix;
float qt_Opacity;
float minimize;
float width;
float height;
float bend;
float side;
} ubuf;
out gl_PerVertex {
vec4 gl_Position;
};
void main() {
qt_TexCoord0 = qt_MultiTexCoord0;
vec4 pos = qt_Vertex;
pos.y = mix(qt_Vertex.y, ubuf.height, ubuf.minimize);
float t = pos.y / ubuf.height;
t = (3.0 - 2.0 * t) * t * t;
pos.x = mix(qt_Vertex.x, ubuf.side * ubuf.width, t * ubuf.bend);
gl_Position = ubuf.qt_Matrix * pos;
}
# Packaging
最后要做的就是很好地打包效果。 为此,将精灵效果代码提取到一个名为GenieEffect
的自定义组件中。 它将着色器效果作为根元素。 移除了鼠标区域,它不应该在组件内部,其效果的触发可以通过minimized
属性进行切换。
// GenieEffect.qml
import QtQuick
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: "genieeffect.vert.qsb"
}
// genieeffect.vert
#version 440
layout(location=0) in vec4 qt_Vertex;
layout(location=1) in vec2 qt_MultiTexCoord0;
layout(location=0) out vec2 qt_TexCoord0;
layout(std140, binding=0) uniform buf {
mat4 qt_Matrix;
float qt_Opacity;
float minimize;
float width;
float height;
float bend;
float side;
} ubuf;
out gl_PerVertex {
vec4 gl_Position;
};
void main() {
qt_TexCoord0 = qt_MultiTexCoord0;
vec4 pos = qt_Vertex;
pos.y = mix(qt_Vertex.y, ubuf.height, ubuf.minimize);
float t = pos.y / ubuf.height;
t = (3.0 - 2.0 * t) * t * t;
pos.x = mix(qt_Vertex.x, ubuf.side * ubuf.width, t * ubuf.bend);
gl_Position = ubuf.qt_Matrix * pos;
}
现在可以像下面这样简单地使用效果:
import QtQuick
Rectangle {
width: 480; height: 240
color: '#1e1e1e'
GenieEffect {
source: Image { source: '../../assets/lighthouse.jpg' }
MouseArea {
anchors.fill: parent
onClicked: parent.minimized = !parent.minimized
}
}
}
通过删除背景矩形来简化代码,并将图像直接分配给效果,而不是将其加载到独立的图像元素中。