DirectX12 콜라이더 디버깅을 위한 직선 렌더링 시스템 구현

2025. 7. 9. 13:25·공부/DirectX12

 엔진 개발이 나름 순조롭게 진행이 되서 이제는 이전에 미뤄뒀었던 물리엔진쪽을 건드리고 있다. 자연스럽게 충돌 감지를 위한 콜라이더 구현을 하게 됐고, 이에 따른 테스트 및 디버깅이 계속 수행되는 중이다. 박스형태의 콜라이더를 먼저 구현하는 중인데 범용성을 위해 AABB 방식은 완전히 배제하고 OBB 방식만을 고려해서 구현 중인데, 문제는 얘네가 진짜 충돌하고 있는건지 시스템이 뱉는 로그나 값만 보고 판단 가능하다는 것이였다.

 지금이야 그냥 박스 오브젝트끼리 충돌시키니까 콜라이더와 오브젝트의 크기가 딱 들어맞지만, 복잡한 메시를 가진 오브젝트에 콜라이더를 적용시킨 경우라면 충돌을 잘못 감지하고 있는건지 내가 알 길이 없기 때문에 실제 충돌 감지 범위를 보여주는 별도의 렌더링이 필요해진 상황이다. 콜라이더는 일반적인 메시 렌더링과는 다르게 최소 구성 단위가 삼각형이 아니라 하나의 선분이다. 그냥 와이어프레임으로 렌더링해서 해결될 문제가 아니라는 뜻.

 

 메시와는 다르게 콜라이더는 정말 간단한 정점 정보를 가지고 있기 때문에 일반적인 파이프라인에 끼워서 같이 렌더링하는 것이 석연치 않다. 실제로 DirectXTK에는 PrimitiveBatch라는 것을 사용해서 개발자가 직접 버퍼를 관리하는 번거로움 없이 간단한 렌더링을 할 수 있게 도와주는 방식이 있는 것 같은데.. 필자의 경우에는 뭐가 문제인지 자꾸 BasicEffect를 선언하고 초기화하는 과정에서 에러를 뱉는 현상이 발생했다. 하루 이틀정도 해결해보겠다고 씨름을 해봤지만 해결이 되지 않았고 시간을 더 지체하는 것이 좋아보이지 않았기 때문에 직접 디버깅용 렌더링 시스템을 구현하기로 했다.

 

 본 시스템에서 디버그 렌더링은 기본적으로 선분을 렌더링하는 것을 전재로 한다. 때문에 기존에 최소 렌더링 단위를 삼각형으로 하고있던 PSO와 별개로 따로 디버그 시스템용 PSO를 작성해줬다.

 

_debugInputLayout =
{
    { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
    { "COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 12, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }
};

auto debug = CreatePSODesc(_debugInputLayout, L"debugVS", L"debugPS");
debug.DepthStencilState.DepthEnable = false;
debug.DepthStencilState.DepthWriteMask = D3D12_DEPTH_WRITE_MASK_ZERO;
debug.DepthStencilState.DepthFunc = D3D12_COMPARISON_FUNC_ALWAYS;
debug.DepthStencilState.StencilEnable = false;
debug.RasterizerState.CullMode = D3D12_CULL_MODE_NONE;
debug.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_LINE;

BuildPSO(PSO_DEBUG, debug);

 

 PSO 서술자의 다른 값은 일반적인 Opaque PSO와 같다. 오브젝트에 가려져도 충돌 범위가 보일 수 있도록 Depth Stencil에 영향을 받지 않도록 했으며, PrimitiveTopolgyType을 선으로 바꿔줬다. 이 다음은 디버그 렌더링을 따로 담당하는 클래스를 구현하고 일반적인 오브젝트들의 렌더링이 끝나면 디버그 렌더링을 수행하도록 했다.

 

for (auto& p : _sortedObjects)
{
    if (_isPSOFixed && p.first != PSO_SKYBOX)
        cmdList->SetPipelineState(_currPSO.Get());
    else
        cmdList->SetPipelineState(_PSOs[p.first].Get());

    for (int i = 0; i < p.second.size(); i++)
        p.second[i]->Render();
}

cmdList->SetPipelineState(_PSOs[PSO_DEBUG].Get());
DEBUG->Render();

 

 여기까지는 사실 그렇게 복잡하지 않은데 문제는 본격적인 렌더링을 어떻게 구현할 것이냐였다. 일단 당연히 기존 정점과는 다르게 최소한의 정보만을 가진 구조체를 만들어주고, 얘네를 GPU로 보내줄 버퍼도 생성해야한다. 여기서부터 고민을 참 많이 했다. 콜라이더라는 녀석이 게임에서 딱 하나 만들어서 사용할 애가 아닌만큼 여러개가 생성될텐데, 그럼 버퍼관리를 어떻게 하냐는 것이였다. 분명 렌더링을 관리하는 DebugManager가 단 하나의 버퍼를 관리해야겠지 싶으면서도 여러개의 콜라이더에 대한 렌더링을 수행해야하는 만큼 유동적으로 변할 수 있는 콜라이더 갯수에 대해 문제가 없어야했다. 그러면 차라리 콜라이더 각자가 버퍼를 들고있는 편이 구현하기 편하지 않을까 싶으면서도 너무 비효율적으로 느껴졌다.

 

 결론적으로는 DebugManager가 단 하나의 버퍼를 관리하는 방식으로 구현했다. 이유는 유지보수와 범용성을 위해서였다. DebugManager를 단순히 콜라이더를 렌더링하는 역할로 보지 않고 디버깅과 관련된 모든 렌더링을 관리하는 객체라고 생각하자 그냥 일반적인 선분을 렌더링 하는데도 문제가 없도록 구현을 해야겠다는 생각이 들었다. 디버그 렌더링을 수행할 콜라이더들을 배열에 받아두고 큐처럼 비워가며 선분 단위로 정점과 인덱스의 정보들을 별도의 배열에 먼저 저장한다. 이렇게 선분 단위로 정점을 저장하면 단순히 하나의 선분에 대한 렌더링이 필요한 경우에도 확장성이 매우 용이해질 것 같았다.

 그리고 변화하는 정점과 인덱스 갯수에 대해서 버퍼의 크기를 조절해나간다. 이 부분에 대해서 제대로 구현된지 사실 잘 모르겠지만 일단 당장에 작동이 제대로하는 것은 확인했기 때문에 문제는 없다고 생각된다. 다음은 DebugManager 객체의 구현부분이다.

 

#pragma once

class DebugManager
{
	DECLARE_SINGLE(DebugManager);
public:
	~DebugManager();

	void Init();
	void Render();

public:
	void AddDebugRender(shared_ptr<Collider> collider);
	void Update();

private:
	vector<shared_ptr<Collider>> _drawQueue;

	vector<VertexPC> _vertices;
	vector<UINT16> _indices;

	UINT _bufferVertexCount = 0;
	UINT _bufferIndexCount = 0;

	unique_ptr<UploadBuffer<VertexPC>> _vertexUploadBuffer;
	unique_ptr<UploadBuffer<UINT16>> _indexUploadBuffer;

	D3D12_VERTEX_BUFFER_VIEW _vertexBufferView;
	D3D12_INDEX_BUFFER_VIEW _indexBufferView;

	Color _colliderDebugColor = { 0.0f, 1.0f, 0.0f, 1.0f };

	const UINT16 _boxColliderIndices[24] = {
				0, 1,
				1, 2,
				2, 3,
				3, 0,
				0, 4,
				1, 5,
				2, 6,
				3, 7,
				4, 5,
				5, 6,
				6, 7,
				7, 4
	};
};

 

#include "pch.h"
#include "DebugManager.h"

DebugManager::~DebugManager()
{

}

void DebugManager::Init()
{
	_vertexBufferView.StrideInBytes = sizeof(VertexPC);
	_indexBufferView.Format = DXGI_FORMAT_R16_UINT;
}

void DebugManager::Update()
{
	if (_vertices.size() != _bufferVertexCount)
	{
		_bufferVertexCount = _vertices.size();
		_vertexUploadBuffer = make_unique<UploadBuffer<VertexPC>>(_bufferVertexCount, false);

		_vertexBufferView.BufferLocation = _vertexUploadBuffer->GetResource()->GetGPUVirtualAddress();
		_vertexBufferView.SizeInBytes = sizeof(VertexPC) * _bufferVertexCount;

		for (int i = 0; i < _bufferVertexCount; i++)
			_vertexUploadBuffer->CopyData(i, _vertices[i]);
	}

	if (_indices.size() != _bufferIndexCount)
	{
		_bufferIndexCount = _indices.size();
		_indexUploadBuffer = make_unique<UploadBuffer<UINT16>>(_bufferIndexCount, false);

		_indexBufferView.BufferLocation = _indexUploadBuffer->GetResource()->GetGPUVirtualAddress();
		_indexBufferView.SizeInBytes = sizeof(UINT16) * _bufferIndexCount;

		for (int i = 0; i < _bufferIndexCount; i++)
		{
			_indexUploadBuffer->CopyData(i, _indices[i]);
		}
	}

	UINT vertexIndex = 0;
	for (auto& collider : _drawQueue)
	{
		switch (collider->GetColliderType())
		{
			case ColliderType::Box:
			{
				if (collider->GetGameObject()->GetFramesDirty() > 0)
				{
					BoundingOrientedBox box = static_pointer_cast<BoxCollider>(collider)->GetBoundingBox();

					Vector3 corners[8];
					box.GetCorners(corners);

					for (int i = 0; i < 8; i++)
						_vertices[i + vertexIndex].Position = corners[i];

					for (int i = 0; i < 8; i++)
						_vertexUploadBuffer->CopyData(i + vertexIndex, _vertices[i + vertexIndex]);

				}

				vertexIndex += 8;
				break;
			}
		}
	}
}

void DebugManager::Render()
{
	if (_bufferVertexCount == 0 || _bufferIndexCount == 0)
		return;

	auto cmdList = GRAPHIC->GetCommandList().Get();

	cmdList->IASetVertexBuffers(0, 1, &_vertexBufferView);
	cmdList->IASetIndexBuffer(&_indexBufferView);
	cmdList->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_LINELIST);

	cmdList->DrawIndexedInstanced(_indices.size(), 1, 0, 0, 0);
}

void DebugManager::AddDebugRender(shared_ptr<Collider> collider)
{
	_drawQueue.push_back(collider);

	switch (collider->GetColliderType())
	{
		case ColliderType::Box:
		{
			BoundingOrientedBox box = static_pointer_cast<BoxCollider>(collider)->GetBoundingBox();

			// 정점 배열에 요소가 추가되기 전 크기를 받아와야되기 때문에 인덱스 먼저 추가
			for (int i = 0; i < 24; i++)
				_indices.push_back(_boxColliderIndices[i] + _vertices.size());

			Vector3 corners[8];
			box.GetCorners(corners);

			VertexPC v[8];
			for (int i = 0; i < 8; i++)
			{
				v[i].Position = corners[i];
				v[i].Color = _colliderDebugColor;
				_vertices.push_back(v[i]);
			}

			break;
		}
	}
}

 

 셰이더 코드의 경우 너무 단순해서, 사실 기존 워크플로우에 맞춰 VS와 PS를 따로 분리해서 작성했지만 본 포스팅에서는 그냥 한 코드에 병합했다.

 

struct VertexColorIn
{
    float3 Pos          : POSITION;
    float4 Color        : COLOR;
};

struct VertexColorOut
{
    float4 Position     : SV_POSITION;
    float4 Color        : COLOR;
};

VertexColorOut VS(VertexColorIn vin)
{
    VertexColorOut vout;

    vout.Position = mul(float4(vin.Pos, 1.0f), ViewProj);
    vout.Color = vin.Color;

    return vout;
}

float4 PS(VertexColorOut pin) : SV_Target
{
    return pin.Color;
}

 

 결과물은 다음과 같다. 콜라이더를 사용하는 정육면체 두개에 대해서만 정상적으로 디버그 렌더링이 진행되는 모습이다.

 

 이제 진짜로 간단한 게임을 만들기에는 거의 모든 기능이 구현된듯 하다.. 엔진 개발뿐만 아니라 실제 만들 간단한 게임에 대한 기획도 병행해야할듯 싶다.

'공부 > DirectX12' 카테고리의 다른 글

Lazy Update(Propagation)를 적용한 Transform  (0) 2025.08.13
std::max()에 3개 이상의 인자를 사용하는 방법이 안될 때  (0) 2025.05.25
DirectX12 ImGUI 라이브러리 세팅  (0) 2025.04.15
메시와 머터리얼의 관계에 따른 문제 발생 및 해결  (0) 2025.04.10
툰 셰이딩에서 얼굴 명암 처리에 대해서  (0) 2025.04.04
'공부/DirectX12' 카테고리의 다른 글
  • Lazy Update(Propagation)를 적용한 Transform
  • std::max()에 3개 이상의 인자를 사용하는 방법이 안될 때
  • DirectX12 ImGUI 라이브러리 세팅
  • 메시와 머터리얼의 관계에 따른 문제 발생 및 해결
ayuriK152
ayuriK152
주로 게임 클라이언트 개발 공부를 해요 상용엔진이나 알고리즘 포스팅도 해요
  • ayuriK152
    아유릭공방
    ayuriK152
  • 전체
    오늘
    어제
    • 분류 전체보기 (24)
      • 공부 (19)
        • DirectX12 (11)
        • 유니티 (2)
        • 알고리즘(PS) (5)
      • 게임 (0)
        • 후기 (0)
      • 프로젝트 (5)
        • 리듬게임 프로젝트 (5)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.5
ayuriK152
DirectX12 콜라이더 디버깅을 위한 직선 렌더링 시스템 구현
상단으로

티스토리툴바