42. 기술: Depth Rim + Screen-Space Edge
[B] rim/edge는 “외곽선”만으로 해결하기 어려운 장면(밝은 배경, 얇은 소품, 복잡한 실루엣)에서 캐릭터를 읽게 만드는 보강 신호입니다. 핵심은 라인을 더 세게 그리는 게 아니라, 저주파(rim)와 고주파(edge)를 분리해 역할을 나누는 것입니다.
목적
- [B] 배경 대비 캐릭터 실루엣을 안정적으로 분리한다.
- [A/B] URP
DepthNormals계약 위에서 재현 가능한 edge/rim 결합식을 제시한다.
증거 등급 요약(A/B/C)
- [A] 플랫폼 발표에서 캐릭터 가독성과 고대비 장면 대응의 중요성이 반복된다.
- [B] Zhihu/CSDN 역분석에서 depth rim + line 보강 조합이 반복된다.
- [C] 내부 샘플 반경/커널 구성은 프로젝트별로 상이하다.
핵심 개념
이론 배경
- [B] rim은 법선-시선 관계의 저주파 신호, edge는 depth 기울기의 고주파 신호라 서로 보완적으로 동작한다.
- [B] 두 신호를 분리하면 내부 디테일 과강조를 피하면서 외곽 가독성만 강화할 수 있다.
- [C] 커널 반경을 키우면 안정성은 늘지만 라인 지연/번짐이 증가하므로 플랫폼별 한계를 정의해야 한다.
핵심은 "림은 밝기 보정, 엣지는 윤곽 보정"으로 역할을 분리하는 것이다. [B]
HLSL
float LinearEye(float rawDepth)
{
return LinearEyeDepth(rawDepth, _ZBufferParams);
}
float EvalDepthEdge(float2 uv, float2 px)
{
float dC = LinearEye(SAMPLE_TEXTURE2D(_CameraDepthTexture, sampler_CameraDepthTexture, uv).r);
float dR = LinearEye(SAMPLE_TEXTURE2D(_CameraDepthTexture, sampler_CameraDepthTexture, uv + float2(px.x, 0)).r);
float dU = LinearEye(SAMPLE_TEXTURE2D(_CameraDepthTexture, sampler_CameraDepthTexture, uv + float2(0, px.y)).r);
float grad = abs(dR - dC) + abs(dU - dC);
return saturate((grad - _EdgeBias) * _EdgeScale);
}
float EvalDepthRim(float3 nWS, float3 vWS, float2 uv)
{
float fres = pow(1.0 - saturate(dot(normalize(nWS), normalize(vWS))), _RimPower);
float edge = EvalDepthEdge(uv, _ScreenParams.zw); // 1/width, 1/height
return saturate(fres * _RimIntensity + edge * _EdgeRimBlend);
}
- [B] line-only 접근은 밝은 배경에서 경계가 사라지므로 rim 가중치를 병행한다.
- [B] edge-only 접근은 내부 음영이 과분리되므로 bias/scale을 거리 기반으로 보정한다.
결론: rim과 edge를 “같은 문제”로 취급하지 않는다
- [B] rim은 캐릭터 표면 방향성(법선-시선) 신호라, 캐릭터 내부 스타일과 어울리는 “광량 보정”에 가깝다.
- [B] edge는 배경과의 분리 신호라, 카메라 거리/배경 복잡도에 따라 가중치를 조절해야 한다.
- [B] 따라서
T006(hull outline)과 함께 쓸 때도 역할을 섞지 말고, “캐릭터 라인”과 “배경 분리 보강”을 분리해 튜닝한다.
URP 매핑 포인트
설계 해석
-
[B] 근접샷은 rim 비중, 원경 군중샷은 edge 비중을 높이는 거리 기반 블렌딩이 안정적이다.
-
[A/B]
DepthNormals비용이 큰 플랫폼은 캐릭터 전용 레이어로 적용 범위를 축소한다. -
[A/B] 입력 계약:
DepthNormals사용 시DepthNormalspass 누락을 금지한다. -
[B] 적용 위치:
- 캐릭터 셰이더 내부 rim 계산(
UniversalForward) - 후처리 edge pass(RendererFeature)에서 최종 line 보강
- 캐릭터 셰이더 내부 rim 계산(
-
[B] 카메라 거리별 line 폭:
_EdgeScale = lerp(_NearScale, _FarScale, saturate(viewDepth * _DepthFade))
실패 패턴/오해
- [B]
_ZBufferParams/역 Z 설정을 고려하지 않아 edge가 반전된다. - [B] MSAA resolve 전 depth를 샘플해 깜빡임이 발생한다.
- [C] 노멀맵 세부 패턴까지 edge로 취급해 "모공 라인화"가 생긴다.
실무 체크리스트
-
DepthNormals/MotionVectors