법선 하나가 밝기를 정한다
삼각형 하나를 화면에 그리면 위치와 모양은 정해집니다. 그런데 그 삼각형이 어떤 밝기로 칠해질지는 위치만으로는 안 정해집니다. 같은 자리에 있는 벽도 빛을 정면으로 받으면 밝고 비스듬히 받으면 어둡습니다.
그 밝기를 정하는 값이 법선입니다.
법선은 “이 면이 어느 쪽을 보는가” 입니다
법선(normal)은 면에 수직으로 서 있는 화살표입니다. 길이는 1로 맞춰 둡니다. 길이는 쓸 일이 없고 방향만 필요해서입니다.
중요한 건 법선이 위치가 아니라 방향이라는 점입니다. 책상 위의 법선은 천장을 봅니다. 벽의 법선은 방 안쪽을 봅니다. 같은 벽이면 어느 자리든 같은 방향입니다.
왜 각도가 밝기를 정하나
손전등을 벽에 정면으로 비추면 작고 밝은 원이 생깁니다. 같은 손전등을 비스듬히 비추면 그 빛이 길쭉하게 늘어나면서 어두워집니다.
빛의 양은 그대로인데 퍼지는 면적이 넓어져서 단위 면적이 받는 양이 줄어든 것입니다. 램버트 코사인 법칙이라고 부르는 게 이겁니다.
퍼지는 정도는 면이 빛을 향해 얼마나 돌아서 있는지, 즉 법선과 빛 방향 사이의 각도로 정해집니다.
각도를 곱셈 한 번으로
각도를 재서 코사인을 구하는 대신, 두 단위 벡터의 내적을 씁니다. 단위 벡터끼리의 내적이 곧 사잇각의 코사인이라서 결과가 같고 훨씬 쌉니다. 곱셈 세 번과 덧셈 두 번이면 끝납니다.
float ndotl = saturate(dot(N, L));
float3 color = albedo * lightColor * ndotl;
N 이 법선, L 이 그 자리에서 광원 쪽을 가리키는 방향입니다.
정면으로 받으면 1, 90도로 스치면 0, 빛을 등지면 음수가 나옵니다.
음수는 “빛을 뺀다” 는 뜻이 되어 버리므로 saturate 로 0에서 자릅니다.
아래에서 빛이 좌우로 움직입니다. 위쪽 띠가 화면에 칠해지는 결과이고, 아래가 그 면을 옆에서 자른 모습입니다. 파란 화살표가 법선입니다.
면은 가만히 있는데 빛의 각도가 바뀌는 것만으로 띠 전체가 같이 밝아지고 어두워집니다. 법선이 모든 자리에서 똑같이 위를 보고 있어서 밝기도 자리마다 같습니다.
법선은 정점에 들어 있습니다
메시는 정점마다 좌표, UV, 그리고 법선을 들고 있습니다. 픽셀 하나하나가 자기 법선을 갖고 있는 게 아닙니다.
래스터라이저가 삼각형을 픽셀로 쪼갤 때 세 정점의 법선을 무게중심 좌표로 섞어서 각 픽셀에 넘겨줍니다. 이 보간 덕분에 각진 저폴리 구체도 둥글게 보입니다. 실제 모양은 각졌는데 법선이 부드럽게 변하니까요.
여기서 이 이야기의 핵심이 나옵니다.
밝기를 정하는 건 실제 모양이 아니라 법선입니다. 조명 계산은 면이 진짜로 어떻게 생겼는지 묻지 않습니다. 이 자리의 법선이 어디를 보느냐만 묻습니다.
각진 구체가 둥글어 보이는 것도 같은 원리입니다. 모양은 그대로 두고 법선만 거짓말을 시킨 것입니다.
정점이 부족하면 거기서 막힙니다
문제는 법선이 정점에 붙어 있다는 점입니다. 한 삼각형 안에서 법선이 자리마다 다르게 꺾이려면 그 자리마다 정점이 있어야 합니다.
벽돌 벽의 줄눈 하나를 법선으로 표현하려면 줄눈 하나마다 정점이 여러 개 필요합니다. 벽 한 장에 정점이 수만 개가 됩니다. 정점이 늘면 정점 셰이더 호출, 메모리, 전송량이 전부 같이 늡니다.
정리
- 법선은 면이 어느 쪽을 보는지를 가리키는 길이 1짜리 방향 벡터입니다.
- 밝기는 법선과 빛 방향의 내적에서 나옵니다. 정면이면 1, 스치면 0, 등지면 음수라 0으로 자릅니다.
- 각도로 밝기가 갈리는 이유는 같은 양의 빛이 비스듬할수록 넓게 퍼지기 때문입니다.
- 법선은 정점에 저장되고 픽셀에는 보간된 값이 갑니다. 각진 구체가 둥글어 보이는 게 이 보간 덕분입니다.
- 조명 계산은 진짜 모양을 모릅니다. 법선만 봅니다. 그래서 법선이 세밀해지려면 정점이 그만큼 많아야 합니다.