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<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

在這裏插入圖片描述
除此之外你還可以調整單個粒子大小、變換旋轉速率等來實現不同延燒的火焰等效果,亦或是你可以改變貼圖及粒子散佈位置來實現雪花等效果。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章