一、粒子
为了给我们当前这个黑漆漆的世界带来一点生机,我们将会渲染一些粒子(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_distributionrndDist(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(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
除此之外你还可以调整单个粒子大小、变换旋转速率等来实现不同延烧的火焰等效果,亦或是你可以改变贴图及粒子散布位置来实现雪花等效果。
版权声明:本文为博主 沉默的舞台剧 原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/qq_35312463/article/details/104786641