QML Book 第九章 着色器渲染 2

9.5 顶点着色器

顶点着色器可用于操纵着色器效果提供的顶点。在正常情况下,着色效果有 4 个顶点(左上角 top-left,右上角 top-right,左下角 bottom-left 和右下角 bottom-righ)。报告的每个顶点都是来自 vec4 的类型。为了可视化顶点着色器,我们将编写一个缩放效果。这种效果通常用于使矩形窗口区域缩放到一个点。

genieeffect

** 设置场景 **

首先我们将再次设置我们的场景。

import QtQuick 2.5

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

这提供了一个具有深色背景和使用图像作为源纹理的着色器效果的场景。原始图像在我们的缩放效果产生的图像上不可见。另外,我们在与着色器效果相同的几何体上添加了一个黑色矩形,因此我们可以更好地感知我们需要点击以实现还原效果的位置。

geniescene

缩放效果是通过点击图像触发的,这是在覆盖缩放效果的鼠标区域中定义的。在 onClicked 处理方法中,我们将自定义布尔属性 minimized。稍后我们将使用此属性实现切换缩放的效果。

** 最小化和恢复原样 **

在我们设置场景之后,我们定义一个类型为 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。我们早已讨论过的变量。 另外,我们将着色器效果的最小化,宽度和高度变量链接到我们的顶点着色器代码中。在主函数中,我们将当前纹理坐标存储在我们的 qt_TexCoord0 中,使其可用于片段着色器。现在我们复制当前位置并修改顶点的 x 和 y 位置:

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

mix(...) 功能在第 3 个参数提供的点(0.0-1.0)上的前 2 个参数之间提供线性插值。所以在我们的例子中,我们根据当前最小化值,在当前 y 位置和高度之间插入 y,与 x 类似。请记住,最小值是由我们的连续动画控制,并从 0.0 到 1.0(反之亦然)。

genieminimize

上面所产生的效果不是真正的缩放效果,但已经迈出了我们的第一步。

** 简单的弯曲 **

至此我们完成了顶点的 x 和 y 分量的最小化。现在我们要稍微修改 x 操作,并根据当前的 y 值进行修改。 所需的变化相当小。 y 位置如前所述计算。x 位置的插值现在取决于顶点 y 位置:

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

这导致当 y 位置较大时向 x 方向趋向于宽度。换句话说,上面的 2 个顶点根本不受影响,因为它们的 y 位置为 0,而较低的两个顶点 x 位置都朝向宽度弯曲,因此它们朝向相同的 x 位置弯曲。

geniebending
import QtQuick 2.5

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;

** 更好的弯曲效果 **

由于目前的弯曲情况并不令人满意,我们会增加几个部分来改善情况。首先我们增强我们的动画来支持自己的弯曲属性。这是必要的,因为弯曲应该立即发生,并且 y 最小化应该被延迟。两个动画的总和相同(300 + 700 + 1000 和 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 }
            }
        }

另外,为了使弯曲成为平滑曲线,x 位置上的 y 效应不会由 0..1 的弯曲函数修改,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 的范围区间。

curve

最直观的变化是增加我们的顶点数量。可以使用网格来增加使用的顶点:

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

着色器效果现在具有 16x16 顶点的相等分布式网格,而不是之前使用的 2×2 个顶点。这使得顶点之间的插值看起来更加平滑。

geniesmoothbending

我们可以看到正在使用的曲线的影响,因为弯曲结束时很好地平滑。这是弯曲效果最强的地方。

** 选择一边 **

作为最终的增强,我们希望能够切换侧面。一方面,缩放效果消失了。到目前为止,它总是朝着 width 方向消失。通过添加一个 side 属性,我们可以将其修改成 0 和 width 之间的点。

ShaderEffect {
    ...
    property real side: 0.5

    vertexShader: "
        ...
        uniform highp float side;
        ...
        pos.x = mix(qt_Vertex.x, side * width, t * bend);
    "
}
geniehalfside

** 打包缩放效果 **

最后要做的是很好地打包我们的效果。为此,我们将缩放效果代码提取到一个名为 GenieEffect 的组件中。它具有着色器作为根元素。我们删除鼠标区域,因为这不应该在组件内,因为效果的触发可以被最小化(minimized)属性代替。

import QtQuick 2.5

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.5

Rectangle {
    width: 480; height: 240
    color: '#1e1e1e'

    GenieEffect {
        source: Image { source: 'assets/lighthouse.jpg' }
        MouseArea {
            anchors.fill: parent
            onClicked: parent.minimized = !parent.minimized
        }
    }
}

我们通过删除我们的背景矩形来简化代码,我们将图像直接分配给效果,而不是将其加载到独立的图像元素中。

9.6 帷幕效果

在自定义着色效果的最后一个例子中,我们想给你带来帷幕效果。2011年5月首次发布此效果作为 Qt 着色器效果实验室 的一部分。

curtain

当时我(原作者)真的很喜欢这些效果,帷幕效果是我最喜欢的。我特别帷幕怎样打开和怎样隐藏背景物体。

只是一个机器人的背景,窗帘实际上是一个名为fabric.jpg的图像,它是着色器效果的来源。效果使用顶点着色器摆动窗帘,并使用片段着色器提供一些阴影。下面是一个简单的图表,让我们更好地了解代码。

curtain_diagram

窗帘的波浪色调通过帘幕宽度上的 7 个上/下(7 * PI = 21.99 ...)的 sin 曲线计算。另一个重要的部分是秋千。 当窗帘打开或关闭时,窗帘的顶部宽度是动画的。bottomWidth 遵循 topWidth 与 SpringAnimation。通过这一点,我们创造了窗帘底部摆动的效果。计算的摆动提供了在顶点的 y 分量上内插的这种摆动的强度。

窗帘效果位于 CurtainEffect.qml 组件中,其中织物图像用作纹理源。在这里使用着色器没有任何新意义,只是在片段着色器中处理顶点着色器中的 gl_Position 和 gl_FragColor 的不同方法。

import QtQuick 2.5

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.png"
        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.5

Item {
    id: root
    width: background.width; height: background.height


    Image {
        id: background
        anchors.centerIn: parent
        source: 'assets/background.png'
    }

    Text {
        anchors.centerIn: parent
        font.pixelSize: 48
        color: '#efefef'
        text: 'Qt5 Cadaques'
    }

    CurtainEffect {
        id: curtain
        anchors.fill: parent
    }

    MouseArea {
        anchors.fill: parent
        onClicked: curtain.open = !curtain.open
    }
}

窗帘通过窗帘效果的定制 open 属性打开。我们使用 MouseArea 来触发窗帘的打开和关闭。

9.7 Qt GraphicsEffect 库

图形效果库是着色器效果的集合。准备由 Qt 开发商制作。这是一个很好的工具集,可用于我们的应用程序,但也是学习如何构建着色器的重要来源。

图形效果库带有一个所谓的手动测试平台,这是一个交互式发现不同效果的好工具。

测试程序位于 $QTDIR/qtgraphicaleffects/tests/manual/testbed 目录下。

graphicseffectstestbed

效果库包含约 20 种效果。效果列表和简短描述可以在下面找到。

** 图形效果列表 **

Graphics Effects List

以下是使用 Blur 类别中的 FastBlur 效果的示例:

import QtQuick 2.5
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
            }
        }
    }
}

左边的图像是原始图像。单击右侧的图像会切换模糊属性,并在 1 秒内将模糊半径从 0 到 32 的动画效果。

fastblur

本章使用的图片资源:

background
background@2x
bug
butterfly
coastline
fabric
fabric@2x
lighthouse
longroad
tulips

本章完,欢迎提出建议和指正翻译问题。

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

推荐阅读更多精彩内容

  • 9.着色器渲染(Shader Effects) 本章的作者:jryannel ** 注意: **最新的构建时间:2...
    赵者也阅读 933评论 0 0
  • 第三章 管线一览 本章我们会学到什么 OpenGL管线的每个阶段做什么的 如果连接着色器和固定功能管线阶段 如果创...
    葭五阅读 6,209评论 2 18
  • 模具工程内部衔接钳工,发号指令。外部对接项目与客户,接收任务。它是模具部的一个枢纽,也如整个模房作战指挥部。 那么...
    止戈魏阅读 395评论 0 0
  • 国王(一) 大家可不要误会,现代社会可不存在什么国王,英国还有,不过一般是女王,在下是一个纯爷们,只是很不巧姓了一...
    邮差国阅读 317评论 0 0