호수에 비친 태양은 내가 걸어가면 따라온다
호수에 비친 태양을 보면서 물가를 걸어가 보세요. 그 반짝이는 자리가 나를 따라 움직입니다. 태양도 안 움직였고 호수도 그대로인데, 내가 움직여서 반짝이는 자리가 바뀐 것입니다.
금속, 젖은 바닥, 광택 나는 플라스틱에 생기는 밝은 점이 전부 이렇습니다. 이걸 담당하는 항이 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 입니다.