粒子系统
本部分主要是实现一个简单的基于CPU的粒子系统。粒子数据存储在主机内存中,每帧在CPU上更新,并在使用预乘alpha渲染之前与设备同步。
一个微粒,从vulkan的角度看就是一个总是面向摄像机方向且(通常)包含一个大部分区域是透明的纹理的小四边形。一个微粒本身主要就是一个sprite,但是当你把成千上万个这些微粒放在一起的时候,就可以创造出令人疯狂的效果。
当处理这些微粒的时候,通常是由一个叫做粒子发射器或粒子生成器的东西完成的,从这个地方,持续不断的产生新的微粒并且旧的微粒随着时间逐渐消亡。如果这个粒子发射器产生一个带着类似烟雾纹理的微粒的时候,它的颜色亮度同时又随着与发射器距离的增加而变暗,那么就会产生出灼热的火焰的效果:
一个单一的微粒通常有一个生命值变量,并且从它产生开始就一直在缓慢的减少.一旦它的生命值少于某个极限值(通常是0)我们就会杀掉这个粒子,这样下一个粒子产生时就可以让它来替换那个被杀掉的粒子.一个粒子发射器控制它产生的所有粒子并且根据它们的属性来改变它们的行为.一个粒子通常有下面的属性:
看上面那个火焰的例子,那个粒子发射器可能在靠近发射器的地方产生每一个粒子,并且有一个向上的速度,这样每个粒子都是朝着正 y y y轴方向移动.那似乎有3个不同区域,只是可能相比其他的区域,给了某个区域内的粒子更快的速度.我们也可以看到, y y y轴方向越高的粒子,它们的黄色或者说亮度就越低.一旦某个粒子到达某个高度的时候,它的生命值就会耗尽然后被杀掉;绝不可能直冲云霄.
你可以想象到用这样一个系统,我们就可以创造一些有趣的效果比如火焰,青烟,烟雾,魔法效果,炮火残渣等等.我们将会创建一个简单的粒子生成器来制作一些有趣的效果,结果看起来就像上面火焰或者下面烟雾这样:
上面那个粒子生成器在这个球的位置产生无数的粒子,根据球移动的速度给了粒子相应的速度,并且根据它们的生命值来改变他们的颜色亮度.
为了渲染这些粒子,我们将会用到有不同实现的着色器:
顶点着色器:
#version 450
#extension GL_ARB_separate_shader_objects : enable
#extension GL_ARB_shading_language_420pack : enable
layout (location = 0) in vec4 inPos;
layout (location = 1) in vec4 inColor;
layout (location = 2) in float inAlpha;
layout (location = 3) in float inSize;
layout (location = 4) in float inRotation;
layout (location = 5) in int inType;
layout (location = 0) out vec4 outColor;
layout (location = 1) out float outAlpha;
layout (location = 2) out flat int outType;
layout (location = 3) out float outRotation;
layout (binding = 0) uniform UBO
{
mat4 projection;
mat4 modelview;
vec2 viewportDim;
float pointSize;
} ubo;
out gl_PerVertex
{
vec4 gl_Position;
float gl_PointSize;
};
void main ()
{
outColor = inColor;
outAlpha = inAlpha;
outType = inType;
outRotation = inRotation;
gl_Position = ubo.projection * ubo.modelview * vec4(inPos.xyz, 1.0);
// 粒子的基本大小
float spriteSize = 8.0 * inSize;
// 尺寸颗粒大小取决于相机的投影
vec4 eyePos = ubo.modelview * vec4(inPos.xyz, 1.0);
vec4 projectedCorner = ubo.projection * vec4(0.5 * spriteSize, 0.5 * spriteSize, eyePos.z, eyePos.w);
gl_PointSize = ubo.viewportDim.x * projectedCorner.x / projectedCorner.w;
}
以及像素着色器:
#version 450
#extension GL_ARB_separate_shader_objects : enable
#extension GL_ARB_shading_language_420pack : enable
layout (binding = 1) uniform sampler2D samplerSmoke;
layout (binding = 2) uniform sampler2D samplerFire;
layout (location = 0) in vec4 inColor;
layout (location = 1) in float inAlpha;
layout (location = 2) in flat int inType;
layout (location = 3) in float inRotation;
layout (location = 0) out vec4 outFragColor;
void main ()
{
vec4 color;
float alpha = (inAlpha <= 1.0) ? inAlpha : 2.0 - inAlpha;
// 旋转纹理坐标
// 旋转UV
float rotCenter = 0.5;
float rotCos = cos(inRotation);
float rotSin = sin(inRotation);
vec2 rotUV = vec2(
rotCos * (gl_PointCoord.x - rotCenter) + rotSin * (gl_PointCoord.y - rotCenter) + rotCenter,
rotCos * (gl_PointCoord.y - rotCenter) - rotSin * (gl_PointCoord.x - rotCenter) + rotCenter);
if (inType == 0)
{
// 火焰
color = texture(samplerFire, rotUV);
outFragColor.a = 0.0;
}
else
{
// 烟雾
color = texture(samplerSmoke, rotUV);
outFragColor.a = color.a * alpha;
}
outFragColor.rgb = color.rgb * inColor.rgb * alpha;
}
以上是初步实现的效果,由于main代码还未完全理解消化,故下一部分着重来描述下粒子数据的生成过程。