Quick Syntax
// GLSL 330 core, world 공간 계산의 핵심 식
// model: mat4, position/normal: vec3, lightPosition: vec3
vec3 worldPosition = vec3(model * vec4(position, 1.0));
mat3 normalMatrix = transpose(inverse(mat3(model)));
vec3 N = normalize(normalMatrix * normal);
vec3 L = normalize(lightPosition - worldPosition);
float diffuse = max(dot(N, L), 0.0);위치·법선·광원 방향은 같은 공간에서 계산합니다. 모델에 비균일 스케일이 있으면 위치용 행렬을 법선에 그대로 곱하지 않습니다. 위 식은 역행렬이 존재하고 정규화 대상 길이가 0이 아닌 경우입니다.
입력과 전달
정점 데이터에는 위치와 법선이 필요하며 VAO의 위치·타입을 각각 연결합니다. 정점 셰이더에서 world 위치·변환한 법선을 출력하고, 프래그먼트 셰이더에서 법선을 다시 정규화한 뒤 조명을 계산하는 구성이 일반적입니다. 보간된 법선은 단위 길이를 유지하지 않을 수 있습니다.
CPU에서 normal matrix를 미리 계산해 mat3 uniform으로 올리면 정점마다 역행렬을 계산하는 비용을 줄일 수 있습니다. 단순 회전만 있는 행렬과 크기 0으로 퇴화한 변환은 다른 조건입니다. 역행렬이 없는 모델 행렬에서 위 코드를 그대로 사용할 수 있다고 설명하지 않습니다.
정점에서 프래그먼트까지
GLSL 330 core의 최소 연결입니다. 위치와 0이 아닌 법선 배열을 VAO location 0·1에 연결하고, CPU에서 model·viewProjection·normalMatrix를 uniform으로 전달합니다. normalMatrix는 transpose(inverse(mat3(model)))를 CPU에서 미리 계산한 값입니다.
#version 330 core
layout(location = 0) in vec3 position;
layout(location = 1) in vec3 normal;
uniform mat4 model;
uniform mat4 viewProjection;
uniform mat3 normalMatrix;
out vec3 worldPosition;
out vec3 worldNormal;
void main() {
vec4 p = model * vec4(position, 1.0);
worldPosition = p.xyz;
worldNormal = normalMatrix * normal;
gl_Position = viewProjection * p;
}같은 프로그램의 프래그먼트 셰이더는 보간 후의 법선을 정규화합니다. 단순 diffuse를 보여 주는 예이므로 거리 감쇠·ambient·specular는 추가하지 않습니다. albedo와 출력 RGB는 선형 공간 기준입니다.
#version 330 core
in vec3 worldPosition;
in vec3 worldNormal;
uniform vec3 lightPosition;
uniform vec3 albedo;
out vec4 color;
void main() {
vec3 delta = lightPosition - worldPosition;
float distanceSquared = dot(delta, delta);
float normalSquared = dot(worldNormal, worldNormal);
float diffuse = 0.0;
if (distanceSquared > 0.0 && normalSquared > 0.0) {
vec3 N = worldNormal * inversesqrt(normalSquared);
vec3 L = delta * inversesqrt(distanceSquared);
diffuse = max(dot(N, L), 0.0);
}
color = vec4(albedo * diffuse, 1.0);
}위치와 법선만 옮기고 lightPosition을 view 공간 값으로 남기면 서로 다른 공간의 값을 빼게 됩니다. 위 소스에서 N과 L이 같으면 diffuse 1, 수직이면 0, 반대 방향이면 음수를 잘라 0입니다. 이 관계로 식과 공간 정의를 읽을 수 있습니다.
광원과 재질 구분
| 입력 | 의미 | 자주 생기는 오류 |
|---|---|---|
| 위치광원 위치 | 선택한 공간의 점 | 방향처럼 w=0으로 변환 |
| 방향광 벡터 | 빛의 진행 방향 또는 빛을 향한 방향 | 정의한 부호와 dot 식이 반대 |
| 법선 | 표면에 수직인 방향 | 비균일 스케일·보간 뒤 길이 문제 |
| 재질색 | 표면 반사 색 | sRGB 값을 선형 조명식에 그대로 사용 |
| 카메라 위치 | 반사·specular 계산의 기준 | world와 view 공간 혼합 |
기본 diffuse 예제에 ambient·specular를 추가할 때도 정의를 명확히 합니다. distance가 0인 점광원에서 방향 정규화와 감쇠의 분모 처리를 고려합니다. 화면이 너무 어둡다고 모든 값을 임의로 크게 하기보다 공간·법선·색 공간을 먼저 확인합니다.
고정 기능에서 옮기는 역할
1.0 계열의 조명·재질·현재 색 상태를 사용하던 코드는 Core의 glEnable(GL_LIGHTING)으로 복구할 수 없습니다. 광원·재질 파라미터를 CPU 자료와 uniform으로 만들고 계산을 GLSL에 작성합니다. 구형 광원 위치는 설정 시 행렬 상태에 영향을 받으므로 그 숫자만 복사해 world 위치라고 해석하지 않습니다.
구형 fog 역시 현대 셰이더에서 거리·깊이에 따른 색 혼합으로 표현할 수 있습니다. 다만 fog의 좌표 기준과 조명 적용 순서를 정의해야 합니다. GL 2.0의 셰이더 도입과 GLSL 330 예제의 지원 버전은 별개입니다.
범위 경계
이 예제는 광원·법선의 입력 계약을 설명합니다. 물리 기반 BRDF, 탄젠트 공간 노멀맵, 그림자 필터 전체를 포함하지 않습니다. 각각 조명 모델, 법선·탄젠트, 그림자 매핑으로 연결합니다.
참고 링크
3 sources