paiams

Go Back

Gaussian Splatting point-light shadow


CrossGaussian의 실시간 360° 영상은 카메라가 설치된 한 지점에서 현장 전체를 보여 준다. 하지만 가구나 벽에 가려진 곳은 확인할 수 없고, 사용자가 시점을 옮겨 그 뒤를 살펴볼 수도 없다. 함께 제공하는 3D Gaussian Splatting 장면에서는 자유롭게 이동할 수 있으므로, 원격 사용자가 라이브 영상에서 보이지 않는 영역을 찾아 탐색할 수 있어야 했다.

여기서 포인트 라이트 그림자를 가시성 판정에 활용했다. 360° 카메라와 포인트 라이트는 한 지점에서 모든 방향을 본다는 구조가 같다. 카메라 위치를 라이트의 위치로 두면 카메라에서 직접 보이는 splat은 빛을 받고, 다른 물체 뒤에 가려진 splat은 그림자 영역에 놓인다. 이 결과로 라이브 영상의 사각지대를 구분하고 원격 사용자에게 더 살펴볼 영역을 알려 줄 수 있다.

기존 UnityGaussianSplatting은 조명과 그림자를 지원하지 않아 360° 카메라 위치를 기준으로 가시 영역을 계산하는 경로가 없었다. 나는 Aras Pranckevičius의 UnityGaussianSplatting을 바탕으로 URP용 포인트 라이트 그림자 생성과 적용 과정을 구현했다. 기존 렌더러의 에셋 불러오기와 Gaussian 데이터 구조, 정렬, 기본 렌더링은 그대로 사용하고 그림자에 필요한 렌더링 경로를 확장했다.

Gaussian splat 그림자 생성

일반적인 메시 그림자는 빛의 시점에서 삼각형을 다시 그려 깊이를 기록한다. Gaussian splat은 화면에 투영한 타원을 겹쳐 장면을 표현하기 때문에 메시용 그림자 패스를 그대로 사용할 수 없었다. 빛의 위치에서도 각 splat의 중심과 형태를 타원으로 다시 투영하고, Gaussian 모양이 남는 픽셀의 깊이를 기록해야 했다.

포인트 라이트는 모든 방향으로 빛을 내므로 라이트를 중심으로 여섯 방향을 렌더링했다. 각 방향의 깊이는 하나의 그림자 큐브맵에 저장했다. 메인 렌더링에서는 현재 픽셀과 라이트 사이의 방향으로 큐브맵을 읽어 그림자 여부를 판단했다.

여섯 면을 만들 때 모든 계산을 반복할 필요는 없었다. splat의 월드 좌표와 3차원 공분산, 불투명도처럼 시점과 무관한 값은 한 번만 계산했다. 이후 각 큐브맵 면에서 달라지는 중심 위치와 화면상의 타원 축만 다시 구했다. 초기에는 여섯 장의 2D 텍스처를 따로 관리했지만, 이를 하나의 RenderTexture 큐브맵으로 합쳐 면별 텍스처 연결과 선택 로직도 줄였다.

그림자 반전과 좌표계

가장 오래 걸린 문제는 좌표계였다. C#에서 만든 뷰·투영 행렬을 Compute Shader에 그대로 넘기면 큐브맵의 일부 면에서 그림자 방향이 뒤집히거나 엉뚱한 위치에 나타났다. CPU에서 준비한 행렬과 GPU 렌더링이 따르는 좌표계 규칙이 맞지 않았기 때문이다.

각 큐브맵 면의 뷰 행렬을 GPU가 사용하는 구조에 맞춰 다시 구성하고 투영 행렬에는 GL.GetGPUProjectionMatrix()를 적용했다. 그림자 깊이를 비교할 때는 _LightZBufferParams와 LinearEyeDepth를 사용했다. Unity가 reversed Z를 사용하는 환경에서도 저장된 깊이를 같은 방식으로 해석하기 위해서였다.

CrossGaussian 적용

완성한 그림자 경로는 CrossGaussian에서 360° 카메라의 사각지대를 표시하고 원격 사용자를 탐색 가능한 영역으로 안내하는 데 사용했다. 아래 데모에서는 Unity의 포인트 라이트를 움직일 때 3DGS 장면의 가시 영역과 그림자가 함께 바뀐다.

Unity에서 포인트 라이트를 움직이며 Gaussian Splatting 장면의 그림자를 확인하는 모습

CrossGaussian에 적용한 포인트 라이트 그림자 데모.

현재 한계

현재 공개 구현은 경계가 단단한 그림자(hard shadow)까지만 지원한다. PCF나 PCSS를 이용한 부드러운 그림자, 큐브맵 면별 culling과 LOD, 그림자 해상도 자동 조절은 구현하지 않았다.

코드에는 구간별 Profiler marker가 있지만 splat 수와 큐브맵 해상도, GPU를 고정해 반복 측정한 성능 자료는 없다. 공통 계산 분리와 큐브맵 통합으로 중복 계산과 자원 관리를 줄였지만, 이를 성능 향상 수치로 설명하지는 않는다. 다시 작업한다면 먼저 조건별 GPU 시간을 측정하고 면별 culling과 그림자 필터링을 각각 적용해 효과를 비교하겠다.

링크