목표

고러드, 램버트, (빌린)퐁 셰이딩 이해하기

들어가며

안녕하세요. 여러분. 오늘은 셰이딩 종류에 대해 이야기 해보려고 합니다.

셰이더는 3D 모델에 빛을 비추고, 재질감을 부여하며, 그림자를 드리워 입체적인 그래픽을 구현하는 핵심 기술입니다. 그 중에서도 기본적으로 알고 있어야할 세 가지 셰이더에 대해 알아봅시다.

Lambert

국소 조명 모델, 확산광(Diffuse)만을 고려하는 셰이더

Lamber1

빛과 표면의 법선 벡터 사이의 각도가 0도일 때 가장 밝으며, 90도일 때 가장 어둡습니다.

특징은 다음과 같습니다:

  • 입체감 있는 조명 표현은 가능, 그러나 하이라이트 효과는 X
  • 매우 효율적인 셰이딩 기법으로 다른 셰이딩과 함께 조합되어 사용
  • 계산이 매우 간단하여 성능에 부담이 적습니다.
1
2
3
4
5
float3 LambertShading(float3 normal, float3 lightDir, float3 lightColor, float3 surfaceColor)
{
float NdotL = max(dot(normal, lightDir), 0.0f);
return surfaceColor * lightColor * NdotL;
}

Lamber2

  • Directx11에서 제작한 램버트 셰이더입니다.

Goraud

국소 조명 모델, 정점에서 빛 계산을 수행하고 선형 보간을 통한 색상을 정하는 셰이더

Goraud1

고러드 셰이딩은 램버트 셰이더의 단점을 보완하기 위해 처음 등장했습니다. 삼각형의 각 꼭짓점에서의 밝기 값을 구한 다음, 그 사이의 픽셀들은 이 밝기 값으로 부드럽게 연결하여 표현합니다.

특징은 다음과 같습니다:

  • Vertex Shader에서 계산을 진행합니다.
  • 계산 비용이 낮습니다.
  • 픽셀 단위의 조명 정확도가 낮습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 해당 코드의 계산은 퐁 셰이딩을 이용했습니다.
VSOutput output;

float3 worldPos = mul(float4(input.Position, 1.0f), WorldMatrix).xyz;
float3 normal = normalize(mul(float4(input.Normal, 0.0f), WorldMatrix).xyz);

float3 toLight = normalize(-LightDirection);
float3 toView = normalize(CameraPosition - worldPos);
float3 reflectDir = reflect(-toLight, normal); // 반사벡터

float diffuse = max(dot(normal, toLight), 0.0f);
float specular = pow(max(dot(reflectDir, toView), 0.0f), Shininess);

float4 finalColor = AmbientColor +
(diffuse * LightColor) +
(specular * SpecularColor);

output.Position = mul(float4(input.Position, 1.0f), WorldViewProjMatrix);
output.Color = saturate(finalColor);

return output;

Goraud1

  • Directx11에서 제작한 고러드 셰이더입니다.

BlinnPong

국소 조명 모델, 확산광, 하이라이트, 주변광까지 함께 고려하여 빛을 계산하는 셰이더

phong1

현재 가장 널리 사용되는 셰이더 중 하나로, 퐁(Phong) 셰이더를 개선한 버전

하이라이트 계산 시 기존의 라이트 벡터와 반사 벡터의 노말 벡터가 아닌, 라이트 벡터와 시점 벡터의 중간 벡터를 사용하여 더 효율적으로 하이라이트를 표현합니다.

특징은 다음과 같습니다:

  • 물체의 재질감을 매우 사실적으로 표현
  • 카메라 각도에 덜 민감하여 하이라이트와 반짝임을 자연스럽게 구현
  • 다른 두 셰이더에 비해 연산량이 많음
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

Lighting OUT;
if (light.diffusePower > 0)
{
float3 lightDir = light.position - pos3D; //3D position in space of the surface
float distance = length(lightDir);
lightDir = lightDir / distance; // = normalize(lightDir);
distance = distance * distance;

//Intensity of the diffuse light. Saturate to keep within the 0-1 range.
float NdotL = dot(normal, lightDir);
float diffuseIntensity = saturate(NdotL);

// Calculate the diffuse light factoring in light color, power and the attenuation
OUT.Diffuse = diffuseIntensity * light.diffuseColor * light.diffusePower / distance;

//Calculate the half vector between the light vector and the view vector.
float3 H = normalize(lightDir + viewDir);

//Intensity of the specular light
float NdotH = dot(normal, H);
float specularIntensity = pow(saturate(NdotH), specularHardness);

//Sum up the specular light factoring
OUT.Specular = specularIntensity * light.specularColor * light.specularPower / distance;
}
return OUT;

phong2

  • Directx11에서 제작한 빌린퐁 셰이더입니다.

마무리

이처럼 고러드, 램버트, 빌린퐁 셰이더는 각각 다른 계산 방식과 특징을 가지고 있습니다.
사실 셰이더 종류에 대해 알아보는 글이라 더 자세히는 적지 않았습니다. 더 자세한 내용은 GPT 혹은 전문 칼럼을 참고하는 것도 좋은 방법이라고 생각합니다.

마지막으로 세 가지 셰이더를 보여드리면서 끝내겠습니다.

goraud-f
lambert-f
phong-f