종제로
종제로 Devlog
종제로
전체 방문자
오늘
어제
  • 분류 전체보기 (43)
    • C, C++ (22)
      • C, C++ (10)
      • Modern C++ (4)
      • 전문가를 위한 C++ (책) (8)
    • DirectX 자체엔진 개발 (8)
    • 자료구조 알고리즘 (10)
      • 공부 (9)
      • 문제풀이 (1)
    • 자기 계발 (1)
    • 기타 (2)

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

인기 글

태그

  • C++
  • directX
  • c++ 11
  • 자료구조
  • DirectX11
  • C
  • 알고리즘
  • 모두의C언어
  • c++ 17
  • 전문가를 위한 C++

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
종제로
DirectX 자체엔진 개발

[DirectX 11] 파티클 시스템

[DirectX 11] 파티클 시스템
DirectX 자체엔진 개발

[DirectX 11] 파티클 시스템

2022. 3. 18. 14:20

Particle System을 이용한 불 표현

 

개발 일자 : 2022년 3월 14일 ~ 18일

 

일본 테마의 맵 중앙에 있는 냄비와 모닥불 모델

현재 제작 중인 일본 테마의 맵 중앙에는 이렇게 큰 냄비와 모닥불이 있습니다.

모닥불에는 당연히 불이 있어야될 것 같아서 파티클 시스템 구현을 시작했습니다.

 

구현 방식을 요약하자면 다음과 같습니다.

  1. 현재 파티클 리스트를 스트림 출력(Stream Output) 전용으로 그립니다. 래스터화기가 비활성화되어 있으므로 그 어떤 파티클도 화면에 렌더링되지는 않습니다.
  2. 기하 쉐이더는 주어진 조건에 따라 파티클들을 생성하거나 파괴합니다 (구체적인 조건은 구체적인 파티클 시스템에 따라 다릅니다. 불 파티클 시스템은 불.fx에 정의된 조건을 따릅니다.)
  3. 갱신된 파티클 리스트를 스트림 출력(Stream Output)을 통해 정점 버퍼에 기록합니다.
  4. 현재 프레임에 갱신된 파티클 목록을 렌더합니다.

 

스트림 출력

스트림 출력 단계를 이용하면 GPU에서 스트림 출력 단계에 묶인 정점 버퍼 V에 기하구조 자체(정점 목록 형태)를 직접 기록할 수 있습니다. 즉, 기하 셰이더에서 출력한 정점들이 V에 기록되게 할 수 있습니다. 그리고 V에 저장된 기하구조를 나중에 렌더링 파이프라인에 입력해서 그리는 것이 가능합니다. 이를 파티클 시스템에 활용하였습니다.

 

기하 셰이더는 기본 도형들을 스트림 출력을 통해서 GPU 메모리 안에 있는 정점 버퍼에 보냅니다.

 

스트림 출력(Stream Output)을 사용하는 경우 특별한 설정이 없는 한 기하 셰이더가 출력한 정점은 스트림으로 출력될 뿐만 아니라 렌더링 파이프라인의 다음 단계(래스터화기)로도 입력됩니다. 자료를 스트림으로 출력하기만 하고 렌더링하지 않기 위해서 픽셀 셰이더와 DepthStencil 버퍼를 비활성화 했습니다.

픽셀 셰이더, 뎁스 버퍼 비활성화

 

위의 과정을 진행하는 코드는 다음과 같습니다.

void ParticleSystem::Draw(const EMath::Matrix& view, const EMath::Matrix& proj)
{
	EMath::Matrix _vp = view * proj;

	ID3D11DeviceContext* _dc = m_DX11Core->GetDC();
	ParticleEffect* _fx = Effects::FireFX;

	// 리소스 매니저로부터 파티클 텍스쳐를 가져온다.
	if(m_TexArraySRV == nullptr)
		m_TexArraySRV = m_ResourceManager->GetParticle(m_ParticleSystemData->m_Name);

	// cb 세팅
	_fx->SetViewProj(_vp);
	_fx->SetGameTime(m_GameTime);
	_fx->SetTimeStep(m_TimeStep);
	_fx->SetEmitPosW(m_ParticleSystemData->m_EmitPos);
	_fx->SetEmitDirW(m_ParticleSystemData->m_EmitDir);
	_fx->SetTexArray(m_TexArraySRV);
	_fx->SetRandomTex(m_RandomTexSRV);

	// IA 세팅
	_dc->IASetInputLayout(InputLayouts::Particle);
	_dc->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_POINTLIST);

	UINT stride = sizeof(Vertex::Particle);
	UINT offset = 0;

	// 최초 실행이면 초기화용 정점 버퍼를 사용하고,
	// 그렇지 않으면 현재의 입자 목록을 담은 정점 버퍼를 사용한다.
	if (m_IsFirstRun)
		_dc->IASetVertexBuffers(0, 1, &m_InitVB, &stride, &offset);
	else
		_dc->IASetVertexBuffers(0, 1, &m_DrawVB, &stride, &offset);

	// 현재 입자 목록을 스트림 출력 전용 기법으로 그려서 입자들을 갱신한다.
	// 갱신된 입자들은 스트림 출력을 통해서 대상 정점 버퍼에 기록된다.
	_dc->SOSetTargets(1, &m_StreamOutVB, &offset);

	D3DX11_TECHNIQUE_DESC techDesc;
	_fx->StreamOutTech->GetDesc(&techDesc);
	for (UINT p = 0; p < techDesc.Passes; ++p)
	{
		_fx->StreamOutTech->GetPassByIndex(p)->Apply(0, _dc);

		if (m_IsFirstRun)
		{
			_dc->Draw(1, 0);
			m_IsFirstRun = false;
		}
		else
		{
			_dc->DrawAuto();
		}
	}

	// 스트림 전용 패스가 끝났다. 정점 버퍼를 떼어낸다.
	ID3D11Buffer* bufferArray[1] = { 0 };
	_dc->SOSetTargets(1, bufferArray, &offset);

	// 정점 버퍼들을 맞바꾼다 (핑퐁)
	std::swap(m_DrawVB, m_StreamOutVB);

	// 방금 스트림 출력된, 갱신된 입자 시스템을 화면에 그린다.
	_dc->IASetVertexBuffers(0, 1, &m_DrawVB, &stride, &offset);

	_fx->DrawTech->GetDesc(&techDesc);
	for (UINT p = 0; p < techDesc.Passes; ++p)
	{
		_fx->DrawTech->GetPassByIndex(p)->Apply(0, _dc);

		_dc->DrawAuto();
	}
}

위의 코드에서 DrawVB와 StreamOutVB를 나눈 이유는, 하나의 정점 버퍼를 출력 병합기 단계와 입력 조립기 단계에 동시에 묶어 둘 수 없기 때문입니다. StreamOutVB는 위의 StreamOutTech 패스, DrawVB는 DrawTech 패스에 사용됩니다.

 

 

게임 엔진과 그래픽스 엔진에서 파티클 시스템을 초기화, Update, Draw, 해제하는 흐름은 다음과 같습니다.

파티클 시스템의 게임 엔진 <-> 그래픽스 엔진 흐름

 

파티클 시스템 컴포넌트 & 그래픽스 엔진 내부 파티클 시스템 클래스
파티클 시스템 매니저

 

파티클 시스템 매니저의 Update()에서는 공유 데이터에 접근해, 활성화되어 있는지 확인하고 활성화되어 있으면 큐에 넣습니다.

Draw()는 큐에 들어있는 파티클 시스템을 모두 그립니다.

void ParticleSystemManager::Update(float dTime, float totalTime)
{
	// 활성화되어있는지 확인하고,
	// Queue에 먼저 담는다.
	for (const auto& it : m_ParticleSystemVec)
	{
		// 활성화되어 있는지
		if (it->GetParticleSystemData()->m_IsActive)
		{
			// 리셋 해야한다면
			if (it->GetParticleSystemData()->m_IsReset)
			{
				it->Reset();
				it->GetParticleSystemData()->m_IsReset = false;
			}

			// 활성화되어있으면 업데이트
			it->Update(dTime, totalTime);

			// 후에 큐에 넣어줌
			m_ParticleSystemQueue.push(it.get());
		}
	}
}

void ParticleSystemManager::Draw(const EMath::Matrix& view, const EMath::Matrix&  proj)
{
	// Queue를 순회하며 Draw 시킨다.
	while (!m_ParticleSystemQueue.empty())
	{
		ParticleSystem* ps = m_ParticleSystemQueue.front();
		ps->Draw(view, proj);
		m_ParticleSystemQueue.pop();
	}
}

 

 

참고

  • DirectX 11을 이용한 3D 게임 프로그래밍 입문 책

 

저작자표시 비영리 변경금지 (새창열림)

'DirectX 자체엔진 개발' 카테고리의 다른 글

[DirectX 11] HDR, Tone Mapping  (0) 2022.04.15
[DirectX 11] 외곽선  (0) 2022.04.14
[DirectX 11] Emissive  (0) 2022.03.14
[DirectX 11] Bloom  (1) 2022.03.11
[DirectX 11] 자체 포맷 개발  (0) 2022.02.25
  • 구현 방식을 요약하자면 다음과 같습니다.
  • 스트림 출력
'DirectX 자체엔진 개발' 카테고리의 다른 글
  • [DirectX 11] HDR, Tone Mapping
  • [DirectX 11] 외곽선
  • [DirectX 11] Emissive
  • [DirectX 11] Bloom
종제로
종제로

티스토리툴바

단축키

내 블로그

내 블로그 - 관리자 홈 전환
Q
Q
새 글 쓰기
W
W

블로그 게시글

글 수정 (권한 있는 경우)
E
E
댓글 영역으로 이동
C
C

모든 영역

이 페이지의 URL 복사
S
S
맨 위로 이동
T
T
티스토리 홈 이동
H
H
단축키 안내
Shift + /
⇧ + /

* 단축키는 한글/영문 대소문자로 이용 가능하며, 티스토리 기본 도메인에서만 동작합니다.