오늘 할작업은 상호작용 가능한 요소들에게 다가갔을때 실제로 상호작용이 가능하다는 느낌을 유저들에게 주기위해 상호작용 요소들에게 흰색 윤곽선을 만들 계획이다.
대상 메시를 CustomDepth버퍼에 스텐실 값과 함께 렌더링하고, 플레이어 카메라에 등록된 포스트 프로세스 머티리얼이 스텐실 경계를 감지해 테두리를 그림
On/Off제어는 기존 프롬프트 표시 지점(ShowPromptFor, HidePrompt)에 두고, PP머티리얼은 스텐실 값 -> 색상 매핑구조로 만들어 향후 색상 추가 시 머티리얼 재작업이 없게 한다.
UMeshComponent에 SetRenderCustomDepth + SetCustomDepthStencilValue 적용상호작용 가능한 액터랑 상호작용을 감지할때 기존 구조는 캐릭터에 상호작용 컴포넌트를 달고 상호작용 인터페이스를 구현하는 액터들이 BeginPlay에서 EnableLocalInteraction() 함수를 통해 실제 콜리전을 가지게 된다.
이때 상호작용 아웃라인을 비동기로드하는 함수를 달아놓는다. 로드 완료되면 소유자 폰의 UCameraComponent에 포스트 프로세스 세팅에 카메라 블렌더블로 머티리얼을 담, 모든 레벨에서 동작 -> 레벨별로 PostProcessVolume처리가 필요없음
매틱 기존처럼 최단거리 대상을 결정함 프롬프트와 아웃라인 거리가 항상 일정함 같은 파이프라인을 타서
대상이 바뀔때만 Update함수가 호출된다(같은 대상이면 얼리리턴) -> 다른 대상으로 Update될때 기존 CustomDepth Off해주는 작업까지 빼먹지 않고 설정
PP 머티리얼은 화면의 각 픽셀에서 CustomStencil 값을 주변과 비교해 경계를 찾고, 경계 픽셀을 스텐실 값에 매핑된 색(1=현재는 흰색)으로 칠함
언리얼에서 관리하는 depth버퍼라는게 있다. 특정 액터만 골라서 렌더링 한 후에 포스트프로세스에서 참조할 수 있도록 하는데
depth(Z-버퍼)란 무엇이냐면 화면에 그려지는 각 픽셀마다 카메라로부터 얼마나 멀리있는 물체인지를 저장해두는 별도의 버퍼(텍스처)
3D 장면을 렌더링할 때 여러 물체가 서로 겹칠 수 있는데, 캐릭터 뒤에 벽이 있으면 벽이 캐릭터를 가려야 상. GPU는 각 픽셀을 그릴때마다 지금 그리려는 픽셀이 이미 저장된 depth값 보다 카메라에 더 가까운가를 비교해서 가까운 것만 최종화면에 반영하고 먼것은 버림, 이 비교용으로 쓰이는게 Depth버퍼
CustomDepth는 뭐가 다른가
일반 depth버퍼는 항상 모든 오브젝트가 자동으로 기록됨(렌더링 자체의 필수 요소)
CustomDepth : 기본적으로 아무것도 안담기고, 개발자가 SetRenderCustomDepth(true)로 선택한 액터만 골라서 여기에 depth를 추가로 기록함
포스트프로세스 머티리얼에서 이 특정 오브젝트가 화면 어디쯤에 있는지 알아내서 그 오브젝트에만 특수 효과를 입힐 수 있음
Stencil은 CustomDepth 버퍼가 이 픽셀이 특정 대상에 속하는가(depth 유무)만 저장한다면, Stencil은 거기에 8비트 정수 값을 하나 더 붙일 수 있게 해주는 부가 채널이다.
CustomDepth로 이 픽셀은 마스킹 대상이다 아니다 (0/1)만 판단 한다면 Stencil이 있으면 이 픽셀이 몇번 그룹에 속한 대상이다 (0~256)식으로 여러 그룹을 숫자로 구분할 수 있게 됨
코드에서는 SetActorOutlineEnabled함수를 호출하면서
Mesh->SetRenderCustomDepth(bEnabled);
// 끌 때는 0으로 되돌려 잔여 스텐실을 남기지 않음
Mesh->SetCustomDepthStencilValue(bEnabled ? OutlineStencilValue : 0);
이쪽에서 OutlineStencilValue를 지정해주고 있음 -> 현재 인터렉션 컴포넌트에서는 1로 하드코딩되어있음 에디터에서 수정은 가능한 상태
머티리얼에서 1에 대한 색은 흰색으로 지정한 상태이다.
LineWidth는 몇 픽셀만큼 옆을 볼지를 픽셀당위로 정한 값(우리는 2), 근데 머티리얼에서는 텍스처를 읽을 때 좌표(UV)는 픽셀이 아니라 0~1 사이의 비율로 쓴다. 화면이 1920px인데 2픽셀 옆을 UV로 표현하려면 2/1920처럼 아주 작은 비율이 되므로 픽셀단위 -> UV비율로 나누기 위해서 전체 해상도로 나눔 ViewSize는 그 화면 전체 해상도를 주는 노드

방향을 4개(상하좌우)를 만들어야 하는데, 오프셋은 원래 벡터(가로 성분, 세로 성분)하나. Mask는 이 벡터에서 한쪽 축만 살리고 나머지는 0으로 지우는 역할을 함
R선택 : 가로(X)성분만 사용 -> 좌우 방향만들때
G선택 : 세로(Y)성분만 사용 -> 상하 방향 만들때
R로 둔이유는 이 방향은 옆으로만 움직이고 상하로는 안 움직인다를 보장하는 순수 방향 벡터를 만드는 역할

Offset이란 지금 픽셀 기준으로 얼마나 떨어진 이웃 픽셀을 볼지를 나타내는 좌표 이동량
R로 가로 성분만 살린 값 = 오른쪽으로 이동 Offset -> 여기다가 -1 곱하면 왼쪽, G도 마찬가지

ScreenPosition은 지금 자신이 그리고 있는 픽셀, 자신의 화면 좌표(0~1 UV)임. 포스트 프로세스는 화면 전체를 한장의 사각형처럼 훑으면서 '지금 픽셀'만 아는 상태로 계산하는데, 아무것도 안하면 항상 내위치만 읽게됨
여기에 Offset을 더하면 내위치가 아니라 그 옆(이웃) 픽셀 위치를 가리키는 좌표가 나옴.
Append노드는 스칼라 두개를 붙여서 벡터 하나로 합치는 노드 이므로 0을 더해서 순수 방향 벡터로 만듬

SceneTexture 노드는 언리얼 렌더러가 이미 계산해둔 다양한 화면 버퍼(원본 색상, 뎁스, 노멀 등)를 포스트 프로세스 머티리얼 안에서 읽어오는 창구
여기서 Scene Texture Id를 CustomStencil로 설정해주어 C++에서 SetCustomStencilValue()로 찍어둔 그 숫자값을 화면픽셀별로 읽어옴
즉, 이 화면 좌표(UV)의 스텐실 값이 얼마인가를 알려주는 노드.
UV를 안넣으면 내 픽셀값을 주고, 4번에서 만든 이웃좌표를 넣으면 이웃 픽셀값을 준다.

목적은 이웃 4곳 중 아무 한곳이라도 대상(스텐실 > 0) 안쪽인지를 확인하고 싶기때문. 4개 값중 가장 큰 값을 뽑으면 하나라도 스텐실이 있으면 그 값이 최대값으로 잡힘 -> OR조건을 Max하나로 구현한것

S0은 지금 그리고 있는 바로 그 픽셀 자신의 스텐실 값
S_4방향 : 그 픽셀을 둘러싼 상하좌우 이웃들 중 가장 큰 스텐실 값
이 둘을 비교하면 나는 대상 밖인데(S0=0), 내 이웃중 하나는 대상 안이다(S_max > 0)라는 경계 상황을 잡아낼 수 있음 -> 이게 바로 윤곽선이 그려질 자리
SmoothStep(Min, Max, Value)는 Value가 Min보다 작으면 0, Max보다 크면 1, 그 사이면 부드럽게 0->1로 변하는것을 만드는 노드
스텐실값은 0, 1 처럼 딱 떨어지는 정수인데 0.5로 기준을 잡아서 0인지 1인지 구분하기 위함

경계조건은 두가지가 만족해야 한다.
1. 내가 대상 밖이다 (S0 기준 판정 결과)
2. 그리고 이웃 중 하나는 대상 안이다 (S_max 기준 판정 결과)
두 판정 결과가 각각 0이나 1이면 이걸 곱했을때 둘다 1이여야만 참이된다. AND연산을 위함
결과물이 바로 여기가 윤곽선 픽셀이다 를 나타내기 위해 명칭은 EdgeMask

PostProcessInput0도 SceneTexture노드의 Scene Texture Id로 고르는 특수한 값이다.
이 머티리얼이 포스트 프로세스 카메라에 등록되는 순간 언리얼 렌더러가 지금까지 그려진 화면 원본을 자동으로 이 이름표에 채워넣음
Lerp(A, B, Alpha)는 그냥 선형 보간
A = 원본화면
B = 흰색(OutlineColor)
Alpha = EdgeMask(경계면 0또는 1)
EdgeMask가 0인 대부분 픽셀은 A 그대로 나오고, EdgeMaks가 1인 딱 그 경계 픽셀들만 흰색이 나오게끔 하기 위함

내 픽셀 스텐실 확인 -> 옆 픽셀들 스텐실 확인 -> 나는 밖, 옆은 안인 경계만 찾기(AND) -> 그 경계만 흰색으로 덮어씌우기 (Lerp) 이 네단계라고 보면 된다.

증상: 대상에게 다가가면 테두리가 아니라 캐릭터 실루엣 전체가 흰색으로 칠해짐.
원인: IsOutside If 노드(S0 < 0.5를 판별해야 함)의 A > B / A < B 분기에 연결된 상수가 뒤바뀌어 있었음. 결과적으로 S0 < 0.5(대상 밖)가 아니라 S0 > 0.5(대상 안)일 때 1이 나오는 IsInside 로직이 되어버림. LineWidth가 작아(2px) 대부분의 내부 픽셀은 이웃도 전부 대상 안이라 NeighborInside도 참이 되므로, IsInside × NeighborInside = 거의 실루엣 전체.
해결: IsOutside의 A > B = 0, A < B = 1로 수정 (NeighborInside는 반대로 A > B = 1, A < B = 0이 맞음 — 두 If 노드는 서로 정반대 패턴이어야 함).

증상: 상호작용 대상에 다가가지 않아도, PIE 시작 직후부터 화면 전체가 하얀색.
원인: If 노드 로직 문제가 아니라 최종 Lerp 노드의 A/B가 서로 바뀜. Lerp(A, B, Alpha)는 Alpha=0일 때 A를 출력하는데, A 핀에 OutlineColor_1(흰색)이, B 핀에 원본 화면(SceneTexture:PostProcessInput0)이 연결되어 있었음. EdgeMask(Alpha)가 0인 곳(화면 대부분, 스텐실이 하나도 없을 때는 전체 화면)에서 흰색이 나오는 게 정상 동작이 되어버린 것.
진단 방법: 스크린샷만으로는 어느 지점이 문제인지 특정이 안 돼서, 머티리얼 그래프를 텍스트로 복사(Ctrl+A → Ctrl+C → 텍스트 파일에 붙여넣기)해서 각 노드의 실제 핀 연결과 상수값을 정확히 대조함. 이렇게 하면 MaterialExpressionIf의 AGreaterThanB/AEqualsB/ALessThanB가 어느 MaterialExpressionConstant를 참조하는지, Lerp의 A/B가 정확히 어디 연결됐는지 텍스트로 명확히 확인 가능.
해결: Lerp의 A ↔ B 연결을 서로 맞바꿈 — A = 원본 화면(SceneColor), B = OutlineColor_1.

증상: 아웃라인 자체는 정상 표시되지만 선이 미세하게 떨리면서 지글거림. LineWidth를 올려도 개선 안 되고 오히려 더 티가 남.
원인: TSR/TAA는 매 프레임 카메라를 서브픽셀 단위로 지터시켜 여러 프레임을 누적해 안정화하는데, CustomStencil 버퍼는 이 시간 누적을 거치지 않고 매 프레임 지터된 원본 그대로 읽힘. 머티리얼의 Blendable Location이 Scene Color After Tonemapping(TSR 처리 완료 이후)으로 설정되어 있어서, 이미 안정화된 화면 위에 "아직 흔들리는 스텐실 기반 선"을 얹는 상태가 됨 → 선만 유독 떨림. LineWidth를 키우면 떨리는 영역도 같이 커져서 역효과.
해결: Blendable Location을 Scene Color After Tonemapping → Scene Color After DOF로 변경. 이러면 아웃라인이 TSR 처리 이전에 합성되어, TSR이 아웃라인까지 포함해 시간 누적·안티에일리어싱을 해주므로 다른 픽셀과 동일하게 안정화됨.
부작용: After DOF는 톤매핑 전(HDR 리니어 색공간)이라 OutlineColor_1=(1,1,1)이 톤매핑 후 순백이 아니라 약간 칙칙하게 보일 수 있음. 필요시 (3,3,3)~(10,10,10) 정도로 값을 올려서 톤매핑 후 흰색에 가깝게 보정 (과하면 블룸 번짐 발생하므로 적당한 값 탐색 필요).
IsOutside: A>B=0, A==B=0, A<B=1 (S0 < 0.5 → 1)NeighborInside: A>B=1, A==B=0, A<B=0 (SMax > 0.5 → 1)EdgeMask = Multiply(IsOutside, NeighborInside)Lerp: A=SceneColor(원본 화면), B=OutlineColor_1(흰색), Alpha=EdgeMaskBlendable Location = Scene Color After DOF (Tonemapping 이후 아님)LineWidth = 2.0 (지터 해결 후에는 굳이 키울 필요 없음)