標籤:qt5官方demo解析集 qml qml粒子 qtquick customparticle
本系列所有文章可以在這裡查看http://blog.csdn.net/cloud_castle/article/category/2123873
接上文Qt5官方demo解析集11——Qt Quick Particles Examples - Affectors
使用Emitter和Affectors強大的功能,我們已經可以構造出豐富多彩的粒子特效,但當這些功能還不能滿足我們的需要時,我們可以轉而採用CustomParticle取代ImageParticle,在CustomParticle中我們可以使用基於GLSL的渲染技術來建立自訂的粒子。
這個demo依然由一些小例子組成,不過比前兩個demo中的都要少,只有三個:
(1)Blur Particles
在這個例子中我們可以看到使用CustomParticle建立模糊化粒子的方法。運行效果如下:
個人感覺在這個示範中模糊效果並不是很清楚,於是換了一張圖片,並類似地使用不帶模糊化的ImageParticle來展示它們的不同:
可以看到,左邊圖為模糊化的CustomParticle,右邊為沒有模糊效果的ImageParticle,區別還是很明顯的。
由於筆者對OpenGL並不是很熟悉,如果有錯誤的地方,還請各位指正。
需要指明的是,對於這種內嵌的GLSL代碼,Qt為我們提供了一些提前定義好的變數。用uniform和attribute類型定義,作為vertexShader的輸入。比如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)。
另外,QML中任何類型的屬性都可以映射到GLSL的uniform類型中去。也就是說,當我們在QML中定義了一個4維RGBA的QColor color,然後再GLSL中聲明一個uniform vec4 color,就可以直接使用它了。我們也可以將型別宣告為var,Qt會自動幫我們完成轉換。下面的例子中大量使用了這種方法。詳細的類型映射可以參考Manual中的ShaderEffect
看源碼時,我們先看一段額外的代碼,我們將其命名為vertexShader_addin:
attribute highp vec2 qt_ParticlePos; // 這些是預定義好的屬性attribute highp vec2 qt_ParticleTex;attribute highp vec4 qt_ParticleData; // x = time, y = lifeSpan, z = size, w = endSizeattribute highp vec4 qt_ParticleVec; // x,y = constant velocity, z,w = accelerationattribute 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.0import QtQuick.Particles 2.0Rectangle { 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:" // 這裡是我們的第一個頂點著色器,當我們編寫完vertexShader代碼後,Qt會為我們將vertexShader_addin中的代碼添加到這一行之後。這些代碼定義了一些基本的變數,以及一個defaultMain()函數,vertexShader中最基本的gl_Position的輸出也由該函數段完成,它為我們實現了基本的粒子功能。 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.0import QtQuick.Particles 2.0ParticleSystem { // 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.0import QtQuick.Particles 2.0Rectangle { // 一個矩形用來作為視窗邊界 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); } }}