顶点着色器(Vertex Shader)

顶点着色器用来操作ShaderEffect提供的顶点。正常情况下,ShaderEffect有4个顶点(左上top-left,右上top-right,左下bottom-left,右下bottom-right)。每个顶点使用vec4类型记录。为了实现顶点着色器的可视化,我们将编写一个吸收的效果。这个效果通常被用来让一个矩形窗口消失为一个点。

顶点着色器(Vertex Shader) - 图1

配置场景(Setting up the scene)

首先我们再一次配置场景。

  1. import QtQuick 2.0
  2. Rectangle {
  3. width: 480; height: 240
  4. color: '#1e1e1e'
  5. Image {
  6. id: sourceImage
  7. width: 160; height: width
  8. source: "assets/lighthouse.jpg"
  9. visible: false
  10. }
  11. Rectangle {
  12. width: 160; height: width
  13. anchors.centerIn: parent
  14. color: '#333333'
  15. }
  16. ShaderEffect {
  17. id: genieEffect
  18. width: 160; height: width
  19. anchors.centerIn: parent
  20. property variant source: sourceImage
  21. property bool minimized: false
  22. MouseArea {
  23. anchors.fill: parent
  24. onClicked: genieEffect.minimized = !genieEffect.minimized
  25. }
  26. }
  27. }

这个场景使用了一个黑色背景,并且提供了一个使用图片作为资源纹理的ShaderEffect。使用image元素的原图片是不可见的,只是给我们的吸收效果提供资源。此外我们在ShaderEffect的位置添加了一个同样大小的黑色矩形框,这样我们可以更加明确的知道我们需要点击哪里来重置效果。

顶点着色器(Vertex Shader) - 图2

点击图片将会触发效果,MouseArea覆盖了ShaderEffect。在onClicked操作中,我们绑定了自定义的布尔变量属性minimized。我们稍后使用这个属性来触发效果。

最小化与正常化(Minimize and normalize)

在我们配置好场景后,我们定义一个real类型的属性,叫做minimize,这个属性包含了我们当前最小化的值。这个值在0.0到1.0之间,由一个连续的动画来控制它。

  1. property real minimize: 0.0
  2. SequentialAnimation on minimize {
  3. id: animMinimize
  4. running: genieEffect.minimized
  5. PauseAnimation { duration: 300 }
  6. NumberAnimation { to: 1; duration: 700; easing.type: Easing.InOutSine }
  7. PauseAnimation { duration: 1000 }
  8. }
  9. SequentialAnimation on minimize {
  10. id: animNormalize
  11. running: !genieEffect.minimized
  12. NumberAnimation { to: 0; duration: 700; easing.type: Easing.InOutSine }
  13. PauseAnimation { duration: 1300 }
  14. }

这个动画绑定了由minimized属性触发。现在我们已经配置好我们的环境,最后让我们看看顶点着色器的代码。

  1. vertexShader: "
  2. uniform highp mat4 qt_Matrix;
  3. attribute highp vec4 qt_Vertex;
  4. attribute highp vec2 qt_MultiTexCoord0;
  5. varying highp vec2 qt_TexCoord0;
  6. uniform highp float minimize;
  7. uniform highp float width;
  8. uniform highp float height;
  9. void main() {
  10. qt_TexCoord0 = qt_MultiTexCoord0;
  11. highp vec4 pos = qt_Vertex;
  12. pos.y = mix(qt_Vertex.y, height, minimize);
  13. pos.x = mix(qt_Vertex.x, width, minimize);
  14. gl_Position = qt_Matrix * pos;
  15. }"

顶点着色器被每个顶点调用,在我们这个例子中,一共调用了四次。默认下提供qt已定义的参数,如qt_Matrix,qt_Vertex,qt_MultiTexCoord0,qt_TexCoord0。我们在之前已经讨论过这些变量。此外我们从ShaderEffect中链接minimize,width与height的值到我们的顶点着色器代码中。在main函数中,我们将当前纹理值保存在qt_TexCoord()中,让它在片段着色器中可用。现在我们拷贝当前位置,并修改顶点的x,y的位置。

  1. highp vec4 pos = qt_Vertex;
  2. pos.y = mix(qt_Vertex.y, height, minimize);
  3. pos.x = mix(qt_Vertex.x, width, minimize);

mix(…)函数提供了一种在两个参数之间(0.0到1.0)的线性插值的算法。在我们的例子中,在当前y值与高度值之间基于minimize的值插值获得y值,x的值获取类似。记住minimize的值是由我们的连续动画控制,并且在0.0到1.0之间(反之亦然)。

顶点着色器(Vertex Shader) - 图3

这个结果的效果不是真正吸收效果,但是已经能朝着这个目标完成了一大步。

基础弯曲(Primitive Bending)

我们已经完成了最小化我们的坐标。现在我们想要修改一下对x值的操作,让它依赖当前的y值。这个改变很简单。y值计算在前。x值的插值基于当前顶点的y坐标。

  1. highp float t = pos.y / height;
  2. pos.x = mix(qt_Vertex.x, width, t * minimize);

这个结果造成当y值比较大时,x的位置更靠近width的值。也就是说上面2个顶点根本不受影响,它们的y值始终为0,下面两个顶点的x坐标值更靠近width的值,它们最后转向同一个x值。

顶点着色器(Vertex Shader) - 图4

  1. import QtQuick 2.0
  2. Rectangle {
  3. width: 480; height: 240
  4. color: '#1e1e1e'
  5. Image {
  6. id: sourceImage
  7. width: 160; height: width
  8. source: "assets/lighthouse.jpg"
  9. visible: false
  10. }
  11. Rectangle {
  12. width: 160; height: width
  13. anchors.centerIn: parent
  14. color: '#333333'
  15. }
  16. ShaderEffect {
  17. id: genieEffect
  18. width: 160; height: width
  19. anchors.centerIn: parent
  20. property variant source: sourceImage
  21. property real minimize: 0.0
  22. property bool minimized: false
  23. SequentialAnimation on minimize {
  24. id: animMinimize
  25. running: genieEffect.minimized
  26. PauseAnimation { duration: 300 }
  27. NumberAnimation { to: 1; duration: 700; easing.type: Easing.InOutSine }
  28. PauseAnimation { duration: 1000 }
  29. }
  30. SequentialAnimation on minimize {
  31. id: animNormalize
  32. running: !genieEffect.minimized
  33. NumberAnimation { to: 0; duration: 700; easing.type: Easing.InOutSine }
  34. PauseAnimation { duration: 1300 }
  35. }
  36. vertexShader: "
  37. uniform highp mat4 qt_Matrix;
  38. uniform highp float minimize;
  39. uniform highp float height;
  40. uniform highp float width;
  41. attribute highp vec4 qt_Vertex;
  42. attribute highp vec2 qt_MultiTexCoord0;
  43. varying highp vec2 qt_TexCoord0;
  44. void main() {
  45. qt_TexCoord0 = qt_MultiTexCoord0;
  46. // M1>>
  47. highp vec4 pos = qt_Vertex;
  48. pos.y = mix(qt_Vertex.y, height, minimize);
  49. highp float t = pos.y / height;
  50. pos.x = mix(qt_Vertex.x, width, t * minimize);
  51. gl_Position = qt_Matrix * pos;

更好的弯曲(Better Bending)

现在简单的弯曲并不能真正的满足我们的要求,我们将添加几个部件来提升它的效果。首先我们增加动画,支持一个自定义的弯曲属性。这是非常必要的,由于弯曲立即发生,y值的最小化需要被推迟。两个动画在同一持续时间计算总和(300+700+100与700+1300)。

  1. property real bend: 0.0
  2. property bool minimized: false
  3. // change to parallel animation
  4. ParallelAnimation {
  5. id: animMinimize
  6. running: genieEffect.minimized
  7. SequentialAnimation {
  8. PauseAnimation { duration: 300 }
  9. NumberAnimation {
  10. target: genieEffect; property: 'minimize';
  11. to: 1; duration: 700;
  12. easing.type: Easing.InOutSine
  13. }
  14. PauseAnimation { duration: 1000 }
  15. }
  16. // adding bend animation
  17. SequentialAnimation {
  18. NumberAnimation {
  19. target: genieEffect; property: 'bend'
  20. to: 1; duration: 700;
  21. easing.type: Easing.InOutSine }
  22. PauseAnimation { duration: 1300 }
  23. }
  24. }

此外,为了使弯曲更加平滑,不再使用y值影响x值的弯曲函数,pos.x现在依赖新的弯曲属性动画:

  1. highp float t = pos.y / height;
  2. t = (3.0 - 2.0 * t) * t * t;
  3. pos.x = mix(qt_Vertex.x, width, t * bend);

弯曲从0.0平滑开始,逐渐加快,在1.0时逐渐平滑。下面是这个函数在指定范围内的曲线图。对于我们,只需要关注0到1的区间。

顶点着色器(Vertex Shader) - 图5

想要获得最大化的视觉改变,需要增加我们的顶点数量。可以使用网眼(mesh)来增加顶点:

  1. mesh: GridMesh { resolution: Qt.size(16, 16) }

现在ShaderEffect被分布为16x16顶点的网格,替换了之前2x2的顶点。这样顶点之间的插值将会看起来更加平滑。

顶点着色器(Vertex Shader) - 图6

你可以看见曲线的变化,在最后让弯曲变得非常平滑。这让弯曲有了更加强大的效果。

侧面收缩(Choosing Sides)

最后一个增强,我们希望能够收缩边界。边界朝着吸收的点消失。直到现在它总是在朝着width值的点消失。添加一个边界属性,我们能够修改这个点在0到width之间。

  1. ShaderEffect {
  2. ...
  3. property real side: 0.5
  4. vertexShader: "
  5. ...
  6. uniform highp float side;
  7. ...
  8. pos.x = mix(qt_Vertex.x, side * width, t * bend);
  9. "
  10. }

顶点着色器(Vertex Shader) - 图7

包装(Packing)

最后将我们的效果包装起来。将我们吸收效果的代码提取到一个叫做GenieEffect的自定义组件中。它使用ShaderEffect作为根元素。移除掉MouseArea,这不应该放在组件中。绑定minimized属性来触发效果。

  1. import QtQuick 2.0
  2. ShaderEffect {
  3. id: genieEffect
  4. width: 160; height: width
  5. anchors.centerIn: parent
  6. property variant source
  7. mesh: GridMesh { resolution: Qt.size(10, 10) }
  8. property real minimize: 0.0
  9. property real bend: 0.0
  10. property bool minimized: false
  11. property real side: 1.0
  12. ParallelAnimation {
  13. id: animMinimize
  14. running: genieEffect.minimized
  15. SequentialAnimation {
  16. PauseAnimation { duration: 300 }
  17. NumberAnimation {
  18. target: genieEffect; property: 'minimize';
  19. to: 1; duration: 700;
  20. easing.type: Easing.InOutSine
  21. }
  22. PauseAnimation { duration: 1000 }
  23. }
  24. SequentialAnimation {
  25. NumberAnimation {
  26. target: genieEffect; property: 'bend'
  27. to: 1; duration: 700;
  28. easing.type: Easing.InOutSine }
  29. PauseAnimation { duration: 1300 }
  30. }
  31. }
  32. ParallelAnimation {
  33. id: animNormalize
  34. running: !genieEffect.minimized
  35. SequentialAnimation {
  36. NumberAnimation {
  37. target: genieEffect; property: 'minimize';
  38. to: 0; duration: 700;
  39. easing.type: Easing.InOutSine
  40. }
  41. PauseAnimation { duration: 1300 }
  42. }
  43. SequentialAnimation {
  44. PauseAnimation { duration: 300 }
  45. NumberAnimation {
  46. target: genieEffect; property: 'bend'
  47. to: 0; duration: 700;
  48. easing.type: Easing.InOutSine }
  49. PauseAnimation { duration: 1000 }
  50. }
  51. }
  52. vertexShader: "
  53. uniform highp mat4 qt_Matrix;
  54. attribute highp vec4 qt_Vertex;
  55. attribute highp vec2 qt_MultiTexCoord0;
  56. uniform highp float height;
  57. uniform highp float width;
  58. uniform highp float minimize;
  59. uniform highp float bend;
  60. uniform highp float side;
  61. varying highp vec2 qt_TexCoord0;
  62. void main() {
  63. qt_TexCoord0 = qt_MultiTexCoord0;
  64. highp vec4 pos = qt_Vertex;
  65. pos.y = mix(qt_Vertex.y, height, minimize);
  66. highp float t = pos.y / height;
  67. t = (3.0 - 2.0 * t) * t * t;
  68. pos.x = mix(qt_Vertex.x, side * width, t * bend);
  69. gl_Position = qt_Matrix * pos;
  70. }"
  71. }

你现在可以像这样简单的使用这个效果:

  1. import QtQuick 2.0
  2. Rectangle {
  3. width: 480; height: 240
  4. color: '#1e1e1e'
  5. GenieEffect {
  6. source: Image { source: 'assets/lighthouse.jpg' }
  7. MouseArea {
  8. anchors.fill: parent
  9. onClicked: parent.minimized = !parent.minimized
  10. }
  11. }
  12. }

我们简化了代码,移除了背景矩形框,直接使用图片完成效果,替换了在一个单独的图像元素中加载它。