QML Book 第九章 著色器渲染 1

9.著色器渲染(Shader Effects

本章的作者:jryannel

** 注意: **
最新的構(gòu)建時間:2016/03/21
這章的源代碼能夠在assetts folder找到。

下列內(nèi)容:

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

簡要介紹著色器效果,然后呈現(xiàn)著色效果及其使用。

著色器允許我們在 SceneGraph API 上創(chuàng)建出強大的渲染效果,直接利用在 GPU 上運行的 OpenGL 的強大功能。著色器使用 ShaderEffect 和
ShaderEffectSource 元素實現(xiàn)。著色器算法本身使用 OpenGL 著色語言實現(xiàn)。

實際上這意味著我們將 QML 代碼與著色器代碼混合在一起。執(zhí)行時將著色器代碼發(fā)送到 GPU,并在 GPU 上編譯并執(zhí)行。著色器 QML 元素允許我們通過屬性與 OpenGL 著色器實現(xiàn)進行交互。

我們先來看看 OpenGL 著色器是什么。

9.1 OpenGL 著色器

OpenGL 使用渲染流水線分割成幾個階段。一個簡化的 OpenGL 管道將包含一個頂點和片段著色器。

openglpipeline

頂點著色器接收頂點數(shù)據(jù),必須將其分配給程序結(jié)束時的 gl_Position。在下一階段,頂點被剪切,變換和光柵化以進行像素輸出。從那里,片段(像素)到達片段著色器,并且可以進一步被操縱,并且所得到的顏色需要被分配給 gl_FragColor。頂點著色器被稱為我們的多邊形的每個角點(頂點= 3D 中的點),并且負責(zé)對這些點的任何 3D 操縱。為每個像素調(diào)用片段(fragment = pixel)著色器,并確定該像素的顏色。

9.2 著色器元素

對于編程著色器 Qt Quick 提供了兩個元素。ShaderEffectSource 和 ShaderEffect。著色器效果應(yīng)用自定義著色器,著色器效果源將 QML 項呈現(xiàn)到紋理中并呈現(xiàn)。由于著色效果可以將自定義著色器應(yīng)用于其矩形形狀,并可使用源作為著色器操作。源可以是用作紋理或著色器效果源的圖像。

下面是默認著色器使用的源代碼,我們對其進行了簡單的修改:

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)設(shè)置 Image 為不可見。著色器仍然會使用圖片數(shù)據(jù),但是源圖像元素將不會被渲染和顯示。

我們來看看著色器代碼。

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(){...} 函數(shù),由 GPU 執(zhí)行。默認情況下,由 qt_ 開始的變量已經(jīng)由 Qt 提供。

這里有一個簡短的變量集:

變量 簡介
uniform 值在處理過程中不會改變
attribute 鏈接到外部數(shù)據(jù)
varying 著色器之間的共享值
highp 高精度
lowp 低精度
mat4 4x4 浮點矩陣
vec2 2=dim 浮點矢量
sampler2D 2D 紋理
float 浮動的標(biāo)量

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

現(xiàn)在我們可以更好地了解變量是什么意思了:

  • qt_Matrix: 模型視圖投影(model-view-projection)矩陣
  • qt_Vertex: 當(dāng)前頂點位置
  • qt_MultiTexCoord0: 紋理坐標(biāo)
  • qt_TexCoord0: 共享紋理坐標(biāo)

所以我們可以使用投影矩陣,當(dāng)前頂點和紋理坐標(biāo)。紋理坐標(biāo)與作為源的紋理相關(guān)。在 main() 函數(shù)中,我們存儲紋理坐標(biāo)以供以后在片段著色器中使用。每個頂點著色器需要分配 gl_Position,這是通過將項目矩陣與頂點相乘來完成的,我們的點在 3D 中。

片段著色器從頂點著色器接收我們的紋理坐標(biāo),并從 QML 源屬性接收紋理。應(yīng)注意,在著色器代碼和 QML 之間傳遞變量是多么容易和優(yōu)雅。另外,我們有著色器效果的不透明度可用作 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 片段著色器

每個像素都要渲染片段著色器。我們將開發(fā)一個小紅色鏡頭,這將增加圖像的紅色通道值。

** 設(shè)置場景 **
首先我們設(shè)置我們的場景,一個以場為中心的網(wǎng)格,并顯示我們的源圖像。

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

** 帶有紋理的紅色著色器 **

現(xiàn)在我們要將紅色應(yīng)用于每個紋理像素。為此,我們需要紋理返回頂點著色器。由于我們在頂點著色器中沒有做任何其他事情,所以默認頂點著色器對我們來說足夠了。

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

完整的著色器現(xiàn)在將我們的圖像源作為 variant 屬性返回,我們已經(jīng)省略了頂點著色器,如果沒有指定,則使用默認頂點著色器。

在片段著色器中,我們選擇紋理片段 texture2D(source,qt_TexCoord0) 并將紅色應(yīng)用于它。

redlense3

** 紅色通道屬性 **

對紅色通道值進行硬編碼并不是很好,所以我們想控制 QML 中的值。為此,我們在我們的著色器效果中添加一個 redChannel 屬性,并在我們的片段著色器中聲明一個 uniform lowp float redChannel。這就是為了從 QML 中可用的著色器代碼創(chuàng)建一個值。很簡單。

        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 中的所有屬性那樣應(yīng)用動畫效果 。所以我們可以使用 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;
                }"
        }

這里是最后的結(jié)果。

redlense5

第二行的圖片的著色器效果從 0.0 到 1.0,持續(xù)時間為 4 秒。因此,圖像從沒有紅色信息(0.0 紅色)到正常圖像(1.0 紅色)。

9.4 波形效果

在這個更復(fù)雜的例子中,我們將使用片段著色器創(chuàng)建一個波形效果。波形基于sin曲線,并且它影響了使用的紋理坐標(biāo)的顏色。

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

波的計算基于脈沖和紋理坐標(biāo)操縱。我們使用一個基于當(dāng)前時間與使用的紋理坐標(biāo)的 sin 波浪方程式來實現(xiàn)脈沖:

highp vec2 pulse = sin(time - frequency * qt_TexCoord0);

如果沒有時間因素,我們就會只有一個扭曲的圖像,而不是像波浪那樣移動的波紋效果。

對于顏色,我們使用不同紋理坐標(biāo)的顏色:

highp vec2 coord = qt_TexCoord0 + amplitude * vec2(pulse.x, -pulse.x);

紋理坐標(biāo)受到脈沖 x 值的影響。其結(jié)果是一個波浪的移動效果。

wave

另外,如果我們還沒有在這個片段著色器中移動像素,那么效果將首先像頂點著色器的作業(yè)一樣。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 9.5 頂點著色器 頂點著色器可用于操縱著色器效果提供的頂點。在正常情況下,著色效果有 4 個頂點(左上角 top...
    趙者也閱讀 1,823評論 0 0
  • 轉(zhuǎn)自http://m.blog.csdn.net/qq_31518167/article/details/5198...
    柚子ziheLiu閱讀 2,101評論 0 0
  • 第三章 管線一覽 本章我們會學(xué)到什么 OpenGL管線的每個階段做什么的 如果連接著色器和固定功能管線階段 如果創(chuàng)...
    葭五閱讀 6,514評論 2 18
  • 紋理(Textures) 我們已經(jīng)了解到,我們可以為每個頂點使用顏色來增加圖形的細節(jié),從而創(chuàng)建出有趣的圖像。但是通...
    IceMJ閱讀 5,854評論 2 13
  • 離離十三載,念念染窗臺 滴滴刻心海,落落哀空釵。 生生凝愛戀,日日織暖棉。 千千躬無間,萬萬夜無眠。 風(fēng)風(fēng)聽默,雨...
    一沐一流閱讀 89評論 0 2

友情鏈接更多精彩內(nèi)容