本系列所有文章可以在這里查看[http://blog.csdn.net/cloud_castle/article/category/2123873](http://blog.csdn.net/cloud_castle/article/category/2123873)
接上文[Qt5官方demo解析集11——Qt Quick Particles Examples - Affectors](http://blog.csdn.net/cloud_castle/article/details/33723715)
使用Emitter和Affectors強大的功能,我們已經可以構造出豐富多彩的粒子特效,但當這些功能還不能滿足我們的需要時,我們可以轉而采用CustomParticle取代ImageParticle,在CustomParticle中我們可以使用基于GLSL的渲染技術來創建自定義的粒子。
這個demo依然由一些小例子組成,不過比前兩個demo中的都要少,只有三個:

(1)Blur Particles
在這個例子中我們可以看到使用CustomParticle創建模糊化粒子的方法。運行效果如下:

個人感覺在這個演示中模糊效果并不是很清楚,于是換了一張圖片,并類似地使用不帶模糊化的ImageParticle來展示它們的不同:

可以看到,左邊圖為模糊化的CustomParticle,右邊為沒有模糊效果的ImageParticle,區別還是很明顯的。
由于筆者對OpenGL并不是很熟悉,如果有錯誤的地方,還請各位指正。
下面的部分內容來自[http://qmlbook.org/ch09/index.html](http://qmlbook.org/ch09/index.html), 在此表示感謝。
為了更深刻理解CustomParticle中的頂點著色器與片元著色器,我們先看一段這兩個著色器基本的代碼:
~~~
// 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;
}"
~~~
其中,vertexShader得到qt_TexCoord0紋理坐標供fragmentShader使用;
gl_Position為圖元各個坐標點的輸出;
texture2D()通過qt_TexCoord0從source中取得紋理值,乘上透明度作為gl_FragColor的片元顏色輸出。
注:如果不寫vertexShader,Qt將采用默認的頂點渲染器,即各點與圖像本身的位置不變。反之也是一樣。
可以看到我們定義了很多qt_XXX的變量,但它們并沒有被賦值。
實際上這是Qt為我們提供了一些提前定義好的變量。但是既然Qt已經定義了,我們為什么又要定義一遍呢?
這就涉及到QML與GLSL之間的相互映射了,不同與其他QML元素,著色器中的GLSL語句是不能夠直接使用QML所定義的屬性變量的。
但是QML中任何類型的屬性都可以通過在著色器語句中聲明相同名稱的屬性來映射到GLSL的變量中去。其類型一般為uniform或attribute。
也就是說,當我們在QML中定義了一個4維RGBA的QColor color,然后在GLSL中聲明一個uniform vec4 color,一個映射就被搭建起來了。
要知道QML中的絕大多數操作都是基于屬性完成的,那么通過這種映射,我們能夠很容易地創建豐富多彩的GLSL顯示效果。
我們用一個小例子來闡述這一點,注意redChannel屬性與source在fragmentShader中的使用:
~~~
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;
}"
}
~~~
我們也可以將類型聲明為var,Qt會自動幫我們完成轉換。下面的例子中大量使用了這種方法。
qt 預定義的變量均以qt_XXX這種格式來表示,比如
uniform mat4 qt_Matrix —— 提供了一個從根項目到ShaderEffect的變換矩陣;
uniform float qt_Opacity —— 提供項目的透明度;
attribute vec4 qt_Vertex ——提供頂點坐標,左上角為(0,0),后下角為(width,height);
attribute vec2 qt_MultiTexCoord0 —— 紋理坐標,左上角是(0,0),右下角為(1,1)等等。
由于兩個著色器的main(){}函數都是在GPU中執行的,這大大提升了我們的圖形渲染速度。
下面是GLSL的變量類型定義:
| uniform | value does not change during processing |
|--|--|
| attribute | linkage to external data |
| varying | shared value between shaders |
| highp | high precision value |
| lowp | low precision value |
| mat4 | 4x4 float matrix |
| vec2 | 2=dim float vector |
| sampler2D | 2D texture |
| float | floating scalar |
詳細的類型映射可以參考Manual中的ShaderEffect。
更多的信息可以參考[OpenGL ES 2.0 API Quick Reference Card](http://www.khronos.org/opengles/sdk/docs/reference_cards/OpenGL-ES-2_0-Reference-card.pdf)
看源碼時,我們先看一段額外的代碼,我們將其命名為vertexShader_addin,
你可以認為它是一個比我們上面的示例更為豐富一些的頂點著色器的基本代碼:
~~~
attribute highp vec2 qt_ParticlePos; // 這些是預定義好的屬性
attribute highp vec2 qt_ParticleTex;
attribute highp vec4 qt_ParticleData; // x = time, y = lifeSpan, z = size, w = endSize
attribute highp vec4 qt_ParticleVec; // x,y = constant velocity, z,w = acceleration
attribute highp float qt_ParticleR;
uniform highp mat4 qt_Matrix;
uniform highp float qt_Timestamp;
varying highp vec2 qt_TexCoord0; // 默認的紋理坐標
void defaultMain() {
qt_TexCoord0 = qt_ParticleTex;
highp float size = qt_ParticleData.z;
highp float endSize = qt_ParticleData.w;
highp float t = (qt_Timestamp - qt_ParticleData.x) / qt_ParticleData.y;
highp float currentSize = mix(size, endSize, t * t);
if (t < 0. || t > 1.)
currentSize = 0.;
highp vec2 pos = qt_ParticlePos
- currentSize / 2. + currentSize * qt_ParticleTex // adjust size
+ qt_ParticleVec.xy * t * qt_ParticleData.y // apply velocity vector..
+ 0.5 * qt_ParticleVec.zw * pow(t * qt_ParticleData.y, 2.);
gl_Position = qt_Matrix * vec4(pos.x, pos.y, 0, 1);
}
~~~
整個例子的源碼如下,blurparticle.qml:
~~~
import QtQuick 2.0
import QtQuick.Particles 2.0
Rectangle {
color: "white"
width: 240
height: 360
ParticleSystem {
id: sys
}
Emitter { // Emitter就不再多做介紹了,不熟悉的朋友可以查看前兩篇博文
system:sys
height: parent.height
emitRate: 1
lifeSpan: 12000
velocity: PointDirection {x:20;}
size: 128
}
ShaderEffectSource { // 著色效果源,用來指明需要著色的對象
id: theSource
sourceItem: theItem // 指明源對象
hideSource: true // 隱藏原圖
}
Image {
id: theItem
source: "../../images/starfish_1.png"
}
CustomParticle { // CustomParticle內部僅有兩個屬性,分別是vertexShader和fragmentShader(頂點著色器與片元著色器),如果你熟悉OpenGL以及GLSL應該對這兩個東西不陌生。這兩個屬性參數為"string",實際也就是GLSL的代碼。我們可以使用這兩個屬性來將CustomParticle渲染成各種自定義的效果
system: sys
//! [vertex]
vertexShader:" // 這里是我們的第一個頂點著色器
uniform lowp float qt_Opacity; // 定義一個只讀的低精度浮點型變量qt_Opacity
varying lowp float fFade; // 定義一個由vertex寫入,fragment讀出的低精度浮點型變量fFade
varying lowp float fBlur;
void main() { // GLSL中規定的程序入口
defaultMain(); // 該函數在vertexShader_addin中定義,我們需要先調用它
highp float t = (qt_Timestamp - qt_ParticleData.x) / qt_ParticleData.y; // 這些qt_XX也是在vertexShader_addin中映射過的,這里計算了粒子存在時間占生命周期的比例
highp float fadeIn = min(t * 10., 1.); // 比例t 從0變化到1時,fadeIn也從0變化到1
highp float fadeOut = 1. - max(0., min((t - 0.75) * 4., 1.)); // t 從0.75變化到1時,fadOut從1變化到0
fFade = fadeIn * fadeOut * qt_Opacity; // 該值越小,圖像透明度越高
fBlur = max(0.2 * t, t * qt_ParticleR); // 模糊系數
}
"
//! [vertex]
property variant source: theSource // 這里回到QML代碼,定義了類型為variant的屬性source以及blurred,為了下面的映射
property variant blurred: ShaderEffectSource { // 這里再次使用了ShaderEffectSource,并將返回值賦給我們的自定義屬性blurred
sourceItem: ShaderEffect { // sourceItem是其屬性成員之一,參數類型為Item,而ShaderEffect繼承于Item
width: theItem.width // 定義為圖像的高寬
height: theItem.height
property variant delta: Qt.size(0.0, 1.0 / height) // 定義變量增量,與高度負相關
property variant source: ShaderEffectSource { // 定義屬性source指向另一個ShaderEffectSource
sourceItem: ShaderEffect {
width: theItem.width
height: theItem.height
property variant delta: Qt.size(1.0 / width, 0.0) // 該增量與寬度相關
property variant source: theSource
fragmentShader: " // 片元著色器
uniform sampler2D source; // 從圖片源采集紋理數據,這里的source在QML代碼中定義
uniform lowp float qt_Opacity; // qt_Opacity由Qt預定義,從vertexShader傳入
uniform highp vec2 delta;
varying highp vec2 qt_TexCoord0;
void main() { // 然后我們在main()函數中定義每個像素點的像素值,下面的算式將每個像素點周圍的的值進行疊加來得到新的像素值,從而形成模糊的效果
gl_FragColor =(0.0538 * texture2D(source, qt_TexCoord0 - 3.182 * delta) // 使用第二個參數中的左邊對source中的紋理進行采樣
+ 0.3229 * texture2D(source, qt_TexCoord0 - 1.364 * delta)
+ 0.2466 * texture2D(source, qt_TexCoord0)
+ 0.3229 * texture2D(source, qt_TexCoord0 + 1.364 * delta)
+ 0.0538 * texture2D(source, qt_TexCoord0 + 3.182 * delta)) * qt_Opacity;
}"
}
}
fragmentShader: "
uniform sampler2D source;
uniform lowp float qt_Opacity;
uniform highp vec2 delta;
varying highp vec2 qt_TexCoord0;
void main() { // 第一次混合以寬度作為增量,第二次以高度作為增量。這里的source是已經被處理過一次的紋理源
gl_FragColor =(0.0538 * texture2D(source, qt_TexCoord0 - 3.182 * delta)
+ 0.3229 * texture2D(source, qt_TexCoord0 - 1.364 * delta)
+ 0.2466 * texture2D(source, qt_TexCoord0)
+ 0.3229 * texture2D(source, qt_TexCoord0 + 1.364 * delta)
+ 0.0538 * texture2D(source, qt_TexCoord0 + 3.182 * delta)) * qt_Opacity;
}"
}
}
//! [fragment]
fragmentShader: " // 最后將vertexShader中定義的數據送入fragmentShader中進行處理
uniform sampler2D source;
uniform sampler2D blurred;
varying highp vec2 qt_TexCoord0;
varying highp float fBlur;
varying highp float fFade;
void main() {
gl_FragColor = mix(texture2D(source, qt_TexCoord0), texture2D(blurred, qt_TexCoord0), min(1.0,fBlur*3.0)) * fFade; // 在每個像素點對源紋理紋理與模糊化紋理之間進行插值,并乘以透明度。
}"
//! [fragment]
}
}
~~~
(2)Fragment Shader
在上個例子我們對使用CustomParticle渲染一個外部png圖像有了一個大致印象,在這個例子中,Qt 向我們介紹了如何直接使用片元著色器來繪制粒子。

屏幕下方的話也揭示了這個例子的主題。
這個例子的層次的結構沒有上一個例子那么復雜,來看看吧,fragmentshader.qml:
~~~
import QtQuick 2.0
import QtQuick.Particles 2.0
ParticleSystem { // ParticleSystem作為根目錄
id: root
width: 320
height: 480
Rectangle { // 矩形背景
z: -1
anchors.fill: parent
color: "black"
Text { // 文字
anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
font.pixelSize: 14
color: "white"
text: "It's all in the fragment shader."
}
}
Emitter {
emitRate: 400
lifeSpan: 8000
size: 24
sizeVariation: 16
velocity: PointDirection {x: root.width/10; y: root.height/10;}
acceleration: PointDirection {x: -root.width/40; y: -root.height/40; xVariation: -root.width/20; yVariation: -root.width/20}
}
CustomParticle {
vertexShader:" // 頂點著色器
uniform lowp float qt_Opacity; // 變量定義
varying lowp float fFade;
varying highp vec2 fPos;
void main() { // 還記得上面的vertexShader_addin嗎,下面的只是將defaltMain()中的代碼提出來了
qt_TexCoord0 = qt_ParticleTex;
highp float size = qt_ParticleData.z;
highp float endSize = qt_ParticleData.w;
highp float t = (qt_Timestamp - qt_ParticleData.x) / qt_ParticleData.y;
highp float currentSize = mix(size, endSize, t * t);
if (t < 0. || t > 1.)
currentSize = 0.;
highp vec2 pos = qt_ParticlePos
- currentSize / 2. + currentSize * qt_ParticleTex // adjust size
+ qt_ParticleVec.xy * t * qt_ParticleData.y // apply velocity vector..
+ 0.5 * qt_ParticleVec.zw * pow(t * qt_ParticleData.y, 2.);
gl_Position = qt_Matrix * vec4(pos.x, pos.y, 0, 1); // 以上都是defaultMain()的代碼,之所以不用defaultMain(),是因為該函數中定義的一些變量在下面還要被繼續使用
highp float fadeIn = min(t * 20., 1.); // 與上一個例子類似的定義了與t相關的fadeIn與fadeOut
highp float fadeOut = 1. - max(0., min((t - 0.75) * 4., 1.));
fFade = fadeIn * fadeOut * qt_Opacity; // 得到粒子透明度的動態變化數值
fPos = vec2(pos.x/320., pos.y/480.); // 這里得到位置的二維矢量
}
"
//! [0]
fragmentShader: " // 片元著色器
varying highp vec2 fPos; // 傳參
varying lowp float fFade;
varying highp vec2 qt_TexCoord0;
void main() {//*2 because this generates dark colors mostly // 官方注釋
highp vec2 circlePos = qt_TexCoord0*2.0 - vec2(1.0,1.0); // qt_TexCoord0是一個內置的渲染坐標,這個算式將坐標原點轉移到了矩形中心,并放大了一倍
highp float dist = length(circlePos); // GLSL內置函數,用來求矢量長度
highp float circleFactor = max(min(1.0 - dist, 1.0), 0.0); // 在一個長度為2矩形框內,以長度為1在中心劃一個區域,那就是一個內切圈
gl_FragColor = vec4(fPos.x*2.0 - fPos.y, fPos.y*2.0 - fPos.x, fPos.x*fPos.y*2.0, 0.0) * circleFactor * fFade; // 最后將每個像素點的像素值乘上這個圓因子,使繪制出來的圓形越靠近中心RGB值越大,遠端小的RGB值被繪制為黑色,從而得到了顏色越來越淡的"圓形粒子"。由四維矢量vec4()的4個參數可以知道,x值越大R值越大,y值越大G值越大,x,y同時影響B的值。那么上方的粒子應該偏紅色,下方的粒子偏綠色,右下角的粒子為RGB值都大的紫色,湛藍色,深灰色等。運行的實際效果也如我們猜想的一樣。
}"
//! [0]
}
}
~~~
(3)Image Color
在這個例子中,Qt向我們展示了一副圖像被“粒子化”的過程,與我們之前用shape覆蓋圖像不同,這里的粒子顏色會隨著圖片中當前像素的變化而變化。

在屏幕上點擊過后,圖像會以粒子的形態展現出來,然后向四周發散。imagecolor.qml:
~~~
import QtQuick 2.0
import QtQuick.Particles 2.0
Rectangle { // 一個矩形用來作為窗口邊界
width: 400
height: 400
Rectangle { // 一個矩形又來限制圖像尺寸
id: root
color: "white"
width: 310
height: 300
anchors.centerIn: parent
ParticleSystem { id: sys }
CustomParticle {
system: sys
property real maxWidth: root.width // 定義了最大寬高
property real maxHeight: root.height
ShaderEffectSource { // 使用這個類型提供圖像的紋理數據
id: pictureSource
sourceItem: picture
hideSource: true
}
Image { // 海星星,我們也可以換成其他的圖片
id: picture
source: "qrc:/images/starfish_3.png"
}
ShaderEffectSource { // 第二個ShaderEffectSource用來支持粒子
id: particleSource
sourceItem: particle
hideSource: true
}
Image { // ImageParticle中常用的fuzzydot,光點
id: particle
source: "qrc:///particleresources/fuzzydot.png"
}
//! [vertex]
vertexShader:"
uniform highp float maxWidth; // 向GLSL傳參
uniform highp float maxHeight;
varying highp vec2 fTex2; // 該參數用來計算點在當前圖像上的位置
varying lowp float fFade; // 這個參數應該不陌生了,提供漸隱效果
uniform lowp float qt_Opacity;
void main() {
fTex2 = vec2(qt_ParticlePos.x, qt_ParticlePos.y);
//Uncomment this next line for each particle to use full texture, instead of the solid color at the center of the particle.
//fTex2 = fTex2 + ((- qt_ParticleData.z / 2. + qt_ParticleData.z) * qt_ParticleTex); //Adjusts size so it's like a chunk of image.
fTex2 = fTex2 / vec2(maxWidth, maxHeight);
highp float t = (qt_Timestamp - qt_ParticleData.x) / qt_ParticleData.y;
fFade = min(t*4., (1.-t*t)*.75) * qt_Opacity;
defaultMain();
}
"
//! [vertex]
property variant particleTexture: particleSource
property variant pictureTexture: pictureSource
//! [fragment]
fragmentShader: "
uniform sampler2D particleTexture; // 然后對粒子紋理采樣
uniform sampler2D pictureTexture; // 對圖像紋理采樣
varying highp vec2 qt_TexCoord0; // 該矢量相當于包含了(0,0)到(1,1)的所有像素點,如果基于它采樣,每個粒子都被渲染成這個圖像的樣子
varying highp vec2 fTex2; // 因此引入這個比例矢量,用來僅僅提供一個像素點的坐標
varying lowp float fFade;
void main() {
gl_FragColor = texture2D(pictureTexture, fTex2) * texture2D(particleTexture, qt_TexCoord0).w * fFade; // 因此第一個因子用來提供顏色,第二個因子用來提供形狀,最后一個因子提供漸隱的效果
}"
//! [fragment]
}
Emitter {
id: emitter
system: sys
enabled: false // 先關閉
lifeSpan: 8000
maximumEmitted: 4000
anchors.fill: parent
size: 16
acceleration: PointDirection { xVariation: 12; yVariation: 12 } // 向四周發散
}
MouseArea {
anchors.fill: parent // 點擊使能
onClicked: emitter.burst(4000);
}
}
}
~~~
- 前言
- 1——Fortune Server/Client
- 2——Multicast Sender/Receiverz
- 3——Broadcast Sender/Receiver
- 4——Blocking Fortune Client
- 5——Threaded Fortune Server
- 5(總結)——Fortune例程的各個實現區別
- 6——Loopback Example
- 7——Analog Clock Example
- 8——Shaped Clock Example
- 9——Analog Clock Window Example
- 10——Qt Quick Particles Examples - Emitters
- 11——Qt Quick Particles Examples - Affectors
- 12——Qt Quick Particles Examples - CustomParticles
- 13——Qt Quick Particles Examples - Image Particles
- 14——Qt Quick Particles Examples - System
- 15——Chapter 1: Creating a New Type
- 16——Chapter 2: Connecting to C++ Methods and Signals
- 17——Chapter 3: Adding Property Bindings
- 18——Chapter 4: Using Custom Property Types
- 19——Chapter 5: Using List Property Types
- 20——Chapter 6: Writing an Extension Plugin
- 21——Extending QML - Adding Types Example
- 22——Extending QML - Object and List Property Types Example
- 23——Extending QML - Inheritance and Coercion Example
- 24——Extending QML - Default Property Example
- 25——Extending QML - Methods Example
- 26——Extending QML - Grouped Properties Example
- 27——Extending QML - Attached Properties Example
- 28——Extending QML - Signal Support Example
- 29——Extending QML - Property Value Source Example
- 30——Extending QML - Binding Example
- 31——StocQt
- 32——Qt Quick Examples - Threading
- 33——Qt Quick Examples - Window and Screen
- 34——Concentric Circles Example
- 35——Music Player
- 36——Wiggly Example
- 37——Vector Deformation