법선
표면이 어느 쪽을 향하고 있는지를 가리키는 방향입니다. 표면 위 한 점을 잡습니다. 그 자리에서 표면에 수직으로 세운 방향이 그 점의 법선입니다. 빛이 표면에 얼마나 닿는지, 어느 쪽으로 튕겨 나가는지는 이 방향에 따라 달라집니다.
상세
울타리를 칠한다고 해 봅시다. 판자는 한 장이지만 칠할 수 있는 쪽은 마당 쪽과 길 쪽으로 둘입니다. 어느 쪽인지 정해 주지 않으면 사람마다 반대쪽을 칠해 놓습니다.
법선은 표면 위 한 점에서 그 표면에 수직인 방향입니다. 평평한 표면은 어디를 짚어도 법선이 같습니다. 굽은 표면은 점마다 향하는 쪽이 조금씩 달라서, 법선도 표면 전체에 하나가 아니라 점마다 따로 있습니다. 크기가 아니라 방향만 뜻합니다. 그래서 보통 길이를 1로 맞춰서 씁니다. 길이를 1로 맞추는 일을 정규화라고 부릅니다. 값으로는 실수 세 개짜리 벡터 하나입니다.
수직인 방향은 앞뒤로 두 개입니다. 표면을 뚫고 나가는 쪽과 안으로 파고드는 쪽입니다. 둘 중 어느 쪽을 법선으로 삼을지는 따로 정해져야 합니다. 그래서 법선에는 부호가 붙습니다. 어느 쪽이 바깥인지 정하지 않으면 같은 표면에 서로 반대인 두 답이 나옵니다.
배경
삼각형을 이어 붙여 만든 모형에는 꼭짓점의 좌표만 적혀 있습니다. 좌표만 놓고는 그 표면이 어느 쪽을 향하는지가 바로 나오지 않습니다. 빛이 표면에 얼마나 비스듬히 닿는지, 어느 쪽으로 튕겨 나가는지는 향하는 쪽에 따라 갈립니다. 향하는 쪽을 모르면 밝기를 계산할 수가 없었습니다.
그래서 향하는 방향을 좌표와 따로 숫자로 들고 다니게 됐습니다. 처음에는 삼각형 하나마다 방향을 하나씩 붙였습니다. 그러면 삼각형 경계에서 밝기가 뚝 끊겨 각진 면이 그대로 드러납니다. 다음에는 꼭짓점 하나마다 방향을 하나씩 붙이는 방법이 나왔습니다. 꼭짓점마다 방향이 조금씩 다르면 삼각형 안쪽 밝기가 이어져서, 각진 면을 이어 붙여 만든 물체도 곡면처럼 보입니다.
이름은 새로 지은 것이 아닙니다. 기하학에서 곡선이나 곡면에 수직인 선을 부르던 이름을 그대로 가져다 씁니다.
갈래
법선이 무엇 하나마다 하나씩 붙느냐가 축입니다.
면 법선
삼각형 하나마다 법선 하나를 둡니다. 삼각형 안쪽 어디를 찍어도 향하는 방향이 같습니다. 그래서 삼각형 경계에서 밝기가 끊기고 면이 각져 보입니다.
flowchart TD
A["삼각형 하나"] --> B["법선 하나"]
B --> C["안쪽 어디를 찍어도 같은 방향"]
B --> D["이웃 삼각형은 자기 법선을 가진다"]
D --> E["경계에서 밝기가 끊긴다"]
glTF(GL Transmission Format) 2.0 명세는 메시에 법선이 적혀 있지 않으면 구현이 평평한 법선을 계산해야 한다고 못 박습니다. 이때 탄젠트가 함께 적혀 있어도 그 값은 무시해야 합니다.
정점 법선
꼭짓점 하나마다 법선 하나를 둡니다. glTF 2.0 명세는 메시 정점 속성의 이름으로 NORMAL 을
정의합니다. 그 값을 정규화된 xyz 정점 법선이라고 적습니다. MDN(Mozilla Developer Network) 의
WebGL(Web Graphics Library) 조명 안내서도 방향광을 넣으려면 꼭짓점마다 표면 법선을 하나씩
딸려 보내야 한다고 적습니다. 그 법선은 그 꼭짓점에서 면에 수직인 벡터입니다.
꼭짓점마다 담긴 방향이 조금씩 다르면 삼각형 안쪽 밝기가 사이값으로 메워집니다. 이웃한 삼각형이 맞닿는 자리에서 같은 방향을 들고 있으면 그 경계에서도 밝기가 이어집니다. 서로 다른 방향을 들고 있으면 그 자리에서 밝기가 갈라집니다.
텍스처에 담은 법선
법선을 꼭짓점보다 더 잘게, 픽셀 단위로 흔드는 방법도 있습니다. glTF 2.0 명세의 재질에는
normalTexture 가 있습니다. 이 텍스처는 접선 공간 법선 벡터의 xyz 성분을 선형 전달 함수로
저장한 RGB(Red-Green-Blue, 빨강·초록·파랑) 값으로 담습니다. 빨강 0.0~1.0 이 x 의 -1~1 로, 초록이 y 로,
파랑 0.5 초과 1.0 이하가 z 의 0 초과 1 이하로 대응합니다. 이 방식 자체는 노멀 맵 쪽에서 다룹니다.
예시
glTF 2.0 의 NORMAL 속성
자산 파일 안에서 법선은 정점 속성 하나로 적힙니다. 명세가 정한 접근자 타입과 성분 타입은 이렇습니다.
NORMAL accessor type: VEC3 component type: FLOAT normalized xyz vertex normals
성분 세 개짜리 실수 벡터라는 것이 여기서 값으로 드러납니다. 실제 메시 프리미티브는 속성 이름에 접근자 번호를 붙여 이렇게 씁니다.
"meshes": [ { "primitives": [ { "attributes": {
"NORMAL": 23, "POSITION": 22, "TANGENT": 24, "TEXCOORD_0": 25
} } ] } ]
23 은 법선 값이 든 접근자의 번호입니다. 좌표와 법선이 서로 다른 접근자에 따로 들어 있습니다.
명세는 조명 계산에 넣기 전에 법선 벡터를 정규화해야 한다고도 적습니다. 재질이 양면으로 표시되면 뒷면 제거가 꺼집니다. 대신 양면 조명이 켜집니다. 이때 뒷면은 법선을 뒤집어야 합니다.
WebGL 튜토리얼의 정점 법선
MDN 의 WebGL 조명 안내서는 정육면체 각 면의 법선을 배열 하나에 그대로 채웁니다.
const vertexNormals = [
// Front
0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0,
// Back
0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0,
];
앞면 네 꼭짓점이 전부 0.0, 0.0, 1.0 입니다. 뒷면은 같은 축에서 부호만 뒤집힌
0.0, 0.0, -1.0 입니다. 실수 세 개가 법선 하나입니다. 마주 보는 두 면은 부호로 갈립니다.
정점 셰이더에서 그 값이 실제로 쓰이는 자리는 이렇습니다.
attribute vec3 aVertexNormal;
uniform mat4 uNormalMatrix;
highp vec4 transformedNormal = uNormalMatrix * vec4(aVertexNormal, 1.0);
highp float directional = max(dot(transformedNormal.xyz, directionalVector), 0.0);
vLighting = ambientLight + (directionalLightColor * directional);
먼저 꼭짓점의 법선에 법선 행렬을 곱해 정육면체의 현재 방향에 맞게 옮깁니다. 그다음 옮긴 법선과 빛이 오는 방향 벡터의 내적을 구해 방향광이 얼마나 걸리는지 계산합니다. 이 값이 0 보다 작으면 0 으로 물립니다. 빛이 0 보다 적을 수는 없기 때문입니다.
Box2D 의 접촉 법선
충돌 계산 쪽에서도 같은 낱말을 씁니다. Box2D 공식 문서는 도형에 광선을 쏘면 처음 만나는 점과 법선 벡터를 얻는다고 적습니다. 다만 원이나 다각형 같은 볼록 도형 안쪽에서 광선이 출발하면 충돌로 잡히지 않습니다.
원과 원, 원과 다각형이면 접촉점과 법선을 하나씩 얻습니다. 다각형끼리면 접촉점이 둘 나옵니다.
이 둘은 같은 법선 벡터를 공유해서, Box2D 는 이를 매니폴드 구조로 묶습니다. b2Manifold 는
법선 벡터 하나와 접촉점 최대 두 개를 담습니다. 각 접촉점은 강체 시뮬레이션에서 계산한 법선
방향 임펄스와 접선 방향 마찰 임펄스를 들고 있습니다.
관련 항목
법선을 이루는 구성 요소
벡터 · 단위 벡터 · 외적 · 내적 · 정규화 · 법선 행렬
법선이 딸리는 기하 요소
정점 · 삼각형 · 폴리곤 메시 · 메시 프리미티브
법선이 저장되는 자료 구조
법선이 접선 공간을 이루는 축
탄젠트 · 바이탄젠트 · 접선 공간
법선이 텍스처로 저장되는 방식
법선을 쓰는 조명 계산
셰이딩 · 조명 · 방향광 · 주변광 · 반사
법선을 다루는 셰이더 단계
법선의 방향이 좌우하는 면 처리
감김 순서 · 뒷면 제거 · 양면 조명
법선이 자리 잡는 그래픽스 영역
법선을 정의하는 표준·문서
법선을 쓰는 충돌 계산
접촉 매니폴드 · 레이캐스트 · 강체 · 볼록 도형 · 임펄스 · Box2D
다른 이름: normal · surface normal · 노멀