首页 > 代码库 > Qt5官方demo解析集12——Qt Quick Particles Examples - CustomParticles
Qt5官方demo解析集12——Qt Quick Particles Examples - CustomParticles
本系列所有文章可以在这里查看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 = 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:" // 这里是我们的第一个顶点着色器,当我们编写完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.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); } } }