유니티에서 God Ray (레이 광선) 구현 공부 - Whitmem
유니티에서 God Ray (레이 광선) 구현 공부
게임 개발 및 엔진
2026-01-11 16:20 게시 e1206aa9ce296f28aedc

0
0
39
이 페이지는 외부 공간에 무단 복제할 수 없으며 오직 있는 그대로 게시되며 부정확한 내용을 포함할 수 있습니다. 법률이 허용하는 한 가이드 라인에 맞춰 게시 내용을 인용하거나 출처로 표기할 수 있습니다.
This page is not to be distributed to external services; it is provided as is and may contain inaccuracies.
이 게시글에서는 직접 God Ray 를 구현하는 과정에 대해서 담고 있다. 필자도 이런 방식의 구현은 생각해본적이 없어서 자료를 구하기도 매우 힘들었다. AI 질의 응답도 해봤는데, 과정이 복잡한지라 사실상 구현은 직접 다 해야했다. 다만, 원리는 인공지능에게 물어보니 광선이 추가되는 것 말고는 일반 그림자 깊이 버퍼 처리랑 비슷 비슷해서 원리 구현에는 별로 힘든 건 없었다.
다만 유니티 엔진에서 여러 그래픽 라이브러리 호환을 위해 좌표계를 뒤집고 처리하는 이 부분이 명확히 이해가 안돼서 갑자기 z 버퍼가 뒤집어지거나, y가 뒤집어지거나, x가 뒤집어지는 등 원인을 파악하는데 거의 하루가 소모되었다. 결론은 다시 뒤집는 행렬을 만들어서 어찌 저찌 구현은 하였으나 실무에서 별로 권장되지는 않는 방법일 것 같다. 따라서 이 게시글에서는 이런 부분에 대해서는 언급하지 않고 주요 원리 과정에 대해서만 기록할 것이다.
완전 세밀한 표현까지는 힘들어서 추후에는 직접 God Ray를 구현하지 않고 엔진에서 제공하는 방법을 사용해볼까 싶다.
원리
원리는 그렇게 어렵지는 않다. 플레이어 카메라를 기준으로 각 픽셀마다 향하는 레이 광선을 발사한다. 레이 광선의 밀도나 횟수는 개발자 마음이다. 이건 쉐이더에서 정해주기로 한다. for문으로 각 픽셀마다 광선을 쏜다. 광선의 방향은 해당 픽셀의 깊이 버퍼가 있는 (즉 물체가 있는) 지점까지를 방향 벡터로 만들어 정규화한다음 일정 간격으로 쏘면 된다. (물체가 없는 지점은 깊이 버퍼가 없을 수 있는데 Far 평면 끝으로 지정해도 된다.) 어차피 물체가 앞에 있든 뒤에 있든 깊이 버퍼가 바뀌긴하지만, 레이를 쏘는 광선 자체는 여전히 픽셀 위에 있으며 (투영했을 시) 레이 광선을 쏠 당시에는 깊이 버퍼는 그렇게 중요하지 않은 것 같다. 다만 레이 광선이 이동하면서 카메라 깊이 버퍼에 도달한 경우 해당 지점은 계산하지 않는다.
각 픽셀마다 광선이 이동하면서 빛 행렬 기준으로 그림자가 지지 않는 부분이면 빛을 받고 있는 것으로 판단하여 일정 세기를 더한다.
최종적으로 해당 광선에 관련된 픽셀에 세기를 더한다. 다시 말하지면 해당 픽셀에서 발사된 레이는 여전히 픽셀 위에 있는 것이다.
i 총 iterator 4번을 수행하는 레이 광선을 쏜다고 할 때, i=1 은 각 픽셀에서 발사된 레이 광선이라고 볼수 있다. 이 픽셀 각각의 광선은 총 4번씩 화면 방향으로 광선 발사를 수행한다.
카메라 깊이 버퍼 비교
여기서 이제 두번의 계산 처리가 수행되는데 먼저 각 iteration 마다 광선의 정점의 깊이 버퍼와 카메라의 깊이 버퍼를 비교한다. 레이 광선이 객체에 부딪힌 경우 해당 지점에는 빛이 도달하지 않는 것을 확정적으로 알 수 있기 때문이다. (빛 지점의 투영 행렬에 가려져도 도달하지 않는다는 것을 알 수 있지만 일단 정확한 판단 방법이니 추가하였다.)이렇게 도달하지 않는 빛을 발견하면 즉시 for을 중단해서는 안되고 일단 레이가 계속 뻗어나갈 필요가 전혀 없다. 뒤
초록색 광선을 보자 이미 객체에 가려진 것은 플레이어 기준에서 카메라에 객체가 가리고 있다. 즉 뒷 부분 빛이 도달하는지는 계산해서는 안된다는 것이다. 객체가 빛을 가리고 있기 때문에 카메라 행렬 기준 객체 뒷면(초록 광선 부분)은 빛이 도달하든 말든 픽셀에 영향을 끼칠 수 없다는 것이다.
즉 객체에 도달하면 break하고 멈춰준다.
빛 투영행렬 기준 그림자 확인
다음으로 Light 의 투영 행렬 기준으로 그림자 맵을 확인하듯이 깊이 버퍼를 확인해줘야 한다. Light에서 맵 전체를 쳐다볼 수 있는 (즉 빛이 도달하는) 방향으로 행렬을 만들어 깊이 맵을 만들어야 한다.
가까우면 밝고, 멀면 어두울지는 개발자 마음이다. 엔진의 특성에 맞기는 것도 방법이다. 나는 공부하기 위해서 직접 다 정의했다. 밝으면 멀어지고 어두우면 가까운 것으로 말 이다. 이 때 배경은 White로 렌더하였다. 물체가 없는 부분은 깊이 버퍼가 Far (즉 제일 멀리)있는 것으로 판별해야 하기 때문이다.
깊이 버퍼는 선형으로 저장하였다. 나는 직교 좌표계를 사용했기 때문에 z 값을 그대로 사용하였다. 이상하게 직교 좌표계를 다시 View로 이동하니 비선형으로 바뀌는 문제가 있었는데 이는 그냥 우선 패스하였다. 이 깊이 버퍼는 객체가 렌더링 되는 Opaque 전에 임의로 전체 객체 Cull (카메라 시점)을 가져와 직접 다시 모두 렌더링하였다. 이때 Cull 할 객체는 태양시점의 객체들이 아니라.. 카메라 시점의 Cull을 사용했는데 어차피 화면 상에 안보이는 객체들은 없는 걸로 쳐도 될 것 같아서 이렇게 했다. 제일 좋은 방법은 태양에서 행렬로 Cull 하는 것이 정석인 듯 싶다.
이제 이렇게 만들어진 깊이 버퍼를 활용하여 광선을 추적하는 Pass (스크린 쉐이더)로 가져 온다.
다시 이 이미지를 봤을 때 Light 시점 기준 행렬 깊이 버퍼는 만들어졌으므로, 이제 월드 좌표에 있는 광선의 정점을 Light 행렬로 변환하면 된다.이것은 Fragment 쉐이더에서 직접 행렬을 곱해서 월드 -> Light 좌표계로 이동해서 직접 UV 좌표를 구해야 한다. 이 과정에서 Light 시점 기준 광선의 깊이을 알 수 있고, 이미 렌더된 Light 시점 기준 객체의 깊이를 텍스처로 가져올 수 있는데 (UV는 광선을 Light 행렬로 변환하면서 계산한 UV이다.) 이를 읽어와 광선의 깊이가 이미 써진 Light 깊이 버퍼보다 작은 경우 (필자는 어두울 수록 카메라에 가까운 것으로 정의하였다.) 광선이 빛에 더 가까운 것이므로 빛을 받아야 하는 것으로 간주, 해당 픽셀에 색상을 더해주도록 계산하였다.
이렇게 계산을 해주면
이 처럼 나타난다...
자세한 구현 과정은 다음 게시글에서 기록할 예정이다.
댓글 0개
댓글을 작성하는 경우 댓글 처리 방침에 동의하는 것으로 간주됩니다. 댓글을 작성하면 일회용 인증키가 발급되며, 해당 키를 분실하는 경우 댓글을 제거할 수 없습니다. 댓글을 작성하면 사용자 IP가 영구적으로 기록 및 부분 공개됩니다.
확인
Whitmemit 개인 일지 블로그는 개인이 운영하는 정보 공유 공간으로 사용자의 민감한 개인 정보를 직접 요구하거나 요청하지 않습니다. 기본적인 사이트 방문시 처리되는 처리 정보에 대해서는 '사이트 처리 방침'을 참고하십시오. 추가적인 기능의 제공을 위하여 쿠키 정보를 사용하고 있습니다. Whitmemit 에서 처리하는 정보는 식별 용도로 사용되며 기타 글꼴 및 폰트 라이브러리에서 쿠키 정보를 사용할 수 있습니다.
이 자료는 모두 필수 자료로 간주되며, 사이트 이용을 하거나, 탐색하는 경우 동의로 간주합니다.