注意
最后一次构建:2014年1月20日下午18:00。
这章的源代码能够在assetts folder找到。
http://labs.qt.nokia.com/2012/02/02/qt-graphical-effects-in-qt-labs/
http://labs.qt.nokia.com/2011/05/03/qml-shadereffectitem-on-qgraphicsview/
http://qt-project.org/doc/qt-4.8/declarative-shadereffects.html
http://www.opengl.org/registry/doc/GLSLangSpec.4.20.6.clean.pdf
http://www.khronos.org/registry/gles/specs/2.0/GLSL_ES_Specification_1.0.17.pdf
着色器允许我们利用SceneGraph的接口直接调用在强大的GPU上运行的OpenGL来创建渲染效果。着色器使用ShaderEffect与ShaderEffectSource元素来实现。着色器本身的算法使用OpenGL Shading Language(OpenGL着色语言)来实现。
实际上这意味着你需要混合使用QML代码与着色器代码。执行时,会将着色器代码发送到GPU,并在GPU上编译执行。QML着色器元素(Shader QML Elements)允许你与OpenGL着色器程序的属性交互。
让我们首先来看看OpenGL着色器。
OpenGL着色器(OpenGL Shader)
OpenGL的渲染管线分为几个步骤。一个简单的OpenGL渲染管线将包含一个顶点着色器和一个片段着色器。
顶点着色器接收顶点数据,并且在程序最后赋值给gl_Position。然后,顶点将会被裁剪,转换和栅格化后作为像素输出。
片段(像素)进入片段着色器,进一步对片段操作并将结果的颜色赋值给gl_FragColor。顶点着色器调用多边形每个角的点(顶点=3D中的点),负责这些点的3D处理。片段(片度=像素)着色器调用每个像素并决定这个像素的颜色。
着色器元素(Shader Elements)
为了对着色器编程,Qt Quick提供了两个元素。ShaderEffectSource与ShaderEffect。ShaderEffect将会使用自定义的着色器,ShaderEffectSource可以将一个QML元素渲染为一个纹理然后再渲染这个纹理。由于ShaderEffect能够应用自定义的着色器到它的矩形几何形状,并且能够使用在着色器中操作资源。一个资源可以是一个图片,它被作为一个纹理或者着色器资源。
默认下着色器使用这个资源并且不作任何改变进行渲染。
import QtQuick 2.0
Rectangle {
width: 480; height: 240
color: '#1e1e1e'
Row {
anchors.centerIn: parent
spacing: 20
Image {
id: sourceImage
width: 80; height: width
source: 'assets/tulips.jpg'
}
ShaderEffect {
id: effect
width: 80; height: width
property variant source: sourceImage
}
ShaderEffect {
id: effect2
width: 80; height: width
// the source where the effect shall be applied to
property variant source: sourceImage
// default vertex shader code
vertexShader: "
uniform highp mat4 qt_Matrix;
attribute highp vec4 qt_Vertex;
attribute highp vec2 qt_MultiTexCoord0;
varying highp vec2 qt_TexCoord0;
void main() {
qt_TexCoord0 = qt_MultiTexCoord0;
gl_Position = qt_Matrix * qt_Vertex;
}"
// default fragment shader code
fragmentShader: "
varying highp vec2 qt_TexCoord0;
uniform sampler2D source;
uniform lowp float qt_Opacity;
void main() {
gl_FragColor = texture2D(source, qt_TexCoord0) * qt_Opacity;
}"
}
}
}
在上边这个例子中,我们在一行中显示了3张图片,第一张是原始图片,第二张使用默认的着色器渲染出来的图片,第三张使用了Qt5源码中默认的顶点与片段着色器的代码进行渲染的图片。
注意
如果你不想看到原始图片,而只想看到被着色器渲染后的图片,你可以设置Image为不可见(visible:false)。着色器仍然会使用图片数据,但是图像元素(Image Element)将不会被渲染。
让我们仔细看看着色器代码。
vertexShader: "
uniform highp mat4 qt_Matrix;
attribute highp vec4 qt_Vertex;
attribute highp vec2 qt_MultiTexCoord0;
varying highp vec2 qt_TexCoord0;
void main() {
qt_TexCoord0 = qt_MultiTexCoord0;
gl_Position = qt_Matrix * qt_Vertex;
}"
着色器代码来自Qt这边的一个字符串,绑定了顶点着色器(vertexShader)与片段着色器(fragmentShader)属性。每个着色器代码必须有一个main(){….}函数,它将被GPU执行。Qt已经默认提供了以qt_开头的变量。
下面是这些变量简短的介绍:
uniform-在处理过程中不能够改变的值。
attribute-连接外部数据
varying-着色器之间的共享数据
highp-高精度值
lowp-低精度值
mat4-4x4浮点数(float)矩阵
vec2-包含两个浮点数的向量
sampler2D-2D纹理
float-浮点数
可以查看OpenGL ES 2.0 API Quick Reference Card获得更多信息。
现在我们可以更好的理解下面这些变量:
qt_Matrix:model-view-projection(模型-视图-投影)矩阵
qt_Vertex:当前顶点坐标
qt_MultiTexCoord0:纹理坐标
qt_TexCoord0:共享纹理坐标
我们已经有可以使用的投影矩阵(projection matrix),当前顶点与纹理坐标。纹理坐标与作为资源(source)的纹理相关。在main()函数中,我们保存纹理坐标,留在后面的片段着色器中使用。每个顶点着色器都需要赋值给gl_Postion,在这里使用项目矩阵乘以顶点,得到我们3D坐标系中的点。
片段着色器从顶点着色器中接收我们的纹理坐标,这个纹理仍然来自我们的QML资源属性(source property)。在着色器代码与QML之间传递变量是如此的简单。此外我们的透明值,在着色器中也可以使用,变量是qt_Opacity。每
个片段着色器需要给gl_FragColor变量赋值,在这里默认着色器代码使用资源纹理(source texture)的像素颜色与透明值相乘。
fragmentShader: "
varying highp vec2 qt_TexCoord0;
uniform sampler2D source;
uniform lowp float qt_Opacity;
void main() {
gl_FragColor = texture2D(source, qt_TexCoord0) * qt_Opacity;
}"
在后面的例子中,我们将会展示一些简单的着色器例子。首先我们会集中在片段着色器上,然后在回到顶点着色器上。
片段着色器(Fragement Shader)
片段着色器调用每个需要渲染的像素。我们将开发一个红色透镜,它将会增加图片的红色通道的值。
配置场景(Setting up the scene)
首先我们配置我们的场景,在区域中央使用一个网格显示我们的源图片(source image)。
import QtQuick 2.0
Rectangle {
width: 480; height: 240
color: '#1e1e1e'
Grid {
anchors.centerIn: parent
spacing: 20
rows: 2; columns: 4
Image {
id: sourceImage
width: 80; height: width
source: 'assets/tulips.jpg'
}
}
}
红色着色器(A red Shader)
下一步我们添加一个着色器,显示一个红色矩形框。由于我们不需要纹理,我们从顶点着色器中移除纹理。
vertexShader: "
uniform highp mat4 qt_Matrix;
attribute highp vec4 qt_Vertex;
void main() {
gl_Position = qt_Matrix * qt_Vertex;
}"
fragmentShader: "
uniform lowp float qt_Opacity;
void main() {
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0) * qt_Opacity;
}"
在片段着色器中,我们简单的给gl_FragColor赋值为vec4(1.0, 0.0, 0.0, 1.0),它代表红色, 并且不透明(alpha=1.0)。
使用纹理的红色着色器(A red shader with texture)
现在我们想要将这个红色应用在纹理的每个像素上。我们需要将纹理加回顶点着色器。由于我们不再在顶点着色器中做任何其它的事情,所以默认的顶点着色器已经满足我们的要求。
ShaderEffect {
id: effect2
width: 80; height: width
property variant source: sourceImage
visible: root.step>1
fragmentShader: "
varying highp vec2 qt_TexCoord0;
uniform sampler2D source;
uniform lowp float qt_Opacity;
void main() {
gl_FragColor = texture2D(source, qt_TexCoord0) * vec4(1.0, 0.0, 0.0, 1.0) * qt_Opacity;
}"
}
完整的着色器重新包含我们的源图片作为属性,由于我们没有特殊指定,使用默认的顶点着色器,我没有重写顶点着色器。
在片段着色器中,我们提取纹理片段texture2D(source,qt_TexCoord0),并且与红色一起应用。
红色通道属性(The red channel property)
这样的代码用来修改红色通道的值看起来不是很好,所以我们想要将这个值包含在QML这边。我们在ShaderEffect中增加一个redChannel属性,并在我们的片段着色器中申明一个uniform lowpfloat redChannel。这就是从一个着色器代码中标记一个值到QML这边的方法,非常简单。
ShaderEffect {
id: effect3
width: 80; height: width
property variant source: sourceImage
property real redChannel: 0.3
visible: root.step>2
fragmentShader: "
varying highp vec2 qt_TexCoord0;
uniform sampler2D source;
uniform lowp float qt_Opacity;
uniform lowp float redChannel;
void main() {
gl_FragColor = texture2D(source, qt_TexCoord0) * vec4(redChannel, 1.0, 1.0, 1.0) * qt_Opacity;
}"
}
为了让这个透镜更真实,我们改变vec4颜色为vec4(redChannel, 1.0, 1.0, 1.0),这样其它颜色与1.0相乘,只有红色部分使用我们的redChannel变量。
红色通道的动画(The red channel animated)
由于redChannel属性仅仅是一个正常的属性,我们也可以像其它QML中的属性一样使用动画。我们使用QML属性在GPU上改变这个值,来影响我们的着色器,这真酷!
ShaderEffect {
id: effect4
width: 80; height: width
property variant source: sourceImage
property real redChannel: 0.3
visible: root.step>3
NumberAnimation on redChannel {
from: 0.0; to: 1.0; loops: Animation.Infinite; duration: 4000
}
fragmentShader: "
varying highp vec2 qt_TexCoord0;
uniform sampler2D source;
uniform lowp float qt_Opacity;
uniform lowp float redChannel;
void main() {
gl_FragColor = texture2D(source, qt_TexCoord0) * vec4(redChannel, 1.0, 1.0, 1.0) * qt_Opacity;
}"
}
下面是最后的结果。
在这4秒内,第二排的着色器红色通道的值从0.0到1.0。图片从没有红色信息(0.0 red)到一个正常的图片(1.0 red)。
波浪效果(Wave Effect)
在这个更加复杂的例子中,我们使用片段着色器创建一个波浪效果。波浪的形成是基于sin曲线,并且它影响了使用的纹理坐标的颜色。
import QtQuick 2.0
Rectangle {
width: 480; height: 240
color: '#1e1e1e'
Row {
anchors.centerIn: parent
spacing: 20
Image {
id: sourceImage
width: 160; height: width
source: "assets/coastline.jpg"
}
ShaderEffect {
width: 160; height: width
property variant source: sourceImage
property real frequency: 8
property real amplitude: 0.1
property real time: 0.0
NumberAnimation on time {
from: 0; to: Math.PI*2; duration: 1000; loops: Animation.Infinite
}
fragmentShader: "
varying highp vec2 qt_TexCoord0;
uniform sampler2D source;
uniform lowp float qt_Opacity;
uniform highp float frequency;
uniform highp float amplitude;
uniform highp float time;
void main() {
highp vec2 pulse = sin(time - frequency * qt_TexCoord0);
highp vec2 coord = qt_TexCoord0 + amplitude * vec2(pulse.x, -pulse.x);
gl_FragColor = texture2D(source, coord) * qt_Opacity;
}"
}
}
}
波浪的计算是基于一个脉冲与纹理坐标的操作。我们使用一个基于当前时间与使用的纹理坐标的sin波浪方程式来实现脉冲。
highp vec2 pulse = sin(time - frequency * qt_TexCoord0);
离开了时间的因素,我们仅仅只有扭曲,而不是像波浪一样运动的扭曲。
我们使用不同的纹理坐标作为颜色。
highp vec2 coord = qt_TexCoord0 + amplitude * vec2(pulse.x, -pulse.x);
纹理坐标受我们的x脉冲值影响,结果就像一个移动的波浪。
如果我们没有在片段着色器中使用像素的移动,这个效果可以首先考虑使用顶点着色器来完成。
顶点着色器(Vertex Shader)
顶点着色器用来操作ShaderEffect提供的顶点。正常情况下,ShaderEffect有4个顶点(左上top-left,右上top-right,左下bottom-left,右下bottom-right)。每个顶点使用vec4类型记录。为了实现顶点着色器的可视化,我们将编写一个吸收的效果。这个效果通常被用来让一个矩形窗口消失为一个点。
配置场景(Setting up the scene)
首先我们再一次配置场景。
import QtQuick 2.0
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
}
}
}
这个场景使用了一个黑色背景,并且提供了一个使用图片作为资源纹理的ShaderEffect。使用image元素的原图片是不可见的,只是给我们的吸收效果提供资源。此外我们在ShaderEffect的位置添加了一个同样大小的黑色矩形框,这样我们可以更加明确的知道我们需要点击哪里来重置效果。
点击图片将会触发效果,MouseArea覆盖了ShaderEffect。在onClicked操作中,我们绑定了自定义的布尔变量属性minimized。我们稍后使用这个属性来触发效果。
最小化与正常化(Minimize and normalize)
在我们配置好场景后,我们定义一个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。我们在之前已经讨论过这些变量。此外我们从ShaderEffect中链接minimize,width与height的值到我们的顶点着色器代码中。在main函数中,我们将当前纹理值保存在qt_TexCoord()中,让它在片段着色器中可用。现在我们拷贝当前位置,并修改顶点的x,y的位置。
highp vec4 pos = qt_Vertex;
pos.y = mix(qt_Vertex.y, height, minimize);
pos.x = mix(qt_Vertex.x, width, minimize);
mix(…)函数提供了一种在两个参数之间(0.0到1.0)的线性插值的算法。在我们的例子中,在当前y值与高度值之间基于minimize的值插值获得y值,x的值获取类似。记住minimize的值是由我们的连续动画控制,并且在0.0到1.0之间(反之亦然)。
这个结果的效果不是真正吸收效果,但是已经能朝着这个目标完成了一大步。
基础弯曲(Primitive Bending)
我们已经完成了最小化我们的坐标。现在我们想要修改一下对x值的操作,让它依赖当前的y值。这个改变很简单。y值计算在前。x值的插值基于当前顶点的y坐标。
highp float t = pos.y / height;
pos.x = mix(qt_Vertex.x, width, t * minimize);
这个结果造成当y值比较大时,x的位置更靠近width的值。也就是说上面2个顶点根本不受影响,它们的y值始终为0,下面两个顶点的x坐标值更靠近width的值,它们最后转向同一个x值。
import QtQuick 2.0
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;
更好的弯曲(Better Bending)
现在简单的弯曲并不能真正的满足我们的要求,我们将添加几个部件来提升它的效果。首先我们增加动画,支持一个自定义的弯曲属性。这是非常必要的,由于弯曲立即发生,y值的最小化需要被推迟。两个动画在同一持续时间计算总和(300+700+100与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 }
}
}
此外,为了使弯曲更加平滑,不再使用y值影响x值的弯曲函数,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)来增加顶点:
mesh: GridMesh { resolution: Qt.size(16, 16) }
现在ShaderEffect被分布为16x16顶点的网格,替换了之前2x2的顶点。这样顶点之间的插值将会看起来更加平滑。
你可以看见曲线的变化,在最后让弯曲变得非常平滑。这让弯曲有了更加强大的效果。
侧面收缩(Choosing Sides)
最后一个增强,我们希望能够收缩边界。边界朝着吸收的点消失。直到现在它总是在朝着width值的点消失。添加一个边界属性,我们能够修改这个点在0到width之间。
ShaderEffect {
...
property real side: 0.5
vertexShader: "
...
uniform highp float side;
...
pos.x = mix(qt_Vertex.x, side * width, t * bend);
"
}
包装(Packing)
最后将我们的效果包装起来。将我们吸收效果的代码提取到一个叫做GenieEffect的自定义组件中。它使用ShaderEffect作为根元素。移除掉MouseArea,这不应该放在组件中。绑定minimized属性来触发效果。
import QtQuick 2.0
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.0
Rectangle {
width: 480; height: 240
color: '#1e1e1e'
GenieEffect {
source: Image { source: 'assets/lighthouse.jpg' }
MouseArea {
anchors.fill: parent
onClicked: parent.minimized = !parent.minimized
}
}
}
我们简化了代码,移除了背景矩形框,直接使用图片完成效果,替换了在一个单独的图像元素中加载它。
剧幕效果(Curtain Effect)
在最后的自定义效果例子中,我们将带来一个剧幕效果。这个效果是2011年5月Qt实验室发布的着色器效果中的一部分。目前网址已经转到blog.qt.digia.com,不知道还能不能找到。
当时我非常喜欢这些效果,剧幕效果是我最喜爱的一个。我喜欢剧幕打开然后遮挡后面的背景对象。
我将代码移植适配到Qt5上,这非常简单。同时我做了一些简化让它能够更好的展示。如果你对整个例子有兴趣,可以访问Qt实验室的博客。
只有一个小组件作为背景,剧幕实际上是一张图片,叫做fabric.jpg,它是ShaderEffect的资源。整个效果使用顶点着色器来摆动剧幕,使用片段着色器提供阴影的效果。下面是一个简单的图片,让你更加容易理解代码。
剧幕的波形阴影通过一个在剧幕宽度上的sin曲线使用7的振幅来计算(7*PI=221.99..)另一个重要的部分是摆动,当剧幕打开或者关闭时,使用动画来播放剧幕的topWidth。bottomWidth使用SpringAnimation来跟随topWidth变化。这样我们就能创建出底部摆动的剧幕效果。计算得到的swing提供了摇摆的强度,用来对顶点的y值进行插值。
剧幕效果放在CurtainEffect.qml组件中,fabric图像作为纹理资源。在阴影的使用上没有新的东西加入,唯一不同的是在顶点着色器中操作gl_Postion和片段着色器中操作gl_FragColor。
import QtQuick 2.0
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.jpg"
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.0
Rectangle {
id: root
width: 480; height: 240
color: '#1e1e1e'
Image {
anchors.centerIn: parent
source: 'assets/wiesn.jpg'
}
CurtainEffect {
id: curtain
anchors.fill: parent
}
MouseArea {
anchors.fill: parent
onClicked: curtain.open = !curtain.open
}
}
剧幕效果通过自定义的open属性打开。我们使用了一个MouseArea来触发打开和关闭剧幕。
Qt图像效果库(Qt GraphicsEffect Library)
图像效果库是一个着色器效果的集合,是由Qt开发者提供制作的。它是一个很好的工具,你可以将它应用在你的程序中,它也是一个学习如何创建着色器的例子。
图像效果库附带了一个手动测试平台,这个工具可以帮助你测试发现不同的效果
测试工具在$QTDIR/qtgraphicaleffects/tests/manual/testbed下。
效果库包含了大约20种效果,下面是效果列表和一些简短的描述。
种类 | 效果 | 描述 |
---|---|---|
混合(Blend) | 混合(Blend) | 使用混合模式合并两个资源项 |
颜色(Color) | 亮度与对比度(BrightnessContrast) | 调整亮度与对比度 |
着色(Colorize) | 设置HSL颜色空间颜色 | |
颜色叠加(ColorOverlay) | 应用一个颜色层 | |
降低饱和度(Desaturate) | 减少颜色饱和度 | |
伽马调整(GammaAdjust) | 调整发光度 | |
色调饱和度(HueSaturation) | 调整HSL颜色空间颜色 | |
色阶调整(LevelAdjust) | 调整RGB颜色空间颜色 | |
渐变(Gradient) | 圆锥渐变(ConicalGradient) | 绘制一个圆锥渐变 |
线性渐变(LinearGradient) | 绘制一个线性渐变 | |
射线渐变(RadialGradient) | 绘制一个射线渐变 | |
失真(Distortion) | 置换(Displace) | 按照指定的置换源移动源项的像素 |
阴影(Drop Shadow) | 阴影 (DropShadow) | 绘制一个阴影 |
内阴影(InnerShadow) | 绘制一个内阴影 | |
模糊 (Blur) | 快速模糊(FastBlur) | 应用一个快速模糊效果 |
高斯模糊(GaussianBlur) | 应用一个高质量模糊效果 | |
蒙版模糊(MaskedBlur) | 应用一个多种强度的模糊效果 | |
递归模糊(RecursiveBlur) | 重复模糊,提供一个更强的模糊效果 | |
运动模糊(Motion Blur) | 方向模糊(DirectionalBlur) | 应用一个方向的运动模糊效果 |
放射模糊(RadialBlur) | 应用一个放射运动模糊效果 | |
变焦模糊(ZoomBlur) | 应用一个变焦运动模糊效果 | |
发光(Glow) | 发光(Glow) | 绘制一个外发光效果 |
矩形发光(RectangularGlow) | 绘制一个矩形外发光效果 | |
蒙版(Mask) | 透明蒙版(OpacityMask) | 使用一个源项遮挡另一个源项 |
阈值蒙版(ThresholdMask) | 使用一个阈值,一个源项遮挡另一个源项 |
下面是一个使用快速模糊效果的例子:
import QtQuick 2.0
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
}
}
}
}
左边是原图片。点击右边的图片将会触发blurred属性,模糊在1秒内从0到32。左边显示模糊后的图片。
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 2291184112@qq.com