[TIL] 2026-07-08 — 레벨 드레싱 & 아웃라인 경계 차등: 볼륨 안만 아웃라인
TeamCarry 레벨 정리(바리케이드·원경) 구현 기록. 작업 레벨은
/Game/Developers/goldb/Maps/L_LevelProto_Dressed(팀 원본L_LevelProto는 무변경 — 복제본에서 작업 후 리뷰를 거쳐 반영 예정). 셰이더M_PP_OutlineHLSLv10, 브랜치feat/barricade-imposter.
1. 맵 가장자리 바리케이드 + 원경 임포스터
문제: 맵 가장자리에서 바닥이 뚝 끊기고 허공이 보인다. 인비저블월(BlockingVolume)이 있어 물리는 막혀 있지만, 시각적으로는 미완성 상태.
- 바리케이드 152개 — 바닥 경계를 라인 트레이스(아래→위, 바닥 액터만 필터)로 120방향 스캔해 폴리라인을 얻고, 그 위에
construction-barrier(Kenney CityRoads)를 260cm 간격으로 배치. 림에 건물이 서 있는 구간은 트레이스가 자동으로 걸러낸다. - 스카이라인 임포스터 29장 — 임포스터는 원경을 실제 메시 대신 그림 카드로 대체하는 최적화 기법. 절차 생성한 도시 실루엣 텍스처(
T_CitySkyline, 2048×512) 한 장을 Unlit 마스크드 머티리얼로 카드에 얹어 2겹 링(근경 진한 톤 / 원경 옅은 톤)으로 배치했다. 카드당 폴리곤 2개, 충돌·그림자 없음. 서쪽은 기존 스카이스크래퍼 지구가 있어 제외. - 원경 바닥 700m 플레인 — 플레이트와 스카이라인 사이 파란 보이드를 메운다.
에셋: /Game/Developers/goldb/CityImposter/ (T_CitySkyline, M_CityImposter, MI_CityImposter_Far, M_ImposterGround)
2. 경계 가시화 — 공사장 해저드 월
문제: 인비저블월은 보이지 않아서 플레이어가 “왜 못 가지?”를 인지할 수 없다.
월드 좌표 기반 대각 줄무늬 frac((x+y+z)/250)로 노랑/검정 패턴을 만들고 Unlit 트랜스루선트로 출력. 충돌·그림자 없는 순수 시각용이며, 물리 차단은 기존 볼륨이 그대로 담당한다. 색은 WallColor 파라미터로 조절.
에셋: /Game/Developers/goldb/Boundary/M_BoundaryWall
3. 아웃라인 경계 차등 — “볼륨 안만 아웃라인”
요구: 캐릭터와 플레이 경계 안쪽만 아웃라인을 유지하고, 경계 밖 배경(도시·원경)은 아웃라인을 제거해 시선 분산을 줄일 것.
벽 너머 건물·원거리 가로등의 실루엣 윤곽이 사라졌다(남은 선은 메시 텍스처의 창틀 디테일). 구역 안쪽 가로등(오른쪽)은 윤곽 유지.
스텐실 규약 (CustomDepthStencil)
| 값 | 의미 | 설정 주체 |
|---|---|---|
| 1 | 인터랙션 하이라이트 (노란 링) | C++ OnFocus/OnUnfocus 훅 — 런타임 |
| 2 | 경계 밖 배경 — 아웃라인 억제 | 레벨 배치 시 일괄 지정 — 에디터 (419개 액터) |
경계 밖 판정: 플레이 직사각형(x −3880~560, y −970~5070 — 볼륨 벽 기준)과 액터 바운드를 비교한다. 완전히 밖이면 배경, 걸치면 중심 위치로 판정, 대형 공유 바닥은 예외로 안쪽 취급.
핵심 — 커스텀뎁스↔씬뎁스 일치 검사
단순히 “스텐실 2가 찍힌 픽셀을 억제”하면 가려진 배경도 커스텀뎁스 버퍼에 남기 때문에 배경 앞에 선 캐릭터·집의 윤곽선까지 꺼진다(커스텀뎁스 패스는 메인 패스의 가림과 무관하게 그려짐). 그래서 “배경이 실제로 화면에 보이는 픽셀”만 억제한다:
1
2
3
// 배경(스텐실2)이면서, 커스텀뎁스 ≈ 씬뎁스 (= 그 배경이 지금 보이는 표면)일 때만
float bgVisible = step(abs(stencil - 2.0), 0.4) // 스텐실 == 2
* step(customDepth, sceneDepth * 1.02 + 50.0); // 뎁스 일치 검사
캐릭터가 배경 앞에 서 있으면 그 픽셀의 스텐실은 2(뒤의 배경)지만, 커스텀뎁스(배경까지 거리) ≫ 씬뎁스(캐릭터까지 거리)라 검사에서 걸러져 윤곽선이 보존된다. 아웃라인의 5방향 샘플 각각에 이 검사를 적용하고, 기존 “엣지에서 가장 가까운 표면” 판정과 결합했다.
4. 시행착오 — 왜 이 방식인가
- ✗ 스텐실 단순 억제 — 가려진 배경이 커스텀뎁스에 남아 배경 앞 캐릭터·집 윤곽선까지 꺼짐. 화면 전체가 무윤곽이 되는 부작용.
- ✗ 월드 좌표 박스 게이트 — PP 도메인에서 WorldPosition 노드·행렬 복원 모두 씬 표면 좌표를 안정적으로 주지 않음.
- ✗ 내부/외부 PPV 분리 (검토만) — PPV는 카메라 위치 기준으로 전환됨. 화면 픽셀 단위의 안/밖 구분이 원리적으로 불가.
- ✓ 스텐실 + 커스텀뎁스 일치 검사 — 표준 오클루전 하이라이트 기법의 응용. 픽셀 정확, 스텐실 2가 없는 다른 맵은 동작이 완전히 동일(영향 없음).
5. 확장 방법 & 최종 결과
- 새 배경 오브젝트 추가 — 메시 컴포넌트에
CustomDepthStencilValue = 2+Render CustomDepth만 켜면 끝. - 새 상호작용 오브젝트 —
OnFocus에서 스텐실 1 + 커스텀뎁스 ON (인터랙션 하이라이트 규약과 동일). - 전제 조건 —
r.CustomDepth=3(DefaultEngine.ini 반영 완료).
오늘 배운 것 정리
- 원경은 메시가 아니라 그림이다. 폴리곤 2개짜리 임포스터 카드 + 실루엣 텍스처 한 장이면 스카이라인이 선다 — 충돌·그림자 없음, 2겹 링의 톤 차이로 원근감까지.
- 인비저블월은 물리만 막는다 — 시각 경계는 따로 전달해야 한다. 월드 좌표
frac줄무늬의 Unlit 해저드 월로 “여기까지”를 보여주고, 차단은 기존 볼륨에 그대로 맡긴다. 시각과 물리의 역할 분리. - 커스텀뎁스 패스는 메인 패스의 가림과 무관하게 그려진다. 스텐실만 보고 억제하면 배경 앞 캐릭터·집의 윤곽까지 꺼진다 — “커스텀뎁스 ≈ 씬뎁스” 일치 검사로 실제로 화면에 보이는 배경 픽셀만 걸러야 한다.
- PP 도메인에서 화면 픽셀 단위의 안/밖 구분은 스텐실+뎁스뿐이다. WorldPosition 복원은 불안정하고 PPV는 카메라 위치 기준 전환이라 원리적으로 불가 — 표준 오클루전 하이라이트 기법의 응용이 답이었다.
- 규약을 정하면 확장이 공짜다. 스텐실 1=하이라이트(런타임 훅)·2=배경 억제(에디터 배치)로 역할을 고정하니, 새 오브젝트는 스텐실 값만 지정하면 끝. 스텐실 2가 없는 맵은 동작이 완전히 동일하다.




