'게임 프로그래밍'에 해당되는 글 58건

  1. 2013.09.06 OpenGL Shader - 32
  2. 2013.09.06 OpenGL Shader - 31
  3. 2013.09.06 OpenGL Shader - 30
  4. 2013.09.06 OpenGL Shader - 29
  5. 2013.09.06 OpenGL Shader - 28
  6. 2013.09.06 OpenGL Shader - 27
  7. 2013.09.06 OpenGL Shader - 26
  8. 2013.09.06 OpenGL Shader - 25
  9. 2013.09.06 OpenGL Shader - 24
  10. 2013.09.06 OpenGL Shader - 23
2013.09.06 12:15
GLSL 예제 : Texture(MultiTexture) - 3/3

원문 : http://www.lighthouse3d.com/opengl/glsl/index.php?textureMulti

멀티 텍스쳐링은 GLSL에서는 정말 쉽다. 해야할 것은 두개의 텍스쳐에 접근하는 일이 전부다. 그리고 이 장에서는 동일한 텍스쳐 좌표를 사용할 것이기 때문에 버텍스 쉐이더를 재작성하지 않고 이전 강좌의 것을 그대로 사용한다. 프레그먼트 쉐이더 또한 두개의 텍스쳐 색상을 추가하는 최소한의 변화만 있다.

varying vec3 lightDir,normal;
uniform sampler2D tex,l3d;

void main()
{
    vec3 ct,cf;
    vec4 texel;
    float intensity,at,af;
		
    intensity = max(dot(lightDir,normalize(normal)),0.0);
	
    cf = intensity * (gl_FrontMaterial.diffuse).rgb + 
         gl_FrontMaterial.ambient.rgb;
    af = gl_FrontMaterial.diffuse.a;
	
    texel = texture2D(tex,gl_TexCoord[0].st)+
            texture2D(l3d,gl_TexCoord[0].st);

    ct = texel.rgb;
    at = texel.a;
	
    gl_FragColor = vec4(ct * cf, at * af);	
}

그리고 이제, 약간 다른 특별한 것을 해보자: Glow in the Drak Effect. 이 특별한 효과는 두번째 텍스쳐를 어둠속에서 타오르는듯하게 보이는 것이다. 예를들어, 완전이 밝아졌다가 어두워지는 듯한 효과.

두 단계로 최종 색상을 다시 계산해야 하는데, 먼저 첫번째 텍스쳐를 프레그먼트 색상으로 조정된 상태의 색상을 계산하고, 이후에 두번째 텍스쳐 유닛을 강도에 기반해서 추가하는 것이다.

intensity가 0이라면, 두번째 텍스쳐가 전체적으로 완전하게 표시된다. 반대로 intensity가 1이라면, 두번째 텍스쳐를 10% 정도만 표시되도록 한다. 이런 표시를 위해 smoothStep라는 함수를 통해 구현할 것이다. 이 함수의 시그니쳐는 다음과 같다.
genType smoothStep(genType edge0, genType edge1, genType x);
만약 x<=edge0이면 결과는 0이고,  x>=edge1이면 1 그리고 edge0< x <edge1이면 Hermit 보간 연산이 수행된다. 우리의 경우 다음 함수를 호출한다.
coef = smoothStep(1.0, 0.2, intensity);
다음은 프레그먼트 쉐이더 코드이다.
varying vec3 lightDir,normal;
uniform sampler2D tex,l3d;

void main()
{
    vec3 ct,cf,c;
    vec4 texel;
    float intensity,at,af,a;
	
    intensity = max(dot(lightDir,normalize(normal)),0.0);
	
    cf = intensity * (gl_FrontMaterial.diffuse).rgb + 
             gl_FrontMaterial.ambient.rgb;
    af = gl_FrontMaterial.diffuse.a;
	
    texel = texture2D(tex,gl_TexCoord[0].st);
		
    ct = texel.rgb;
    at = texel.a;
	
    c = cf * ct;
    a = af * at;
		
    float coef = smoothstep(1.0,0.2,intensity);
    c += coef *  vec3(texture2D(l3d,gl_TexCoord[0].st));
	
    gl_FragColor = vec4(c, a);	
}

출처 : http://www.gisdeveloper.co.kr/332



신고
Posted by 우엉 여왕님!! ghostkyow
2013.09.06 12:14
GLSL 예제 : Texture(Combine Texture + Fragment) - 2/3

원문 : http://www.lighthouse3d.com/opengl/glsl/index.php?dirlightpix

OpenGL은 다양한 방법으로 텍스쳐 색상과 프레그먼트 생상을 혼합하는 방법을 제공한다.  바로 아래의 내용은 RGBA 모드를 위한 몇가지 모드이다.

GL_REPLACE      C = Ct                           A = At 
GL_MODULATE     C = Ct*Cf                        A = At*Af 
GL_DECAL        C = Cf * (1 - At) + Ct * At      A = Af

Ct와 At는 텍스쳐의 색상과 알파값이고, Cf와 Af는 프레그먼트의 색상과 알파값이며 C와 A는 최종적인 색상과 알파값이다.

이전 섹션에서 제공되었던 예제는 GL_REPLACE의 경우의 예였다. 여기서는 정육면체에 GL_MODULATE의 내용을 구현해보자. 쉐이더들은 하얀색의 Diffuse Directional Light를 가지고 Diffuse와 Ambient 요소만을 계산한다. 재질에 대한 완전한 정의는 Lighting(광원) 섹션을 살펴보길 바란다.

빛을 사용하기 때문에, 그래서 법선벡터에 대해,  버텍스 쉐이더는 몇가지 작업을 해야한다. 주로 법선벡터를 카메라 좌표계로 변환하고 정규화하는 일이며 빛의 방향에 대해서도 정규화를 해야한다. (빛의 방향은 이미 OpenGL에 의해서 카메라 좌표계로 변환되어졌다). 지금 단계에서 버텍스 쉐이더는 다음과 같다.

varying vec3 lightDir,normal;
	
void main()
{
    normal = normalize(gl_NormalMatrix * gl_Normal);
    lightDir = normalize(vec3(gl_LightSource[0].position));
		
    gl_TexCoord[0] = gl_MultiTexCoord0;
    gl_Position = ftransform();
}

프레그먼트 쉐이더에서, 프레그먼트의 색상과 알파값은 각각 cf와 af에 계산되어진다. 위에서 언급한 GL_MODULATE 공식을 계산하는 나머지 쉐이더 코드는 다음과 같다.

varying vec3 lightDir,normal;
uniform sampler2D tex;
	
void main()
{
    vec3 ct,cf;
    vec4 texel;
    float intensity,at,af;
		
    intensity = max(dot(lightDir,normalize(normal)),0.0);
		
    cf = intensity * (gl_FrontMaterial.diffuse).rgb + 
              gl_FrontMaterial.ambient.rgb;
    af = gl_FrontMaterial.diffuse.a;
		
    texel = texture2D(tex,gl_TexCoord[0].st);
    ct = texel.rgb;
    at = texel.a;
		
    gl_FragColor = vec4(ct * cf, at * af);	
}




신고
Posted by 우엉 여왕님!! ghostkyow
2013.09.06 12:14
GLSL 예제 : Texture(Simple Texture) - 1/3

원문 : http://www.lighthouse3d.com/opengl/glsl/index.php?dirlightpix

GLSL에서 텍스쳐링 연산을 하기 위해서, 버텍스에 대한 텍스쳐 좌표에 접근해야 한다. GLSL은  각 텍스쳐 유닛 각각에 대한 Attribute 변수들을 제공한다.

attribute vec4 gl_MultiTexCoord0;
attribute vec4 gl_MultiTexCoord1;
attribute vec4 gl_MultiTexCoord2;
attribute vec4 gl_MultiTexCoord3;
attribute vec4 gl_MultiTexCoord4;
attribute vec4 gl_MultiTexCoord5;
attribute vec4 gl_MultiTexCoord6;
attribute vec4 gl_MultiTexCoord7;

또한 GLSL은 uniform 변수 배열 형태로 각 텍스쳐에 대한 텍스쳐 행렬에 접근할 수 있다.

uniform mat4 gl_TextureMatrix[gl_MaxTextureCoords];

버텍스 쉐이더는 OpenGL 어플리케이션에서 지정한 텍스쳐 좌표 등을 얻을 수 있다. 버텍스에 대한 텍스쳐 좌표를 계산을 해야하고, 계산후에 미리 정의된 varying 변수인 gl_TexCoord[i] 변수에 계산된 텍스쳐 좌표를 저장하는데, 여기서 i는 텍스쳐 유닛에 대한 인덱스이다.

텍스쳐에 대한 텍스쳐 좌표를 설정하는 간단한 버텍스 쉐이더는 아래와 같다. 사용하는 텍스쳐 유닛은 0이다.

void main() {
    gl_TexCoord[0] = gl_MultiTexCoord0;
    gl_Position = ftransform();
}

만약 텍스쳐 행렬을 사용하길 원한다면 아래와 같다.

void main() {
    gl_TexCoord[0] = gl_TextureMatrix[0] * gl_MultiTexCoord0;
    gl_Position = ftransform();
}

gl_TexCoord가 Varying 변수라고 언급했었는데, 예를들어서 이 gl_TexCoord는 프레그먼트 쉐이더에서 계산된 텍스쳐 좌표를 접근하는데 사용될 수 있다.

텍스쳐 값에 접근하기 위해서, 프레그먼트 쉐이더에서는 특별한 타입의 변수을 선언할 필요가 있다. 2D 텍스쳐에 대해서는 다음과 같다.

uniform sampler2D tex;

1D와 3D 텍스쳐에 대한 데이터 타입도 가능한데, 일반적인 포멧은 sampler_i_D이며 _i_에 해당하는 것이 텍스쳐의 차원이다. 사용하고자 하는 텍스쳐 유닛을 포함하는 tex 변수를 선언한다. 텍셀(텍스쳐 이미지의 화소 색상)을 제공하는 함수는 texture2D이다. 이 함수는 sampler2D와 텍스쳐의 좌표를 인자로 받으며 텍셀값을 반환한다. 아래에 texture2D 함수에 대한 시그니쳐가 있다.

vec4 texture2D(sampler2D, vec2);

반환값은 OpenGL 어플리케이션에서 설정된 모든 텍스쳐 설정값을 고려해서 계산되어진 값인데, 예를들어서 필터링, 밉맵, 클램프 등이다. 프레그먼트 쉐이더에서는 아래처럼 작성할 수 있다.

uniform sampler2D tex;
	
void main()
{
    vec4 color = texture2D(tex,gl_TexCoord[0].st);
    gl_FragColor = color;
}

gl_TexCoord에 접근할때 선택자 st의 사용에 주의하라. GLSL의 데이터 타입과 변수에 관해 앞서 언급했던것처럼, 텍스쳐 좌표에 접근할때는 선택자는 s,t,p,q가 될 수 있다. r은 rgb 선택자들과 충돌하기 때문에 사용하지 않는다.



신고
Posted by 우엉 여왕님!! ghostkyow
2013.09.06 12:13
GLSL 예제 : Lighting(Spot Light Per Pixel) - 6/6

원문 : http://www.lighthouse3d.com/opengl/glsl/index.php?dirlightpix

이 강좌는 이전 강좌에서 제공되는 코드의  90% 이상의 내용에 기반하고 있다. Point Light와 비교해 Spot Light에서 유일하게 차이점은, Point Light는 모든 방향으로 빛을 방사하는데 반해서 Spot Light는 제한 영역에만 빛을 받는다는 점이다.

OpenGL 어플리케이션의 관점에서 다음과 같은 차이들이 있다.

  • Spot Light는 방향을 가지고 있으며(spotDirection) 이 방향은 Spot Light의 Cone의 축이다.
  • Cone의 각도가 있다. GLSL은 어플리케이션에서 지정되어진 것으로써와 spotCosCutoff라는 cosine 변수로써, 이둘 모두를 제공한다.
  • 마지막으로 감쇄 비율이 있다(spotExponent). 예를들어서, 빛의 강도가 Cone의 원 모양의 밑바닥의 중심점으로부터 어떤식으로 감소하는지를 측정하는 것이다.

버텍스 쉐이더는 이전 강좌의 Point Light와 동일하고 프레그먼트 쉐이더에서 몇가지 변경이 있다. Diffuse, Specular 그리고 Ambient 요소는 오직 빛의 Cone의 안쪽에서 프레그먼트에만 영향을 미친다. 그러므로 우리가 먼저 해야할 첫번째것은 Cone 안쪽의 프레그먼트의 검사이다.

버텍스 벡터에서 빛 사이의 Cosine 각도와 Spot 방향은 반드시 spotCosCutoff보다 넓어야 한다. 만약 그렇지 않으면, 프레그먼트는 Cone의 바깓에 있고 단지 전역 Ambient 요소의 광원 효과만 받게 된다.

    ...

    n = normalize(normal);

    /* compute the dot product between normal and ldir */
    NdotL = max(dot(n,normalize(lightDir)),0.0);

    if (NdotL > 0.0) {
        spotEffect = dot(normalize(gl_LightSource[0].spotDirection), 
                         normalize(-lightDir));
        if (spotEffect > gl_LightSource[0].spotCosCutoff) {
	
            /* compute the illumination in here */

        }
    }
	
gl_FragColor = ...

조도의 계산은 Point Light의 경우에 매우 많이 비슷한데, 유일한 차이점은 감쇄값은 아래의 공식의 Spot Light의 효과(spotEffect)와 곱해져야 한다는 것이다.

spotDirection은 빛의 상태로부터 얻어진 값이며, lightDir은 광원으로부터 버텍스까지의 벡터이며, spotExp는 Spot의 감쇄비율이다. 이것 역시 OpenGL의 상태로부터 가져오며, 빛들의 강도가 Cone의 중심으로부터 Cone의 경계까지 어떻게 점점 감쇄하는지를 조정한다. 값이 크면 클수록 더 빨리 빛의 강도가 감쇄하며, Zero(0)의 의미는 빛의 Cone 안에서 일정한 빛의 강도를 의미한다.

spotEffect = pow(spotEffect, gl_LightSource[0].spotExponent);
att = spotEffect / (gl_LightSource[0].constantAttenuation +
      gl_LightSource[0].linearAttenuation * dist +
      gl_LightSource[0].quadraticAttenuation * dist * dist);

color += att * (diffuse * NdotL + ambient);

halfV = normalize(halfVector);
NdotHV = max(dot(n,halfV),0.0);
color += att * gl_FrontMaterial.specular * 
         gl_LightSource[0].specular * 
         pow(NdotHV,gl_FrontMaterial.shininess);
아래의 이미지는 OpenGL의 고정기능의 결과와 우리의 Spot Light Per Pixel에 대한 결과이다. OpenGL의 고정기능은 버텍스에 대해서 빛의 효과를 계산하는데 반해 우리의 Spot Light는 픽셀(프레그먼트)에 대해 빛의 효과를 계산한다.


신고
Posted by 우엉 여왕님!! ghostkyow
2013.09.06 12:12
GLSL 예제 : Lighting(Point Light Per Pixel) - 5/6

원문 : http://www.lighthouse3d.com/opengl/glsl/index.php?dirlightpix

이 튜터리얼은 Directional Lights의 코드의 99% 상당 부분에 기반하고 있다. 이 튜토리얼은 Directional Light와 Point Light의 차이점에 대해 기반한다. Directional Light는 무한이 멀리 있다고 가정되므로 빛에 대한 광선이 모든 물체에 도달할때 평행하다.  반면에, Point Light는 위치를 가지고 있으며 모든 방향에서 광선을 보낸다. 더욱이 Point Light에서는, 버텍스와의 거리가 멀어질 수 록 빛의 강도가 감쇄한다.

OpenGL 어플리케이션의 관점으로부터, Directional Light와 Point Light 사이에는 두가지 차이점이 있다.

  • 광원의 위치 필드중 w 요소 : Directional Light에서는 이 값은 0이지만 Point Light에서는 1이다.
  • 빛의 감쇄는 세개의 계수에 기반한다 : 상수항, 1차항, 2차항

계산식 관점에서 이들 차이를 살펴봐야한다. Directional Light의 경우, 빛의 광선의 방향은 모든 버텍스에 대해서 일정하지만, Point Light의 경우, 빛의 위치에서 시작해서 각 버텍스까지의 벡터이다. 그러므로 버텍스 쉐이더에서 변경해야할 것은 빛의 방향을 계산하는 것이다. 빛의 감쇄는 OpenGL에서 다음 공식에 기반하여 계산된다.

k0은 상수항 감쇄이고, k1은 1차항 감쇄, k2는 2차항감쇄이며 d는 빛의 위치와 버텍스 사이의 거리이다. 


주의할것은, 빛의 감쇄 모델은 거리에 따른 선형회귀가 아니라는 점이다. 그래서 버텍스에 대해 감쇄를 계산할 수 없으며 프레그먼트 쉐이더에서 보간된 값을 사용할 수 없다. 그러나 우리는 버텍스 쉐이더에서 거리를 계산할 수 있고 프레그먼트 쉐이더에서 보간된 거리를 사용해 감쇄를 계산할 수 있다. 

Point Light에 대한 색상에 대한 공식은 다음과 같다.

위의 공식을 보면, ambient항은 반드시 2개로 나눠야 한다: 광원 모델 Ambient 설정을 이용한 전역 Ambient 항과 빛에 대한 일반 Ambient 항. 버텍스 쉐이더는 정확하게 Ambient 항의 계산을 구분해야 한다. 아래는 새로운 버텍스 쉐이더에 대한 코드이다.
varying vec4 diffuse,ambientGlobal,ambient;
varying vec3 normal,lightDir,halfVector;
varying float dist;
	
void main()
{	
    vec4 ecPos;
    vec3 aux;
		
    normal = normalize(gl_NormalMatrix * gl_Normal);
		
    /* these are the new lines of code to compute the light's direction */
    ecPos = gl_ModelViewMatrix * gl_Vertex;
    aux = vec3(gl_LightSource[0].position-ecPos);
    lightDir = normalize(aux);
    dist = length(aux);
	
    halfVector = normalize(gl_LightSource[0].halfVector.xyz);
		
    /* Compute the diffuse, ambient and globalAmbient terms */
    diffuse = gl_FrontMaterial.diffuse * gl_LightSource[0].diffuse;
		
    /* The ambient terms have been separated since one of them */
    /* suffers attenuation */
    ambient = gl_FrontMaterial.ambient * gl_LightSource[0].ambient;
    ambientGlobal = gl_LightModel.ambient * gl_FrontMaterial.ambient;
	
    gl_Position = ftransform();
}
프래그먼트 쉐이더는 감쇄를 계산해야 한다. 해석된 빛의 방향을 정규화도 해야해야 하는데, 방향은 모든 버텍스에 대해서 다르기 때문이다.
varying vec4 diffuse,ambientGlobal, ambient;
varying vec3 normal,lightDir,halfVector;
varying float dist;	
	
void main()
{
    vec3 n,halfV,viewV,ldir;
    float NdotL,NdotHV;
    vec4 color = ambientGlobal;
    float att;

    /* a fragment shader can't write a varying variable, hence we need
       a new variable to store the normalized interpolated normal */
    n = normalize(normal);

    /* compute the dot product between normal and normalized lightdir */
    NdotL = max(dot(n,normalize(lightDir)),0.0);
	
    if (NdotL > 0.0) {
        att = 1.0 / (gl_LightSource[0].constantAttenuation +
              gl_LightSource[0].linearAttenuation * dist +
              gl_LightSource[0].quadraticAttenuation * dist * dist);
        color += att * (diffuse * NdotL + ambient);
		
        halfV = normalize(halfVector);
        NdotHV = max(dot(n,halfV),0.0);
        color += att * gl_FrontMaterial.specular * 
                 gl_LightSource[0].specular * 
                 pow(NdotHV,gl_FrontMaterial.shininess);
    }

    gl_FragColor = color;
}

아래의 이미지는 Point Light와 OpenGL의 고정 기능에 의해 계산된 빛에 대한 차이를 보여준다. 하나는 버텍스에 대한 광원이고 다른 하나는 이 튜터리얼의 쉐이더를 이용한 픽셀에 대한 광원이다.

출처 : http://www.gisdeveloper.co.kr/328


신고
Posted by 우엉 여왕님!! ghostkyow
2013.09.06 12:09
GLSL 예제 : Lighting(Directional Light Per Pixel) - 3/6

원문 : http://www.lighthouse3d.com/opengl/glsl/index.php?dirlightpix

이 션섹에서는 이전 셕센에서의 쉐이더를 Directional Light를 픽셀 마다 계산하도록 수정할 것이다.

먼저 버텍스 당 우리가 받는 정보를 살펴보면, ...

  • 법선벡터
  • Half 벡터
  • 빛의 방향

법선벡터를 카메라 공간 좌표계로 변환하고 정규화해야한다. 또한 이미 카메라 공간 좌표계인 Half 벡터와 빛의 방향 벡터 역시 정규화해야 한다. 이들 정규화된 벡터는 보간되어질 것이고 프레그먼트 쉐이더로 보내지는데, 이를 위해서 정규화된 벡터를 유지하기 위해서 varying 변수를 선언할 필요가 있다.

버텍스 쉐이더에서는 광원설정값과 재질을 조합하는 몇가지 연산을 수행할 수 있다. 

아래는 버텍스 쉐이더의 코드이다.

varying vec4 diffuse,ambient;
varying vec3 normal,lightDir,halfVector;
	
void main()
{	
    /* first transform the normal into eye space and 
    normalize the result */
    normal = normalize(gl_NormalMatrix * gl_Normal);
		
    /* now normalize the light's direction. Note that 
       according to the OpenGL specification, the light 
       is stored in eye space. Also since we're talking about 
       a directional light, the position field is actually direction */
    lightDir = normalize(vec3(gl_LightSource[0].position));
	
    /* Normalize the halfVector to pass it to the fragment shader */
    halfVector = normalize(gl_LightSource[0].halfVector.xyz);
					
    /* Compute the diffuse, ambient and globalAmbient terms */
    diffuse = gl_FrontMaterial.diffuse * gl_LightSource[0].diffuse;
    ambient = gl_FrontMaterial.ambient * gl_LightSource[0].ambient;
    ambient += gl_LightModel.ambient * gl_FrontMaterial.ambient;
	
    gl_Position = ftransform();
}

이제 프레그먼트 쉐이더에 대해서 살펴보자. 동일한 Varying 변수가 선언되어야 한다. 법선벡터를 다시 정규화해야한다. 하지만 빛의 방향벡터는 다시 정규화할 필요가 없다. 우리는 Directional Light에 대해 이야기 하고 있으므로 이 마지막 벡터는 모든 버텍스에 공통이다(쌩뚱맞은 말같은데....... =_=). 두개의 동일한 벡터 사이의 보간에 대한 결과는 같은 벡터이므로, 다시 정규화할 필요가 없는 것이다. 다음으로 우리는 보간되고 정규화된 법선벡터와 빛의 방향 벡터를 내적계산한다. 아래가 여기서 언급한 프레그먼트 쉐이더에 대한 시작 부분에 대한 코드이다.

varying vec4 diffuse,ambient;
varying vec3 normal,lightDir,halfVector;

void main()
{
    vec3 n,halfV;
    float NdotL,NdotHV;
		
    /* The ambient term will always be present */
    vec4 color = ambient;
		
    /* a fragment shader can't write a varying variable, hence we need
       a new variable to store the normalized interpolated normal */
    n = normalize(normal);
		
    /* compute the dot product between normal and ldir */
    NdotL = max(dot(n,lightDir),0.0);
	
    ....
}

만약 NdotL이 0보다 크다면, Diffuse 요소를 계산해야 하는데, 버텍스 쉐이더로부터 받은 Diffuse 설정값은 내적값으로 곱해진다. Specular 요소도 반드시 계산해야 한다. Specular 요소를 계산하기 위해서는 먼저 버텍스 쉐이더로부터 받은 halfVector를 정규화해야하고, halfVector와 normal 간의 내적 계산을 한다.

    ....

    if (NdotL > 0.0) {
        color += diffuse * NdotL;
        halfV = normalize(halfVector);
        NdotHV = max(dot(n,halfV),0.0);
        color += gl_FrontMaterial.specular * 
        gl_LightSource[0].specular * 
        pow(NdotHV, gl_FrontMaterial.shininess);
    }
	
    gl_FragColor = color;
}


다음 이미지는 Per Pixel과 Per Vertex 광원에 대한 결과화면이다.



출처 : http://www.gisdeveloper.co.kr/326

신고
Posted by 우엉 여왕님!! ghostkyow
2013.09.06 12:08
GLSL 예제 : Lighting(Directional Lights 2) - 3/6

원문 : http://www.lighthouse3d.com/opengl/glsl/index.php?ogldir2

OpenGL의 Directional 빛에 대한 Specular 요소에 대한 시간이다. 사용한 광원공식 모델은 Blinn-Phong 모델로써, 이 모델은 Phong 모델을 간략화한 것이다.

Phong 모델은 Specular 요소는 빛의 반사벡터와 시선벡터 사이의 cosine에 비례함을 나타낸다.



L은 광원에서 버텍스까지로 형성된 벡터이다. N은 법선벡터이고 Eye는 버텍스로부터 눈(카메라)까지의 벡터이다. R은 벡터 L이 표면에 반사되어진 벡터다. Specular 요소는 각 alpha의 consine에 비례한다.

만약 눈(카메라) 벡터(시선벡터)가 반사 벡터와 일치한다면, 최대의 강도를 갖는 Specular를 얻을것이다. 시선벡터가 반사벡터로부터 갈라지면, Specular 강도는 쇠퇴한다. 쇠퇴정도는 Shininess 값으로 조절할 수 있다. 더 높은 Shininess 값은 더 빠르게 Specular를 감퇴시킨다. 이것의 의미는 높은 Shininess를 가진 것이 더 낮은 Shininess를 가진것보다 밝기스팟의 크기가 작아짐을 의미한다. OpenGL에서 Shininess는 0~128 사이의 값이고 밝기스팟의 크기를 조절하는 값이라고 생각하면 쉽다.

반사 벡터에 대한 공식은 다음과 같다.


그리고 Phong 모델식을 사용하는 OpenGL에서 Specular 요소는 다음과 같다.


지수 s가 Shininess 값이고 Ls는 빛의 Specular 강도이며, Ms는 재질에 대한 Specular 계수이다.

Blinn은 좀더 빠르고 이해하기 쉬운 Phong 모델을 간략화해서 제안하였는데, 그것이 바로 Blinn-Phong 모델이며 Half-Vector에 기반한다. Half-Vector는 시선벡터와 광원벡터 사이의 각을 반으로 하여 고려한다는 것으로 다음 그림을 보면 이해가 쉬울것이다.


Specular 요소의 강도는 법선벡터와 H 벡터 사이의 Cosine 값에 의존한다. Half-Vector에 대한 공식은 반사벡터에 대한 것보다 훨씬 간단하다.


그리고 Blinn-Phong 모델을 사용하는 OpenGL에서의 Specualr 요소 값은 다음과 같다.



이것이 실제 그래픽스 하드웨어의 고정기능에서 일반적으로 사용되는 식이다. 우리는 지금 OpenGL의 Directional 빛을 모방하길 원하므로, 쉐이더에서 이 마지막 공식을 사용한다. 여기에 좋은 소식이 있는데, OpenGL은 우리를 위해 Half-Vector를 계산해 준다는 것이다! 아래 코드를 살펴보길 바란다.
/* compute the specular term if NdotL is larger than zero */
if (NdotL > 0.0) {
    // normalize the half-vector, and then compute the 
    // cosine (dot product) with the normal
    NdotHV = max(dot(normal, gl_LightSource[0].halfVector.xyz),0.0);
    specular = gl_FrontMaterial.specular * gl_LightSource[0].specular * 
        pow(NdotHV,gl_FrontMaterial.shininess);
}
출처 : http://www.gisdeveloper.co.kr/322


신고
Posted by 우엉 여왕님!! ghostkyow
2013.09.06 12:05
GLSL 예제 : Lighting(Directional Lights 1) - 2/6

원본 : 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를 사용해야할 것이다. 이 행렬은 모델뷰행렬로부터 3x3 좌상단매트릭스의 역전치행렬이다.

다음 버텍스 쉐이더는 위에서 언급한 것들을 위한 버텍스 쉐이더 코드이다.
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 요소에 대해서 살펴보겠다.



신고
Posted by 우엉 여왕님!! ghostkyow
2013.09.06 12:02

GLSL 예제 : Lighting(광원) - 1/6
원문 : http://www.lighthouse3d.com/opengl/glsl/index.php?lights

OpenGL에는 세가지 종류의 빛이 있습니다: Directional, Point, Spotlight. 이 장에서는 Directional 광원을 구현하는 것으로 시작해 보겠다. 먼저 GLSL을 이용해서 OpenGL의 광원효과를 모방해 보겠다.

우리는 Ambient 빛으로 시작해서 GLSL을 점진적으로 발전시켜 Specular 빛까지 구현해보겠다.  



다음으로 좀더 나은 결과를 제공하는 Lighting Per Pixel을 구현해보겠다.

그리고 이 다음으로는, Point와 Spot Lighting Per Pixel을 구현해보겠다. 빛에 대한 총 6개의 장은 Directional Lights에 관련된 장에서의 코드의 내용을 공통적으로 사용할 것이다.  


툰쉐이더에서 언급했듯이, GLSL은 광원 설정에 대한 데이터를 포함하는 OpenGL 상태값에 접근할 수 있다. 이 데이터는 광원 설정에 대한 세세한 내용을 담고 있는 구조체형식의 전역변수이다.
struct gl_LightSourceParameters {
    vec4 ambient;
    vec4 diffuse;
    vec4 specular;
    vec4 position;
    vec4 halfVector;
    vec3 spotDirection;
    float spotExponent;
    float spotCutoff; // (range: [0.0, 90.0], 180.0)
    float spotCosCutoff; // (range: [1.0, 0.0], -1.0)
    float constantAttenuation;
    float linearAttenuation;
    float quadraticAttenuation;
};

uniform gl_LightSourceParameters gl_LightSource[gl_MaxLights];

struct gl_LightModelParameters {
    vec4 ambient;
};

uniform gl_LightModelParameters gl_LightModel;
재질 속성도 역시 GLSL에서 접근할 수 있다.
struct gl_MaterialParameters {
    vec4 emission;
    vec4 ambient;
    vec4 diffuse;
    vec4 specular;
    float shiness;
};

uniform gl_MaterialParameters gl_FrontMaterial;
uniform gl_MaterialParameters gl_BackMaterial;
광원과 재질에 대한 이 파라메터들의 대부분의 사용은 OpenGL 어플리케이션에서 사용하는 것과 유사하다. 이제 우리는 이들 속성을 사용해서 Directional 광원을 구현해 볼 것이다.
출처 : http://www.gisdeveloper.co.kr/315


신고
Posted by 우엉 여왕님!! ghostkyow
2013.09.06 12:01

GLSL 예제 - 툰쉐이딩 마지막 장.. (4/4)
원본 : http://www.lighthouse3d.com/opengl/glsl/index.php?toon3

툰쉐이딩을 끝내기 전에 한가지 더 살펴보자 : lightDir 변수를 사용하는 대신에 OpenGL 빛을 사용한 것. OpenGL에서 빛을 하나 정의하고 이 빛의 방향을 쉐이더에서 사용하는 방법이다. 주의: glEnable을 사용해서 빛을 활성화할 필요가 없는데, OpenGL에서 이 빛을 실제로 사용하지는 않을 것이기 때문이다.

우리는 OpenGL에서 첫번째 빛(GL_LIGHT0)이 Directional 빛이라고 가정하겠다.

GLSL은 OpenGL 상태의 일부에 접근할 수 있는데, 바로 빛과 같은 속성에 접근할 수 있다. GLSL은 빛의 속성에 대한 C언어 형식의 구조체를 정의하고 있는데 각 빛에 대한 속성을 정의하기 위한 배열로 존재한다.

struct gl_LightSourceParameters {
    vec4 ambient;
    vec4 diffuse;
    vec4 specular;
    vec4 position;
};

uniform gl_LightSourceParamters gl_LightSource[gl_MaxLights];

위의 구조체와 변수를 이용해서 버텍스 쉐이더에서 빛의 방향을 얻어낼 수 있는데, 구조체의 필드중에 position을 이용하면 된다. 여기서 다시 우리는 OpenGL 어플리케이션에서 빛의 방향 벡터가 정규화되었다고 가장하겠다.

OpenGL은 스펙상 빛의 위치가 지정되면 이 위치 좌표가 자동으로 눈 공간 좌표계(eye space coordinate), 예를 들어서 카메라 좌표계로 바뀐다. 우리는 좌표체계가 바뀌어도 빛의 위치가 정규화된 상태로 유지된다고 가정할 수 있다. 이 가정은 모델뷰해열의 좌측상단의 3x3 부분의 행렬이 직교일때 옳다(만약 gluLookAt함수를 사용하고, 어플리케이션에서 좌표계의 크기조정을 하지 않았다면 확실히 옳다).

우리는 법선벡터를 눈 공간 좌표계(카메라 좌표계)로 변환해야 하며, 빛의 방향벡터와 법선벡터 사이의 각을 계산하기 위해 내적 계산을 해야 한다. 

법선벡터를 카메라 좌표계로  변환하기 위해서는 미리 정의된 Uniform  변수인 gl_NormalMatrix를 사용한다. 이 행렬 변수는 모델뷰 매트릭스의 좌상단의 3x3 부분의 역행렬의 전치 행렬이다. 우리는 하나의 버텍스 마다 법선 변환을 수행할 것인데 아래의 코드가 바로 이 변환에 대한 코드이다.

varying vec3 normal;

void main()
{
    normal = gl_NormalMatrix * gl_Normal;
    gl_Position = ftransform();
}

아래의 코드처럼 프레그먼트 쉐이더에서 빛의 위치를 얻어와 빛의 밝기값을 계산한다.

varying vec3 normal;

void main()
{
    float intensity;
    vec3 color;
    vec3 n = normalize(normal);

    intensity = dot(vec3(gl_LightSource[0].position, n);

    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);

    gl_FragColor = color;
}

최종 소스 코드는 다음을 통해 다운로드 받길 바란다.

toonglut.zip

GLUT 예제

toonglut_2.0.zip

OpenGL 2.0 예제



신고
Posted by 우엉 여왕님!! ghostkyow

티스토리 툴바