원본 : http://www.lighthouse3d.com/opengl/glsl/index.php?ogldir1
OpenGL Programming Guide(일명: Red Book)라는 책에 The Mathematics of Lights 챕터에 본 내용에 나오는 공식이 있다.
먼저 Diffuse 용어부터 시작해보자. OpenGL에서 Diffuse 빛은 관찰자의 위치에 상관없이 일정한 빛의 강도이다.Diffuse는 재질의 Diffuse 반사 계수뿐만이 아니라 빛의 Diffuse 강도에 대해 비례한다. 강도는 빛의 방향과 표면의 법선벡터 사이의 각도에 비례한다.
다음 공식은 OpenGL에서 Diffuse를 계산하는데 사용되는 공식이다.
위의 식에서, I는 반사강도, Ld는 빛의 Diffuse 색상(gl_LightSource[0].diffuse), 그리고 Md는 재질의 Diffuse 계수(gl_FrontMaterial.diffuse)이다.
위의 식은 Lambertian 반사식으로 알려져있다. Lambert의 Cosine 법칙은 평면에 대한 표면의 Diffuse 밝기는 시선과 표면의 법선벡터로 형성되어진 Cosine 각에 비례함을 나타낸다. 이 법칙은 200년전의 이론이다. (Johann Heinrich Lambert, 1728~1777)
버텍스 쉐이더에서 이 공식을 구현하기 위해서는 빛의 속성을 사용할 것이고, 주로 빛의 위치와 Diffuse 강도에 대한 속성이다. 또한 재질에 대한 Diffuse 설정 속성도 이용할 것이다. 따라서 버텍스 쉐이더에서는 OpenGL에서 했던 것처럼 빛을 설정하면된다. 그러나 주의할 것은 고정기능을 사용하지 않을 것이기때문에, 빛을 활성화 시킬 필요는 없다.
Cosine을 계산할 필요가 있으므로, 먼저 우리는 법선벡터와 빛의 방향 벡터(gl_LightSource[0].position)를 정규화할 필요가 있다. 그리고 Cosine 값을 얻기 위해서 내적을 사용할 것이다. Directional Lights에 대해서는, OpenGL이 빛의 방향을 위의 그림에서 보는 것처럼 반대로 하고 있음을 알수 있다.
OpenGL은 빛의 방향을 Eye 공간좌표계로 저장하고 있다; 그러므로 우리는 내적을 계산하기 위해서 법선벡터를 Eye 공간좌표계로 변환해야한다. 법선벡터를 Eye 공간좌표계로 변환하기 위해서는, 미리 정의된 Uniform 변수인 mat3 gl_NormalMatrix를 사용해야할 것이다. 이 행렬은 모델뷰행렬로부터 3×3 좌상단매트릭스의 역전치행렬이다.
다음 버텍스 쉐이더는 위에서 언급한 것들을 위한 버텍스 쉐이더 코드이다.
void main() {
vec3 normal, lightDir;
vec4 diffuse;
float NdotL;
/* 먼저 법선벡터를 Eye 공간좌표계로 변환하고 정규화한다 */
normal = normalize(gl_NormalMatrix * gl_Normal);
/* 이제 빛의 방향 벡터를 정규화한다. OpenGL의 소펙을 보면, 빛은 Eye 공간좌표계이다.
또한 우리가 지금 Directional Light에 대해 이야기하고 있으므로, 빛의 위치 필드는
실제 빛의 방향이다. */
lightDir = normalize(vec3(gl_LightSource[0].position));
/* 법선벡터와 빛의 방향 사이의 각도에 대한 Cos 값을 계산한다. 빛은 Directional이므로
모든 좌표에 대해서 빛의 방향은 일정하다. 이 두벡터는 정규화되었으므로 내적을 구하면된다.
결과는 0~1 범위 사이로 맞춘다. */
NdotL = max(dot(normal, lightDir), 0.0);
/* Diffuse를 계산한다. */
diffuse = gl_FrontMaterial.diffuse * gl_LightSource[0].diffuse;
gl_FrontColor = NdotL * diffuse;
gl_Position = ftransform();
}
이제, 프레그먼트 쉐이더에서 해야할 것이 남았는데, gl_Color Varying 변수를 사용해서 프레그먼트의 색상을 설정해보자.
void main()
{
gl_FragColor = gl_Color;
}
다음 이미지가 주전자 모델에 대해 적용된 결과이다. 주전자의 밑바닦을 보면 매우 어둡게 표현이 되었음을 주목하기 바란다. 이것은 OpenGL에서 Ambient 빛을 아직 적용하지 않았기 때문이다.
Ambient 빛을 통합시키는 것은 어렵지 않다. 아래의 식은 Ambient 빛에 대한 공식이다.
버텍스 쉐이더에서 Ambient를 계산하기 위해서 몇가지 코드를 추가할 필요가 있다.
void main()
{
vec3 normal, lightDir;
vec4 diffuse, ambient, globalAmbient;
float NdotL;
normal = normalize(gl_NormalMatrix * gl_Normal);
lightDir = normalize(vec3(gl_LightSource[0].position));
NdotL = max(dot(normal, lightDir), 0.0);
diffuse = gl_FrontMaterial.diffuse * gl_LightSource[0].diffuse;
/* Compute the ambient and globalAmbient terms */
ambient = gl_FrontMaterial.ambient * gl_LightSource[0].ambient;
globalAmbient = gl_LightModel.ambient * gl_FrontMaterial.ambient;
gl_FrontColor = NdotL * diffuse + globalAmbient + ambient;
gl_Position = ftransform();
}
다음 이미지가 최종 결과이다. Ambient 빛을 추가하자 색상을 씻어내는 듯(밝게하다)하고 있다. 결국 효과가 별로라는 얘기이다.
이제 다음 색선에서 Specular 요소에 대해서 살펴보겠다.
잘 봤습니다. 큰 도움이 됐습니다~. 감사합니다!
이래 저러 경황이 없어 댓글에 대한 감사의 글을 제때 못달고 있네요. 댓글 달린 시간을 보니… 지난 새벽녘에 제가 글 하나 올릴때 다신듯합니다. 어젠 지방에 출장 다녀와 이제서야 확인했습니다.. 다소 늦었지만 댓글 감사드립니다!
손수 답글까지 달아주시다니 정말 감사합니다. 김형준 님의 글을 보면서 끊임없이 정진하겠습니다!