고도 엔진에서 시차 맵핑 (Parallax Mapping) 구현 방법 - Whitmem
고도 엔진에서 시차 맵핑 (Parallax Mapping) 구현 방법
게임 개발 및 엔진
2026-01-12 23:00 게시 73386314136e081d8fdd

1
0
54
이 페이지는 외부 공간에 무단 복제할 수 없으며 오직 있는 그대로 게시되며 부정확한 내용을 포함할 수 있습니다. 법률이 허용하는 한 가이드 라인에 맞춰 게시 내용을 인용하거나 출처로 표기할 수 있습니다.
This page is not to be distributed to external services; it is provided as is and may contain inaccuracies.
이번에는 시차 맵핑, Parallax Mapping을 직접 구현해보고자 한다. 원래는... 상용 엔진을 사용하고자 하였으나 상용 엔진에서 제공하는 고급 Lit 쉐이더를 자세히 다룰줄을 몰라 ㅋㅋㅋ.... 이 구현은 고도 엔진을 사용하기로 결정했다. 상용 엔진 역시 PBR과 관련된 (Albedo, Normal 등을 넘기면 알아서 빛 처리를 수행하는) 다양한 고급 기능과 저수준 쉐이더 작업이 제공되지만, 아직 익히지 못한 관계로 사용하지 못했다. 물론 Unlit 모드에서도 구현이야 가능하겠지만, 빛의 처리, 노말에 대한 처리를 해야 눈에 즉시 보이기 때문에 이 과정을 건너뛰고자 PBR 쉐이더를 사용하고자 하였다. 텍스처는 저작권 문제가 없도록 제공해주는 Ambient CG 를 사용하기로 하였다. https://ambientcg.com/view?id=Bricks102 여기서 제공하는 텍스처가 있는데, 이 파일들을 모두 내려받는다. 제공되는 파일 중에 blend 라는 파일이 있어 열어봤는데, 파일의 문제인지 아무것도 보이지는 않는다. 관련해서는 문의 공간이 있어 한 번 문의를 남겨놨으니 기다려봐야겠다. 잘 사용하고 있다는 피드백도 덤으로 남겨두었다... 우선 메시는 Plane으로 지정해서 쉐이더를 구현 해 볼 예정이다.
자세히 보면 아래 이미지는 약간 더 깊이가 생겨있는 것을 볼 수 있는데, 이는 시차 맵핑을 활용하여 구현한 것이다. 실제 버텍스에 깊이를 준 것은 아니다.
이 게시글에서 설명할 것은 일반 시차 맵핑인데, 이 방법은 일단 좀 크게 왜곡된다는 문제가 있다. 그 이유는 아래에서 설명할 것이다. 일단 시차 맵핑이 어떤식으로 구현되는지를 설명해보겠다.
일단 기본 시차 맵핑은 카메라 방향 벡터를 활용한다. 즉 카메라가 각 픽셀에 향하는 벡터이다. 원래 텍스처 맵핑에 따르면 각 픽셀은 해당하는 UV 좌표로 텍스처를 샘플링해야 하지만, 여기에 실제 깊이가 있다고 보는 것이다. 깊이가 가리고 있다면 더 뒤에 있는 텍스처가 샘플링 되어야 하며, 위에서는 초록색 지점이 샘플링되어야 하는 것과 같다. 다만 이 지점을 바로 구할 수가 없기 때문에, 일단 높이에 해당하는 x, y 비율(즉 방향 벡터의 비율로 본다.) 만큼 더 나아간 위치를 텍스처 샘플링하는 것이다. 그러면 위 예시 그림에서 파란색 영역이 샘플링되는데, 약간의 오차가 발생하는 것을 볼 수 있다. 이는 즉 현재 지점의 깊이를 통해 카메라 뷰의 비례식으로 x,y 의 오프셋을 예측한 것에 불과하다. 즉 근사하는 값이다.
이 작업은 모두 텍스처 좌표계 안에서 수행된다. 즉 TBN 좌표계 안에서 수행되는데, 텍스처가 6면체 어느곳에 있든 동일하게 처리하고, 이미지 위에서 깊이로 들어가듯이 계산하는게 편하기 때문이다.
쉐이더 코드를 예시로 보겠다.
일단 현재 픽셀의 높이 맵(displacement)를 샘플링한다. 여기서는 깊이 0인 것이 표면에 가깝고, 1인 것이 제일 깊은, 깊이 맵으로 활용할 것이기 때문에 1에서 높이맵을 빼 준다.
그리고 TBN 행렬을 만들어준다. 여기서 n과 b에 - 처리한 것을 볼 수 있는데, 노말 처리가 잘못되는 것 같아 임의로 n과 b를 마이너스 처리해주었다. 정확한 좌표계 내부 상황은 파악하지 않았으므로 이 부분은 언급하지 않고 패스한다. 구현하고자 하는 공간의 좌표계를 파악하고 TBN 행렬을 생성하길 바란다. (여기서는 TBN 내부에서 Z를 높이로 보겠다. x, y 는 UV XY 그대로이다.)
다음으로 TBN 행렬에 현재 카메라 방향 벡터(고도엔진의 VIEW 벡터는 화면 픽셀 -> 카메라의 벡터를 view 공간에서 나타낸다.)를 곱해 탄젠트 공간으로 이동해준다. 그리고 카메라에서 픽셀로 향하는 탄젠트 벡터를 구하기 위해 음수처리 해준다.
이제 이 뷰 벡터(카메라 to 픽셀)는 노말라이즈가 된 상황이므로 세기만 곱해주면 된다. 비례식에 의해서 depth/z 깊이를 곱해주면 된다.
기존뷰x:기존뷰z = 새로운x : 현지점 깊이맵, 기존뷰z * 새로운x = 기존뷰x * 현지점 깊이맵 새로운x = 기존뷰x * 현지점 깊이맵 / 기존뷰z 이므로 OffsetX = ViewX * Depth / ViewZ 이다. 이 값을 새로운 UV로 만들어주면 된다. 오프셋을 더해주는 이유는 해당 오프셋만큼 뒤에있는 샘플을 현재 자리에 가져오기 위함이다.
실제 실행해보면 조금 찢어지는 문제가 발생한다.
이는 왜곡이 발생하기 때문인데, 따라서 실제 RayMarching 기법을 사용한다고 한다.
RayMarching
위 이미지를 보면 깊이가 확실하게 생겨있는 모습을 볼 수 있는데. 조금씩 점이 나있는 것을 볼 수 있다. 즉 층이 있는데 이 원리는 퍼 쉐이더를 구현할때 여러겹을 만들었던 것 처럼 비슷한 효과를 낸다. 퍼 쉐이더 처럼 드로우 콜을 여러개 그린다는 것은 아니다. 플랜 하나 안에 샘플링을 여러 층을 겹쳐서 수행하는 것이다.
즉 표면으로부터 더 깊이 파고들어가면서 레이마칭을 수행한다. 이 작업은 모두 탄젠트 공간에서 수행한다. 현재 방향 벡터 (in tangent)를 노말라이즈하고 매 작업마다 일정 distance만큼 이동한다. 이동 한번 할 때 마다 해당 새로운 UV 지점의 깊이맵과 비교하여 아직 충돌하지 않았는지 확인한다. 충돌하지 않았으면 다음 for문으로 넘어가 계속 이동할 수 있다.충돌하면 해당 충돌한 새로운 UV 지점을 사용해 텍스처를 샘플링한다.
그런데 위 방법에서 주의할 것이 z 깊이 이다. x,y는 임의 distance만큼 계속이동하면 되는데, z는 임의 distance만큼만 이동하는 경우, 깊이의 끝까지 탐색을 하지 못하는 문제가 발생한다.
fragment 안에서 무한한 for문은 사용할 수 없으므로 횟수를 제한해야 하는데, 위에서는 화살표 3개로 제한하였다. 즉 for문을 3번 수행하는 것인데, 위 예시에서는 충돌했지만 옆 깊이가 더 긴 지점은 충돌하지 못한다.
따라서 z 깊이는 최대 깊이인 1까지 하여 비율로 나누어서 일정 깊이만큼 파고들어가게끔 구현해야 한다.
즉 x,y 는 사전 정의한 일정한 비율(노말라이즈 한 경우 정말 조금씩)로 더해지지만, 깊이는 x,y가 이동하는 간격과 관련 없이 무조건 z의 일정 간격으로만 더해진다. 즉 이라고 보면된다. 깊이맵은 무조건 0~1 범위 이므로, Ray Marching을 5번 수행하는 경우 한 번 Ray를 이동할 때 마다 z는 무조건 1/5씩 움직이는 것이다.
코드를 보면 일단 레이 마칭은 0,0,0에서 시작한다고 가정한다. (오프셋이라 0,0,0으로 했다 나중에 픽셀 좌표의 UV에 더해줄 것이다. 실제 0,0라고 오해하면 안된다.) go_z_layer_per 라는 변수를 하나 만들어 1회 Ray Marching 때 1/100 씩 z로 빠져들게끔 구현하였다.
실제 이동하는 UV XY 좌표는 go_distance라는 별개의 사이즈씩 움직인다. 즉 x,y가 더해지는 스케일과 z가 빠져드는 깊이가 다르다고 보면된다. 이렇게 이동한 지점의 실제 깊이 맵을 가져와, 깊이 real_uv_height 와, 현재 빠져든 레이의 깊이를 비교한다. 아직 레이의 깊이가 높이위에 있어 충돌하지 않은 경우 계속 이동하고, 충돌할 때 까지 for을 수행한다. 이 작업은 깊이 1까지 도달할 때 까지 수행된다. 오차가 생기더라도 무조건 z는 1에 도달하도록 1.0/100 씩 100번의 for에 거쳐서 더해지도록 했기 때문이다.
실제 결과물을 보면 세기를 세게 하면 층이 보이는 것을 볼 수 있다. 마치 Fur 쉐이더 처럼 말이다. 이 층은 내가 세밀하게 하면 할 수록 스케일을 크게 했을 때 세밀하게 보인다. 다만 성능 문제가 있을 수 있다.
5번만 수행한 모습이다. 5번만 수행하고 깊이를 적당하게 조절하면 위와 같은 결과를 얻을 수 있다.
댓글 0개
댓글을 작성하는 경우 댓글 처리 방침에 동의하는 것으로 간주됩니다. 댓글을 작성하면 일회용 인증키가 발급되며, 해당 키를 분실하는 경우 댓글을 제거할 수 없습니다. 댓글을 작성하면 사용자 IP가 영구적으로 기록 및 부분 공개됩니다.
확인
Whitmemit 개인 일지 블로그는 개인이 운영하는 정보 공유 공간으로 사용자의 민감한 개인 정보를 직접 요구하거나 요청하지 않습니다. 기본적인 사이트 방문시 처리되는 처리 정보에 대해서는 '사이트 처리 방침'을 참고하십시오. 추가적인 기능의 제공을 위하여 쿠키 정보를 사용하고 있습니다. Whitmemit 에서 처리하는 정보는 식별 용도로 사용되며 기타 글꼴 및 폰트 라이브러리에서 쿠키 정보를 사용할 수 있습니다.
이 자료는 모두 필수 자료로 간주되며, 사이트 이용을 하거나, 탐색하는 경우 동의로 간주합니다.