QML Book 第九章 着色器渲染 1

9.着色器渲染(Shader Effects

本章的作者:jryannel

** 注意: **
最新的构建时间:2016/03/21
这章的源代码能够在assetts folder找到。

下列内容:

http://doc.qt.io/qt-5/qml-qtquick-shadereffect.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
http://doc.qt.io/qt-5/qtquick-shadereffects-example.html

简要介绍着色器效果,然后呈现着色效果及其使用。

着色器允许我们在 SceneGraph API 上创建出强大的渲染效果,直接利用在 GPU 上运行的 OpenGL 的强大功能。着色器使用 ShaderEffect 和
ShaderEffectSource 元素实现。着色器算法本身使用 OpenGL 着色语言实现。

实际上这意味着我们将 QML 代码与着色器代码混合在一起。执行时将着色器代码发送到 GPU,并在 GPU 上编译并执行。着色器 QML 元素允许我们通过属性与 OpenGL 着色器实现进行交互。

我们先来看看 OpenGL 着色器是什么。

9.1 OpenGL 着色器

OpenGL 使用渲染流水线分割成几个阶段。一个简化的 OpenGL 管道将包含一个顶点和片段着色器。

openglpipeline

顶点着色器接收顶点数据,必须将其分配给程序结束时的 gl_Position。在下一阶段,顶点被剪切,变换和光栅化以进行像素输出。从那里,片段(像素)到达片段着色器,并且可以进一步被操纵,并且所得到的颜色需要被分配给 gl_FragColor。顶点着色器被称为我们的多边形的每个角点(顶点= 3D 中的点),并且负责对这些点的任何 3D 操纵。为每个像素调用片段(fragment = pixel)着色器,并确定该像素的颜色。

9.2 着色器元素

对于编程着色器 Qt Quick 提供了两个元素。ShaderEffectSource 和 ShaderEffect。着色器效果应用自定义着色器,着色器效果源将 QML 项呈现到纹理中并呈现。由于着色效果可以将自定义着色器应用于其矩形形状,并可使用源作为着色器操作。源可以是用作纹理或着色器效果源的图像。

下面是默认着色器使用的源代码,我们对其进行了简单的修改:

import QtQuick 2.5

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;
                }"
        }
    }
}
defaultshader

在上面的例子中,我们有一排3张图像。第一个是真实的形象。第二个使用默认着色器渲染,第三个渲染使用从 Qt 5 源代码提取的片段和顶点的默认着色器代码。

** 注意: **
如果我们不想看到源图片,而只想看到被着色器渲染后的图片,我们可以通过(visible:false)设置 Image 为不可见。着色器仍然会使用图片数据,但是源图像元素将不会被渲染和显示。

我们来看看着色器代码。

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 浮点矩阵
vec2 2=dim 浮点矢量
sampler2D 2D 纹理
float 浮动的标量

更好的参考是 OpenGL ES 2.0 API 快速参考卡

现在我们可以更好地了解变量是什么意思了:

  • qt_Matrix: 模型视图投影(model-view-projection)矩阵
  • qt_Vertex: 当前顶点位置
  • qt_MultiTexCoord0: 纹理坐标
  • qt_TexCoord0: 共享纹理坐标

所以我们可以使用投影矩阵,当前顶点和纹理坐标。纹理坐标与作为源的纹理相关。在 main() 函数中,我们存储纹理坐标以供以后在片段着色器中使用。每个顶点着色器需要分配 gl_Position,这是通过将项目矩阵与顶点相乘来完成的,我们的点在 3D 中。

片段着色器从顶点着色器接收我们的纹理坐标,并从 QML 源属性接收纹理。应注意,在着色器代码和 QML 之间传递变量是多么容易和优雅。另外,我们有着色器效果的不透明度可用作 qt_Opacity。每个片段着色器需要分配 gl_FragColor 变量,这是通过从源纹理中选择像素并将其与不透明度相乘来在默认着色器代码中完成的。

fragmentShader: "
    varying highp vec2 qt_TexCoord0;
    uniform sampler2D source;
    uniform lowp float qt_Opacity;
    void main() {
        gl_FragColor = texture2D(source, qt_TexCoord0) * qt_Opacity;
    }"

在接下来的例子中,我们将使用一些简单的着色器技巧。首先我们专注于片段着色器,然后我们再回到顶点着色器。

9.3 片段着色器

每个像素都要渲染片段着色器。我们将开发一个小红色镜头,这将增加图像的红色通道值。

** 设置场景 **
首先我们设置我们的场景,一个以场为中心的网格,并显示我们的源图像。

import QtQuick 2.5

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'
        }
    }
}
redlense1

** 红色着色器 **

接下来,我们将添加一个着色器,它通过为每个片段提供一个红色的颜色值来显示一个红色的矩形。

            fragmentShader: "
                uniform lowp float qt_Opacity;
                void main() {
                    gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0) * qt_Opacity;
                }"

在片段着色器中,我们简单地为每个片段分配一个表示具有完全不透明度(alpha = 1.0)的红色的 vec4(1.0,0.0,0.0,1.0)到 gl_FragColor 属性。

redlense2

** 带有纹理的红色着色器 **

现在我们要将红色应用于每个纹理像素。为此,我们需要纹理返回顶点着色器。由于我们在顶点着色器中没有做任何其他事情,所以默认顶点着色器对我们来说足够了。

        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;
                }"
        }

完整的着色器现在将我们的图像源作为 variant 属性返回,我们已经省略了顶点着色器,如果没有指定,则使用默认顶点着色器。

在片段着色器中,我们选择纹理片段 texture2D(source,qt_TexCoord0) 并将红色应用于它。

redlense3

** 红色通道属性 **

对红色通道值进行硬编码并不是很好,所以我们想控制 QML 中的值。为此,我们在我们的着色器效果中添加一个 redChannel 属性,并在我们的片段着色器中声明一个 uniform lowp float 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 变量。

redlense4

** 红色通道动画 **

由于 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;
                }"
        }

这里是最后的结果。

redlense5

第二行的图片的着色器效果从 0.0 到 1.0,持续时间为 4 秒。因此,图像从没有红色信息(0.0 红色)到正常图像(1.0 红色)。

9.4 波形效果

在这个更复杂的例子中,我们将使用片段着色器创建一个波形效果。波形基于sin曲线,并且它影响了使用的纹理坐标的颜色。

import QtQuick 2.5

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 值的影响。其结果是一个波浪的移动效果。

wave

另外,如果我们还没有在这个片段着色器中移动像素,那么效果将首先像顶点着色器的作业一样。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,287评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,346评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,277评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,132评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,147评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,106评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,019评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,862评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,301评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,521评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,682评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,405评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,996评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,651评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,803评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,674评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,563评论 2 352

推荐阅读更多精彩内容

  • 9.5 顶点着色器 顶点着色器可用于操纵着色器效果提供的顶点。在正常情况下,着色效果有 4 个顶点(左上角 top...
    赵者也阅读 1,722评论 0 0
  • 转自http://m.blog.csdn.net/qq_31518167/article/details/5198...
    柚子ziheLiu阅读 1,905评论 0 0
  • 第三章 管线一览 本章我们会学到什么 OpenGL管线的每个阶段做什么的 如果连接着色器和固定功能管线阶段 如果创...
    葭五阅读 6,249评论 2 18
  • 纹理(Textures) 我们已经了解到,我们可以为每个顶点使用颜色来增加图形的细节,从而创建出有趣的图像。但是通...
    IceMJ阅读 5,632评论 2 13
  • 离离十三载,念念染窗台 滴滴刻心海,落落哀空钗。 生生凝爱恋,日日织暖棉。 千千躬无间,万万夜无眠。 风风听默,雨...
    一沐一流阅读 74评论 0 2