Vulkan:粒子及粒子系统数据生成

一、粒子

为了给我们当前这个黑漆漆的世界带来一点生机,我们将会渲染一些粒子(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 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(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

最新文章