블로그 이미지
대갈장군

calendar

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 29 30

Notice

2012. 1. 26. 03:32 OpenGL
Projetive Texture Mapping 은 3차원 텍스쳐 기법 중의 하나로 3차원 물체위에 텍스쳐를 투영시키는 기법이다. 

OpenGL은 잘 알고 있다시피 화면을 그리기 위해 Fixed pipeline를 이용한 방법과 GLSL 과 같은 쉐이더를 이용한 방법이 있다. Fixed pipeline을 이용해 Projective Texture Mapping을 구현한 예는 이미 널리 퍼져 있다. (http://www.opengl.org/resources/code/samples/mjktips/projtex/index.html)

헌데 GLSL을 이용한 방법은 안타깝게도 완벽한 예제가 없어 몇일을 구글링 했던 기억이 난다. 가장 완벽에 가까운 Tutorial은 http://www.ozone3d.net/tutorials/glsl_texturing_p08.php인데 이것도 쉐이더의 코드만 제공하고 있을뿐 중요한 부분에 대한 구현 방법은 생략했다. 

우선 Projective Texture Mapping을 위해 이해해야 할 것이 있다. 바로 텍스쳐의 좌표 생성에 관한 것인데, 일반적으로 2D 텍스쳐의 경우 0에서 1의 범위를 가지는 텍스쳐 좌표 값을 텍스쳐가 입혀질 물체의 버텍스에 설정함으로써 텍스쳐가 입혀지는데 Projective Texture Mapping은 이렇게 텍스쳐 좌표값을 생성하는 것이 아니라 마치 공간상에 프로젝터를 한대 놓고 그 프로젝터에서 발사되는 색상이 물체 위로 영사되게 되는 방식이다. (아래 그림처럼)



그렇다면 어떻게 저게 가능할까? 방법은 의외로 간단하다. (이론적으로는..) 프로젝터를 원하는 위치에 놓고 원하는 방향을 바라보게 한 다음 텍스쳐를 쏘면 된다... 말은 참 쉽다...

이런 원리에 대한 대답은 첨부된 10.1.1.104.6914 (5).pdf 파일에 있다... Projective Texture Mapping의 원리에 대한 아주 잘 설명하고 있다. 여기서 중간쯤 보면 Eye Linear Texgen 이라는 부분이 중요한데, Eye space에 근거하여 텍스쳐 좌표를 생성해내는 방법을 설명하고 있다. 바로 거기 행렬 곱들이 보이는데 저놈이 우리가 구현해야 할 부분이다.

 



제일 첫번째에 들어가있는 행렬은 Bias를 위한 행렬로써 Projective Texture Mapping 을 위해 [-1, 1]로 노말라이즈 되어 있는 월드 좌표계를 변환하여 텍스쳐가 사용하는 [0, 1]의 좌표 공간으로 바꿔주는 역활을 한다. 알고보면 각 축의 양의 방향으로 1만큼 이동시킨후 크기를 반으로 축소시키는 Translate + Scale 행렬 이다.

그리고 뒤에 좌라락 붙어 있는 놈들이 Projector's Projection Matrix, Projector's View Matrix, Eye's View Matrix 이다. 이 각 부분을 실제적으로 OpenGL에서 어떻게 가져 오는가가 바로 내가 그토록 찾던 부분이었다. 

이제 본격적으로 GLSL 코드를 살펴보자. 우선 버텍스 쉐이더는 다음과 같다.
uniform mat4 TexGenMatCam0;
uniform mat4 ViewMat;

void main()
{
	mat4 InvViewMat = inverse(ViewMat);	
	
	vec4 posEye =  gl_ModelViewMatrix * gl_Vertex;
	vec4 posWorld = InvViewMat * posEye;
		
	gl_TexCoord[0] = TexGenMatCam0 * posWorld;

	gl_Position = ftransform();		
} 
다음은 프래그먼트 쉐이더...
uniform sampler2D projMap_forCam1;

void main (void)
{
    vec4 final_color = vec4(0.0, 0.0, 0.0, 1.0);
    if( gl_TexCoord[0].q > 0.0 )
    {
		vec4 ProjMapColor_forCam1 = texture2DProj(projMap_forCam1, gl_TexCoord[0]);
		final_color = ProjMapColor_forCam1;			
    }

		
    gl_FragColor = final_color;			
}
 
일단 버텍스 쉐이더만 이해가 되면 프래그먼트는 자연스럽게 흘러간다. 알다시피 텍스쳐 좌표는 버텍스 쉐이더에서 계산이 되어야 한다. 하지만 어플리케이션에서 공급해줘야 할 것이 있으니 바로 TexGenMatCam0와 ViewMat이다. TexGenMatCam0가 바로 앞서 본 텍스쳐 생성 매트릭스이고 ViewMat은 Eye Space의 view matrix다. 자 이것들을 어떻게 얻어 오느냐... 우선 TexGenMatCam0를 얻어오는 방법은,

	glMatrixMode(GL_MODELVIEW_MATRIX);
	glPushMatrix();
	glLoadIdentity();
			
	gluLookAt(0.0f, 0.5f, 0.0f, 0, 0, 1.0f, 0, 1.0f, 0.0f);
	glGetFloatv(GL_MODELVIEW_MATRIX, ProjViewMatCam0);

	glLoadIdentity();
	gluPerspective( 90.0, 1.0, 0.0, 5.0);
	glGetFloatv(GL_MODELVIEW_MATRIX, ProjProjectionMatCam0);

	glLoadIdentity();
	glLoadMatrixd(bias);

	glMultMatrixf(ProjProjectionMatCam0);
	glMultMatrixf(ProjViewMatCam0);

	glGetFloatv(GL_MODELVIEW_MATRIX, TenLinearGexMatCam0);
 

요렇게 얻어오면 된다. gluLookAt을 이용해 View 행렬을 셋팅하고 gluPerspective를 이용해서 Projection 매트릭스 셋팅, 그리고 얻어진 요 녀석과 bias 행렬을 곱해서 TexLinearGexMatCam0를 얻어낸다. 이렇게 얻어낸 녀석을 바로 쉐이더로 전다... 참고로 bias 행렬은 다음과 같다.

const GLdouble bias[16] = { 0.5, 0.0, 0.0, 0.0,
                            0.0, 0.5, 0.0, 0.0,
							0.0, 0.0, 0.5, 0.0,
							0.5, 0.5, 0.5, 1.0 };
자 이제 버텍스 쉐이더에 대해 설명하자면 이 녀석은 각각의 버텍스가 들어오면 어플리케이션으로부터 받은 ViewMat의 역행렬을 일단 구해 놓고 Pre-defined 된 ModelView 매트릭스에 각각의 버텍스를 곱하여 Eye space의 좌표를 계산한다. 이것이 바로 posEye이다.

그리고 이제 계산된 Eye space 좌표계에 ViewMat의 Inverse matrix인 InvViewMat을 곱해주면 Eye space에서 World space로 좌표계를 변환시켜 준다. 그리고 얻어지는 녀석이 posWorld이다.

이제 남은 일은 이 월드 좌표계의 점을 우리가 셋팅 해놓은 프로젝터의 View matrix와 Projection matrix로 곱해주고 bias 행렬을 통해 위치와 크기를 변환시켜 주면 되는데 그 세개의 매트릭스를 미리 계산해서 구해 놓은 값이 바로 TexGenMatCam0이다. 이것은 위에 나온 코드로 어플리케이션에서 가져오면 된다.

그리하여 만들어지는 것이 바로 텍스쳐 좌표이고 이 값들을 Pre-define 된 텍스쳐 좌표계 0번에 집어 넣는다.

그러고 나서 이제 프로그먼트 쉐이더로 넘어가면 일단 입력으로 텍스쳐 하나가 들어오는데 바로 우리가 영사할 텍스쳐다.  
		glActiveTexture(GL_TEXTURE0);
		glBindTexture(GL_TEXTURE_2D, TexID_Cam1);
		glEnable(GL_TEXTURE_2D);

어플리케이션에서는 위와 같이 텍스쳐를 활성화 시키면 된다. 그리고 재미있는 부분이 있는데 gl_TexCoord[0].q 가 0보다 큰가를 체크하는데 바로 이부분 때문에 내가 Fixed 파이프라인을 이용하는 방법에서 GLSL로 바꾸었다.

Fixed 파이프 라인을 이용하면 바로 이 부분을 콘트롤 할 수가 없다. Projective Texture Mapping의 문제점이 바로 Reverse Projection인데 이것은 Fixed 파이프 라인에서는 Reverse projection을 근본적으로 제거하는 것이 힘들다. 고로 결국은 쉐이더를 이용해야 편한다. 바로 q 값이 그 것을 하게 해주는 인자 값이다. 만약 이 q 값이 0보다 적다면 이것은 reverse projection을 의미하는 것으로써 이 경우에는 아무것도 그리지 않으면 물체에 reverse projection이 일어나지 않는다.

그렇게 해서 만들어지는 결과는 다음과 같다.



 위 그림은 4개의 Projective Texture Mapping이 3차원 그릇의 각 면에 영사되고 있는 형태다. 파노라마 이미지가 있다면 위와 같은 방식으로 360도 Virtual Space를 손쉽게 만들수 있겠다. 아직 코드가 정리가 안되었는데 조만간 정리 좀 해서 올려야 겠다. 간단하게 바꿔서... 
posted by 대갈장군