Company
교육 철학

Wireframe 모드

개요

Wireframe 모드는 3D 메시를 폴리곤의 선(edge)만으로 렌더링하는 기법입니다. 게임 엔진 개발과 3D 그래픽스 작업에서 메시 구조를 시각적으로 분석하고 디버깅하는 데 필수적인 도구입니다.

Wireframe 모드의 필요성

모델 구조 분석
폴리곤 분포와 토폴로지 확인
버텍스 연결 관계 시각화
LOD(Level of Detail) 단계별 메시 검증
성능 최적화
과도한 폴리곤 수 식별
불필요한 지오메트리 발견
메시 복잡도 평가
디버깅 및 개발
렌더링 문제 진단
컬링(Culling) 동작 확인
클리핑(Clipping) 영역 검증
교육 및 프레젠테이션
3D 구조 설명 및 시연
기술 데모 및 워크플로우 시각화
아티스트와 프로그래머 간 커뮤니케이션 도구

1. Rasterizer State 개념

Rasterization이란?

정의
Rasterization은 3D 공간의 벡터 정보(정점, 삼각형)를 2D 화면의 픽셀로 변환하는 과정입니다. 이 단계에서 폴리곤이 어떻게 화면에 그려질지 결정됩니다.
렌더링 파이프라인에서의 위치
1.
Vertex Shader: 정점 위치 계산 및 변환
2.
Rasterizer: 정점으로 이루어진 삼각형을 픽셀로 변환
3.
Pixel Shader: 각 픽셀의 최종 색상 계산

Rasterizer State의 역할

Fill Mode 제어
폴리곤을 채워서 렌더링할지(Solid)
선만 렌더링할지(Wireframe)
Culling 제어
어떤 면을 제거(컬링)할지 결정
성능 최적화의 핵심 요소
Depth 및 Clipping 설정
Z-버퍼 사용 방식
뷰 프러스텀(View Frustum) 밖 지오메트리 처리

2. D3D11_RASTERIZER_DESC 구조체 상세

구조체 정의

typedef struct D3D11_RASTERIZER_DESC { D3D11_FILL_MODE FillMode; D3D11_CULL_MODE CullMode; BOOL FrontCounterClockwise; INT DepthBias; FLOAT DepthBiasClamp; FLOAT SlopeScaledDepthBias; BOOL DepthClipEnable; BOOL ScissorEnable; BOOL MultisampleEnable; BOOL AntialiasedLineEnable; } D3D11_RASTERIZER_DESC;
C++
복사

각 속성 상세 설명

FillMode
enum D3D11_FILL_MODE { D3D11_FILL_WIREFRAME = 2, // 와이어프레임 (선만 그리기) D3D11_FILL_SOLID = 3 // 솔리드 (면 채우기) };
C++
복사
D3D11_FILL_WIREFRAME: 삼각형의 엣지만 렌더링
D3D11_FILL_SOLID: 삼각형 내부를 모두 채워서 렌더링 (기본값)
CullMode
enum D3D11_CULL_MODE { D3D11_CULL_NONE = 1, // 컬링 없음 (모든 면 렌더링) D3D11_CULL_FRONT = 2, // 앞면 컬링 D3D11_CULL_BACK = 3 // 뒷면 컬링 (기본값) };
C++
복사
D3D11_CULL_BACK: 카메라 반대 방향을 향하는 면을 제거 (가장 일반적)
D3D11_CULL_FRONT: 카메라를 향하는 면을 제거 (특수한 경우)
D3D11_CULL_NONE: 양면 렌더링 (와이어프레임이나 투명 오브젝트에 유용)
컬링의 필요성
일반적인 닫힌 메시는 뒷면이 보이지 않음
뒷면을 렌더링하지 않으면 성능 약 50% 향상
와이어프레임 모드에서는 모든 엣지를 보기 위해 CULL_NONE 사용
FrontCounterClockwise
rsDesc.FrontCounterClockwise = false; // 시계방향이 앞면 rsDesc.FrontCounterClockwise = true; // 반시계방향이 앞면
C++
복사
FALSE (기본값): 정점이 시계 방향(Clockwise)으로 배열된 삼각형이 앞면
TRUE: 정점이 반시계 방향(Counter-Clockwise)으로 배열된 삼각형이 앞면
DirectX는 기본적으로 시계 방향을 앞면으로 사용
OpenGL은 반시계 방향을 앞면으로 사용 (좌표계 차이)
Depth Bias 관련 속성
rsDesc.DepthBias = 0; // 정수 깊이 바이어스 rsDesc.DepthBiasClamp = 0.0f; // 최대 바이어스 값 rsDesc.SlopeScaledDepthBias = 0.0f; // 경사 기반 바이어스
C++
복사
용도: Z-Fighting 문제 해결
Z-Fighting: 거의 같은 깊이의 두 면이 겹쳐져 깜빡이는 현상
활용 예: 데칼(Decal), 그림자 매핑, 아웃라인 렌더링
DepthClipEnable
rsDesc.DepthClipEnable = true; // 깊이 클리핑 활성화 (일반적) rsDesc.DepthClipEnable = false; // 깊이 클리핑 비활성화 (특수한 경우)
C++
복사
TRUE: 카메라의 Near/Far Plane 밖의 지오메트리를 클리핑
FALSE: 클리핑 비활성화 (그림자 볼륨 등 특수 기법에 사용)
ScissorEnable
rsDesc.ScissorEnable = false; // 시저 테스트 비활성화 rsDesc.ScissorEnable = true; // 시저 테스트 활성화
C++
복사
용도: 화면의 특정 사각형 영역만 렌더링
활용: UI 클리핑, 분할 화면, 미니맵
MultisampleEnable
rsDesc.MultisampleEnable = false; // MSAA 비활성화 rsDesc.MultisampleEnable = true; // MSAA 활성화
C++
복사
MSAA (Multi-Sample Anti-Aliasing): 폴리곤 엣지의 계단 현상 제거
렌더 타겟이 멀티샘플을 지원해야 함
AntialiasedLineEnable
rsDesc.AntialiasedLineEnable = false; // 라인 AA 비활성화 rsDesc.AntialiasedLineEnable = true; // 라인 AA 활성화
C++
복사
용도: 와이어프레임이나 라인 프리미티브의 안티앨리어싱
MultisampleEnable이 FALSE일 때만 동작

3. Wireframe 모드 구현

Rasterizer State 생성

#pragma region rasterize state // 기본 설정 초기화 D3D11_RASTERIZER_DESC rsDesc = {}; rsDesc.AntialiasedLineEnable = false; rsDesc.CullMode = D3D11_CULL_BACK; rsDesc.DepthBias = 0; rsDesc.DepthBiasClamp = 0.0f; rsDesc.DepthClipEnable = true; rsDesc.FillMode = D3D11_FILL_SOLID; rsDesc.FrontCounterClockwise = false; rsDesc.MultisampleEnable = false; rsDesc.ScissorEnable = false; rsDesc.SlopeScaledDepthBias = 0.0f; // 1. Solid Back: 일반 렌더링 (뒷면 컬링) GetDevice()->CreateRasterizerState( &rsDesc, rasterizerStates[static_cast<UINT>(eRasterizerState::SolidBack)].GetAddressOf() ); // 2. Solid Front: 앞면 컬링 (내부 보기) rsDesc.FillMode = D3D11_FILL_SOLID; rsDesc.CullMode = D3D11_CULL_FRONT; GetDevice()->CreateRasterizerState( &rsDesc, rasterizerStates[static_cast<UINT>(eRasterizerState::SolidFront)].GetAddressOf() ); // 3. Solid None: 양면 렌더링 (컬링 없음) rsDesc.FillMode = D3D11_FILL_SOLID; rsDesc.CullMode = D3D11_CULL_NONE; GetDevice()->CreateRasterizerState( &rsDesc, rasterizerStates[static_cast<UINT>(eRasterizerState::SolidNone)].GetAddressOf() ); // 4. Wireframe: 와이어프레임 모드 (모든 엣지 표시) rsDesc.FillMode = D3D11_FILL_WIREFRAME; rsDesc.CullMode = D3D11_CULL_NONE; GetDevice()->CreateRasterizerState( &rsDesc, rasterizerStates[static_cast<UINT>(eRasterizerState::Wireframe)].GetAddressOf() ); #pragma endregion
C++
복사

Rasterizer State 열거형 정의

enum class eRasterizerState { SolidBack, // 기본 솔리드 렌더링 (뒷면 컬링) SolidFront, // 앞면 컬링 (드물게 사용) SolidNone, // 양면 렌더링 Wireframe, // 와이어프레임 모드 Count }; // 전역 또는 Renderer 클래스 멤버 Microsoft::WRL::ComPtr<ID3D11RasterizerState> rasterizerStates[static_cast<UINT>(eRasterizerState::Count)];
C++
복사

왜 여러 Rasterizer State를 만드는가?

상태 전환의 효율성
런타임에 설정을 변경하는 것보다 미리 생성된 상태를 전환하는 것이 빠름
상태 객체 생성은 비용이 높은 작업
한 번 생성하고 재사용하는 패턴
다양한 렌더링 시나리오 지원
일반 렌더링 (SolidBack)
디버그 렌더링 (Wireframe)
특수 효과 (SolidNone, SolidFront)

4. Shader 클래스에서 Wireframe 바인딩

동적 Wireframe 전환 시스템

void Shader::Bind() { // Wireframe 모드가 활성화되어 있으면 특수 처리 if (bWireframe) { // Wireframe 전용 셰이더 로드 Shader* wireframeShader = Resources::Find<Shader>(L"WireframeShader"); // 셰이더 스테이지 가져오기 Microsoft::WRL::ComPtr<ID3D11VertexShader> wireframeShaderVS = wireframeShader->GetVS(); Microsoft::WRL::ComPtr<ID3D11PixelShader> wireframeShaderPS = wireframeShader->GetPS(); // Wireframe Rasterizer State 가져오기 Microsoft::WRL::ComPtr<ID3D11RasterizerState> wireframeRasterizerState = renderer::rasterizerStates[static_cast<UINT>(eRasterizerState::Wireframe)]; // 파이프라인에 바인딩 GetDevice()->BindVS(wireframeShaderVS.Get()); GetDevice()->BindPS(wireframeShaderPS.Get()); GetDevice()->BindRasterizerState(wireframeRasterizerState.Get()); GetDevice()->BindBlendState( renderer::blendStates[static_cast<UINT>(mBlendState)].Get(), nullptr, 0xffffff ); GetDevice()->BindDepthStencilState( renderer::depthStencilStates[static_cast<UINT>(mDepthStencilState)].Get(), 0 ); return; // Wireframe 처리 완료, 함수 종료 } // 일반 모드: 기본 셰이더 바인딩 if (mVS) GetDevice()->BindVS(mVS.Get()); if (mPS) GetDevice()->BindPS(mPS.Get()); // 일반 Rasterizer State 바인딩 GetDevice()->BindRasterizerState( renderer::rasterizerStates[static_cast<UINT>(mRasterizerState)].Get() ); GetDevice()->BindBlendState( renderer::blendStates[static_cast<UINT>(mBlendState)].Get(), nullptr, 0xffffff ); GetDevice()->BindDepthStencilState( renderer::depthStencilStates[static_cast<UINT>(mDepthStencilState)].Get(), 0 ); }
C++
복사

설계 패턴 분석

전략 패턴 (Strategy Pattern)
bWireframe 플래그에 따라 다른 렌더링 전략 선택
런타임에 동적으로 렌더링 방식 전환
리소스 관리
Wireframe 전용 셰이더를 별도로 관리
리소스 매니저를 통한 중앙 집중식 관리

Wireframe Shader 구현

Vertex Shader (WireframeVS.hlsl)
Pixel Shader (WireframePS.hlsl)
고급 Wireframe Shader (색상 변화)

5. 실전 활용 예제

Wireframe 토글 시스템

class Renderer { private: bool mWireframeMode = false; public: void ToggleWireframeMode() { mWireframeMode = !mWireframeMode; } bool IsWireframeMode() const { return mWireframeMode; } void SetWireframeMode(bool enabled) { mWireframeMode = enabled; } }; // 사용 예시 void EditorApplication::Update() { // F2 키로 Wireframe 모드 토글 if (Input::GetKeyDown(KeyCode::F2)) { Renderer::GetInstance()->ToggleWireframeMode(); } }
C++
복사

ImGui 통합

void DebugWindow::OnGUI() { ImGui::Begin("Rendering Debug"); static bool wireframeEnabled = false; if (ImGui::Checkbox("Wireframe Mode", &wireframeEnabled)) { Renderer::GetInstance()->SetWireframeMode(wireframeEnabled); } ImGui::SameLine(); ImGui::Text("(F2)"); // 키보드 단축키 표시 ImGui::End(); }
C++
복사

오브젝트별 Wireframe 적용

class MeshRenderer : public Component { private: bool mDebugWireframe = false; public: void Render() { // 재질의 셰이더 가져오기 Shader* shader = mMaterial->GetShader(); // 이 오브젝트만 Wireframe으로 렌더링 if (mDebugWireframe) { shader->SetWireframe(true); } shader->Bind(); // 메시 렌더링 mMesh->Render(); // Wireframe 모드 복원 shader->SetWireframe(false); } void SetDebugWireframe(bool enabled) { mDebugWireframe = enabled; } };
C++
복사

하이브리드 렌더링 (Solid + Wireframe)

void RenderWithWireframeOverlay(GameObject* obj) { // 1단계: 일반 렌더링 (Solid) obj->GetMaterial()->GetShader()->SetWireframe(false); obj->Render(); // 2단계: Wireframe 오버레이 // Depth 테스트는 유지하되, Depth 쓰기는 비활성화 renderer::BindDepthStencilState(eDepthStencilState::DepthReadOnly); // Wireframe 렌더링 (약간 앞으로 오프셋) obj->GetMaterial()->GetShader()->SetWireframe(true); obj->GetMaterial()->SetWireframeColor(Color::Yellow); obj->Render(); // 상태 복원 renderer::BindDepthStencilState(eDepthStencilState::Default); }
C++
복사
결과
솔리드 렌더링 위에 노란색 와이어프레임이 오버레이
메시 구조와 셰이딩을 동시에 확인 가능
기술 아트 및 디버깅에 유용

6. 고급 기법

애니메이션 와이어프레임

cbuffer WireframeAnimBuffer : register(b2) { float Time; float PulseSpeed; float PulseMin; float PulseMax; }; float4 main(PS_INPUT input) : SV_TARGET { // 시간에 따라 밝기가 변하는 와이어프레임 float pulse = lerp(PulseMin, PulseMax, (sin(Time * PulseSpeed) + 1.0f) * 0.5f); return float4(pulse, pulse, pulse, 1.0f); }
C++
복사

Depth-based Wireframe

struct PS_INPUT { float4 Position : SV_POSITION; float Depth : TEXCOORD0; }; VS_OUTPUT main(VS_INPUT input) { VS_OUTPUT output; float4 worldPos = mul(float4(input.Position, 1.0f), World); float4 viewPos = mul(worldPos, View); output.Position = mul(viewPos, Projection); // 깊이 정보 전달 output.Depth = output.Position.z / output.Position.w; return output; } float4 PSMain(PS_INPUT input) : SV_TARGET { // 깊이에 따라 색상 변화 (가까우면 밝게, 멀면 어둡게) float depthColor = 1.0f - saturate(input.Depth); return float4(depthColor, depthColor, depthColor, 1.0f); }
C++
복사

선택적 Wireframe (에디터 하이라이트)

void RenderSelectedObjectWireframe(GameObject* selectedObject) { // 선택된 오브젝트만 하이라이트 와이어프레임 if (selectedObject == nullptr) return; // Stencil 버퍼에 선택 오브젝트 마킹 renderer::BindDepthStencilState(eDepthStencilState::StencilWrite); selectedObject->Render(); // Wireframe으로 아웃라인 렌더링 renderer::BindDepthStencilState(eDepthStencilState::StencilRead); renderer::BindRasterizerState(eRasterizerState::Wireframe); Shader* outlineShader = Resources::Find<Shader>(L"OutlineWireframeShader"); outlineShader->SetColor(Color::Orange); outlineShader->Bind(); selectedObject->Render(); // 상태 복원 renderer::BindDepthStencilState(eDepthStencilState::Default); renderer::BindRasterizerState(eRasterizerState::SolidBack); }
C++
복사

7. 성능 및 최적화

Wireframe 모드의 성능 영향

렌더링 비용 분석
Vertex 처리: 동일 (정점 수 변화 없음)
Rasterization: 감소 (픽셀 수 감소)
Pixel Shader: 대폭 감소 (선만 렌더링)
실제 성능
일반적으로 Solid 렌더링보다 빠름
복잡한 재질일수록 성능 차이 증가
Fillrate에 제한받는 상황에서 유리

최적화 팁

조건부 Wireframe 컴파일
#ifdef _DEBUG bool bWireframeAvailable = true; #else bool bWireframeAvailable = false; #endif
C++
복사
LOD와 결합
if (IsWireframeMode() && distance > farDistance) { // 먼 거리에서는 Wireframe 스킵 (성능 최적화) return; }
C++
복사

8. 문제 해결

Wireframe이 보이지 않을 때

체크리스트
1.
FillModeD3D11_FILL_WIREFRAME으로 설정되어 있는가?
2.
Rasterizer State가 파이프라인에 바인딩되었는가?
3.
Wireframe Shader가 올바르게 로드되었는가?
4.
Viewport가 올바르게 설정되어 있는가?

일부 면만 보일 때

원인: Culling 설정 문제
해결: CullModeD3D11_CULL_NONE으로 설정
rsDesc.CullMode = D3D11_CULL_NONE; // 양면 렌더링
C++
복사

Wireframe 선이 너무 얇거나 두꺼울 때

해결 방법
GPU/드라이버에서 제어하는 라인 굵기는 변경 불가
대안: Geometry Shader로 커스텀 두께 구현

9. 추가 Rasterizer State 활용

Shadow Mapping

// 그림자 렌더링 시 앞면 제거로 Shadow Acne 해결 D3D11_RASTERIZER_DESC shadowRsDesc = {}; shadowRsDesc.FillMode = D3D11_FILL_SOLID; shadowRsDesc.CullMode = D3D11_CULL_FRONT; // 앞면 컬링! shadowRsDesc.DepthBias = 100; // Depth bias 추가 shadowRsDesc.DepthBiasClamp = 0.0f; shadowRsDesc.SlopeScaledDepthBias = 1.0f;
C++
복사

Two-Sided Material

// 나뭇잎, 천 등 양면 렌더링이 필요한 재질 D3D11_RASTERIZER_DESC twoSidedRsDesc = {}; twoSidedRsDesc.FillMode = D3D11_FILL_SOLID; twoSidedRsDesc.CullMode = D3D11_CULL_NONE; // 컬링 없음 twoSidedRsDesc.FrontCounterClockwise = false;
C++
복사

결론

Wireframe 모드는 단순해 보이지만 3D 그래픽스 개발의 필수 도구입니다.

주요 학습 포인트

Rasterizer State 이해
DirectX 11 렌더링 파이프라인의 핵심 단계
FillMode, CullMode 등 다양한 속성 제어
상태 객체의 효율적인 관리
실용적 활용
메시 구조 분석 및 디버깅
에디터 도구로서의 가치
성능 프로파일링 보조 도구
확장 가능성
하이브리드 렌더링 (Solid + Wireframe)
커스텀 셰이더와의 결합
고급 시각화 기법
Wireframe 모드의 구현을 통해 Rasterizer State를 깊이 이해하게 되면, 이후 더 복잡한 렌더링 기법(그림자, 아웃라인, 특수 효과 등)을 구현할 때 큰 도움이 됩니다. 단순한 디버그 기능을 넘어, 렌더링 파이프라인의 작동 원리를 체득하는 중요한 경험이 됩니다.