호수에 비친 태양은 내가 걸어가면 따라온다
Esc

    호수에 비친 태양은 내가 걸어가면 따라온다

    –렌더링셰이더

    호수에 비친 태양을 보면서 물가를 걸어가 보세요. 그 반짝이는 자리가 나를 따라 움직입니다. 태양도 안 움직였고 호수도 그대로인데, 내가 움직여서 반짝이는 자리가 바뀐 것입니다.

    금속, 젖은 바닥, 광택 나는 플라스틱에 생기는 밝은 점이 전부 이렇습니다. 이걸 담당하는 항이 Specular 입니다.

    하나만 기억하면 이것입니다

    Diffuse 는 보는 방향을 안 쓰고, Specular 는 씁니다.

    두 항이 헷갈리는 이유는 둘 다 “빛을 받으면 밝아진다” 로 요약되기 때문인데, 갈리는 지점은 그 한 줄입니다.

    거친 표면은 들어온 빛을 사방으로 고르게 흩습니다. 어디서 봐도 비슷하게 돌아오니 보는 방향이 필요 없습니다. 매끈한 표면은 거울처럼 한쪽으로만 튕겨 보냅니다. 그 튕겨 나가는 길 위에 내 눈이 있을 때만 밝게 보입니다.

    빛을 고정해 두고 시점만 돌렸습니다. Diffuse 로 칠해진 부분은 한 칸도 안 움직이고 하이라이트만 표면 위를 미끄러집니다. 오른쪽 막대에서 Diffuse 폭이 그대로인 채로 Specular 칸만 커졌다 사라집니다.

    어떻게 계산하나

    들어온 빛이 표면에서 튕겨 나가는 방향을 구하고, 그게 내 시선과 얼마나 가까운지를 봅니다. 이것도 내적입니다.

    float3 R = reflect(-L, N);
    float  spec = pow(saturate(dot(R, V)), shininess);

    V 는 표면에서 카메라 쪽을 가리키는 방향입니다. 반사 방향과 시선이 정확히 겹치면 1, 벌어질수록 0으로 갑니다.

    요즘은 반사 벡터 대신 하프 벡터를 씁니다. 빛 방향과 시선 방향의 한가운데를 가리키는 벡터입니다.

    float3 H = normalize(L + V);
    float  spec = pow(saturate(dot(N, H)), shininess);

    표면이 그 하프 벡터 쪽을 보고 있으면 빛이 정확히 눈으로 튕겨 온다는 뜻입니다. reflect 를 부르는 것보다 싸고, 스치는 각도에서 하이라이트가 뚝 끊기는 문제도 덜합니다. 앞쪽을 퐁, 뒤쪽을 블린퐁이라고 부릅니다.

    거듭제곱이 광택을 만듭니다

    pow 의 지수가 광택도입니다. 내적 결과는 0에서 1 사이라 거듭제곱할수록 빠르게 0으로 떨어집니다. 그래서 지수가 클수록 하이라이트가 좁아집니다.

    4쯤이면 넓고 흐릿하게 퍼져서 고무나 거친 플라스틱처럼 보입니다. 256쯤이면 바늘로 찌른 것처럼 좁아져서 금속이나 유리처럼 보입니다.

    하이라이트는 재질 색을 안 따릅니다

    Diffuse 는 재질 색을 곱하는데 Specular 는 보통 광원 색만 곱합니다.

    파란 플라스틱 공에 흰 빛을 쏘면 몸통은 파랗고 하이라이트는 흽니다. 표면에서 바로 튕겨 나온 빛이라 재질 속으로 들어갔다 나온 게 아니기 때문입니다. 금속은 예외입니다. 금이나 구리의 하이라이트는 금색과 구릿빛을 띱니다.

    왜 항을 나누나

    여기까지 오면 나누는 이유가 보입니다. 재질마다 비중이 다르기 때문입니다.

    재질 Diffuse Specular
    천, 종이, 벽지 크다 거의 없다
    플라스틱 중간 중간 넓이로 조금
    광택 금속 작다 강하고 아주 좁다
    젖은 바닥 중간 강하고 좁다

    계수를 재질마다 다르게 주면 이 질감들이 전부 나옵니다. 하나로 뭉뚱그리면 그 조절이 불가능합니다. 항을 나누는 것 자체가 재질을 표현하는 손잡이를 만드는 일입니다.

    물리 기반이 아닙니다

    여기가 이 모델의 한계가 드러나는 자리입니다.

    pow(dot(N, H), shininess) 에는 물리적 근거가 없습니다. “이렇게 하니까 그럴듯해 보이더라” 에서 나온 식입니다. 그래서 에너지 보존이 안 지켜집니다.

    Diffuse 계수와 Specular 계수를 둘 다 크게 잡으면 표면이 받은 빛보다 많은 빛을 반사합니다. 위 애니메이션에서 더한 값이 1.0 금을 넘어가는 순간이 그겁니다. 실제로는 있을 수 없는 상태입니다.

    당장 화면이 깨지지는 않습니다. 문제는 재질이 조명 환경에 따라 다르게 보인다는 데서 터집니다. 실내에서 잘 맞춰 놓은 재질을 야외에 가져다 놓으면 허옇게 뜹니다. 조명을 바꿀 때마다 재질을 다시 만져야 합니다.

    그래서 PBR 로 갔습니다

    요즘 엔진의 기본 셰이더는 계수 대신 금속성과 거칠기를 파라미터로 둡니다. 거칠기 하나가 하이라이트의 넓이와 세기를 같이 정하고, 반사가 세지면 확산이 그만큼 줄도록 식이 묶여 있습니다. 에너지 보존이 식 안에 들어가 있는 것입니다.

    덕분에 한 번 만든 재질이 어떤 조명 환경에서도 일관되게 보입니다. 아티스트가 “이건 거칠기 0.4짜리 플라스틱” 이라고 정하면 실내든 야외든 그 재질입니다. Unity 의 Standard 셰이더와 URP Lit 이 이쪽입니다.

    이름은 달라졌어도 하는 일은 같습니다. 확산으로 돌아오는 빛과 표면에서 바로 튕기는 빛을 갈라서 계산하고 더합니다. 시선이 뒤쪽에만 들어간다는 구조도 그대로입니다.

    정리

    • Specular 는 매끈한 표면에서 빛이 한쪽으로 튕겨 나가 생기는 밝은 점입니다.
    • 네 항 중 유일하게 보는 방향이 들어갑니다. 카메라가 움직이면 하이라이트가 표면 위를 미끄러집니다. Diffuse 와 갈리는 지점이 이 하나입니다.
    • 반사 벡터와 시선의 내적, 또는 법선과 하프 벡터의 내적을 거듭제곱해서 구합니다. 지수가 클수록 좁고 날카로워집니다.
    • 재질 색이 아니라 광원 색을 곱합니다. 표면에서 바로 튕긴 빛이라서 그렇습니다. 금속은 예외입니다.
    • 항을 나누는 이유는 재질마다 비중이 달라서입니다. 계수를 따로 주면 천부터 금속까지 나옵니다.
    • 이 식은 경험적이라 에너지 보존이 안 됩니다. 그래서 조명 환경이 바뀌면 재질을 다시 만져야 하고, 그 문제를 없앤 것이 PBR 입니다.