Browse Source

添加第九章shader effect

cwc 10 years ago
parent
commit
4e1753a30a

+ 8 - 0
SUMMARY.md

@@ -58,4 +58,12 @@
    * [粒子控制(Affecting Particles)](particle_simulations/affecting_particles.md)
    * [粒子组(Particle Group)](particle_simulations/particle_group.md)
    * [总结(Summary)](particle_simulations/summary.md)
+* [Shader Effect](shader_effect/README.md)
+   * [OpenGL着色器(OpenGL Shader)](shader_effect/openglopengl_shader.md)
+   * [着色器元素(Shader Elements)](shader_effect/shader_elements.md)
+   * [片段着色器(Fragement Shader)](shader_effect/fragement_shader.md)
+   * [波浪效果(Wave Effect)](shader_effect/wave_effect.md)
+   * [顶点着色器(Vertex Shader)](shader_effect/vertex_shader.md)
+   * [剧幕效果(Curtain Effect)](shader_effect/curtain_effect.md)
+   * [Qt图像效果库(Qt GraphicsEffect Library)](shader_effect/qtqt_graphicseffect_library.md)
 

+ 29 - 0
shader_effect/README.md

@@ -0,0 +1,29 @@
+# Shader Effect
+
+**注意**
+
+**最后一次构建:2014年1月20日下午18:00。**
+
+**这章的源代码能够在[assetts folder](http://qmlbook.org/assets)找到。**
+
+* 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
+
+* http://www.lighthouse3d.com/opengl/glsl/
+
+* http://wiki.delphigl.com/index.php/Tutorial_glsl
+
+* [Qt5Doc qtquick-shaders](http://doc.qt.nokia.com/5.0-snapshot/qtquick-shaders.html)
+
+着色器允许我们利用SceneGraph的接口直接调用在强大的GPU上运行的OpenGL来创建渲染效果。着色器使用ShaderEffect与ShaderEffectSource元素来实现。着色器本身的算法使用OpenGL Shading Language(OpenGL着色语言)来实现。
+
+实际上这意味着你需要混合使用QML代码与着色器代码。执行时,会将着色器代码发送到GPU,并在GPU上编译执行。QML着色器元素(Shader QML Elements)允许你与OpenGL着色器程序的属性交互。
+
+让我们首先来看看OpenGL着色器。

+ 128 - 0
shader_effect/curtain_effect.md

@@ -0,0 +1,128 @@
+# 剧幕效果(Curtain Effect)
+
+在最后的自定义效果例子中,我们将带来一个剧幕效果。这个效果是2011年5月Qt实验室发布的着色器效果中的一部分。目前网址已经转到blog.qt.digia.com,不知道还能不能找到。
+
+![](http://qmlbook.org/_images/curtain.png)
+
+当时我非常喜欢这些效果,剧幕效果是我最喜爱的一个。我喜欢剧幕打开然后遮挡后面的背景对象。
+
+我将代码移植适配到Qt5上,这非常简单。同时我做了一些简化让它能够更好的展示。如果你对整个例子有兴趣,可以访问Qt实验室的博客。
+
+只有一个小组件作为背景,剧幕实际上是一张图片,叫做fabric.jpg,它是ShaderEffect的资源。整个效果使用顶点着色器来摆动剧幕,使用片段着色器提供阴影的效果。下面是一个简单的图片,让你更加容易理解代码。
+
+![](http://qmlbook.org/_images/curtain_diagram.png)
+
+剧幕的波形阴影通过一个在剧幕宽度上的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来触发打开和关闭剧幕。

+ 135 - 0
shader_effect/fragement_shader.md

@@ -0,0 +1,135 @@
+# 片段着色器(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'
+        }
+    }
+}
+```
+
+![](http://qmlbook.org/_images/redlense1.png)
+
+**红色着色器(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)。
+
+![](http://qmlbook.org/_images/redlense2.png)
+
+**使用纹理的红色着色器(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),并且与红色一起应用。
+
+![](http://qmlbook.org/_images/redlense3.png)
+
+**红色通道属性(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变量。
+
+![](http://qmlbook.org/_images/redlense4.png)
+
+**红色通道的动画(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;
+                }"
+        }
+```
+
+下面是最后的结果。
+
+![](http://qmlbook.org/_images/redlense5.png)
+
+在这4秒内,第二排的着色器红色通道的值从0.0到1.0。图片从没有红色信息(0.0 red)到一个正常的图片(1.0 red)。

+ 8 - 0
shader_effect/openglopengl_shader.md

@@ -0,0 +1,8 @@
+# OpenGL着色器(OpenGL Shader)
+
+OpenGL的渲染管线分为几个步骤。一个简单的OpenGL渲染管线将包含一个顶点着色器和一个片段着色器。
+
+![](http://qmlbook.org/_images/openglpipeline.png)
+
+顶点着色器接收顶点数据,并且在程序最后赋值给gl_Position。然后,顶点将会被裁剪,转换和栅格化后作为像素输出。
+片段(像素)进入片段着色器,进一步对片段操作并将结果的颜色赋值给gl_FragColor。顶点着色器调用多边形每个角的点(顶点=3D中的点),负责这些点的3D处理。片段(片度=像素)着色器调用每个像素并决定这个像素的颜色。

+ 84 - 0
shader_effect/qtqt_graphicseffect_library.md

@@ -0,0 +1,84 @@
+# Qt图像效果库(Qt GraphicsEffect Library)
+
+图像效果库是一个着色器效果的集合,是由Qt开发者提供制作的。它是一个很好的工具,你可以将它应用在你的程序中,它也是一个学习如何创建着色器的例子。
+
+图像效果库附带了一个手动测试平台,这个工具可以帮助你测试发现不同的效果
+测试工具在$QTDIR/qtgraphicaleffects/tests/manual/testbed下。
+
+![](http://qmlbook.org/_images/graphicseffectstestbed.png)
+
+效果库包含了大约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。左边显示模糊后的图片。
+
+![](http://qmlbook.org/_images/fastblur.png)

+ 126 - 0
shader_effect/shader_elements.md

@@ -0,0 +1,126 @@
+# 着色器元素(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;
+                }"
+        }
+    }
+}
+```
+
+![](http://qmlbook.org/_images/defaultshader.png)
+
+在上边这个例子中,我们在一行中显示了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](http://www.khronos.org/opengles/sdk/docs/reference_cards/OpenGL-ES-2_0-Reference-card.pdf)获得更多信息。
+
+现在我们可以更好的理解下面这些变量:
+
+* 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;
+    }"
+```
+
+在后面的例子中,我们将会展示一些简单的着色器例子。首先我们会集中在片段着色器上,然后在回到顶点着色器上。

+ 359 - 0
shader_effect/vertex_shader.md

@@ -0,0 +1,359 @@
+# 顶点着色器(Vertex Shader)
+
+顶点着色器用来操作ShaderEffect提供的顶点。正常情况下,ShaderEffect有4个顶点(左上top-left,右上top-right,左下bottom-left,右下bottom-right)。每个顶点使用vec4类型记录。为了实现顶点着色器的可视化,我们将编写一个吸收的效果。这个效果通常被用来让一个矩形窗口消失为一个点。
+
+![](http://qmlbook.org/_images/genieeffect.png)
+
+**配置场景(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的位置添加了一个同样大小的黑色矩形框,这样我们可以更加明确的知道我们需要点击哪里来重置效果。
+
+![](http://qmlbook.org/_images/geniescene.png)
+
+点击图片将会触发效果,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之间(反之亦然)。
+
+![](http://qmlbook.org/_images/genieminimize.png)
+
+这个结果的效果不是真正吸收效果,但是已经能朝着这个目标完成了一大步。
+
+**基础弯曲(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值。
+
+![](http://qmlbook.org/_images/geniebending.png)
+
+```
+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的区间。
+
+![](http://qmlbook.org/_images/curve.png)
+
+想要获得最大化的视觉改变,需要增加我们的顶点数量。可以使用网眼(mesh)来增加顶点:
+
+```
+mesh: GridMesh { resolution: Qt.size(16, 16) }
+```
+
+现在ShaderEffect被分布为16x16顶点的网格,替换了之前2x2的顶点。这样顶点之间的插值将会看起来更加平滑。
+
+![](http://qmlbook.org/_images/geniesmoothbending.png)
+
+你可以看见曲线的变化,在最后让弯曲变得非常平滑。这让弯曲有了更加强大的效果。
+
+**侧面收缩(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);
+    "
+}
+```
+
+![](http://qmlbook.org/_images/geniehalfside.png)
+
+**包装(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
+        }
+    }
+}
+```
+
+我们简化了代码,移除了背景矩形框,直接使用图片完成效果,替换了在一个单独的图像元素中加载它。

+ 65 - 0
shader_effect/wave_effect.md

@@ -0,0 +1,65 @@
+# 波浪效果(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脉冲值影响,结果就像一个移动的波浪。
+
+![](http://qmlbook.org/_images/wave.png)
+
+如果我们没有在片段着色器中使用像素的移动,这个效果可以首先考虑使用顶点着色器来完成。