볼륨 메트릭 포그로 완성하는 어두운 분위기

볼륨 메트릭 포그로 완성하는 어두운 분위기

볼륨 메트릭 포그가 빛과 거리감을 어떻게 바꾸는지 살펴보고 어두운 장면에서 읽기 쉬운 분위기를 만드는 설정과 셰이더 원리를 정리한다.

안개는 화면을 흐리게 만드는 효과가 아니다

어두운 장면에서 안개는 단순한 시야 제한 장치가 아니다. 공기 중의 미세 입자가 빛을 산란시키면서 광원 주변에 부피를 만들고 멀리 있는 물체의 대비를 낮추며 플레이어가 바라볼 깊이를 정한다. 이 성질을 이용하면 적은 광원으로도 폐광, 지하 통로, 비 내리는 숲 같은 공간에 설득력 있는 분위기를 부여할 수 있다.

일반 거리 안개는 카메라와 표면 사이의 거리에 따라 최종 색을 안개색으로 섞는다. 반면 볼륨 메트릭 포그는 카메라 광선을 여러 구간으로 나누고 각 구간에서 빛이 얼마나 흡수되고 산란되는지 누적한다. 따라서 스포트라이트의 광선, 창문으로 들어오는 빛기둥, 물체 뒤의 그림자가 표현될 수 있다.

어두운 복도에서 천장 조명이 볼륨 메트릭 포그를 통해 빛기둥으로 보이는 장면

핵심 원리: 빛을 더하는 만큼 가려진다

카메라에서 표면까지의 거리 dd에서 빛이 통과할 수 있는 비율을 투과율이라고 한다. 매질의 소멸 계수를 σt\sigma_t라고 하면 균일한 안개에서 투과율은 다음처럼 근사할 수 있다.

T(d)=eσtdT(d)=e^{-\sigma_t d}

σt\sigma_t가 커질수록 먼 물체는 빨리 사라진다. 여기에는 빛을 흡수하는 성분과 다른 방향으로 흩뜨리는 성분이 함께 포함된다.

σt=σa+σs\sigma_t=\sigma_a+\sigma_s

최종 화면에는 표면에서 온 빛이 투과율만큼 남고 카메라 광선을 따라 안개가 산란한 빛이 더해진다.

Lfinal=T(d)Lsurface+0dT(s)σsLin(s)p(θ)dsL_{final}=T(d)L_{surface}+\int_0^d T(s)\,\sigma_s\,L_{in}(s)\,p(\theta)\,ds

실시간 렌더링에서는 이 적분을 정확히 풀기보다 화면과 깊이를 격자로 나눈 뒤 격자마다 조명과 그림자를 샘플링해 누적한다. 엔진에서 말하는 froxel은 이 3차원 안개 격자의 한 칸이다.

flowchart LR
    A[카메라 광선] --> B[깊이 방향 격자 분할]
    B --> C[각 격자의 밀도와 색 계산]
    D[광원과 그림자] --> E[격자별 산란광 계산]
    C --> F[광선 방향 누적]
    E --> F
    F --> G[표면 색과 합성]

이 과정 때문에 볼륨 메트릭 포그는 일반 안개보다 비용이 크다. 특히 해상도, 깊이 방향 샘플 수, 그림자 처리, 볼륨에 영향을 주는 광원 수가 성능을 좌우한다.

어두운 분위기를 위한 세 가지 조절점

1. 밀도는 낮게 시작한다

어두운 장면을 만들 때 포그 밀도를 먼저 크게 올리면 화면 전체가 회색 또는 유색으로 들뜨기 쉽다. 먼저 거의 느껴지지 않을 정도의 낮은 밀도에서 시작하고 카메라가 바라보는 거리와 랜드마크가 남는지 확인하며 조금씩 올리는 편이 안전하다.

좋은 기준은 다음과 같다.

  • 가까운 상호작용 대상은 형태와 재질을 구분할 수 있어야 한다.
  • 중거리의 벽, 기둥, 나무는 대비가 줄어들어 깊이를 만들어야 한다.
  • 원거리에는 완전한 검정 대신 미세한 명도 차이를 남겨야 한다.

포그가 어두움을 대신 만들어서는 안 된다. 기본 노출과 광원 밝기로 명암 구조를 만든 뒤 포그로 거리를 분리하는 순서가 좋다.

2. 포그 색은 광원 색보다 덜 채색한다

짙은 파란 안개와 푸른 조명을 함께 사용하면 화면이 단조로워지기 쉽다. 포그 색은 광원보다 채도를 낮추고 약간 중성적인 회청색이나 회갈색으로 두면 광원의 색이 더 또렷하게 드러난다. 따뜻한 안전등이나 횃불을 배치할 때도 이 대비가 중요하다.

낮은 채도의 차가운 포그 속에서 따뜻한 주황색 조명이 강조되는 지하 공간

3. 산란 방향을 의도적으로 선택한다

입자가 어느 방향으로 빛을 잘 산란시키는지는 위상 함수가 결정한다. 실무에서는 Henyey-Greenstein 위상 함수와 비슷한 비등방성 제어를 엔진 옵션으로 다루는 일이 많다.

p(θ)=1g24π(1+g22gcosθ)3/2p(\theta)=\frac{1-g^2}{4\pi\left(1+g^2-2g\cos\theta\right)^{3/2}}

여기서 gg가 양수에 가까울수록 빛이 진행하던 앞쪽으로 산란되는 경향이 강해진다. 카메라를 향해 비추는 스포트라이트가 더 선명한 빛기둥을 만들 수 있지만 값이 과하면 광원 주변이 지나치게 밝고 인공적으로 보인다. 어두운 공포나 잠입 장면에서는 약한 전방 산란부터 시험하는 편이 낫다.

광원 배치로 안개에 리듬 만들기

볼륨 메트릭 포그는 광원이 있는 곳에서 가장 잘 보인다. 따라서 장면 전체에 약한 조명을 넓게 깔기보다 시선 유도와 공간 구분에 필요한 위치에 광원을 배치해야 한다.

스포트라이트는 목적을 하나만 갖게 한다

스포트라이트 하나에 통로 전체 조명, 길 안내, 분위기 연출을 모두 맡기면 빛기둥이 넓고 평평해진다. 아래처럼 역할을 나누면 제어하기 편하다.

  • 주 조명은 플레이 경로와 바닥의 최소 가시성을 확보한다.
  • 연출 조명은 창문, 환풍구, 문틈처럼 빛이 들어올 이유가 있는 곳에 둔다.
  • 유도 조명은 다음 목표 지점이나 안전한 방향을 보여 준다.

광선이 보이게 하려고 아무 곳에나 스포트라이트를 두면 안 된다. 광원의 물리적 또는 서사적 근거가 있어야 플레이어도 그 빛을 자연스럽게 받아들인다.

그림자가 있어야 빛기둥이 읽힌다

볼륨 메트릭 포그에서 그림자는 단지 표면을 어둡게 하지 않는다. 안개 안의 산란광을 끊어 빛의 형태를 만든다. 창살, 나뭇가지, 회전하는 팬 같은 차단물은 빛기둥에 변화와 깊이를 준다.

다만 모든 포인트 라이트에 볼륨 그림자를 켜면 비용이 빠르게 늘어난다. 플레이어 시야에 직접 들어오는 핵심 광원만 우선 적용하고 나머지는 그림자 없는 보조 조명이나 구운 조명으로 처리한다.

Unity에서 확인할 항목

Unity의 설정 명칭은 렌더 파이프라인과 버전에 따라 달라질 수 있지만 HDRP에서는 Volume 시스템과 안개 설정을 중심으로 작업한다. 우선 프로젝트의 HDRP Asset에서 볼륨 메트릭 포그 기능과 필요한 조명 품질이 활성화되어 있는지 확인한다. 그다음 씬의 Global Volume에 Fog 계열 오버라이드를 추가하고 평균 밀도, 색, 최대 거리, 비등방성을 조절한다.

점검 순서는 다음과 같다.

  1. Directional Light 또는 Spot Light 하나만 남겨 광선이 생기는지 확인한다.
  2. 포그 최대 거리를 플레이 공간보다 과도하게 크게 잡지 않는다.
  3. 품질 설정에서 해상도와 깊이 방향 품질을 낮춘 상태로 구도를 먼저 맞춘다.
  4. 그림자가 필요한 소수의 조명만 볼륨 그림자에 참여시킨다.
  5. 카메라 이동 중 격자 경계가 흔들리는지 확인한 뒤 품질과 temporal filtering을 조정한다.

HDRP에서는 로컬 볼륨을 사용해 방 하나나 습한 지대처럼 특정 구역만 더 짙게 만들 수 있다. 전역 밀도를 높이는 대신 로컬 볼륨으로 변화를 주면 장면 전체의 가시성을 잃지 않고도 분위기 전환을 만들 수 있다.

Unreal Engine에서 확인할 항목

Unreal Engine에서는 Exponential Height Fog 액터에서 Volumetric Fog를 켜고 밀도와 높이 분포를 조절하는 흐름이 일반적이다. 광원은 Volumetric Scattering Intensity로 안개에 기여하는 정도를 제어한다. Directional Light와 Spot Light부터 시작해 결과를 확인하는 편이 좋다.

특히 다음 조합이 유용하다.

  • 낮은 Fog Density: 화면 전체의 대비를 보존한다.
  • 제한된 View Distance: 불필요한 원거리 볼륨 계산을 줄인다.
  • 핵심 Spot Light의 높은 산란 기여도: 필요한 빛기둥만 선명하게 만든다.
  • 선택적인 Cast Volumetric Shadow: 창살이나 기둥 뒤에 뚜렷한 어둠을 만든다.

포인트 라이트의 볼륨 그림자는 비용이 큰 편이다. 작은 조명이 많은 실내라면 모든 조명에 적용하기보다 카메라 가까이에서 그림자 형태가 필요한 조명만 골라야 한다.

커스텀 셰이더에서의 최소 구현

엔진 기능을 쓰지 못하는 상황이라면 우선 표면 깊이까지 카메라 광선을 일정 횟수로 샘플링하는 간단한 ray marching으로 원리를 검증할 수 있다. 아래 코드는 개념을 보여 주기 위한 HLSL 형태의 의사 구현이다. 실제 프로젝트에서는 깊이 재구성, 그림자 맵 좌표 변환, 노이즈, temporal reprojection을 렌더 파이프라인에 맞게 추가해야 한다.

float3 IntegrateFog(float3 rayOrigin, float3 rayDir, float maxDistance)
{
    const int stepCount = 32;
    float stepLength = maxDistance / stepCount;
    float transmittance = 1.0;
    float3 inscattering = 0.0;

    for (int i = 0; i < stepCount; ++i)
    {
        float distanceOnRay = (i + 0.5) * stepLength;
        float3 samplePos = rayOrigin + rayDir * distanceOnRay;
        float density = SampleFogDensity(samplePos);
        float3 light = SampleVolumetricLighting(samplePos, rayDir);

        float extinction = density * _Extinction;
        float3 scattering = density * _Scattering * light;

        inscattering += transmittance * scattering * stepLength;
        transmittance *= exp(-extinction * stepLength);
    }

    return inscattering;
}

균일한 간격으로 샘플을 많이 늘리는 것은 가장 단순하지만 비효율적이다. 카메라 가까운 곳은 작은 간격으로 먼 곳은 큰 간격으로 나누거나 저해상도 버퍼와 시간 누적을 사용하면 비용을 줄일 수 있다. 다만 시간 누적은 빠르게 움직이는 카메라나 깜빡이는 조명에서 잔상과 끌림을 만들 수 있으므로 테스트가 필요하다.

자주 생기는 문제와 해결 방향

화면이 우윳빛으로 뜬다

대개 포그 밀도, 포그 알베도, 노출 중 하나가 너무 높다. 먼저 포그 색의 밝기와 채도를 낮추고 밀도를 줄인다. 이후 노출을 조정한다. 검은 안개색을 사용해 억지로 어둡게 만드는 방법은 권하지 않는다. 산란광은 사라져도 표면 대비와 광원 표현이 함께 무너질 수 있다.

빛기둥이 계단처럼 보이거나 흔들린다

깊이 방향 격자 해상도가 부족하거나 temporal filtering이 카메라 움직임을 따라가지 못하는 경우가 많다. 우선 안개 최대 거리를 줄여 같은 품질을 더 가까운 공간에 집중한다. 그다음 깊이 해상도와 시간 누적 설정을 조절한다. 디더링을 적용할 때는 노이즈가 고정되어 보이지 않는지도 확인한다.

작은 광원이 너무 비싸다

원인은 보통 볼륨 그림자를 켠 포인트 라이트가 많기 때문이다. 시야 밖 조명, 장식용 조명, 이미 충분히 밝은 영역의 조명부터 안개 기여도와 그림자를 끈다. 플레이어가 실제로 구분해야 하는 조명만 남긴 뒤 프로파일러로 GPU 시간을 다시 측정한다.

안개가 벽을 뚫고 새어 나온다

저해상도 격자와 깊이 불연속 때문에 생기는 누수는 완전히 없애기 어렵다. 벽 가까이에 강한 국소 광원과 짙은 포그를 겹치지 않게 배치하고 가능하면 볼륨 격자 품질을 높인다. 카메라 각도에 따라만 보이는 문제라면 실제 플레이 카메라의 이동 경로에서 확인해야 한다.

마무리

좋은 볼륨 메트릭 포그는 장면을 무조건 어둡게 만들지 않는다. 가까운 위험과 먼 목표 사이에 대비의 층을 만들고 빛이 존재하는 이유를 보여 주며 플레이어가 공간의 깊이를 느끼게 한다.

낮은 밀도와 적은 수의 핵심 광원으로 시작한 뒤 그림자가 필요한 빛기둥만 선별해 추가하자. 마지막에는 정지 화면뿐 아니라 플레이 중인 카메라 이동, 노출 변화, 성능 측정까지 함께 확인해야 어두운 분위기와 읽기 쉬운 플레이 공간을 동시에 얻을 수 있다.

#volumetric-fog#lighting#unity#unreal-engine#shader

계속 읽어보기

이런 글은 어떠세요?

< Back to Logs