粒子及粒子系统
一、粒子
为了给我们当前这个黑漆漆的世界带来一点生机,我们将会渲染一些粒子(Sprite)来填补这些空虚。粒子有很多种定义,但这里主要是指一个2D图片,它通常是和一些摆放相关的属性数据一起使用,比如位置、旋转角度以及二维的大小。简单来说,精灵就是那些可以在2D游戏中渲染的图像/纹理对象。
渲染一个实际的粒子应该不会太复杂。我们创建一个有纹理的四边形,它在之后可以使用一个模型矩阵来变换,然后我们会用之前定义的正射投影矩阵来投影它。
首先我们创建一个粒子结构体,永远存储一个粒子的所有属性信息:
struct Particle {
glm::vec4 pos;
glm::vec4 color;
float alpha;
float size;
float rotation;
uint32_t type;
// Attributes not used in shader
glm::vec4 vel;
float rotationSpeed;
};
第二步我们加载火焰和烟雾的ktx贴图,然后我们来通过设定的火焰的初始顶底位置,并根据自定有的粒子数量来初始化粒子:
#define PARTICLE_COUNT 1 //粒子数
#define PARTICLE_SIZE 10.0f //例子图片大小
#define PARTICLE_TYPE_FLAME 0
#define PARTICLE_TYPE_SMOKE 1
glm::vec3 emitterPos = glm::vec3(0.0f, -FLAME_RADIUS + 2.0f, 0.0f);
glm::vec3 minVel = glm::vec3(-3.0f, 0.5f, -3.0f);
glm::vec3 maxVel = glm::vec3(3.0f, 7.0f, 3.0f);
std::default_random_engine rndEngine;
std::vector<Particle> particleBuffer;//粒子系统集合
void prepareParticles()
{
particleBuffer.resize(PARTICLE_COUNT);
for (auto& particle : particleBuffer)
{
initParticle(&particle, emitterPos);
particle.alpha = 1.0f - (abs(particle.pos.y) / (FLAME_RADIUS * 2.0f));
}
particles.size = particleBuffer.size() * sizeof(Particle);
VK_CHECK_RESULT(vulkanDevice->createBuffer(
VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
particles.size,
&particles.buffer,
&particles.memory,
particleBuffer.data()));
// Map the memory and store the pointer for reuse
VK_CHECK_RESULT(vkMapMemory(device, particles.memory, 0, particles.size, 0, &particles.mappedMemory));
}
void initParticle(Particle *particle, glm::vec3 emitterPos)
{
particle->vel = glm::vec4(0.0f, minVel.y + rnd(maxVel.y - minVel.y), 0.0f, 0.0f);
particle->alpha = rnd(0.75f);
particle->size = 1.0f + rnd(0.5f);
particle->color = glm::vec4(1.0f);
particle->type = PARTICLE_TYPE_FLAME;
particle->rotation = rnd(2.0f * float(M_PI));
particle->rotationSpeed = rnd(2.0f) - rnd(2.0f);
// Get random sphere point
float theta = rnd(2.0f * float(M_PI));
float phi = rnd(float(M_PI)) - float(M_PI) / 2.0f;
float r = rnd(FLAME_RADIUS);
particle->pos.x = r * cos(theta) * cos(phi);
particle->pos.y = r * sin(phi);
particle->pos.z = r * sin(theta) * cos(phi);
particle->pos += glm::vec4(emitterPos, 0.0f);
}
float rnd(float range)
{
std::uniform_real_distribution<float> rndDist(0.0f, range);
return rndDist(rndEngine);
}
此后是一顿原有的创建uniformBuffer、描述符、管线、命令缓冲等一些列基本操作…
在创建完毕后,我们还需要做的就是在mainLoop中对所有粒子数据进行实施更新, 具体如下:
void mainLoop() {
while (!glfwWindowShouldClose(window)) {
auto tStart = std::chrono::high_resolution_clock::now();
...
updateUniformBuffers();
updateParticles();
draw();
auto tEnd = std::chrono::high_resolution_clock::now();
auto tDiff = std::chrono::duration<double, std::milli>(tEnd - tStart).count();
frameTimer = (float)tDiff / 1000.0f;
camera.UpdataCameraPosition();
timer += 2 * frameTimer;
}
vkDeviceWaitIdle(device);
}
//更新粒子数据
void updateParticles()
{
float particleTimer = frameTimer * 0.45f;
for (auto& particle : particleBuffer)
{
particle.type = particleType;
switch (particle.type)
{
case PARTICLE_TYPE_FLAME:
particle.pos.y -= particle.vel.y * particleTimer * 3.5f;
particle.alpha += particleTimer * 2.5f;
particle.size -= particleTimer * 0.5f;
break;
case PARTICLE_TYPE_SMOKE:
particle.pos -= particle.vel * frameTimer * 1.0f;
particle.alpha += particleTimer * 1.25f;
particle.size += particleTimer * 0.125f;
particle.color -= particleTimer * 0.05f;
break;
}
particle.rotation += particleTimer * particle.rotationSpeed;
// 火转烟效果
if (particle.alpha > 2.0f)
{
transitionParticle(&particle);
}
}
size_t size = particleBuffer.size() * sizeof(Particle);
memcpy(particles.mappedMemory, particleBuffer.data(), size);
}
//实现火焰与烟雾渐变
void transitionParticle(Particle *particle)
{
switch (particle->type)
{
case PARTICLE_TYPE_FLAME:
if (rnd(1.0f) < 0.05f)
{
particle->alpha = 0.0f;
particle->color = glm::vec4(0.25f + rnd(0.25f));
particle->pos.x *= 0.5f;
particle->pos.z *= 0.5f;
particle->vel = glm::vec4(rnd(1.0f) - rnd(1.0f), (minVel.y * 2) + rnd(maxVel.y - minVel.y), rnd(1.0f) - rnd(1.0f), 0.0f);
particle->size = 1.0f + rnd(0.5f);
particle->rotationSpeed = rnd(1.0f) - rnd(1.0f);
particle->type = PARTICLE_TYPE_SMOKE;
}
else
{
initParticle(particle, emitterPos);
}
break;
case PARTICLE_TYPE_SMOKE:
// Respawn at end of life
initParticle(particle, emitterPos);
break;
}
}
此处的时间为了进一步控制粒子的旋转,截止此,一套完整的粒子效果应该可以呈现出来了,运行代码,可以看到逐渐向上移动并不断变化透明度的单个粒子:
粒子系统
如果单个系统你已经完成,那么我们仅需在现有代码中,将粒子数量精细调整便可看到火焰等粒子系统的效果了:
#define PARTICLE_COUNT 10
#define PARTICLE_COUNT 128
#define PARTICLE_COUNT 1024
除此之外你还可以调整单个粒子大小、变换旋转速率等来实现不同延烧的火焰等效果,亦或是你可以改变贴图及粒子散布位置来实现雪花等效果。