GLSL 예제 – 툰 쉐이딩(Toon Shading) – 2장(총4장)
원문 : http://www.lighthouse3d.com/opengl/glsl/index.php?toon1
이 장에서 보일 첫번째 툰 쉐이딩은 버텍스에 대한 명도를 계산한다. 다음으로 프레그먼트 쉐이더는 프레그먼트에 대한 톤을 계산하기 위해 버텍스에 대한 보간된 명도를 사용한다. 그래서 버텍스 쉐이더는 반드시 명도를 저장하는 변수를 Varying으로 선언해야 한다. 프레그먼트 쉐이더는 같은 변수를 선언해야하며 Varying 지정자를 사용하여 버텍스 쉐이더에서 계산된 명도값을 받을 수 있다.
빛의 방향은 지역변수나 상수로써 버텍스 쉐이더 안에 정의될 수 있다. 그러나 Uniform 변수로써 정의되면 융통성이 좋은데, OpenGL 어플리케이션에서 자유롭게 설정할 수 있기 때문이다. 빛의 방향에 대한 변수는 다음과 같이 쉐이더 안에서 정의된다.
uniform vec3 lightDir;
지금부터는, 빛의 방향을 World 공간에서 정의되었다고 가정하겠다.
버텍스 쉐이더는 OpenGL 어플리케이션에서 지정된 법선벡터에 접근할 수 있는데, gl_Normal이라는 Attribute 변수를 통해 가능하다. 이 법선벡터는 OpenGL 어플리케이션에서 glNormal 함수를 통해 정의한 것이며 Local 공간에서 정의된다.
만약 OpenGL 어플리케이션에서 모델에 대해 회전이나 크기조정이 수행되지 않는다면, World 공간에서 정의된 법선벡터는 gl_Normal 변수를 통해 버텍스 쉐이더에 제공되며 Local 공간에서 정의된 법선벡터와 일치한다. 법선 벡터는 방향벡터이므로 이동에는 영향을 받지 않는다. 법선벡터와 빛의 방향 모두 같은 공간에서 지정되므로, 버텍스 쉐이더는빛의 방향 사이의 각(밫의 방향벡터와 법선벡터 사이 각)에 대한 코사인(cos) 계산으로 바로 계산할 수 있다. 코사인(cos)은 다음 공식을 사용해 계산할 수 있다.
cos(lightDir, normal) = (lightDir dot-product normal) / ( |lightDir| * |normal| )
법선벡터(normal)과 빛의방향벡터(lightDir)가 단위벡터라면 위의 공식은 다음처럼 간단하게 된다.
cos(빛과 법선벡터의 사이각) = lightDir dot-product normal
lightDir 변수는 OpenGL 어플리케이션에서 제공받으므로, 이 변수는 이미 단위벡터라고 가정할 수 있다. 이런 가정이 가능하다면 모든 버텍스에 대해 매번 단위벡터화를 해주지 않고도, 단지 lightDir 변수가 바뀔때 단 한번만 단위벡터로 바꾸면 된다.
GLSL에서 제공하는 dot 함수를 사용해서 intensity라는 이름의 변수에 위에서 설명한 값을 저장한다.
intensity = dot(lightDir, gl_Normal);
이제 버텍스 쉐이더 부분에서 마지막으로 해야할 것은 버텍스 좌표로 변환하는 것이다. 아래가 완성된 코드이다.
uniform vec3 lightDir;
varying float intensity;
void main()
{
intensity = dot(lightDir, gl_Normal);
gl_Position = ftransform();
}
이제, 프레그먼트 쉐이더에서 해야할 것은 intensity에 기반해서 프레그먼트의 색을 지정하는 것이다. intensity는 반드시 프레그먼트 쉐이더로 넘겨줘야하는데, 프레그먼트의 색상을 설정할 책임이 바로 프레그먼트 쉐이더에 있기 때문이다. 이전에 언급했듯이 intensity는 버텍스 쉐이더나 프레그먼트 쉐이더 모두에서 Varying 지정자로 정의된다. Varying 변수는 버텍스 쉐이더에서 설정되고 난뒤에 프레그먼트 쉐이더에서 읽혀진다.
색상은 다음처럼 프레그먼트 쉐이더에서 계산되어진다.
vec4 color;
if(intensity > 0.95)
color = vec4(1.0, 0.5, 0.5, 1.0);
else if(intensity > 0.5)
color = vec4(0.6, 0.3, 0.3, 1.0);
else if(intensity > 0.25)
color = vec4(0.4, 0.2, 0.2, 1.0);
else
color = vec4(0.2, 0.1, 0.1, 1.0);
위의 코드를 보면, 코사인값(intensity)이 0.95보다 크면 가장 밝은 색상으로 지정하고 0.25보다 작으면 가장 어두운 색상으로 지정하고 있다. 프레그먼트 쉐이더에서 해야할 것은 color 변수수를 기반으로 해서 gl_FragColor를 설정하는 것이다. 프레그먼트 쉐이더에 대한 전체 코드는 다음과 같다.
varying float intensity;
void main()
{
vec4 color;
if(intensity > 0.95)
color = vec4(1.0, 0.5, 0.5, 1.0);
else if(intensity > 0.5)
color = vec4(0.6, 0.3, 0.3, 1.0);
else if(intensity > 0.25)
color = vec4(0.2, 0.1, 0.1, 1.0);
gl_FragColor = color;
}
아래의 이미지가 최종 결과이다. 근데 그다지 멋있지는 않은것 같은데, 어떻게 생각하는가? 좀더 근사한 툰 쉐이딩을 연출하기 위해서는 intensity 계산(보간) 방법을 개선해야 한다. 이에 대해서는 다음 섹션에서 다루도록 하겠다.