Stencil Buffer로 구현하는 외곽선 오브젝트 하이라이팅
Stencil Buffer를 마스크로 사용해 선택된 오브젝트의 윤곽만 강조하는 하이라이팅 기법을 정리합니다. Unity Built-in Render Pipeline 기준의 셰이더 예제와 렌더링 순서, 흔한 문제 해결 방법을 함께 다룹니다.

왜 Stencil Buffer를 사용하는가
게임에서 상호작용 가능한 물체, 현재 선택한 유닛, 퀘스트 목표를 눈에 띄게 만들 때 외곽선 효과를 자주 사용한다. 가장 단순한 방법은 원본 메시를 조금 크게 한 번 더 렌더링하는 방식이다. 하지만 이 방식만으로는 벽이나 다른 오브젝트 뒤에 가려진 부분을 어떻게 보일지 제어하기 어렵고 여러 하이라이트 효과가 겹칠 때 렌더링 규칙도 복잡해진다.
Stencil Buffer는 화면의 각 픽셀에 작은 정수 값을 기록하는 버퍼다. 깊이 버퍼처럼 직접 색을 보관하지는 않지만 이후 패스가 특정 픽셀에서만 렌더링되도록 마스크를 만들 수 있다. 따라서 먼저 선택 오브젝트의 화면 영역을 스텐실에 기록하고 확대된 외곽선 메시를 스텐실 조건과 함께 그리면 원본 내부를 제외한 테두리만 남길 수 있다.

렌더링 흐름
핵심은 두 번의 렌더링이다.
- 첫 번째 패스는 원본 메시가 차지하는 픽셀에 스텐실 값
1을 기록한다. - 두 번째 패스는 정점을 법선 방향으로 약간 확장한 메시를 그린다.
- 이때 스텐실 값이
1이 아닌 픽셀만 통과시키면 확대된 메시 중 원본 바깥 부분만 색이 남는다.
flowchart TD
A[선택 오브젝트] --> B[패스 1: 스텐실 값 1 기록]
B --> C[패스 2: 메시를 법선 방향으로 확장]
C --> D{스텐실 값이 1인가?}
D -- 예 --> E[픽셀 폐기]
D -- 아니오 --> F[외곽선 색상 출력]
이 방식의 외곽선은 메시의 실루엣을 기준으로 만들어진다. 화면 공간 후처리 기반 윤곽선처럼 모든 물체 경계가 강조되지 않으며 지정한 오브젝트에만 적용하기 좋다.
Unity ShaderLab 예제
아래 예제는 Unity Built-in Render Pipeline에서 사용할 수 있는 최소 구성이다. 첫 패스는 색상 버퍼에 아무것도 쓰지 않고 스텐실만 기록한다. 두 번째 패스는 앞면을 제거하고 뒷면만 그린다. 확장된 메시의 뒷면이 원본 모델 주변으로 드러나므로 외곽선처럼 보인다.
Shader "Custom/StencilOutline"
{
Properties
{
_OutlineColor ("Outline Color", Color) = (1, 0.75, 0, 1)
_OutlineWidth ("Outline Width", Range(0, 0.1)) = 0.02
}
SubShader
{
Tags { "RenderType" = "Opaque" "Queue" = "Geometry" }
Pass
{
Name "StencilMask"
ColorMask 0
ZWrite On
Stencil
{
Ref 1
Comp Always
Pass Replace
}
}
Pass
{
Name "Outline"
Cull Front
ZWrite Off
ZTest LEqual
Stencil
{
Ref 1
Comp NotEqual
Pass Keep
}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
fixed4 _OutlineColor;
float _OutlineWidth;
struct appdata
{
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f
{
float4 position : SV_POSITION;
};
v2f vert(appdata v)
{
v2f o;
float3 expandedVertex = v.vertex.xyz + v.normal * _OutlineWidth;
o.position = UnityObjectToClipPos(float4(expandedVertex, 1.0));
return o;
}
fixed4 frag(v2f i) : SV_Target
{
return _OutlineColor;
}
ENDCG
}
}
}
ColorMask 0은 첫 패스가 화면 색상을 바꾸지 않도록 한다. Ref 1은 비교와 기록에 사용할 기준값이다. Pass Replace는 깊이 및 스텐실 테스트를 통과한 픽셀의 값을 1로 바꾼다.
두 번째 패스의 Comp NotEqual은 스텐실 값이 1이 아닌 곳에서만 외곽선 픽셀을 렌더링한다. 원본 메시 내부는 첫 패스에서 이미 1로 표시되었으므로 제거되고 확장된 메시에서 밖으로 나온 영역만 남는다.
설정값이 만드는 차이
Cull Front
확장된 메시의 앞면까지 그리면 원본 표면을 덮을 수 있다. Cull Front로 앞면을 제거하면 확장된 메시의 뒷면만 렌더링되어 실루엣 주변이 안정적으로 드러난다.
ZTest LEqual
ZTest LEqual은 현재 픽셀이 깊이 버퍼의 값보다 가깝거나 같을 때만 외곽선을 표시한다. 즉 다른 불투명 오브젝트에 완전히 가려진 대상의 외곽선은 보이지 않는다. 일반적인 선택 강조에는 이 동작이 자연스럽다.
반대로 장애물 뒤에서도 목표물을 보여 주고 싶다면 외곽선 전용 패스의 깊이 비교를 Always로 바꿀 수 있다. 다만 모든 가림 관계를 무시하므로 화면이 복잡해질 수 있다. 보통은 가려진 상태용 색상이나 알파를 별도 패스로 두어 구분한다.
여러 효과와 함께 쓸 때의 스텐실 설계
스텐실은 대개 8비트 값이므로 한 화면에서 사용할 수 있는 마스크 공간이 제한되어 있다. 외곽선 하나만 쓴다면 1 하나면 충분하지만 아군 표시, 적 표시, 투명 처리, 포스트 프로세싱 마스크를 함께 쓴다면 비트 단위로 역할을 나누는 편이 안전하다.
예를 들어 외곽선 여부에는 최하위 비트만 사용하도록 ReadMask와 WriteMask를 지정할 수 있다.
Stencil
{
Ref 1
ReadMask 1
WriteMask 1
Comp Always
Pass Replace
}
이 설정은 비트 0만 읽고 쓴다. 다른 시스템이 상위 비트를 사용하더라도 외곽선 패스가 그 값을 덮어쓰지 않는다. 프로젝트 초기에 스텐실 비트 배정표를 만들고 렌더 기능별로 소유 비트를 정해 두면 충돌을 크게 줄일 수 있다.
화면상 두께가 일정하지 않은 이유
예제는 오브젝트 공간의 법선 방향으로 정점을 확장한다. 구현은 단순하지만 카메라와의 거리, 메시의 기울기, 투영 방식에 따라 화면에서 보이는 선 두께가 달라진다. 작은 물체가 멀어질수록 외곽선이 얇아지거나 사라질 수 있다.
화면상 두께를 일정하게 유지해야 한다면 정점 위치를 클립 공간으로 변환한 뒤 화면 방향으로 이동시키는 방법을 검토할 수 있다. 다만 메시 실루엣과 법선의 관계를 세심하게 처리해야 하고 스킨드 메시나 비균일 스케일에서도 검증이 필요하다. 대부분의 상호작용 강조에서는 오브젝트 공간 확장부터 적용한 뒤 실제 카메라 거리에서 문제가 드러날 때 화면 공간 확장으로 발전시키는 편이 구현 부담이 적다.
자주 발생하는 문제
외곽선이 원본 표면을 덮는다
두 번째 패스에 Cull Front가 빠졌거나 스텐실 비교가 잘못된 경우가 많다. 먼저 확대 패스가 앞면을 제거하는지 확인하고 Comp NotEqual과 첫 패스의 Pass Replace가 모두 적용됐는지 점검한다.
외곽선이 아예 보이지 않는다
확장 폭이 너무 작거나 메시의 법선 데이터가 올바르지 않을 수 있다. 또한 선택 오브젝트가 투명 큐에 있으면 불투명 오브젝트와의 깊이 및 렌더 순서가 예상과 달라질 수 있다. 시작 단계에서는 불투명 메시에서 효과를 검증하는 것이 좋다.
벽 뒤의 외곽선이 보이지 않는다
현재 예제의 ZTest LEqual이 깊이 버퍼를 존중하기 때문이다. 의도한 동작이라면 그대로 두고 벽 뒤에서도 보여야 한다면 가려진 상태만 별도 패스로 처리한다. ZTest Always를 무조건 적용하면 캐릭터가 벽을 뚫고 보이는 듯한 인상을 줄 수 있다.
일부 모델에서 선이 갈라지거나 두께가 들쭉날쭉하다
하드 엣지와 분리된 법선은 정점 확장 방향을 불연속적으로 만든다. 모델의 법선 설정을 확인하고 필요하다면 외곽선 전용 메시를 만들거나 화면 공간 방식으로 전환한다.
적용 전 점검 목록
- 하이라이트 대상이 불투명 오브젝트인지 확인한다.
- 스텐실 값 또는 비트가 다른 렌더링 기능과 충돌하지 않는지 확인한다.
- 가려진 대상도 표시할지 먼저 결정한다.
- 다양한 거리와 카메라 각도에서 외곽선 두께를 확인한다.
- 스킨드 메시, 비균일 스케일, 하드 엣지가 있는 모델을 별도로 테스트한다.
Stencil Buffer 기반 외곽선은 단순한 셰이더 기법이지만 렌더링 순서와 깊이 규칙을 명확하게 통제할 수 있다는 장점이 있다. 특히 선택 상태처럼 특정 오브젝트만 정확히 강조해야 하는 기능에 잘 맞는다. 먼저 기본 2패스 구조를 적용한 뒤 프로젝트의 가림 규칙과 아트 스타일에 맞춰 스텐실 비트 배정과 외곽선 두께 방식을 확장해 보자.



