MOONSUN
[그래픽스] Toon Shading의 OutLine 표현 : 실루엣 Pass 본문
앞 글에서는 Toon Shading에 대해 알아보았는데,
이번에는 Toon Shading과 함께 사용되는 외곽선 실루엣 표현 기법에 대해 알아보도록 하겠다.
0. 외곽선(Outline) Pass란?
만화풍 렌더링에서 오브젝트의 윤곽을 둘러 강조해주는 역할을 해준다.
이 외곽선은 후처리(Post-Process) 로도 만들 수 있지만,
이번에는 모델 자체를 이용한 기하학적(Geometry-based) 외곽선 표현 방법을 알아보도록 하겠다.
0-1. 외곽선 Pass의 기본 개념
모델의 뒷면(back face) 을 법선 방향으로 살짝 확장하여 렌더링한 후, 그 바깥쪽을 칠한다.
과정을 단계별로 살펴보면서 알아보자.
1. Outline 생성 원리
1-1. 법선 방향으로 확장
모델의 각 정점(Vertex)을 법선 벡터 방향으로 살짝 밀어내, 조금 더 큰 형태의 모델을 만든다.
expandedPos = Input.Position + float4(N * OutlineThickness, 0.0);
- N : 정점의 법선 벡터 (Normal)
- OutlineThickness : 외곽선 두께 조절 값
이렇게 하면 모델 전체가 법선 방향으로 확장되어 살짝 커진다.
1-2. 뒷면(Back Face)만 렌더링
확장된 모델은 원래 모델보다 약간 커졌기 때문에 겉으로는 뒷면(back face) 만 보이게 된다.
- 그래서 이 Pass에서는 앞면(front face) 을 제거하고 뒷면만 렌더링 해야 한다.
CullMode = CULL_CW; // 시계 방향(CW) 면 제거 → 뒷면만 남김
(참고)
RenderMonkey 기준에서는 CW가 앞면(Front Face) 으로 인식되므로,
이 설정은 “앞면 제거 → 뒷면만 렌더링”을 의미한다.
( DirectX 기본 규칙에서는 반시계(CCW)가 앞면이므로 환경에 따라 설정이 달라진다 아래 설명 추가 )
1-3. 단색으로 색칠
렌더링된 뒷면은 윤곽선처럼 보이게 단색으로 칠한다.
Output.Color = OutlineColor; // 예: float4(0, 0, 0, 1)
이렇게 하면 모델의 원래 표면보다 살짝 바깥쪽에 검은 실루엣이 둘러진 것처럼 보이게 된다.
2. Pass 구성 예시
Toon 렌더링에서는 보통 두 개의 Pass 로 외곽선을 처리한다.
| Pass | 설명 | Cull Mode | 역할 |
| Pass 0 (Outline Pass) | 모델의 뒷면을 확장하여 단색으로 렌더링 | CULL_CW | 외곽선 생성 |
| Pass 1 (Toon 본체) | 원래 모델을 Toon Shading 방식으로 렌더링 | CULL_CCW | 본체 색상 표현 |
이 두 Pass를 순서대로 겹쳐서 그리면 윤곽선 둘러진 만화풍 Toon 모델이 완성 되는 것.
3. Cull Mode 주의사항
렌더링 엔진마다 “어느 면이 앞면(Front Face)” 인지는 다를 수 있기 때문에,
환경에 따라 Cull 설정을 맞춰줘야 한다.
| 환경 | 앞면 정의 | Outline Pass Cull | Toon Pass Cull |
| DirectX / HLSL (일반) | CCW (반시계 방향) | CULL_CCW | CULL_CW |
| RenderMonkey (ATI 파이프라인) | CW (시계 방향) | CULL_CW | CULL_CCW |
즉, 환경마다 반대가 될 수 있으므로, 렌더링 결과가 뒤집히면 Cull 설정을 바꿔보면 된다.
4. Toon Rendering + OutLine 전체 렌더링 과정 요약
Pass 0 (Outline)
- 각 정점을 법선 방향으로 확장
- 앞면 제거 (Cull CW)
- 단색으로 뒷면 렌더링 → 윤곽선 생성
Pass 1 (Toon Shading)
- 원래 모델에 Toon 셰이딩 적용
- 뒷면 제거 (Cull CCW)
- 본체 렌더링
5. Toon Rendering + OutLine 구현 결과
void TestApp::Render()
{
m_D3DDevice.BeginFrame(clearColor); // 화면 초기화
// 상수 버퍼 업데이트 등...
...
// [ PASS 0 : 외곽선 ]
{
context->RSSetState(m_pRS_CullFront.Get()); // 앞면 제거 -> 뒷면만 렌더링
context->VSSetShader(m_pOutlineVS.Get(), nullptr, 0);
context->PSSetShader(m_pOutlinePS.Get(), nullptr, 0);
// 외곽선 버퍼 업데이트
OutlineBuffer outline{};
outline.OutlineThickness = 1.0f; // 외곽선 두께
outline.OutlineColor = XMFLOAT4(1, 1, 1, 1); // 외곽선 색상 (흰색)
context->UpdateSubresource(m_pOutlineBuffer.Get(), 0, nullptr, &outline, 0, 0);
context->VSSetConstantBuffers(3, 1, m_pOutlineBuffer.GetAddressOf());
// 메쉬 렌더
boxHuman.Render(context, m_pSamplerLinear.Get());
}
// [ PASS 1 : Toon 본체 ]
{
context->RSSetState(m_pRS_CullBack.Get()); // 뒷면 제거 (기본)
context->VSSetShader(m_pVertexShader.Get(), nullptr, 0);
context->PSSetShader(m_pPixelShader.Get(), nullptr, 0);
boxHuman.Render(context, m_pSamplerLinear.Get());
}
m_D3DDevice.EndFrame();
}

'D3D' 카테고리의 다른 글
| [그래픽스] PBR 이론 및 구현 : BSDF, BRDF, Cook–Torrance (0) | 2025.12.02 |
|---|---|
| [그래픽스] PBR(Physically Based Rendering) : 빛의 물리 기반 렌더링의 이해 (0) | 2025.12.01 |
| [그래픽스] Toon Shading (툰 셰이딩) : Lambert 단계화 & Ramp Texture 방식 (0) | 2025.11.10 |
| [D3D] 알파값(투명도) 처리 방식 : Alpha Test, Alpha Sorting, Alpha Blending (0) | 2025.10.21 |
| [D3D] 노멀 매핑(Normal Mapping) 과 접선 공간 (Tangent Space) (0) | 2025.10.02 |