본문으로 바로가기

2020_1130 Particle Editor

category DX11 3D Game Programming 2020. 11. 29. 23:13

[소스코드]

SourceCode.z01
10.00MB
SourceCode.z02
10.00MB
SourceCode.z03
10.00MB
SourceCode.zip
0.51MB


 

0. ParticleSpark Shader

더보기
#include "Header.hlsli"

cbuffer SparkBuffer : register(b10)
{
    float time;
    float duration;
	//
    float2 startSize;
    float3 startDirection;
	//
    float sizeOverLifeTime;
    float3 rotOverLifeTime;
}

struct VertexInput
{
	float4 pos : Position;
	float2 size : Size;
    float3 direction : Direction;
	//
    float startLifeTime : LifeTime;
    float speed : Speed;
    float4 particleColor : Color;
};

struct VertexOutput
{
	float3 pos : Position;
	float2 size : Size;
    float time : Time;
	//
    float4 particleColor : Color;
};

VertexOutput VS(VertexInput input)
{
	VertexOutput output;
	// 0 ~ 1의 비율로 전달
    output.time = time / input.startLifeTime;
    //output.time = time / duration;
	//
    input.direction += startDirection * time;
    input.pos = mul(input.pos, world);
    output.pos = input.pos.xyz + (input.direction * time) * input.speed;
	output.size = input.size;
    output.particleColor = input.particleColor;
	//
	return output;
}

struct PixelInput
{
	float4 pos : SV_Position;
	float2 uv : UV;
    float time : Time;
    float4 particleColor : Color;
};

static const float2 TEXCOORD[4] =
{
	float2(0.0f, 1.0f),
	float2(0.0f, 0.0f),
	float2(1.0f, 1.0f),
	float2(1.0f, 0.0f)
};

[maxvertexcount(4)]
void GS(point VertexOutput input[1], inout TriangleStream<PixelInput> output)
{
	float3 camPos = invView._41_42_43;
	//
	float3 up = invView._21_22_23;
	//
	float3 forward = camPos - input[0].pos;
	forward = normalize(forward);
	//
	float3 right = normalize(cross(up, forward));
	//
	float halfWidth = input[0].size.x * 0.5f;
	float halfHeight = input[0].size.y * 0.5f;
	//
	float4 vertices[4];
	vertices[0] = float4(input[0].pos + halfWidth * right - halfHeight * up, 1.0f);
	vertices[1] = float4(input[0].pos + halfWidth * right + halfHeight * up, 1.0f);
	vertices[2] = float4(input[0].pos - halfWidth * right - halfHeight * up, 1.0f);
	vertices[3] = float4(input[0].pos - halfWidth * right + halfHeight * up, 1.0f);
	//
	PixelInput pixelInput;
    pixelInput.time = input[0].time;
	//
	[unroll]
	for (int i = 0; i < 4; ++i)
	{
		pixelInput.pos = mul(vertices[i], view);
		pixelInput.pos = mul(pixelInput.pos, projection);
		pixelInput.uv = TEXCOORD[i];
        pixelInput.particleColor = input[0].particleColor;
		// pushback
		output.Append(pixelInput);
	}
}
//
cbuffer ColorBuffer : register(b10)
{
    float4 totalColor;
    float4 colorOverLifeTime;
}
//
float4 PS(PixelInput input) : SV_Target
{
    float4 albedo = diffuseMap.Sample(samp, input.uv) * mDiffuse;
	//
    float4 color = lerp(input.particleColor, colorOverLifeTime, input.time);
	//
    return albedo * color;
}

 

1. Spark

더보기
#pragma once

class Spark : public Particle
{
public:
	enum SHAPE
	{
		SPHERE,
		CIRCLE,
		CONE
	};
	//
public:
	struct ParticleInfo
	{
		int shape{};
		bool loop{ true };
		UINT count{ 100 };
		float duration{ 3.0f };
		//
		Float2 startLifeTime	{ 1.0f, 3.0f };
		Float2 startSpeed		{ 1.0f, 3.0f };
		Float2 startRadius		{ 1.0f, 5.0f };
		Float2 startSize		{ 0.1f, 1.0f };
		Float4 startColor[2]	{ {1.0f, 1.0f, 1.0f, 1.0f}, {1.0f, 1.0f, 1.0f, 1.0f} };
		//
		Float4 colorOverLifeTime{ 1.0f, 1.0f, 1.0f, 0 };
		float sizeOverLifeTime	{ 0.0f };
		Float3 rotOverLifeTime	{};
	};
	///////////////////////////////
public:
	class SparkBuffer : public ConstBuffer
	{
	public:
		struct Data
		{
			float time{};
			float duration{};
			//
			Float2 startSize{};
			Float3 startDirection{};
			//
			float sizeOverLifeTime{};
			Float3 rotOverLifeTime{};
			//
			float padding{};
		}data;
		SparkBuffer() :ConstBuffer(&data, sizeof(Data)) {}
		SparkBuffer(Data sparkData) :ConstBuffer(&sparkData, sizeof(Data)) {}
	};
	///////////////////////////////
private:
	struct VertexParticle
	{
		Float3 position{};
		Float2 size{ 1.0f, 1.0f };
		Float3 direction{};
		//
		float startLifeTime{ 1.0f };
		float speed{ 1.0f };
		Float4 color{1.0f, 1.0f, 1.0f, 1.0f};
		float padding[2]{};
		//
		VertexParticle() {};
	};
	///////////////////////////////
private:
	//vector<VertexParticle*> vertices;
	VertexParticle* vertices;
	///////////////////////////////
	SparkBuffer* sparkBuffer;
	ParticleColorBuffer* colorBuffer;
	//
	ParticleInfo* info;
	///////////////////////////////
public:
	Spark(wstring diffuseFile, ParticleInfo* info);
	~Spark();
	// Particle을(를) 통해 상속됨
	virtual void Create() override;
	virtual void Update() override;
	virtual void Render() override;
	virtual void PostRender() override;
	virtual void Play(Vector3 pos) override;
	///////////////////////////////
	void UpdateParticle();
	///////////////////////////////
	ParticleColorBuffer* GetColorBuffer() { return colorBuffer; }
	SparkBuffer* GetSparkBuffer() { return sparkBuffer; }
	Texture* GetTexture() { return material->diffuseMap; }
	//
	void SetDirectionByShape(int shape, int i);
};
========================================================================
#include "Framework.h"

Spark::Spark(wstring diffuseFile, ParticleInfo* info)
	: Particle(L"Textures/Effect/" + diffuseFile + L".png"),
	sparkBuffer(nullptr), colorBuffer(nullptr), info(info)
{
	material->SetShader(L"ParticleSpark");
	geometryShader = Shader::AddGS(L"ParticleSpark");
	//
	sparkBuffer = new SparkBuffer();
	colorBuffer = new ParticleColorBuffer();
	//
	Create();
}

Spark::~Spark()
{
	delete[] vertices;
	//
	delete colorBuffer;
	delete sparkBuffer;
}

void Spark::Create()
{
	vertices = new VertexParticle[MAX_COUNT];
	//
	UpdateParticle();
	//
	vertexBuffer = new VertexBuffer(vertices, sizeof(VertexParticle), MAX_COUNT);
}

void Spark::Update()
{
	if (!isActive || !isPlay) return;
	//
	sparkBuffer->data.time += DELTA;
	//
	if (sparkBuffer->data.time > sparkBuffer->data.duration)
		if (info->loop == false)
			Stop();
		else
			Play(this->position);
}

void Spark::Render()
{
	if (isActive == false) return;
	//
	sparkBuffer->SetVSBuffer(10);
	colorBuffer->SetPSBuffer(10);
	//
	Particle::Render();
}

void Spark::PostRender()
{
	ImGui::Text("[Particle]");
	ImGui::Text("playTime : %.2f", sparkBuffer->data.time);
	ImGui::Text("Loop : %d", info->loop);
}

void Spark::Play(Vector3 pos)
{
	Particle::Play(pos);
	UpdateParticle();
	vertexBuffer->Update(vertices, info->count);
}

void Spark::UpdateParticle()
{
	sparkBuffer->data.time = 0;
	sparkBuffer->data.duration = info->duration;
	particleCount = info->count;
	//
	for (UINT i = 0; i < info->count; ++i)
	{
		{ // LifeTime
			float min = info->startLifeTime.x;
			float max = info->startLifeTime.y;
			float value = Random(min, max);
			vertices[i].startLifeTime = value;
		}
		{ // Speed
			float min = info->startSpeed.x;
			float max = info->startSpeed.y;
			float value = Random(min, max);
			vertices[i].speed = value;
			//sparkBuffer->data.startSpeed = value;
		}
		{ // Size
			float min = info->startSize.x;
			float max = info->startSize.y;
			float value = Random(min, max);
			vertices[i].size = Float2(value, value);
		}
		{ // Direction
			SetDirectionByShape(info->shape, i);
		}
		// Color
		Float4 color0 = info->startColor[0];
		Float4 color1 = info->startColor[1];
		Float4 color = {
			(color1.x - color0.x) * Random(0.0f, 1.0f) + color0.x,
			(color1.y - color0.y) * Random(0.0f, 1.0f) + color0.y,
			(color1.z - color0.z) * Random(0.0f, 1.0f) + color0.z,
			1.0f
			//(color1.w - color0.w) * Random(0.0f, 1.0f) + color0.w
		};
		vertices[i].color = color;
	}
	colorBuffer->data.colorOverLifeTime = info->colorOverLifeTime;
}

void Spark::SetDirectionByShape(int shape, int i)
{
	float min = info->startRadius.x;
	float max = info->startRadius.y;
	float radius = Random(min, max);
	//
	Vector3 dir = Forward() * radius;
	Float3 rot;
	//
	switch (shape)
	{
	case Spark::SPHERE:
	{
		rot.x = Random(0.0f, XM_2PI);
		rot.y = Random(0.0f, XM_2PI);
		rot.z = Random(0.0f, XM_2PI);
		Matrix matRot = XMMatrixRotationRollPitchYaw(rot.x, rot.y, rot.z);
		XMStoreFloat3(&vertices[i].direction,
			XMVector3TransformCoord(dir.data, matRot));
	}
		break;
	case Spark::CIRCLE:
	{
		float r1 = Random(-1.0f, 1.0f);
		float r2 = Random(-1.0f, 1.0f);
		dir = (Forward() * r1 + Right() * r2).Normal() * radius;
		XMStoreFloat3(&vertices[i].direction, dir.data);
	}
		break;
	case Spark::CONE:
		break;
	default:
		break;
	}
}

 

2. Editor Scene

더보기
#pragma once

class ParticleScene : public Scene
{
private:
	Collider* collider;
	//
	Spark::ParticleInfo particleInfo;
	//
	Particle* particle;
	//
	Texture* selectTexture;
	Texture* defaultImage;
	ImVec2 imgSize{ 64.0f, 64.0 };
	vector<const char*> items{ "image1", "image2", "image3", "image4" };
	int e{};
	int selectedItem{};
	wstring filename{};
	////////////////////////////////
public:
	ParticleScene();
	~ParticleScene();
	// Scene을(를) 통해 상속됨
	virtual void Update() override;
	virtual void PreRender() override;
	virtual void Render() override;
	virtual void PostRender() override;
	////////////////////////////////
	void Pick();
	////////////////////////////////
	void Create();
	void Play();
	void Pause();
	void Resume();
	void Clear();
	void Load();
	void Save();
};
========================================================================
#include "Framework.h"
#include "ParticleScene.h"

ParticleScene::ParticleScene()
	:particle(nullptr)
{
	collider = new SphereCollider();
	collider->UpdateWorld();
	collider->scale *= 0.5f;
	//
	CAMERA->position = { 0, 13.0f, -15.0f };
	CAMERA->rotation.x = 0.5f;
	//
	defaultImage = Texture::Add(L"Textures/Effect/default.png");
}

ParticleScene::~ParticleScene()
{
	if(particle)
		delete particle;
	//
	delete collider;
}

void ParticleScene::Update()
{
	collider->UpdateWorld();
	//
	if(particle)
		particle->Update();
}

void ParticleScene::PreRender()
{
}

void ParticleScene::Render()
{
	//collider->Render();
	//
	if(particle)
		particle->Render();
}

void ParticleScene::PostRender()
{
	////////////////////////////////////////////
	// Buttons //
	if (particle != nullptr)
	{
		ImGui::Begin("[Particle Transform]");
		///////////
		ImGui::InputFloat3("Pos", (float*)&particle->position);
		ImGui::InputFloat3("Rot", (float*)&particle->rotation);
		///////////
		ImGui::End();
	}
	////////////////////////////////////////////
	// Buttons //
	ImGui::Begin("[ParticleEdior]");
	///////////
	if (ImGui::Button("Create")) Create();
	if (ImGui::Button("Play")) Play();
	if (ImGui::Button("Pause")) Pause();
	if (ImGui::Button("Resume")) Resume();
	if (ImGui::Button("Clear")) Clear();
	if (ImGui::Button("Save")) Save();
	if (ImGui::Button("Load")) Load();
	//
	if(particle)
		particle->PostRender();
	///////////
	ImGui::End();
	///////////////////////////////
	// Particle Info //
	ImGui::Begin("[ParticleInfo]");
	///////////
	{
		ImGui::Text("Loop");
		ImGui::Checkbox("LP", &particleInfo.loop);
		//
		ImGui::Text("Shape");
		ImGui::RadioButton("sphere", &e, 0); ImGui::SameLine();
		ImGui::RadioButton("circle", &e, 1); ImGui::SameLine();
		ImGui::RadioButton("cone", &e, 2);
		particleInfo.shape = e;
		//
		ImGui::Text("Texture");
		filename = L"Textures/Effect/" + ToWString(items[selectedItem]) + L".png";
		selectTexture = Texture::Add(filename);
		if (selectTexture && selectTexture->GetSRV())
			ImGui::Image(selectTexture->GetSRV(), imgSize);
		else
			ImGui::Image(defaultImage->GetSRV(), imgSize);
		//
		ImGui::ListBox("Images", &selectedItem, items.data(), items.size());

		ImGui::Text("Count");
		ImGui::InputInt("CT", (int*)&particleInfo.count);
		ImGui::Text("Duration");
		ImGui::InputFloat("DR", &particleInfo.duration);
	}
	{
		ImGui::Text("StartLifeTime");
		ImGui::InputFloat2("lt", (float*)&particleInfo.startLifeTime);

		ImGui::Text("StartSpeed");
		ImGui::InputFloat2("sp", (float*)&particleInfo.startSpeed);

		ImGui::Text("StartRadius");
		ImGui::InputFloat2("rd", (float*)&particleInfo.startRadius);
		float min = particleInfo.startRadius.x *  particleInfo.startSpeed.x;
		float max = particleInfo.startRadius.y *  particleInfo.startSpeed.y;
		switch (particleInfo.shape)
		{
		case 0:
			collider->scale = { max, max, max };
			collider->SetColor({ 1,0,0,1 });
			collider->Render();
			collider->scale = { min, min, min };
			collider->SetColor({ 0,0,1,1 });
			collider->Render();
			break;
		case 1:
			collider->scale = { max, 0, max };
			collider->SetColor({ 1,0,0,1 });
			collider->Render();
			collider->scale = { min, 0, min };
			collider->SetColor({ 0,0,1,1 });
			collider->Render();
			break;
		case 2:
			break;
		default:
			break;
		}


		ImGui::Text("StartSize");
		ImGui::InputFloat2("sz", (float*)&particleInfo.startSize);
	}
	ImGui::Text("StartColor");
	ImGui::ColorEdit4("cr1", (float*)&particleInfo.startColor[0]);
	ImGui::ColorEdit4("cr2", (float*)&particleInfo.startColor[1]);
	{
		ImGui::Text("ColorOverLifeTime");
		ImGui::ColorEdit4("COLT", (float*)&particleInfo.colorOverLifeTime);

		ImGui::Text("SizeOverLifeTime");
		ImGui::InputFloat("SOLT", &particleInfo.sizeOverLifeTime);

		ImGui::Text("RotOverLifeTime");
		ImGui::InputFloat3("ROLT", (float*)&particleInfo.rotOverLifeTime);
	}
	///////////
	ImGui::End();
}

void ParticleScene::Create()
{
	if (particle)
		Clear();
	//
	wstring diffuseFile = ToWString(items[selectedItem]);
	//selectTexture;
	particle = new Spark(diffuseFile, &particleInfo);
}

void ParticleScene::Play()
{
	if (particle == nullptr) return;
	//
	particle->Play(collider->position);
}

void ParticleScene::Pause()
{
	if (particle == nullptr) return;
	//
	particle->Pause();
}

void ParticleScene::Resume()
{
	if (particle == nullptr) return;
	//
	particle->Resume();
}

void ParticleScene::Clear()
{
	delete particle;
	particle = nullptr;
}

void ParticleScene::Load()
{
	if (particle)
		Clear();
	Spark::ParticleInfo info{};
	// Create Binary File
	HANDLE file{};
	DWORD read{}; // unsigned long
	file = CreateFile(L"Textures/Effect/myParticle.fx", GENERIC_READ, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
	V(ReadFile(file, &info, sizeof(Spark::ParticleInfo), &read, nullptr));
	//
	V(ReadFile(file, &selectedItem, sizeof(int), &read, nullptr));
	// Load End
	CloseHandle(file);

	particleInfo = info;
	e = info.shape;
	Create();
}

void ParticleScene::Save()
{
	// Create Binary File
	HANDLE file;
	DWORD write; // unsigned long
	file = CreateFile(L"Textures/Effect/myParticle.fx", GENERIC_WRITE, 0, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
	V(WriteFile(file, &particleInfo, sizeof(Spark::ParticleInfo), &write, nullptr));
	//
	V(WriteFile(file, &selectedItem, sizeof(int), &write, nullptr));
	// Save End
	CloseHandle(file);
}