3줄 요약

  1. 그래픽스 엔지니어 찰스 그라시(Charles Grassi)가 2026년 8월 19일 구름의 다중 산란 계산을 작은 신경망으로 대체하는 뉴럴 셰이더(Neural Shader)를 처음부터 구현해 경로 추적 정답과 정량 비교한 글을 공개했다.
  2. 실시간 렌더러가 쓰는 기존 해석적 근사는 태양 방향에 따라 밝기가 정답의 5분의 1(0.208)에서 2배 이상(2.173)까지 달라지지만, 입력 13개로 곱셈-누산을 512번 수행하는 신경망은 태양 각도 13곳 중 12곳에서 정답 밝기와의 차이를 10% 이내(0.904~1.010)로 유지한다.
  3. 텐서 하드웨어 가속을 쓸 수 없는 Unity 6 기본 HLSL 프래그먼트 셰이더에서 스칼라 연산으로 실행했음에도, GPU 시간은 기존 근사보다 약 20%(1080p 기준 약 0.5ms 대 0.6ms) 늘어나는 데 그쳤다.

1. 왜 구름으로 뉴럴 셰이더를 검증하는가

피부의 반사나 금속의 하이라이트, 물의 굴절은 물리 수식으로 유도해 그래픽카드에서 바로 실행할 수 있지만 구름은 사정이 다르다. 구름 내부로 들어간 빛은 물방울 사이에서 수천 번 튕긴 뒤에야 빠져나온다. 이 다중 산란(multiple scattering) 때문에 최종 산란광을 깔끔한 수식으로 적을 수 없다.

정공법은 경로 추적(path tracing)이다. 빛 경로 수백만 개를 하나씩 따라가며 구름을 빠져나올 때까지 시뮬레이션해 평균을 내면 물리적으로 정확한 이미지가 나오지만, 한 프레임을 그리는 데 수 분이 걸려 실시간 게임에는 쓸 수 없다. 실시간 렌더러는 사람이 눈으로 보며 조율한 곡선 몇 개로 다중 산란을 근사하는데, 태양 각도에 따라 밝기가 최대 10배까지 달라진다.

뉴럴 셰이더(Neural Shader)는 셰이더 내부의 복잡한 계산 하나를 수식 대신 학습된 소형 신경망으로 교체하는 기법이다.1 오프라인에서 경로 추적기로 정답 사례 수십만 건을 생성해 신경망을 학습시키면, 실행 시점에는 곱셈-누산(multiply-add) 512번만으로 정답에 가까운 값을 즉시 추론한다. 비용은 텍스처 조회 두어 번과 비슷해서 광선 위의 모든 샘플 지점에서 초당 60프레임으로 실행된다.

구름을 그리는 코드 중 극도로 복잡한 부분은 다중 산란 조명 계산뿐이어서, 조명 함수 호출 하나만 신경망으로 교체하고 다른 조건은 똑같이 유지할 수 있다. 실험에 쓴 구름의 3차원 형태도 약 40줄짜리 밀도 함수 Sample(float3 p) 하나로 정의된다.

  • 바람 밀림(Wind shear): p.xz -= shear * p.y로 고도가 높아질수록 수평 좌표를 바람 방향으로 밀어 기울어진 구름을 만든다.
  • 모루형 퍼짐(Anvil): 상단 고도에서 p.xz /= 1.0 + anvilSpread / radius * t로 질의 좌표를 좁혀 구름 윗부분이 옆으로 퍼지게 한다.
  • 구름 몸통(Body): 여러 구체(lobe)를 부드러운 최댓값(SmoothMax)으로 합쳐 뭉게구름 덩어리를 만든다.
  • 평평한 바닥(Flat bottom): 기준 고도 위쪽 반공간과 SmoothMin으로 교차시켜 적운 특유의 평평한 아랫면을 만든다.
  • 표면 침식(Erosion): 바닥 위쪽부터 프랙탈 노이즈(Fbm)를 더해 평평한 바닥은 유지하면서 표면 디테일을 깎아낸다.

실시간 렌더러는 그리기 전에 이 함수를 복셀 그리드(voxel grid)로 구워서 쓰고 경로 추적기는 같은 함수를 직접 호출하므로, 두 렌더러의 결과를 완전히 똑같은 구름으로 비교할 수 있다.

2. 광선 행진 루프와 신경망이 교체하는 단일 계산

볼류메트릭 구름을 그리는 과정은 시선 광선(ray)을 따라 작은 보폭(stepSize)으로 전진하며 값을 누적하는 광선 행진(ray marching)이다. 약 30줄짜리 RaymarchCloud 루프는 스텝마다 네 단계를 수행하며, 뉴럴 셰이더는 그중 2단계 산란광 계산 함수 호출만 교체한다.2

  1. 밀도 샘플링: 구워 둔 복셀 피라미드에서 구름 밀도를 조회하고(SampleDensity(v, p)), 구간의 투과율 감쇠(exp(-density * stepSize))를 구한다.
  2. 산란광 계산 (유일한 교체 지점): 모든 방향에서 튕겨 카메라로 들어오는 빛 ShadeSample(v, s, p, ray.dir, toSun)을 구한다. 해석적 근사는 순차 텍스처 조회 14번이 필요한 다중 산란 수식을 쓰고, 뉴럴 셰이더는 독립 텍스처 조회 12번과 곱셈-누산 512번으로 구성된 13-16-16-3 신경망을 쓴다.
  3. 스텝 적분: 구간의 투과율 감쇠와 산란광을 곱한다(inScatter * (1 - stepT)).
  4. 투과율과 색상 누적: 앞쪽 구름에 의한 감쇠를 반영해 색상을 누적한다(transmittance *= stepT).

이 예측값(카메라 방향으로 산란되는 빛의 양)을 최종 조명 계산에 넣지 않고 구름 표면에 색으로 직접 칠하면 신경망의 출력을 눈으로 확인할 수 있다.

신경망이 예측한 카메라 방향 산란광의 강도를 구름 표면에 직접 시각화한 디버그 화면 신경망이 예측한 산란광 출력을 구름에 직접 입힌 디버그 화면. 이 화면은 전부 차가운 파란색 쪽이어서 카메라 방향으로 들어오는 산란광이 적은 상태를 보여준다. 출처: Charles Grassi

3. 신경망이 읽는 13개 입력과 미터 단위 반경

뉴럴 셰이더에서 가장 중요한 설계 판단은 신경망에 무엇을 입력으로 보여줄지 정하는 일이다. 신경망은 입력으로 받은 정보보다 더 정확해질 수 없는데, 입력을 하나씩 늘릴 때마다 실시간 실행 비용도 함께 커진다.

구름 조명에서 효과적인 입력은 주변에 구름이 얼마나 있는지 여러 크기(스케일)로 한꺼번에 측정한 값이다. 안개 속에 서서 팔 길이, 방 하나, 골목 하나, 동네 전체 범위의 안개가 얼마나 짙은지 재는 것과 같다. 실제 입력은 13개다. 현재 지점에서 여섯 반경의 밀도를 재고, 태양 방향을 따라가며 여섯 반경의 밀도를 다시 잰 뒤, 시선과 태양이 이루는 각도를 더한다.

구름 내부의 각 지점과 태양 사이에 얼마나 많은 구름이 가로막고 있는지 보여주는 디버그 화면 신경망이 읽는 입력 중 태양 방향 밀도 디스크립터를 시각화한 화면. 구름 깊숙이 파묻힌 지점일수록 직사광이 적게 도달한다. 출처: Charles Grassi

입력 목록에는 구름의 전체 생김새나 덩어리 개수, 구름의 종류에 관한 정보가 전혀 없다. 구름이라는 객체 자체를 설명하는 대신 주변 환경(neighbourhood)만 기술해 스텝당 입력을 숫자 13개로 줄였다.

입력 인덱스측정 대상세부 구성과 역할
[0:6)현재 샘플 지점 주변의 평균 밀도반경 6, 12, 24, 48, 96, 192m에서 측정한 국소 밀도
[6:12)태양 방향으로 이동하며 측정한 평균 밀도반경 6, 12, 24, 48, 96, 192m의 태양 방향 밀도 (가장 지배적인 예측 변수)
[12]시선 방향과 태양 방향 사이 각도의 코사인cos(view, sun)으로 전방과 후방 산란 위상 조건을 전달

여섯 반경을 텍셀(복셀) 단위 대신 미터(m) 단위로 고정한 것이 신경망을 다른 구름에도 쓸 수 있게 만든 핵심 판단이다. 반경을 텍셀 단위로 정의하면 베이킹 해상도나 구름 크기가 바뀔 때마다 입력의 물리적 의미가 달라져 다른 구름에서 오작동한다. 미터 단위로 고정하면 어느 구름에서든 같은 물리적 거리를 뜻한다. 각 반경의 평균 밀도는 미리 생성해 둔 밀도 볼륨의 밉 피라미드(mip pyramid)에서 읽으므로, 192m 반경도 6m 반경처럼 텍스처 조회 한 번이면 된다.

신경망의 출력은 3개다. 태양 직사광 산란에 더해 위쪽 하늘에서 도달하는 빛(sky light)과 아래쪽 지면에서 반사되어 올라오는 빛(ground bounce)도 함께 예측한다. 세 출력이 16유닛짜리 은닉 단계 2개를 공유하므로, 하늘빛과 지면 반사광은 텍스처를 더 조회하지 않고 곱셈-누산을 각각 16번만 더 하면 얻을 수 있다.

구름 내부의 각 지점까지 위쪽 하늘빛이 얼마나 도달하는지 시각화한 디버그 화면 구름 내부 각 지점이 볼 수 있는 하늘의 비율을 나타낸 디버그 화면. 출처: Charles Grassi

4. 제작 4단계와 HLSL 추론 코드

뉴럴 셰이더를 만드는 과정은 네 단계로 이루어진다.

  1. 정답 경로 추적기 작성: 수천 번 빛을 튕기며 평균을 내는 오프라인 경로 추적기를 구현해 학습 데이터 생성과 최종 채점 기준(ground truth)으로 쓴다.
  2. 신경망 입력 정의: 스텝당 숫자 13개(주변과 태양 방향 6개 반경 밀도, 시선과 태양 각도)로 특정 구름 에셋에 종속되지 않는 주변 환경을 표현한다.
  3. 오프라인 학습: Python에서 경로 추적 정답에 맞춰 신경망 가중치를 피팅한다.
  4. 셰이더 내부 추론 실행: 가중치를 셰이더 상수에 올리고 외부 프레임워크 없이 HLSL 프래그먼트 셰이더에서 직접 실행한다.

3단계 학습에서는 샘플 개수보다 학습 샘플을 어디서 뽑는가가 훨씬 중요했다. 처음에는 구름 내부 전체에 학습 지점을 균일하게 배치했다. 그런데 구름 부피의 대부분은 카메라에 보이지 않는 깊숙한 곳이어서, 화면에 나오지 않는 영역을 학습하느라 연산이 낭비됐다. 실제 카메라처럼 구름 외부에서 광선을 쏘아 처음 충돌하는 지점 위주로 학습 샘플을 뽑자, 신경망 구조나 크기를 전혀 바꾸지 않고도 눈에 보이는 밝은 영역의 정확도($R^2$)가 0.34에서 0.70으로 두 배 이상 향상됐다.

치비 서소영이 구름 외부에서 망원경으로 하늘색 광선을 쏘아 구름 표면의 첫 충돌 지점을 샘플링하는 모습

최종 신경망은 입력 13개, 16유닛 은닉 단계 2개, 출력 3개(13-16-16-3)로 총 512번의 가중치 행렬 곱셈-누산 연산을 수행한다. 별도의 추론 프레임워크나 외부 의존성 없이 일반 HLSL 프래그먼트 셰이더에서 두 개의 은닉 단계 언롤([unroll]) 루프와 활성화 함수 하나로 동작한다.

// Squared ReLU: 양수 구간에서 x*x, 0 이하에서는 0을 반환한다.
// 일반 ReLU에 곱셈 한 번을 더한 형태이며 시각적 아티팩트를 막기 위해 선택했다.
float Act(float x)
{
    float r = max(x, 0.0);
    return r * r;
}

float3 CloudNetEvaluate(float descriptor[13])
{
    // 가중치와 함께 저장된 평균(_NetMean)과 스케일(_NetScale)로 입력을 표준화한다.
    float x[13];
    [unroll] for (int i = 0; i < 13; i++)
        x[i] = (descriptor[i] - _NetMean[i]) * _NetScale[i];

    float h0[16], h1[16];

    [unroll] for (int j = 0; j < 16; j++)
    {
        float sum = _NetBias0[j];
        [unroll] for (int i = 0; i < 13; i++) sum += x[i] * _NetW0[j][i];
        h0[j] = Act(sum);
    }

    [unroll] for (int j = 0; j < 16; j++)
    {
        float sum = _NetBias1[j];
        [unroll] for (int i = 0; i < 16; i++) sum += h0[i] * _NetW1[j][i];
        h1[j] = Act(sum);
    }

    // 출력을 제곱해 결과값이 항상 0 이상이 되도록 보장한다.
    float3 y;
    [unroll] for (int k = 0; k < 3; k++)
    {
        float sum = _NetBias2[k];
        [unroll] for (int i = 0; i < 16; i++) sum += h1[i] * _NetW2[k][i];
        y[k] = sum * sum;
    }

    return min(y, _NetClamp);
}
추론 단계입출력 크기곱셈-누산(MAC)
입력 표준화13→1313회(합계 제외)
은닉 1단13→16208회
은닉 2단16→16256회
출력단16→348회

출력단에서 마지막 값을 0으로 자르는 클램프(max(sum, 0)) 대신 제곱(sum * sum)을 쓴 데도 이유가 있다. 클램프는 0 아래에서 기울기(gradient)가 0이 되므로, 학습 도중 출력이 한 번 음수가 된 뉴런은 남은 학습 내내 갱신되지 않고 죽어 버린다. 제곱은 출력을 0 이상으로 유지하면서 기울기도 잃지 않는다.

5. 태양 각도 전 구간 검증: 왜 스크린샷 한 장으로는 정확도를 알 수 없는가

스크린샷 한 장으로는 구름 렌더러의 실제 정확도를 거의 알 수 없다. 수동 조율된 근사 곡선은 특정 조명 각도에 맞춰져 있어서 태양이 이동하면 오차의 크기와 방향이 함께 변한다. 저자는 태양을 카메라 뒤쪽(0도, 순광)부터 구름 뒤쪽(180도, 역광)까지 15도 간격으로 돌리며 13개 각도에서 경로 추적 정답(1.000) 대비 에너지 비율과 픽셀 단위 평균 제곱근 오차(RMSE)를 측정했다.3

태양 각도기존 근사뉴럴 셰이더
0° (순광)0.2080.963
15°0.2270.958
30°0.3090.960
45°0.4100.952
60°0.5090.943
75°0.6020.956
90° (측광)0.6740.957
105°0.7380.932
120°0.7940.904
135°0.8930.939
150°1.0800.937
165°1.4331.010
180° (역광)2.1731.199

기존 해석적 근사로 그린 구름은 순광(0도)에서 정답 밝기의 5분의 1 수준(0.208)으로 어둡다가 역광(180도)에서는 정답의 두 배 이상(2.173)으로 밝아진다. 뉴럴 셰이더는 13개 각도 중 12곳에서 0.904~1.010 범위를 유지한다. 픽셀 단위 평균 제곱근 오차(RMSE)도 순광(0도)에서 3.2968 대 0.8763, 측광(90도)에서 0.6027 대 0.3491, 역광(180도)에서 2.8441 대 1.3775로 세 대표 각도 모두 뉴럴 셰이더가 1.7~3.8배 낮다.

태양이 카메라 뒤에 있는 순광(0도) 조건에서 경로 추적 정답, 기존 해석적 근사, 뉴럴 셰이더를 함께 비교한 화면 순광(0도). 왼쪽부터 정답, 기존 근사(0.208), 뉴럴 셰이더(0.963). 출처: Charles Grassi

기존 근사가 태양 각도에 따라 크게 달라지는 원인은 구름 물방울의 산란 특성에 있다. 구름의 물방울은 빛을 진행 방향(전방)으로는 강하게 산란하고 반대 방향(후방)으로는 매우 약하게 산란한다. 태양이 카메라 뒤에 있는 순광(0도)에서는 빛이 약 180도 되돌아와야 눈에 들어오므로 한 번만 튕겨서 돌아오는 빛은 거의 없다. 순광 구름에서 보이는 빛은 거의 전부 구름 내부에서 수없이 튕긴 다중 산란광이고, 저렴한 수식 근사는 이 빛을 가장 부정확하게 다룬다.

태양이 구름 뒤에 있는 역광(180도)에서는 빛이 직진하기만 하면 되므로 물방울의 강한 전방 산란 특성이 그대로 드러난다. 한 번만 튕기고 그대로 직진하는 빛이 매우 밝고 좁은 피크(forward peak)를 이루는 구간이다. 모든 상황을 고르게 맞히도록 학습된 신경망은 이 좁은 피크에서 약 20% 과대 예측(1.199)하며 가장 큰 오차를 낸다. 기존 근사는 1차 산란 공식을 직접 쓰는 데다 다중 산란 근사치까지 더해져 밝기가 2.173까지 커지고, 구름의 어두운 몸통이 사라진다.

태양이 구름 뒤에 있는 역광(180도) 조건에서 경로 추적 정답, 기존 해석적 근사, 뉴럴 셰이더를 비교한 화면 역광(180도). 왼쪽부터 정답, 기존 근사(2.173), 뉴럴 셰이더(1.199). 출처: Charles Grassi

6. 실행 속도와 툴링의 간극, 그리고 Unity 통합

텍스처 조회 횟수만 놓고 보면 뉴럴 셰이더가 기존 근사보다 유리하다. 기존 해석적 근사는 스텝당 텍스처를 14번(태양 방향 그림자 추적 6번, 상하 주변광 각 4번) 조회하는데, 앞선 조회가 끝나야 다음 조회를 시작하는 순차 실행이다. 뉴럴 셰이더는 12번의 텍스처 조회가 모두 서로 독립적이어서 그래픽카드가 12번을 한꺼번에 요청하므로 대기 시간이 서로 겹쳐 전체 지연이 줄어든다.

실제 GPU 실행 시간을 재면 신경망의 스칼라 연산(스텝당 512 MAC)이 추가되어 뉴럴 셰이더가 약 20% 더 걸린다. 1080p 해상도에서는 구름 렌더링 시간이 1ms 미만이라 측정 오버헤드가 더 크므로, 저자는 4K부터 8K까지 고해상도에서 빈 프레임 시간을 빼는 방식으로 순수 구름 패스 시간을 측정했다.4

측정 해상도기존 해석적 근사뉴럴 셰이더상대 소요 시간 배율
1080p (환산치)약 0.50 ms약 0.60 ms약 1.20x
4K1.88 ms2.32 ms1.23x
5K3.34 ms4.24 ms1.27x
8K7.83 ms9.21 ms1.18x

1080p부터 8K까지 픽셀 수가 16배 늘어나도 뉴럴 셰이더의 추가 비용은 일관되게 약 20%다. 최신 그래픽카드에는 스레드 그룹 전체가 단일 명령어로 소형 행렬-벡터 곱을 처리하는 텐서 하드웨어가 들어 있고, DirectX 12와 Vulkan은 이를 협력 벡터(Cooperative Vectors) API로 제공하며 셰이딩 언어 Slang도 이를 직접 지원한다. 이 API를 쓰면 스텝당 512번의 곱셈-누산 비용은 거의 무시할 수준으로 줄지만, 2026년 현재 Unity의 HLSL에서는 이 하드웨어 가속 경로에 접근할 수 없다. 일반 셰이더 코어에서 스칼라 연산으로 실행하고도 추가 비용이 20%에 그친 셈이다.

Unity 6(URP)에서는 가중치 바이너리를 에셋으로 넣어 로드 시 셰이더 상수에 업로드하고 일반 프래그먼트 셰이더로 실행한다. 주의할 실무 함정은 셰이더 상수 배열의 고정 길이다. 배열 길이는 에디터 세션의 첫 업로드 시점에 정해져서, 에디터가 켜진 채로 신경망 크기를 늘리면 새 슬롯이 0으로 읽혀 구름이 하얀 덩어리로 깨진다. 저자는 배열을 넉넉한 고정 크기로 패딩하고 마지막 슬롯에 보초값(sentinel value)을 넣어, 보초값이 읽히지 않으면 기존 근사 수식으로 폴백하게 방어했다.

7. 구현 과정에서 겪은 네 가지 시행착오와 확장 가능성

개발을 어렵게 만든 원인은 신경망 크기나 데이터 양보다, 에러 없이 조용히 결과물을 망가뜨린 네 가지 실수였다.

  1. 예측 대상의 비선형 변환: 노이즈가 섞인 정답에 세제곱근($\sqrt[3]{x}$)을 씌워 학습하면 변환 전후의 평균이 달라져 추론 뒤 세제곱을 해도 에너지가 손실된다. 변환 없이 원래 값에 바로 피팅하자 절반으로 줄었던 밝기가 즉시 정상으로 복구됐다.
  2. 구간별 선형 활성화 함수의 주름: 기본 ReLU나 Leaky ReLU는 직선 구간을 이어 붙인 형태(2차 차분이 평균 기울기의 4.1배)여서 광선 적분 시 구름 표면에 조개껍데기 모양의 등고선 주름이 생긴다. 양수 구간을 제곱하는 Squared ReLU(max(x,0)^2)를 적용해 곱셈 한 번 추가로 2차 차분을 0.5로 낮추고 주름을 완전히 없앴다.
  3. 동전 던지기 추정의 노이즈: 하늘빛 도달량은 부드럽게 변하지만 개별 광선은 탈출 성공과 실패로 갈리는 베르누이 시행($\sqrt{p(1-p)/n}$)이라 광선 8개에서는 노이즈(0.174)가 신호 변동 폭(0.31)의 절반을 초과한다. 밴드당 광선 수를 64개(노이즈 0.061)로 늘려 얻을 수 있는 상관도 상한을 0.79에서 0.98로 높였다.
  4. 외삽과 일반화의 혼동: 가장 두껍고 큰 마지막 구름을 검증용으로 빼두어 일반화 대신 학습 범위를 초과한 외삽을 측정했다. 크기 분포 한가운데의 구름을 검증용으로 바꾸자 기존 소형 신경망이 그대로 통과했다.

치비 서소영이 각진 자 아래 주름진 구름 모형과 매끄러운 하늘색 포물선 리본이 감싸는 부드러운 구름 모형을 비교하는 모습

구름은 정답 시뮬레이션이 있지만 실시간으로 돌리기에는 너무 느리고, 주변 상황을 숫자 몇 개로 요약할 수 있는 문제의 대표적인 사례다. 이 설계는 다음 다섯 가지 그래픽스 문제에도 똑같이 적용된다.

  • 표면하 산란(피부, 밀랍, 대리석, 우유): 기존에는 눈대중으로 폭을 맞춘 화면 공간 블러(blur)로 근사했으나, 표면 내부 다중 산란 시뮬레이션을 소형 신경망으로 학습해 실시간 추론할 수 있다.
  • 신경망 텍스처 압축: 고정 비율 블록 압축 포맷으로 픽셀을 저장하는 대신 재질을 신경망 가중치로 수 배 작게 압축해 셰이딩 시점에 복원한다.
  • 여러 겹으로 된 복합 재질(자동차 도장, 광택 직물, 진주층): 해석적 로브(lobe) 여러 단과 수동 상수로 근사하던 다단 반사와 산란을 단일 소형 신경망으로 통합한다.
  • 실내 간접광(Indirect Light): 광선 공간 근사나 베이크된 프로브 보간 대신 방 규모 경로 추적기가 계산한 반사광을 프레임 속도에 맞춰 추론한다.
  • 머리카락과 직물: 수작업 셰이더 트릭으로 모사하던 수천 개 섬유 간 다중 산란을 오프라인 시뮬레이션 정답으로 학습해 추론한다.

물론 이번에 구현한 구름 셰이더에도 명확한 적용 한계가 있다. 물방울의 반사율과 전방 산란 강도가 가중치에 고정되어 런타임에 바꿀 수 없고, 하늘 조명을 수평선 위아래 두 밴드로만 단순화해 한쪽만 붉게 물든 석양의 비대칭 디테일은 표현하지 못한다. 180도 정역광에서는 1차 산란의 강한 피크를 정확한 수식으로 직접 계산하고 신경망에는 2차 이후의 다중 산란만 맡기는 편이 낫다. 하늘 전체를 구름으로 채우기 위한 LOD(Level of Detail) 체계도 별도로 갖춰야 한다.

가장 흥미로운 지점

이 실험에서 가장 놀라운 수치는 현재 게임들이 널리 쓰는 실시간 구름 근사식의 밝기가 태양 각도에 따라 정답의 0.21배에서 2.17배까지 달라진다는 실측이다. 신경망이 그 오차를 줄였다는 결과보다, 기존 근사가 그만큼 크게 틀리고 있었다는 점이 먼저 눈에 들어온다. 순광에서 정답 밝기의 5분의 1로 칙칙하게 그려진 구름도 스크린샷 한 장만 보면 그럴듯하다. 경로 추적 정답과 대조하며 태양을 0도부터 180도까지 돌려 봐야 문제가 드러난다.

전통적인 실시간 그래픽스 최적화가 “얼마나 더 그럴듯하게 속일 것인가”를 두고 수식과 상수를 조율하는 일이었다면, 뉴럴 셰이더는 느리더라도 물리적으로 정확한 경로 추적기를 먼저 짜서 기준을 세우고 그 계산 비용을 오프라인 학습에서 미리 치른다. 추론 코드는 은닉 단계 루프 두 개와 곱셈 512번에 불과하다. 이 결과를 믿을 수 있게 만든 것은 조명 계산 하나만 바꾸고 나머지 변인을 통제한 실험 설계, 그리고 태양 각도 전 구간의 계측이었다.

출처

  • 저자: Charles Grassi
  • 발표일: 2026년 8월 19일
  • 원문: Neural Shaders, Explained With Clouds
  • 삽화: 본문의 치비 서소영 삽화 2장은 「느낌적인 느낌을 숫자로 옮기는 일」의 치비 서소영 라인아트를 참조해 gpt-image-2.5-flare로 따로 생성했다.

  1. 셰이더(shader)는 화면의 각 픽셀이 최종적으로 어떤 색상을 띨지 정하는 작은 GPU 프로그램이다. ↩︎

  2. 복셀 해상도로 정하는 보폭, 밴딩 아티팩트를 막는 픽셀 디더링(Dither(pixel)), 거친 밉(coarse mip)이 0일 때 빈 공간을 건너뛰는 스킵 로직(SampleCoarse), 불투명도가 찼을 때 멈추는 조기 종료(transmittance < 0.01)는 해석적 근사 버전과 신경망 버전이 비트 단위로 동일하다. 조명 함수 하나만 교체하는 통제가 실험 전체의 핵심이다. ↩︎

  3. 저자는 공정성을 위해 한 가지 단서를 함께 밝혔다. 비교 대상인 해석적 근사에는 이 구름에 맞춰 다시 피팅하지 않은 기본 상수를 썼다. 상수를 이 구름에 맞춰 다시 조정하면 에너지 곡선 전체를 위로 들어 올릴 수는 있으나, 순광과 역광의 산란 특성이 달라 밝기가 10배까지 차이 나는 문제는 상수 한 벌로 교정할 수 없다. ↩︎

  4. 측정 환경은 노트북용 RTX 4080(12GB, 드라이버 32.0.15.9636), Unity 6000.5.8, Direct3D 12다. 라이브 에디터의 노이즈와 노트북 스로틀링 영향을 배제하기 위해 배치 단위로 단일 GPU 펜스를 걸어 3회 측정 중 최솟값을 택했다. ↩︎