개요
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.
FillMode가 D3D11_FILL_WIREFRAME으로 설정되어 있는가?
2.
Rasterizer State가 파이프라인에 바인딩되었는가?
3.
Wireframe Shader가 올바르게 로드되었는가?
4.
Viewport가 올바르게 설정되어 있는가?
일부 면만 보일 때
원인: Culling 설정 문제
해결: CullMode를 D3D11_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를 깊이 이해하게 되면, 이후 더 복잡한 렌더링 기법(그림자, 아웃라인, 특수 효과 등)을 구현할 때 큰 도움이 됩니다. 단순한 디버그 기능을 넘어, 렌더링 파이프라인의 작동 원리를 체득하는 중요한 경험이 됩니다.

