粒子及粒子系統
一、粒子
爲了給我們當前這個黑漆漆的世界帶來一點生機,我們將會渲染一些粒子(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
除此之外你還可以調整單個粒子大小、變換旋轉速率等來實現不同延燒的火焰等效果,亦或是你可以改變貼圖及粒子散佈位置來實現雪花等效果。