본문 바로가기

DirectX11 3D/기본 문법

<DirectX11 3D> 89 - ParticleSystem

 

 

필요한 개념


우리가 눈, 비 할때 입자들을 규칙을 적용해서 움직이게 만들었다.
공간으로 가둬서 그 공간에서 벨로시티에 의해 떨어지도록 만들었다.(단순하게 위에서 밑으로 내려오거나, 정한 방향으로 움직이는 방식으로 써왔다.)

발전시켜서 우리가 값을 준 상황으로 불규칙척인거 처럼 모양을 만들었놓고 그 안에서 불규칙적으로 움직이게 불이라던가 폭발이라던가 이런걸 만들어
내는 것이 파티클이다.

입자들을 다루는 것은 전부 파티클
눈이나 비도 사실 파티클시스템이다.
눈이나, 비, 폭포, 커버할 수 있도록 하는데, 우리는 이렇게 복잡하게 하지 않고
어떻게 운용되고, 어떻게 식을 추가하고 그런 내용으로 한다.

파티클 써주는것은 프로젝트 만들고(PaticleEditor) 파티클 운용은 Framework 프로젝트에서 한다.

Viewer란? 파티클 시스템 하나만 렌더링 하는 클래스, 테스트용으로 사용

ParticleData란? 입자들의 이동과 회전, 색상들을 운영할 값들을 저장한 구조체

ParticleSystem.h은 파티클을 가지고 운용할 클래스

XML 파일을 가지고 파티클을 운용할 것이다.

ParticleSystem 생성자에서 XML 파일을 읽어들임(파티클 데이터는 XML 파일로 부터 불러온다.)

Corner : UV와 포지션 등을 만들기 위해 사용

 

그림 2-1

원형큐의 특성은 끝까지 차면 처음으로 돌아간다.
그 특성으로 Add()를 정의한다.(그림 2-1 참고)

아래의 값들은 모두 순환 큐처럼 순환하는 변수들
UINT leadCount = 0; // 가장 앞서는 개수
UINT gpuCount = 0; // leadCount를 따라가면서 GPU에 복사할 개수
UINT activeCount = 0; // gpuCount를 따라가며, 활성화활 개수 
UINT deactiveCount = 0; // activeCount를 따라가며, 비활성화시킬 개수

GpuCount를 이용해서 Map으로 Gpu로 보내준다.

그림 3-1
그림 3-2



leadcount를 gpuCount가 따라가고 그 다음 activeCount가 따라가고
그 뒤를 deactiveCount가 따라간다.

deactiveCount가 있는 이유는 ReadyTime이 끝나면 비활성화가 되어야 하기때문에

vRam에 하는것!
Write - Cpu_Access_Write일때만 접근, Discard랑 유사하게 덮어씌움
Write_Discard - 이전에 쓰여있는 데이터에 덮어씌움(Discard-> 기존 데이터를 다 날리고 새로 다시 씀)
Write_No_Overwrite - 이전에 씌어 있는 상태에서 지우지 않고 지정한 위치부터 해줌


activeCount를 이용해서 그린다.
leadCount가 activeCount를 다따라잡았다면 그릴게 없는것이다.

그림 4-1


Render() 함수에서
그림 4-1 참고
1)
leadCount > activeCount:leadCount
-activeCount 차이만큼 그리면 된다.

2)
leadCount < activeCount:maxParticle
-activeCount 처음부터 leadCount까지 그린다.

결과를 보니
불이 겹쳐서 올라오지만, 검은색 네모로 겹치게 되는 현상이 있다. 이 이유는?
IA - VS - | RS         - PS -OM -> backBuffer
---->3D  | 3D->2D   2D픽셀을 처리

OM(OutputMerger) : RenderTargetView와 DepthStencilView의 출력결과를 합치는 과정
픽셀은 컬러다 PS의 결과들인 컬러를 저장하기 위한 텍스쳐가 있다.  OM 단계에서 이를 RenderTargetView(RTV)가 가지고 있는 텍스쳐
RTV이 가지고 있는 텍스쳐는 BackBuffer와 사이즈를 동일하게 만든다.
우리 1280 x 720 쓰고 있다. IA에다가 도형을 그리라고 입력했다고 정점에 WVP해서 모양을 만들고 RS에서 뒤에 안보이는 공간은 다 잘라냄(Culling)

 

 

그림 5-1

 


컬링이 끝나면 보일 부분만 2D 변환이 일어나고, 얘를 픽셀하나하나를 PS에서 처리, 처리된 결과를 RTV에다가 써주게 된다. 이 단계가 OM단계이다.(그림 5-1 참고)
OM단계에 RTV 뿐만아니라 DSV(DepthStencilView)가 있다. DSV도 똑같은 사이즈의 텍스쳐가 있다.
RTV의 텍스처에는 픽셀(기본적으로 R8 G8 B8 A8 -> 32비트)의 색상을 쓴다.
DSV의 텍스처에는 픽셀(기본적으로 R24_G8(D24_S8) -> 깊이로 24비트를 사용하고, Stencil로 8비트를 사용(깊이는 NDC 공간에서 z은 0 ~ 1까지(x, y는 -1 ~ 1, Z은 뒤를 볼 필요가 없기 때문에))의 깊이를 쓴다.
DSV는 RS에서 바로 OM으로 간다.(PS를 거치지 않음)
OM단계에서 RTV에 픽셀색상이 하나 찍혔다면 DSV도 그 위치에 깊이가 하나찍힌다.
왜 찍냐?
어떤 애가 깊이로 가려진다면(깊이값으로 가린다.) 깊이가 가까우면 가까울수록 먼애를 가리게 된다.
그거를 판단하기 위해 DSV를 준다.
DSV는 어떻게 가려지는지는 다음시간에...


요약
픽셀셰이더에서 처리된것은 RTV에 저장이 되고, RS -> OM에서 바로 넘어가는 것은 DSV이다. DSV에 연결된 텍스쳐에 똑같은 픽셀의 색상이 기록될때(RTV에) 똑같은 깊이가 기록됨(DSV에)

Culling : 정점을 보이지 않게 잘라내는 기술
Cliping : 픽셀을 보이지 않게 잘라내는 기술


OM에서 이 두개(RTV, DSV)를 기록하고, BackBuffer에 이 두개를 내보냄 최종적인 결과화면이 나옴

그림 6-1


(그림 6-1 참고)
일반적으로 DSV에서 초기값으로 안에 다 깊이가 1로 채워져 있다. 큐브가 그려지면서 깊이(ex 0.5)가 쓰여질 것이고, 그런데 사각형이 큐브 앞을 가린다면
사각형의 깊이 0 < 큐브의 깊이가 0.5 이므로 깊이 0이 기록된다.
조건이 참이 되어서 해당 사각형의 픽셀을 RTV에 기록하게 된다.
요약하면 원래 써져 있던 깊이랑 현재 들어오는 깊이를 비교해서 현재 쓰려는 깊이가 더 작다면 깊이를 기록하고 해당 위치에 그릴 픽셀 색상(RTV에)을 기록한다.

픽셀을 찍을때는 깊이를 비교한다. 현재 깊이가 더 작거나 같다면 현재 그릴려는 깊이를 기록하고 RTV에 픽셀 색상을 기록함
Src <= dest(DX에서는 DSS에서 Less_Equall)


만약 그릴려는 애가 깊이를 0.5로 그렸다면 그 다음에 사각형을 0.5에 그렸다면
누가 앞에 써줘야하는지 판단 불가?
이것을 Z-Fighting이라 부름
Z-Fighting이란?  깊이를 비교할 수 없으므로 서로가 먼저 그려지려는 현상

반투명인 애들도 Z-Fighting현상이 발생(싸우다보니 외곽선이 생김)
언리얼에서 z값이 같은 애들을 서로 겹쳐놓으면 막 서로 앞에 그려지려고 지지직거림

불이 겹쳐서 올라오지만, 검은색 네모로 겹치게 되는 현상이 있다. 이 이유는?
이 이유는 Z-Fighting 때문이다. 서로 싸우다 보니까 외곽선이 끊어지고 지직거리고 하는 효과가 나타남

해결 방법은 여러가지가 있다.
1. AlphaSotring(알파값으로 정렬) 2. 깊이 정렬(깊이 값으로 정렬) 3. 깊이를 이용하는 방법
실제 실무에서는 Alpha Sorting함
Alpha Sorting이란? 알파 값이 가장 큰 것(불투명에 가까운것을)을 뒤에서 부터 그릴 수 있도록 정렬하는 기술
이렇게 하면 Z이 겹치더라도 가장 찐한 것이 뒤에서 그려지기 때문에 해결이 된다.
깊이를 정렬함, 어느정도 깊이를 만들어놓고, 알파에 의해 깊이를 정렬함 
알파와 깊이가 겹쳐있지 않으면, 그냥 AlphaSorting으로 해결이 됨(혹은 깊이 sorting도 가능)

지금 우리는 깊이로 판단불가? 누가 먼저 그려질지
현재 상황에서는 깊이 값이 같은 상황이고 픽셀 셰이더까지 완료되어야 알파 값을 알수 있는데 현재로서는 불가능하다.(그래서 AlphaSorting 불가)
그래서 깊이를 이용한 다른 방식으로 해결한다.
-> 깊이를 이용하는 방법(밑에 설명)

우리가 이전에 깊이를 껐었는데, 깊이를 끄면 그리는 순서대로 그려졌었다.
그래서 이전에 했던 말이 있다.
하늘 할때, 깊이를 끄고 하늘을 그리고, 나머지 3D를 그림
근데 만약 3D를 그리고, 깊이를 끄고 하늘을 그리면 -> 하늘이 3D를 모두 가려버린다.(그리는 순서대로니까)

깊이를 끄고 출력하니
그 문제가 사라졌다.

그런데 문제가 구 앞에서 불이 그려져서 구에 불이 안가려진다. -> 깊이가 무시되고 맨 끝에 그려지니까, 하늘도 이렇게 하면 나머지 3D를 다 가리게 된다.

대략 해결되는 것처럼 보이나 파티클이 항상 맨앞에 나오는 문제가 생긴다.
-> 그래서 일단 깊이는 켜야함

// 깊이를 키겠다는 것
DepthStencilState DepthRead_Particle
{
    DepthEnable = true;
    // 작거나 같은 값에 대해 비교하겠다.
    // Less_Equal이 기본값이므로 원래의 현상과 같아진다.
    // 그래서 DepthWriteMask도 준다.
    DepthFunc = Less_Equal;
    // DepthWriteMask의 원래 기본 값은 전체를 쓰도록되어있는데,
    // 0를 만들어서 꺼버림
    DepthWriteMask = 0;
};

DepthWriteMask = 0으로 문제가 해결된다. 가려지고, 뒤에서 불이 잘나타남

왜 해결이 되었나면?
깊이를 비교한다음에 Less_Equal이면 작거나 같으면 깊이를 쓰고 픽셀을 찍는다.
그런데 DepthWriteMask = 0를 주면 깊이를 비교하지 않는다. 무조건 정점내에서는 다시 찍을때는 서로간에 깊이가 무시가됨, 정점들 렌더링 할때는
자기 정점간의 깊이를 비교하지 않음 -> 이 안에서는 깊이를 비교하지 않아서 그리는 순서대로 됨
Depth를 끄면 전체에서 깊이 비교가 안되는 것이다.
근데 Depth는 켜놨고, 픽셀들을 렌더링 할때는 깊이를 비교하지 않겠다.(파티클 파이어 하나에 대해서는 깊이를 비교하지 않고 순서대로 써짐)
-> 한번에 그려지는 정점 간에는 깊이를 비교하지 않기 때문에 정점이 앞에 있는 것부터 순서대로 그려진다. -> Z-Fighting이 안일어남


ParticleEditor : 파티클 파일 편집, 설정값 바꿀 수 있게

Particle 편집시에는 Editor.h 쓰고
Particle 테스트 시에는 Viewer.h 쓰면 된다.

MaxParticles : 파티클 입자 수
ReadyRandomTime : 얼마만큼 빠르게
ReadyTime : 길이 조정가능
StartVelocity : 시작 속도
StartVelocity : 끝까지 가는 속도(방향이 바뀜)

수평 속도
MinHorizontalVelocity
MaxHorizontalVelocity

수직 속도
MinVerticalVelocity
MaxVerticalVelocity

Gravity : 중력(중력을 반대로 주면 밑으로 떨어짐 -> 눈이나, 비도 이렇게 적용)

ColorAmount : 색의 진함 조정가능

텍스쳐의 색이 들어가있어서 색이 다르게 보임(텍스쳐와 색을 곱함)
MinColor
MaxColor

얼마만큼 빠르게 회전할 것인지(회전속도)
MinRotateSpeed
MaxRotateSpeed

최소 최대 시작크기
MinStartSize
MaxStartSize 

최소 최대 끝 크기
MinEndSize
MaxEndSize


더 하고 싶다면 수식 추가하면됨(뭐 공식이 있는것은 아님)


나중에 Bloom이랑 결합하여 훨씬 더 있어보임(뽀샤시 해보임)
수식들은 찾으면 많이 있다.

 

 

 

 

ParticleData.h


더보기
#pragma once
#include "Framework.h"

/*
	ParticleData란? 입자들의 이동과 회전, 색상들을 운영할 값들을 저장한 구조체
*/

struct ParticleData
{
	// BlendType같은 경우는 Pass가 됨
	// 0, 1, 2 그대로 Pass번호가 됨
	enum class BlendType
	{
		Opaque = 0, Additive, AlphaBlend
	} Type = BlendType::Opaque;


	// 반복할 거냐 말거냐도 추가
	bool bLoop = false;


	// 입자가 어떤 텍스쳐를 할지 
	wstring TextureFile = L"";

	// 전체 파티클의 개수
	UINT MaxParticles = 100;

	// 계속 1초마다 파티클이 발생하면 이상하다.
	// 그래서 불규칙성을 주기위해 RandomTime을 준다.
	float ReadyTime = 1.0f;
	float ReadyRandomTime = 0;

	// 시작 속도에 곱해줄(예를 들어 0.5라면 시작속도가 반 줄어서 시작)
	float StartVelocity = 1;
	// 끝으로 가면 갈수록 곱해줄 값(2를 주면 끝으로 가면갈수록 2배 빨라짐)
	float EndVelocity = 1;

	// 수평으로 점점 진행될 값
	float MinHorizontalVelocity = 0;
	float MaxHorizontalVelocity = 0;

	// 수직으로 점점 진행될 값(수직이라기 보단 Z값이다)
	float MinVerticalVelocity = 0;
	float MaxVerticalVelocity = 0;

	// 중력 값
	Vector3 Gravity = Vector3(0, 0, 0);
	

	// 색상을 단지 Lerp만 해주면 색의 진하기 정도를 조정할 수 없으므로
	// 결과에 색의 진하기 정도를 곱할 수 있도록 추가했다.
	float ColorAmount = 1.0f;

	// 최소, 최대 컬러(안에서 최소 최대 컬러 값 사이로 랜덤 결정)
	Color MinColor = Color(1, 1, 1, 1);
	Color MaxColor = Color(1, 1, 1, 1);

	// 회전 관련
	float MinRotateSpeed = 0;
	float MaxRotateSpeed = 0;

	// 시작할때 작은 크기, 큰 크기 (그 사이 값을 랜덤으로 가짐)
	float MinStartSize = 100;
	float MaxStartSize = 100;

	float MinEndSize = 100;
	float MaxEndSize = 100;
};

 

 

 

 

 

 

 

 

ParticleSystem.h


더보기
#pragma once

class ParticleSystem : Renderer
{
public:
	// file : 파티클을 읽는 XML 파일
	ParticleSystem(wstring file);
	~ParticleSystem();

	void Reset();
	// 외부에서 입자 추가하는 함수
	void Add(Vector3& position);

public:
	// 외부에서 콜하는
	void Update();

private:
	// 추가된 정점들을 GPU로 옮겨줌
	// leadCount를 gpuCount가 따라가며
	// 두개의 차이만큼 Map을 실행한다.
	void MapVertices();
	
	// 우리가 현재 활성화되어있는 것만 그릴것이다.
	void Activate();
	void Deactivate();

public:
	void Render();

	// 외부에서 수정해서 쓸 수 있도록 &
	ParticleData& GetData() { return data; }
	void SetTexture(wstring file);

private:
	void ReadFile(wstring file);

private:
	struct VertexParticle
	{
		Vector3 Position;
		Vector2 Corner; //(-1 ~ +1) 이걸로 Uv나 면을 만들어냄
		Vector3 Velocity; // 속도
		Vector4 Random; //x:주기, y:크기, z:회전, w:색상 (모두 랜덤으로 줌)
		float Time; // 현재 시간
	};

private:
	struct Desc
	{
		Color MinColor;
		Color MaxColor;

		Vector3 Gravity;
		float EndVelocity;

		Vector2 StartSize;
		Vector2 EndSize;

		Vector2 RotateSpeed;
		float ReadyTime;
		float ReadyRandomTime;

		float ColorAmount;
		float CurrentTime;
		float Padding[2];
	} desc;

private:
	ParticleData data;

	Texture* map = NULL;
	ID3DX11EffectShaderResourceVariable* sMap;

	ConstantBuffer* buffer;
	ID3DX11EffectConstantBuffer* sBuffer;

	VertexParticle* vertices = NULL;
	UINT* indices = NULL;

	float currentTime = 0.0f; // 현재 시간
	float lastAddTime = 0.0f; // 마지막 입자가 추가된 시간

	// 아래의 값들은 모두 순환 큐처럼 순환하는 변수들
	UINT leadCount = 0; // 가장 앞서는 개수
	UINT gpuCount = 0; // leadCount를 따라가면서 GPU에 복사할 개수
	UINT activeCount = 0; // gpuCount를 따라가며, 활성화활 개수 
	UINT deactiveCount = 0; // activeCount를 따라가며, 비활성화시킬 개수

};

 

 

 

 

 

 

 

ParticleSystem.cpp


더보기
#include "Framework.h"
#include "ParticleSystem.h"
#include "Utilities/Xml.h"

ParticleSystem::ParticleSystem(wstring file) :
	Renderer(L"89_Particle.fx")
{
	ReadFile(L"../../_Textures/Particles/" + file + L".xml");

	buffer = new ConstantBuffer(&desc, sizeof(Desc));
	sBuffer = shader->AsConstantBuffer("CB_Particle");

	sMap = shader->AsSRV("ParticleMap");


	Reset();
}

ParticleSystem::~ParticleSystem()
{
	SafeDelete(map);
	SafeDelete(buffer);

	SafeDeleteArray(vertices);
	SafeDeleteArray(indices);
}

// MaxParticle이 바뀌면 정점의 개수가 바뀐다.
// 바뀌는 거 처리하기 위해
// Reset()은 정점의 개수가 바뀔때 사용한다.
void ParticleSystem::Reset()
{
	currentTime = 0.0f; // 현재 시간 0으로 초기화
	lastAddTime = Time::Get()->Running(); // 마지막 추가된 시간은 현재시간으로 넣어놈
	gpuCount = leadCount = activeCount = deactiveCount = 0; // 변수들 다 0으로 

	SafeDeleteArray(vertices);
	SafeDeleteArray(indices);

	SafeDelete(vertexBuffer);
	SafeDelete(indexBuffer);

	// 한면 : 입자 개수 * 4
	vertices = new VertexParticle[data.MaxParticles * 4];
	for (UINT i = 0; i < data.MaxParticles; i++)
	{
		// Corner: UV와 포지션 등을 만들기 위해 사용
		vertices[i * 4 + 0].Corner = Vector2(-1, -1);
		vertices[i * 4 + 1].Corner = Vector2(-1, +1);
		vertices[i * 4 + 2].Corner = Vector2(+1, -1);
		vertices[i * 4 + 3].Corner = Vector2(+1, +1);
	}

	// 인덱스 개수 : 입자 * 6
	indices = new UINT[data.MaxParticles * 6];
	for (UINT i = 0; i < data.MaxParticles; i++)
	{
		indices[i * 6 + 0] = i * 4 + 0;
		indices[i * 6 + 1] = i * 4 + 1;
		indices[i * 6 + 2] = i * 4 + 2;
		indices[i * 6 + 3] = i * 4 + 2;
		indices[i * 6 + 4] = i * 4 + 1;
		indices[i * 6 + 5] = i * 4 + 3;
	}

	vertexBuffer = new VertexBuffer(vertices, data.MaxParticles * 4, sizeof(VertexParticle), 0, true);
	indexBuffer = new IndexBuffer(indices, data.MaxParticles * 6);
}

// 한번에 업데이트에서 여러개 추가할 수도 있다.
void ParticleSystem::Add(Vector3& position)
{
	// 60프레임마다 추가할 수 있도록 프레임 제한
	// 60 / 1000 보다 작으면 실행을 안함
	// 안그러면 미친듯이 추가됨, 제한 걸어야함
	if (Time::Get()->Running() - lastAddTime < 60.0f / 1000.0f)
		return;

	lastAddTime = Time::Get()->Running();

	// leadCount : 가장 먼저 시작해서 갈 카운트
	UINT count = leadCount + 1;

	// 끝까지 갔다면 0으로 돌아온다. 원형큐 방식으로
	if (count >= data.MaxParticles)
	{

		if (data.bLoop == true)
		{
			// 무한반복
			count = 0;
		}

		// 무한 루프 방지
		else
		{
			// 멈춰 버리게 한다.
			count = data.MaxParticles;

			return;
		}
	}

	// deactiveCount라면 추가시키지 않는다.
	if (count == deactiveCount)
		return;

	// 기본 속도이자 방향으로 1을 줌(모든 방향으로 1임)
	Vector3 velocity = Vector3(1, 1, 1);
	// StartVelocity가 0.5였다면 시작속도가 반 줄 것이다.
	velocity *= data.StartVelocity;

	// 수평으로 이동할 속도를 만듬
	// Lerp : 선형보간 함수
	// 이렇게 하면 선형으로 랜덤으로 섞이면서 나온다.
	float horizontalVelocity = Math::Lerp(data.MinHorizontalVelocity, data.MaxHorizontalVelocity, Math::Random(0.0f, 1.0f));

	// z 방향 회전할 것이다.
	// PI(180) * 2 * 0 ~ 1
	float horizontalAngle = Math::PI * 2.0f * Math::Random(0.0f, 1.0f);

	// 속도 : 시간만큼 이동했을 때의 이동 위치, sin/cos을 이용하여 회전 이동 시킨다.
	// 속도를 가산 시키면 이동이라는 의미도 갖는다.(얼마만큼 이동할지)
	// 항등식 정리에 의하면 x는 cos, y는 sin에 매핑됨
	// 그만큼 회전하면서 이동하게 됨
	velocity.x += horizontalVelocity * cos(horizontalAngle);
	velocity.y += horizontalVelocity * sin(horizontalAngle);
	velocity.z += Math::Lerp(data.MinHorizontalVelocity, data.MaxHorizontalVelocity, Math::Random(0.0f, 1.0f));

	// cos -sin sin cos
	// x    y    y   x


	Vector4 random = Math::RandomVec4(0.0f, 1.0f);

	for (UINT i = 0; i < 4; i++)
	{
		// 속도(Velocity)를 줘서 position에 더하면 그만큼 이동한다.
		// 속도가 10이라면 1프레임은 10이동했고 2프레임은 20이동했다면
		// Position 0이 었다면, 1프레임에 10만큼 이동하고..
		vertices[leadCount * 4 + i].Position = position;
		vertices[leadCount * 4 + i].Velocity = velocity;
		vertices[leadCount * 4 + i].Random = random;
		// CurrentTime은 현재 시간이며, Update에서 계속 갱신이 될 것이다.
		// Time은 결국 자기가 활성화된(추가된) 시간이 된다.
		vertices[leadCount * 4 + i].Time = currentTime;
	}

	leadCount = count;
}

void ParticleSystem::Update()
{
	Super::Update();

	currentTime += Time::Delta();

	MapVertices();
	Activate();
	Deactivate();

	// 처음부터 다시 반복해주려고
	// 다시 처음부터 반복해주려고
	if (activeCount == leadCount)
		currentTime = 0.0f;

	desc.MinColor = data.MinColor;
	desc.MaxColor = data.MaxColor;
	desc.ColorAmount = data.ColorAmount;

	desc.Gravity = data.Gravity;
	desc.EndVelocity = data.EndVelocity;

	// 다루기가 편해서 vector2
	desc.RotateSpeed = Vector2(data.MinRotateSpeed, data.MaxRotateSpeed);
	desc.StartSize = Vector2(data.MinStartSize, data.MaxStartSize);
	desc.EndSize = Vector2(data.MinEndSize, data.MaxEndSize);

	desc.ReadyTime = data.ReadyTime;
	desc.ReadyRandomTime = data.ReadyRandomTime;
}


void ParticleSystem::Activate()
{
	// 시간에 따라 그릴 것을 활성화 시킴
	while (activeCount != gpuCount)
	{
		// 정점은 값을 모두 동일하게 저장해놓았다.
		// 그래서 맨 앞에 값을 준다.(아무거나)
		// .Time : 추가되었거나 활성화된 시간
		// currentTime : 현재 시간
		float age = currentTime - vertices[activeCount * 4].Time;


		// 두개의 차가 ReadyTime보다 적다면
		// 준비가 안 끝났다.
		if (age < data.ReadyTime)
			return;
		
		// 활성화된 현재 시간 넣어줌
		vertices[activeCount * 4].Time = currentTime;
		
		// leadCount와 activeCount을 이용해서 개수를 만들어서 그릴 것이다.
		activeCount++;

		// 얘도 크다면 처음으로 돌아감
		// 순환 시킴
		// 얘도 bLoop가 true일시 무한 반복이고 아니라면 maxParticles에서 멈춤
		if (activeCount >= data.MaxParticles)
			activeCount = data.bLoop == true ? 0 : data.MaxParticles;
	}
}


void ParticleSystem::Deactivate()
{
	// deactiveCount가 있는 이유는 ReadyTime이 끝나면 비활성화가 되어야 하기때문에
	// deactive는 active를 따라간다.
	// leadcount를 gpuCount가 따라가고 그 다음 activeCount가 따라가고
	// 그 뒤를 deactiveCount가 따라간다.
	while (activeCount != deactiveCount)
	{
		float age = currentTime - vertices[deactiveCount * 4].Time;

		// 대기 시간이 끝났다면 활성화가 되었다는 얘기
		if (age > data.ReadyTime)
			return;

		// 따라감
		deactiveCount++;

		if (deactiveCount >= data.MaxParticles)
			deactiveCount = data.bLoop == true ? 0 : data.MaxParticles;
	}
}


void ParticleSystem::Render()
{
	Super::Render();

	desc.CurrentTime = currentTime;

	buffer->Render();
	sBuffer->SetConstantBuffer(buffer->Buffer());

	sMap->SetResource(map->SRV());

	// 같다면 수행안함(즉, 같지 않다면 그린다.)
	// leadCount가 ActiveCount를 다따라잡았다면 그릴게 없는것이다.
	if (leadCount == activeCount)
		return;

	UINT pass = (UINT)data.Type;
	// leadCount와 activeCount의 차이만큼 그려주면 된다.
	if (activeCount < leadCount)
	{
		// 인덱스번호니깐
		shader->DrawIndexed(0, pass, (leadCount - activeCount) * 6, activeCount * 6);
	}

	//lead보다 active가 작다면
	// 배열 크기만큼 그려주고, 시작 ~ lead만큼 또 그려준다.
	else
	{
		shader->DrawIndexed(0, pass, (data.MaxParticles - activeCount) * 6, activeCount * 6);

		if (leadCount > 0)
			shader->DrawIndexed(0, pass, leadCount * 6);
	}
}


void ParticleSystem::MapVertices()
{
	// 원형큐로 생각하면 됨
	// lead 카운트 만큼 복사하려고 하는 것
	if (gpuCount == leadCount) return;

	D3D11_MAPPED_SUBRESOURCE subResource;
	
	// 이런 상황이 왜 발생하냐면? 

	// Add 함수에서 최대 입자보다 크다면 0으로 돌려주기 때문
	// 끝까지 갔다면 0으로 돌아온다. 원형큐 방식으로
	//if (count >= data.MaxParticles)
	//	count = 0;

	/*
		Write - Cpu_Access_Write일때만 접근, Discard랑 유사하게 덮어씌움
		Write_Discard - 이전에 쓰여있는 데이터에 덮어씌움(Discard-> 기존 데이터를 다 날리고 새로 다시 씀)
		Write_No_Overwrite - 이전에 씌어 있는 상태에서 지우지 않고 지정한 위치부터 해줌
	
	*/
	if (leadCount > gpuCount)
	{
		D3D::GetDC()->Map(vertexBuffer->Buffer(), 0, D3D11_MAP_WRITE_NO_OVERWRITE, 0, &subResource);
		{
			// 시작 위치를 잡는다.(No_Overwrite여서 이어서 쓸 위치를 구한다.)
			// 정점은 4개니까 * 4
			UINT start = gpuCount * 4;
			UINT size = (leadCount - gpuCount) * sizeof(VertexParticle) * 4;
			// gpu에다가 gpu 시작번호가 됨
			// 이이서 쓸거여서
			UINT offset = gpuCount * sizeof(VertexParticle) * 4; 

			// 시작 + 오프셋(이어서 쓸 위치)를 더함
			BYTE* p = (BYTE*)subResource.pData + offset;
			memcpy(p, vertices + start, size);
		}
		D3D::GetDC()->Unmap(vertexBuffer->Buffer(), 0);
		
	}

	else
	{
		// leadCount가 gpuCount보다 작으면 역전되어있는 상태
		// gpuCount에서 마지막까지 쓰고, 시작부터 leadCount까지 쓴다.

		D3D::GetDC()->Map(vertexBuffer->Buffer(), 0, D3D11_MAP_WRITE_NO_OVERWRITE, 0, &subResource);
		{
			UINT start = gpuCount * 4;
			// gpuCount부터 MaxParticle까지 써줌
			UINT size = (data.MaxParticles - gpuCount) * sizeof(VertexParticle) * 4;
			UINT offset = gpuCount * sizeof(VertexParticle) * 4;

			BYTE* p = (BYTE*)subResource.pData + offset;
			memcpy(p, vertices + start, size);
		}

		if (leadCount > 0)
		{
			UINT size = leadCount * sizeof(VertexParticle) * 4;

			// 시작 주소 부터 사이즈만큼 써감
			memcpy(subResource.pData, vertices, size);
		}
		D3D::GetDC()->Unmap(vertexBuffer->Buffer(), 0);
	}

	// 같게 만들어줌
	gpuCount = leadCount;
}

void ParticleSystem::SetTexture(wstring file)
{
	SafeDelete(map);

	map = new Texture(file);
}

// 파티클 데이터는 XML 파일로 부터 불러온다.
void ParticleSystem::ReadFile(wstring file)
{
	Xml::XMLDocument* document = new Xml::XMLDocument();
	Xml::XMLError error = document->LoadFile(String::ToString(file).c_str());
	assert(error == Xml::XML_SUCCESS);

	Xml::XMLElement* root = document->FirstChildElement();

	Xml::XMLElement* node = root->FirstChildElement();
	data.Type = (ParticleData::BlendType)node->IntText();

	node = node->NextSiblingElement();
	data.bLoop = node->BoolText();

	node = node->NextSiblingElement();
	wstring textureFile = String::ToWString(node->GetText());
	data.TextureFile = L"Particles/" + textureFile;
	map = new Texture(data.TextureFile);

	node = node->NextSiblingElement();
	data.MaxParticles = node->IntText();

	node = node->NextSiblingElement();
	data.ReadyTime = node->FloatText();

	node = node->NextSiblingElement();
	data.ReadyRandomTime = node->FloatText();

	node = node->NextSiblingElement();
	data.StartVelocity = node->FloatText();

	node = node->NextSiblingElement();
	data.EndVelocity = node->FloatText();

	node = node->NextSiblingElement();
	data.MinHorizontalVelocity = node->FloatText();

	node = node->NextSiblingElement();
	data.MaxHorizontalVelocity = node->FloatText();

	node = node->NextSiblingElement();
	data.MinVerticalVelocity = node->FloatText();

	node = node->NextSiblingElement();
	data.MaxVerticalVelocity = node->FloatText();

	node = node->NextSiblingElement();
	data.Gravity.x = node->FloatAttribute("X");
	data.Gravity.y = node->FloatAttribute("Y");
	data.Gravity.z = node->FloatAttribute("Z");

	node = node->NextSiblingElement();
	data.ColorAmount = node->FloatText();

	node = node->NextSiblingElement();
	data.MinColor.r = node->FloatAttribute("R");
	data.MinColor.g = node->FloatAttribute("G");
	data.MinColor.b = node->FloatAttribute("B");
	data.MinColor.a = node->FloatAttribute("A");

	node = node->NextSiblingElement();
	data.MaxColor.r = node->FloatAttribute("R");
	data.MaxColor.g = node->FloatAttribute("G");
	data.MaxColor.b = node->FloatAttribute("B");
	data.MaxColor.a = node->FloatAttribute("A");

	node = node->NextSiblingElement();
	data.MinRotateSpeed = node->FloatText();

	node = node->NextSiblingElement();
	data.MaxRotateSpeed = node->FloatText();

	node = node->NextSiblingElement();
	data.MinStartSize = node->FloatText();

	node = node->NextSiblingElement();
	data.MaxStartSize = node->FloatText();

	node = node->NextSiblingElement();
	data.MinEndSize = node->FloatText();

	node = node->NextSiblingElement();
	data.MaxEndSize = node->FloatText();

	SafeDelete(document);
}

 

 

Viewer.h


더보기
#pragma once
#include "Systems/IExecute.h"

class Viewer : public IExecute
{
public:
	virtual void Initialize() override;
	virtual void Ready() override {};
	virtual void Destroy() override;
	virtual void Update() override;
	virtual void PreRender() override {};
	virtual void Render() override;
	virtual void PostRender() override {};
	virtual void ResizeScreen() override {};

private:
	void Mesh();

private:
	Shader* shader;

	CubeSky* sky;

	ParticleSystem* particleSystem;

	Material* floor;
	Material* stone;
	
	MeshRender* sphere;
	MeshRender* grid;
};

 

 

 

 

 

 

 

Viewer.cpp


더보기
#include "stdafx.h"
#include "Viewer.h"

void Viewer::Initialize()
{
	Context::Get()->GetCamera()->RotationDegree(20, 0, 0);
	Context::Get()->GetCamera()->Position(1, 36, -85);

	shader = new Shader(L"82_NormalMapping.fxo");
	sky = new CubeSky(L"Environment/GrassCube1024.dds");
	
	particleSystem = new ParticleSystem(L"Fire");
	//particleSystem = new ParticleSystem(L"Explosion");


	Mesh();
}

void Viewer::Destroy()
{
}

void Viewer::Update()
{
	sky->Update();

	sphere->Update();
	grid->Update();

	Vector3 position;
	sphere->GetTransform(0)->Position(&position);

	if (Keyboard::Get()->Press('L'))
		position.x += 20 * Time::Delta();
	else if (Keyboard::Get()->Press('J'))
		position.x -= 20 * Time::Delta();

	if (Keyboard::Get()->Press('I'))
		position.z += 20 * Time::Delta();
	else if (Keyboard::Get()->Press('K'))
		position.z -= 20 * Time::Delta();

	if (Keyboard::Get()->Press('O'))
		position.y += 20 * Time::Delta();
	else if (Keyboard::Get()->Press('U'))
		position.y -= 20 * Time::Delta();

	sphere->GetTransform(0)->Position(position);
	sphere->UpdateTransforms();

	particleSystem->Add(position);
	particleSystem->Update();
}

void Viewer::Render()
{
	sky->Render();

	stone->Render();
	sphere->Render();

	floor->Render();
	grid->Render();


	// 반투명이어서 맨뒤에서 렌더링되어야함
	particleSystem->Render();
}

void Viewer::Mesh()
{
	floor = new Material(shader);
	floor->DiffuseMap("Floor.png");
	floor->Specular(1, 1, 1, 20);
	floor->SpecularMap("Floor_Specular.png");
	floor->NormalMap("Floor_Normal.png");

	stone = new Material(shader);
	stone->DiffuseMap("Stones.png");
	stone->Specular(1, 1, 1, 20);
	stone->SpecularMap("Stones_Specular.png");
	stone->Emissive(1.0f, 1.0f, 1.0f, 0.3f);
	stone->NormalMap("Stones_Normal.png");

	Transform* transform = NULL;

	grid = new MeshRender(shader, new MeshGrid(5, 5));
	transform = grid->AddTransform();
	transform->Position(0, 0, 0);
	transform->Scale(12, 1, 12);
	grid->UpdateTransforms();

	sphere = new MeshRender(shader, new MeshSphere(0.5f, 20, 20));
	transform = sphere->AddTransform();
	transform->Position(0, 5, 0);
	transform->Scale(5, 5, 5);
	sphere->UpdateTransforms();
}

 

 

 

 

89_Particle.fx


더보기
#include "00_Global.fx"

// 텍스쳐
Texture2D ParticleMap;

struct ParticleDesc
{
    float4 MinColor;
    float4 MaxColor;
    
    float3 Gravity;
    float EndVelocity;
    
    float2 StartSize;
    float2 EndSize;
    
    float2 RotateSpeed;
    float ReadyTime;
    float ReadyRandomTime;
    
    float ColorAmount;
    float CurrentTime;
};

cbuffer CB_Particle
{
    ParticleDesc Particle;
};

struct VertexInput
{
    float4 Position : Position;
    float2 Corner : Uv;
    float3 Velocity : Velocity;
    float4 Random : Random; //x : 주기, y - 크기, z - 회전, w - 색상
    float Time : Time;
};


struct VertexOutput
{
    float4 Position : SV_Position;
    float4 Color : Color;
    float2 Uv : Uv;
};

// 1. 랜덤값, 2. 정규화된시간
float ComputeSize(float value, float normalizeAge)
{
    // x는 min, y는 max
    float start = lerp(Particle.StartSize.x, Particle.StartSize.y, value);
    float end = lerp(Particle.EndSize.x, Particle.EndSize.y, value);
    
    // start 선택된 값, end 선택된 값, 0 ~ 1사이(정규화된 시간에 따라 lerp가 선택이 됨)
    // 시간에 따라 랜덤한 값으로 선택됨
    return lerp(start, end, normalizeAge);
}


float4 ComputePosition(float3 position, float3 velocity, float age, float normalizedAge)
{
    // 이동할 방향, 크기는 속도가 됨
    float start = length(velocity);
    
    
    // start 속도를 크기만큼 늘림
    float end = start * Particle.EndVelocity;
    
    
    // normalize = 정규화된 시간
    // 시작 * 정규화된 시간 + 시작 부터 끝까지 차에 대한걸 만듬
    // 뒤에 중력곱할려고 normalizedAge 함
    // 결국엔 이동시작해서 끝날 방향이됨
    // normalizedAge가 0.5라면 시작지점이 반이됨
    // end - start안하고 end만 하면 start만큼 온데서 end만큼 더감(중심점 잡으려고와 비슷함)
    // 그래서 딱 start에서 end 지점까지 길이가 나오게 된다.
    // /2 한 이유는 대략 반정도, Particle.ReadyTime, age 뒤에 곱하는거 때문임
    float amount = start * normalizedAge + (end - start) * normalizedAge / 2;
    
    // normalize(velocity) -> 길이는 사라지고 방향만 나옴
    // 방향에 * amount(start -> end 길이)만큼 늘림
    position += normalize(velocity) * amount * Particle.ReadyTime;
    // 정규화된 시간을 곱해서 중력값까지 적용해서 포지션이 이동할 수 있도록 함
    // 위에 /2를 빼면 normalizedAge가 안먹을 것이다.
    // Particle.ReadyTime * age 곱한거에 줄여버리니까, 차이가 거의 없다.
    position += Particle.Gravity * age * normalizedAge;
    
    // position는 로컬이자 월드
    // (Local -> World, View, Projection)에서 Local이 이동되어 있다고 간주, World를 생략해서 Local -> (view, Projection)
    // 여기서 world 변환되었다고 간주
    
    return ViewProjection(float4(position, 1));
}

// z축 평면회전만 할꺼여서 2x2(z축회전하면 z축은 안바뀐다 -> z축 빼고)
// value : 랜덤 값, age : 주기
float2x2 ComputeRotation(float value, float age)
{
    float angle = lerp(Particle.RotateSpeed.x, Particle.RotateSpeed.y, value);
    // 시간에 따라 변하도록
    float radian = angle * age;
    
    float c = cos(radian);
    float s = sin(radian);
    
    // 1행 1열에 c, 1행 2열에 -s
    // 2행 1열에 s, 2행 2열에 c
    // 삼각함수의 항등식 정리 그대로 나옴
    // cos -sin sin cos
	// x    y    y   x
    return float2x2(c, -s, s, c);
}

float4 ComputeColor(float value, float normalizeAge)
{
    float4 color = lerp(Particle.MinColor, Particle.MaxColor, value) * normalizeAge;
    
    // 색을 조정할 수 있도록
    return color * Particle.ColorAmount;
}

VertexOutput VS(VertexInput input)
{
    VertexOutput output;
    // 주기를 만든다.
    float age = Particle.CurrentTime - input.Time;
    // ReadyRandomTime : 대기 시간 랜덤으로 주는것
    // 주기 * 대기시간 + 1
    // + 1 이유는 0이 들어오면 값이 사라져서
    age *= input.Random.x * Particle.ReadyRandomTime + 1;

    // age / 대기시간 = 대기시간에 대한 age의 비율 값
    // 왜? age는 대기시간을 넘어서는 안된다.
    float normalizeAge = saturate(age / Particle.ReadyTime);
    
    output.Position = ComputePosition(input.Position.xyz, input.Velocity, age, normalizeAge);
    
    float size = ComputeSize(input.Random.y, normalizeAge);
    // noramlize된 age는 필요없어서, age줌
    // z에 회전 랜덤값 들어감
    // view, projection 변환된 후에 진행하는 거여서 2D 공간이다.
    // 2D 공간에서 회전은 z축회전과 같다.(2D 공간에서 회전시킴)
    float2x2 rotation = ComputeRotation(input.Random.z, age);
    
    // Corner는 -1 ~ +1이니까, 중심점을 기준으로 rotation만큼 회전한다.
    // -0.5 ~ 0.5를 사용하니깐, 반 줄임
    output.Position.xy += mul(input.Corner, rotation) * size * 0.5f;
    
    // -1 ~ 1까지를 0 ~ 1까지로 바꿈(uv는 0 ~ 1까지니까)
    output.Uv = (input.Corner + 1.0f) * 0.5f;
    
    output.Color = ComputeColor(input.Random.w, normalizeAge);

    
    return output;
}

float4 PS(VertexOutput input) : SV_Target
{
    return ParticleMap.Sample(LinearSampler, input.Uv) * input.Color;
}

technique11 T0
{
    P_DSS_BS_VP(P0, DepthRead_Particle, OpaqueBlend, VS, PS)
    P_DSS_BS_VP(P1, DepthRead_Particle, AdditiveBlend_Particle, VS, PS)
    P_DSS_BS_VP(P2, DepthRead_Particle, AlphaBlend, VS, PS)
}

 

 

 

00_Global.fx 추가된 내용


더보기
...

DepthStencilState DepthEnable_False
{
    DepthEnable = false;
};

BlendState OpaqueBlend
{
    BlendEnable[0] = true;
    SrcBlend[0] = One;
    DestBlend[0] = Zero;
    BlendOp[0] = ADD;
    
    SrcBlendAlpha[0] = One;
    DestBlendAlpha[0] = Zero;
    BlendOpAlpha[0] = Add;
    
    RenderTargetWriteMask[0] = 15; //Ox0F
};

BlendState AlphaBlend
{
    AlphaToCoverageEnable = false;

    // 이런식으로도 되지만, 이런식으로 잘 안쓴다.
    //RenderTarget[0].BlendEnable
    // 이렇게 씀(배열 번호를 넣어줌)
    BlendEnable[0] = true; // 블랜딩 할꺼다(true 시에 속도 느려져서, 할곳만 켜야함)
    SrcBlend[0] = SRC_ALPHA;
    DestBlend[0] = INV_SRC_ALPHA;
    BlendOp[0] = ADD;

    SrcBlendAlpha[0] = One;
    DestBlendAlpha[0] = Zero;
    BlendOpAlpha[0] = Add;

    // 모든 색을 다 섞겠다.(전문적인 경우 제외하고 쓰는 경우없어서 이렇게 쓰는게 공식이라고 생각하자)
    RenderTargetWriteMask[0] = 15; //0x0F;
};

BlendState AlphaBlend_AlphaToCoverageEnable
{
    AlphaToCoverageEnable = true;

    BlendEnable[0] = true; 
    SrcBlend[0] = SRC_ALPHA;
    DestBlend[0] = INV_SRC_ALPHA;
    BlendOp[0] = ADD;

    SrcBlendAlpha[0] = One;
    DestBlendAlpha[0] = Zero;
    BlendOpAlpha[0] = Add;

    RenderTargetWriteMask[0] = 15; //0x0F;
};

// 색을 진하게 만드는 효과
BlendState AdditiveBlend
{
    AlphaToCoverageEnable = false;

    BlendEnable[0] = true; 

    // 둘다 One을 줘서 더해서 더 밝은 색을 만듬 -> 그래서 Additive
    SrcBlend[0] = One; // 파란색
    DestBlend[0] = One; // 빨간색
    BlendOp[0] = ADD; // 더해서 마젠타(자홍색) 색이나옴

    SrcBlendAlpha[0] = One;
    DestBlendAlpha[0] = Zero;
    BlendOpAlpha[0] = Add;

    RenderTargetWriteMask[0] = 15; //0x0F;
};

BlendState AdditiveBlend_Particle
{
    AlphaToCoverageEnable = false;
    
    BlendEnable[0] = true;
    SrcBlend[0] = SRC_ALPHA;
    DestBlend[0] = One;
    BlendOp[0] = ADD;
    
    SrcBlendAlpha[0] = One;
    DestBlendAlpha[0] = Zero;
    BlendOpAlpha[0] = Add;
    
    RenderTargetWriteMask[0] = 15; //Ox0F
};

BlendState AdditiveBlend_AlphaToCoverageEnable
{
    AlphaToCoverageEnable = true;

    BlendEnable[0] = true;

    SrcBlend[0] = One;
    DestBlend[0] = One;
    BlendOp[0] = ADD;

    SrcBlendAlpha[0] = One;
    DestBlendAlpha[0] = Zero;
    BlendOpAlpha[0] = Add;

    RenderTargetWriteMask[0] = 15; //0x0F;
};

// 깊이를 키겠다는 것
DepthStencilState DepthRead_Particle
{
    DepthEnable = true;
    // 작거나 같은 값에 대해 비교하겠다.
    // Less_Equal이 기본값이므로 원래의 현상과 같아진다.
    // 그래서 DepthWriteMask도 준다.
    DepthFunc = Less_Equal;
    // DepthWriteMask의 원래 기본 값은 전체를 쓰도록되어있는데,
    // 0를 만들어서 꺼버림
    DepthWriteMask = 0;
};

 

 

 

 

 

 

결과


 

 

 

 

 

Editor.h


더보기
#pragma once
#include "Systems/IExecute.h"

class Editor : public IExecute
{
public:
	virtual void Initialize() override;
	virtual void Ready() override {};
	virtual void Destroy() override;
	virtual void Update() override;
	virtual void PreRender() override {};
	virtual void Render() override;
	virtual void PostRender() override {};
	virtual void ResizeScreen() override {};

private:
	void Mesh();

private:
	void UpdateParticleList();
	void UpdateTextureList();

	void OnGUI();
	void OnGUI_List();
	void OnGUI_Settings();
	void OnGUI_Write();

	void WriteFile(wstring file);

private:
	Shader* shader;

	CubeSky* sky;

	float windowWidth = 500; // 윈도우 창(UI 사이즈)

	bool bLoop = false; // 루프 관련 저장
	UINT maxParticle = 0; // maxParticle 관련 저장

	vector<wstring> particleList; // 파티클 폴더 있는 파일리스트
	vector<wstring> textureList; // 파티클 폴더 안에 있는 텍스쳐리스트

	wstring file = L""; // 선택한 파일명을 저장하는 변수

	ParticleSystem* particleSystem = NULL;

	Material* floor;
	Material* stone;
	
	MeshRender* sphere;
	MeshRender* grid;
};

 

 

 

 

 

 

 

Editor.cpp


더보기
#include "stdafx.h"
#include "Editor.h"
#include "Utilities/Xml.h"

void Editor::Initialize()
{
	Context::Get()->GetCamera()->RotationDegree(20, 0, 0);
	Context::Get()->GetCamera()->Position(1, 36, -85);

	shader = new Shader(L"82_NormalMapping.fxo");
	sky = new CubeSky(L"Environment/GrassCube1024.dds");

	Mesh();
	UpdateParticleList();
	UpdateTextureList();
}

void Editor::Destroy()
{
}

void Editor::Update()
{
	OnGUI();

	sky->Update();

	sphere->Update();
	grid->Update();

	Vector3 position;
	sphere->GetTransform(0)->Position(&position);

	if (Keyboard::Get()->Press('L'))
		position.x += 20 * Time::Delta();
	else if (Keyboard::Get()->Press('J'))
		position.x -= 20 * Time::Delta();

	if (Keyboard::Get()->Press('I'))
		position.z += 20 * Time::Delta();
	else if (Keyboard::Get()->Press('K'))
		position.z -= 20 * Time::Delta();

	if (Keyboard::Get()->Press('O'))
		position.y += 20 * Time::Delta();
	else if (Keyboard::Get()->Press('U'))
		position.y -= 20 * Time::Delta();

	sphere->GetTransform(0)->Position(position);
	sphere->UpdateTransforms();

	if (particleSystem != NULL)
	{
		particleSystem->Add(position);
		particleSystem->Update();
	}
}

void Editor::Render()
{
	sky->Render();

	stone->Render();
	sphere->Render();

	floor->Render();
	grid->Render();


	// 반투명이어서 맨뒤에서 렌더링되어야함
	if (particleSystem != NULL)
		particleSystem->Render();
}

void Editor::Mesh()
{
	floor = new Material(shader);
	floor->DiffuseMap("Floor.png");
	floor->Specular(1, 1, 1, 20);
	floor->SpecularMap("Floor_Specular.png");
	floor->NormalMap("Floor_Normal.png");

	stone = new Material(shader);
	stone->DiffuseMap("Stones.png");
	stone->Specular(1, 1, 1, 20);
	stone->SpecularMap("Stones_Specular.png");
	stone->Emissive(1.0f, 1.0f, 1.0f, 0.3f);
	stone->NormalMap("Stones_Normal.png");

	Transform* transform = NULL;

	grid = new MeshRender(shader, new MeshGrid(5, 5));
	transform = grid->AddTransform();
	transform->Position(0, 0, 0);
	transform->Scale(12, 1, 12);
	grid->UpdateTransforms();

	sphere = new MeshRender(shader, new MeshSphere(0.5f, 20, 20));
	transform = sphere->AddTransform();
	transform->Position(0, 5, 0);
	transform->Scale(5, 5, 5);
	sphere->UpdateTransforms();
}

void Editor::UpdateParticleList()
{
	// 폴더를 불러옴
	particleList.clear();
	// false 파라미터는 자식 폴더는 가져오지 않음
	Path::GetFiles(&particleList, L"../../_Textures/Particles/", L"*.xml", false);

	for (wstring& file : particleList)
		file = Path::GetFileNameWithoutExtension(file);
}

void Editor::UpdateTextureList()
{
	textureList.clear();

	vector<wstring> files;
	Path::GetFiles(&files, L"../../_Textures/Particles/", L"*.*", false);

	for (wstring file : files)
	{
		// 대문자로 바꿔서 비교하기 위해
		wstring ext = Path::GetExtension(file);
		transform(ext.begin(), ext.end(), ext.begin(), toupper);

		// 3중에 하나만 선택해옴
		file = Path::GetFileName(file);
		if (ext == L"PNG" || ext == L"TGA" || ext == L"JPG")
			textureList.push_back(file);
	}
}

void Editor::OnGUI()
{
	float width = D3D::Width();
	float height = D3D::Height();

	bool bOpen = true;
	bOpen = ImGui::Begin("Particle", &bOpen);
	ImGui::SetWindowPos(ImVec2(width - windowWidth, 0));
	ImGui::SetWindowSize(ImVec2(windowWidth, height));
	{
		OnGUI_List();
		OnGUI_Settings();
	}
	ImGui::End();
}

// 리스트들 보여주는 부분
void Editor::OnGUI_List()
{
	// CollapsingHeader : 눌러서 리스트를 펼치고 하는 부분
	// ImGuiTreeNodeFlags_DefaultOpen : 처음부터 펼쳐져 있어라
	if (ImGui::CollapsingHeader("Particle List", ImGuiTreeNodeFlags_DefaultOpen))
	{
		for (UINT i = 0; i < particleList.size(); i++)
		{
			// Button 클릭시 안에 내용이 실행됨
			if (ImGui::Button(String::ToString(particleList[i]).c_str(), ImVec2(200, 0)))
			{
				SafeDelete(particleSystem);

				// 파일에 넣어놓고
				file = particleList[i];
				// 생성하고
				particleSystem = new ParticleSystem(particleList[i]);

				// 2개는 Vertex 정보가 바뀌기 때문에 리턴이 콜 되어야하는 애들
				bLoop = particleSystem->GetData().bLoop;
				maxParticle = particleSystem->GetData().MaxParticles;
			}
		}//for(i)
	}//ImGui::CollapsingHeader
}

void Editor::OnGUI_Settings()
{
	if (particleSystem == NULL) return;

	ImGui::Spacing();

	if (ImGui::CollapsingHeader("Particle Settings", ImGuiTreeNodeFlags_DefaultOpen))
	{
		ImGui::Separator();

		ImGui::SliderInt("MaxParticles", (int*)&maxParticle, 1, 1000);
		ImGui::Checkbox("Loop", &bLoop);

		// 적용시에 reset 콜함
		if (ImGui::Button("Apply"))
		{
			particleSystem->GetData().bLoop = bLoop;
			particleSystem->GetData().MaxParticles = maxParticle;
			particleSystem->Reset();
		}


		ImGui::Separator();

		const char* types[] = { "Opaque", "Additive", "AlphaBlend" };
		ImGui::Combo("BlendType", (int*)&particleSystem->GetData().Type, types, 3);

		ImGui::SliderFloat("ReadyTime", &particleSystem->GetData().ReadyTime, 0.1f, 10.0f);
		ImGui::SliderFloat("ReadyRandomTime", &particleSystem->GetData().ReadyRandomTime, 0.0f, 100.0f);

		ImGui::SliderFloat("StartVelocity", &particleSystem->GetData().StartVelocity, 0.0f, 10.0f);
		ImGui::SliderFloat("EndVelocity", &particleSystem->GetData().EndVelocity, -100.0f, 100.0f);

		ImGui::SliderFloat("MinHorizontalVelocity", &particleSystem->GetData().MinHorizontalVelocity, -100.0f, 100.0f);
		ImGui::SliderFloat("MaxHorizontalVelocity", &particleSystem->GetData().MaxHorizontalVelocity, -100.0f, 100.0f);

		ImGui::SliderFloat("MinVerticalVelocity", &particleSystem->GetData().MinVerticalVelocity, -100.0f, 100.0f);
		ImGui::SliderFloat("MaxVerticalVelocity", &particleSystem->GetData().MaxVerticalVelocity, -100.0f, 100.0f);

		ImGui::SliderFloat3("Gravity", particleSystem->GetData().Gravity, -100, 100);

		ImGui::SliderFloat("Color Amount", &particleSystem->GetData().ColorAmount, 0.1f, 5.0f);

		ImGui::ColorEdit4("MinColor", particleSystem->GetData().MinColor);
		ImGui::ColorEdit4("MaxColor", particleSystem->GetData().MaxColor);

		ImGui::SliderFloat("MinRotateSpeed", &particleSystem->GetData().MinRotateSpeed, -10, 10);
		ImGui::SliderFloat("MaxRotateSpeed", &particleSystem->GetData().MaxRotateSpeed, -10, 10);

		ImGui::SliderFloat("MinStartSize", &particleSystem->GetData().MinStartSize, 0, 500);
		ImGui::SliderFloat("MaxStartSize", &particleSystem->GetData().MaxStartSize, 0, 500);

		ImGui::SliderFloat("MinEndSize", &particleSystem->GetData().MinEndSize, 0, 500);
		ImGui::SliderFloat("MaxEndSize", &particleSystem->GetData().MaxEndSize, 0, 500);

		ImGui::Spacing();
		OnGUI_Write();


		// TextureList 보여주는 부분
		ImGui::Spacing();
		ImGui::Separator();
		if (ImGui::CollapsingHeader("TextureList", ImGuiTreeNodeFlags_DefaultOpen))
		{
			for (wstring textureFile : textureList)
			{
				if (ImGui::Button(String::ToString(textureFile).c_str(), ImVec2(200, 0)))
				{
					particleSystem->GetData().TextureFile = textureFile;
					particleSystem->SetTexture(L"Particles/" + textureFile);
				}
			}//for(i)
		}
	}
}

void Editor::OnGUI_Write()
{
	ImGui::Separator();

	if (ImGui::Button("WriteParticle"))
	{
		D3DDesc desc = D3D::GetDesc();

		// SaveDialog 창 띄움
		Path::SaveFileDialog
		(
			file, L"Particle file\0*.xml", L"../../_Textures/Particles",
			bind(&Editor::WriteFile, this, placeholders::_1),
			desc.Handle
		);
	}
}

void Editor::WriteFile(wstring file)
{
	// XML로 써주는 부분
	Xml::XMLDocument* document = new Xml::XMLDocument();

	Xml::XMLDeclaration* decl = document->NewDeclaration();
	document->LinkEndChild(decl);

	Xml::XMLElement* root = document->NewElement("Particle");
	root->SetAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance");
	root->SetAttribute("xmlns:xsd", "http://www.w3.org/2001/XMLSchema");
	document->LinkEndChild(root);


	Xml::XMLElement* node = NULL;

	node = document->NewElement("BlendState");
	node->SetText((int)particleSystem->GetData().Type);
	root->LinkEndChild(node);


	string textureFile = String::ToString(particleSystem->GetData().TextureFile);
	String::Replace(&textureFile, "Particles/", "");

	node = document->NewElement("Loop");
	node->SetText(particleSystem->GetData().bLoop);
	root->LinkEndChild(node);

	node = document->NewElement("TextureFile");
	node->SetText(textureFile.c_str());
	root->LinkEndChild(node);


	node = document->NewElement("MaxParticles");
	node->SetText(particleSystem->GetData().MaxParticles);
	root->LinkEndChild(node);


	node = document->NewElement("ReadyTime");
	node->SetText(particleSystem->GetData().ReadyTime);
	root->LinkEndChild(node);

	node = document->NewElement("ReadyRandomTime");
	node->SetText(particleSystem->GetData().ReadyRandomTime);
	root->LinkEndChild(node);

	node = document->NewElement("StartVelocity");
	node->SetText(particleSystem->GetData().StartVelocity);
	root->LinkEndChild(node);

	node = document->NewElement("EndVelocity");
	node->SetText(particleSystem->GetData().EndVelocity);
	root->LinkEndChild(node);


	node = document->NewElement("MinHorizontalVelocity");
	node->SetText(particleSystem->GetData().MinHorizontalVelocity);
	root->LinkEndChild(node);

	node = document->NewElement("MaxHorizontalVelocity");
	node->SetText(particleSystem->GetData().MaxHorizontalVelocity);
	root->LinkEndChild(node);

	node = document->NewElement("MinVerticalVelocity");
	node->SetText(particleSystem->GetData().MinVerticalVelocity);
	root->LinkEndChild(node);

	node = document->NewElement("MaxVerticalVelocity");
	node->SetText(particleSystem->GetData().MaxVerticalVelocity);
	root->LinkEndChild(node);


	node = document->NewElement("Gravity");
	node->SetAttribute("X", particleSystem->GetData().Gravity.x);
	node->SetAttribute("Y", particleSystem->GetData().Gravity.y);
	node->SetAttribute("Z", particleSystem->GetData().Gravity.z);
	root->LinkEndChild(node);

	node = document->NewElement("ColorAmount");
	node->SetText(particleSystem->GetData().ColorAmount);
	root->LinkEndChild(node);

	node = document->NewElement("MinColor");
	node->SetAttribute("R", particleSystem->GetData().MinColor.r);
	node->SetAttribute("G", particleSystem->GetData().MinColor.g);
	node->SetAttribute("B", particleSystem->GetData().MinColor.b);
	node->SetAttribute("A", particleSystem->GetData().MinColor.a);
	root->LinkEndChild(node);

	node = document->NewElement("MaxColor");
	node->SetAttribute("R", particleSystem->GetData().MaxColor.r);
	node->SetAttribute("G", particleSystem->GetData().MaxColor.g);
	node->SetAttribute("B", particleSystem->GetData().MaxColor.b);
	node->SetAttribute("A", particleSystem->GetData().MaxColor.a);
	root->LinkEndChild(node);


	node = document->NewElement("MinRotateSpeed");
	node->SetText(particleSystem->GetData().MinRotateSpeed);
	root->LinkEndChild(node);

	node = document->NewElement("MaxRotateSpeed");
	node->SetText(particleSystem->GetData().MaxRotateSpeed);
	root->LinkEndChild(node);

	node = document->NewElement("MinStartSize");
	node->SetText((int)particleSystem->GetData().MinStartSize);
	root->LinkEndChild(node);

	node = document->NewElement("MaxStartSize");
	node->SetText((int)particleSystem->GetData().MaxStartSize);
	root->LinkEndChild(node);

	node = document->NewElement("MinEndSize");
	node->SetText((int)particleSystem->GetData().MinEndSize);
	root->LinkEndChild(node);

	node = document->NewElement("MaxEndSize");
	node->SetText((int)particleSystem->GetData().MaxEndSize);
	root->LinkEndChild(node);

	wstring folder = Path::GetDirectoryName(file);
	wstring fileName = Path::GetFileNameWithoutExtension(file);

	document->SaveFile(String::ToString(folder + fileName + L".xml").c_str());
	SafeDelete(document);

	UpdateParticleList();
}

 

 

 

 

 

 

 

 

 

결과


 

'DirectX11 3D > 기본 문법' 카테고리의 다른 글

<DirectX11 3D> 99 - RenderTargetView  (0) 2022.03.17
<DirectX11 3D> 96 - GS(Geometry Shader)  (0) 2022.03.16
<DirectX11 3D> 88 - Snow  (0) 2022.03.14
<DirectX11 3D> 86 - AlphaBlend  (0) 2022.03.14
<DirectX11 3D> 85 - Rain  (0) 2022.03.14