블로그 이미지
대갈장군

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 대갈장군
2010. 5. 15. 04:20 OpenGL
OpenGL에서 작게는 10만개에서 많게는 100만개 정도 까지의 점을 그려야 하는 일이 있었다. 각각의 점들은 UTM 좌표들인데 이 UTM은 타원인 지구를 2차원 평면을 평평하게 편 다음 각 구역을 나누어 기준점으로부터의 거리를 미터로 나타낸다.

그래서 UTM은 좌표의 구성이 Easting, Northing, Altitude 요렇게 세가지 축으로 구성된다. 뭐, 걍 X,Y,Z라고 보면 된다. 

이 UTM 좌표 값은 미터로 표시되기 때문에 그 값이 무지 크다. 내가 스캔한 영역의 점들은 기본적으로 Easting 값이 710,000 이상이었으니까 무지하게 큰 값이다.

이론에 따라 이 UTM 좌표를 XYZ로 생각하고 각 점들을 그려내 보았는데 이상하게도 몇개의 점들이 서로 서로 모여서 Z 방향으로 라인을 만드는 것이었다.

다른 형태로 출력한 같은 데이터를 QT modeler라는 프로그램을 이용해서 보면 훨씬 조밀조밀하게 나오는데 이상하게 OpenGL로 렌더링 한 점들은 모두가 띄엄띄엄 한 것이었다.

아무리 옵션을 바꿔봐도 문제는 고쳐지지 않았고 데이터 스트럭쳐 확인 및 브레이크 포인트를 이용해 각각의 요소를 확인해 보았지만 어디에도 타입 변경에 따른 문제는 없었다.

고민고민하다가 생각한 것이 너무 큰 정수부분을 없애자는 것이었다. 임의의 인접한 두점은 대략 0.001 정도의 간격을 가지는데 정수부분의 70만 인것을 감안하면 소수 부분은 턱없이 적은 차이라는 점이 맘에 걸렸었다.

그래서 모든 점들을 다 읽어들인후 중간값을 찾아내 그 정수값을 모두 빼내었다. 그리고 나서 다시 똑같은 방법으로 렌더링을 했더니 이제서야 모든 점들이 제대로 분포하는 것이었다.

그렇게 고치고 나서 생각해보니 한 3년전에 들었던 수치 분석 수업에서 제한된 표현크기를 가지는 컴퓨터의 타입들(예를 들자면 integer, double 같은 것)을 사용할때는 아주가까운 두값이 연산에서 수많은 에러가 발생가능하다는 교수님의 말이 스쳐 지나갔다.

아직 왜 인지는 정확히 모르겠으나 분명히 큰 정수부분의 표현을 위해 제한된 비트를 소모해 버리느라 소수점 이하 영역의 차이를 제대로 계산해 내지 못한 것은 분명하다.

아무튼 이번 경험은 소중한 헤딩이었다. ㅋㅋㅋ
posted by 대갈장군
2010. 3. 11. 03:47 OpenGL
FBO는 OpenGL에서 사용하는 '외부에 그리기' 기술중 하나로써 나도 종종 사용한다. FBO를 사용함으로써 사용자는 다양한 작업이 가능해 진다. 

대표적인 FBO 사용예로 그려질 scene 내부에 또 다른 scene을 그려야 할 때다. 집안에 있는 TV를 표현할 때 종종 이 방법이 사용된다. FBO의 장점으로는 간단한 셋업과 사용 그리고 context switching이 필요없다는 점 (오버헤드 적음) 각종 버퍼 (depth buffer, stencil buffer, accumulation buffer) 등을 사용할 수 있어 유용하다는 점 등이다. 


1. 셋업

우선 다른 OpenGL 객체와 마찬가지로 FBO 핸들을 받을 변수를 선언해야 하고 그 변수에 핸들을 생성해서 대입해야 한다. glGenFramebuffersEXT() 함수의 첫번째 인자는 glGenTextures() 함수의 첫번째 인자처럼 몇개나 만들건지 지정해주는 인자다.
GLuint fbo;
glGenFramebuffersEXT(1, &fbo);
명령을 수행하려면 바인딩 부터 해야 한다. 바인딩을 함으로써 다음에 이어지는 명령들이 바인딩한 객체에 대해 수행되도록 한다.
glBindFramebufferEXT
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo);
첫번째 인자가 Target 인자 인데 GL_FRAMEBUFFER_EXT는 고정된 것이라고 보면 된다. 물론 나중에 확장이 되면 다른 형태가 추가 될수 있지만 현재로서는 저 인자가 고정이라고 보면 된다. 두번째 인자로 앞서 정의한 핸들을 넘겨주면 오케이.

2. 깊이 버퍼 (Depth buffer) 추가

FBO 객체 자체로는 할 수 있는게 없다. 왜냐면 걍 객체(식별표)이니까... 이제 이 식별표에 실제적인 저장 공간(버퍼)들을 추가해 줘야 한다. 알다시피 OpenGL이 기본적으로 제공하는 Frame buffer도 깊이 버퍼, 스텐실 버퍼 등등으로 구성되어 있다. 고로 FBO 객체에도 Renderable (렌더링 가능한) 버퍼를 추가 시켜 주어야 한다. 이 버퍼로 사용가능한 것이 텍스쳐 또는 렌더버퍼 중 하나다. 

렌더버퍼는 FBO의 offscreen rendering을 지원하기 위한 객체이다. 적절한 텍스쳐 포멧을 지원하지 않는 스텐실 버퍼나 깊이 버퍼를 표현하기 위해서 사용하는 것이 바로 렌더버퍼 객체다. 이 예제에서는 렌더버퍼를 FBO의 깊이 버퍼로 사용할 것이다. 

그러니까 요약하자면, FBO는 OpenGL이 기본적으로 가지고 있는 깊이 버퍼, 스텐실 버퍼, 색상 버퍼등을 '연결'할 수 있는 객체인데 이때 이 연결 가능한 놈들은 두 종류로써 하나는 일반 텍스쳐이고 다른 하나는 '렌더버퍼(Renderbuffer)'이다. 일반 텍스쳐의 경우 색상 버퍼로 사용할 수 있으나 깊이 버퍼나 스텐실 버퍼와 같이 텍스쳐로 표현할 수 없는 버퍼들은 렌더버퍼를 이용해서 FBO에 연결한다는 말인듯 하다. 

아무튼, 일단 렌더버퍼를 생성하는 방법은 다음과 같다.
GLuint depthbuffer;
glGenRenderbuffersEXT(1, &depthbuffer);
depthbuffer라는 변수 (핸들을 받기위한)를 선언하고 generation 함수를 통해 렌더버퍼를 생성해 낸다. (그래서 함수 이름도 glGenRenderbuffersEXT() 이다. 

이제 이렇게 만든 렌더버퍼를 바인딩 시킴으로써 다음에 이어지는 일련의 명령이 depthbuffer 렌더버퍼에 대해 실행되도록 한다.
glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, depthbuffer);
앞서 본 것과 마찬가지로 GL_RENDERBUFFER_EXT는 고정이라고 보면 된다. 그리고 두번째 인자로 앞서 만든 렌더버퍼의 핸들을 넘겨준다. 

자, 이제 해야 할 일은 이 깊이 버퍼의 크기를 지정해 주는 일이다. (폭과 높이 지정하기)
glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT, 
                         width, height);
이 과정은 일종의 메모리 할당이라고 보면 되겠네. 한가지 알아둘 것은 이 렌더버퍼는 기본 RGB/RGBA 저장용으로 사용가능하며 스텐실 버퍼로써 연결도 가능하단다. RGB/RGBA 저장 공간으로 사용될 수 있다는 점은 조금 신선하긴 하다만 텍스쳐를 사용하는 것이 더 유연할 것이라 생각된다.

다음은 이렇게 실제적으로 메모리를 할당한 깊이 렌더버퍼를 우리가 앞서 만든 fbo 객체에 가져다 붙이는 것이다. 
glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, 
                             GL_RENDERBUFFER_EXT, depthbuffer);
위 명령이 좀 복잡해 보여도 실은 단순하다. 현재 바인딩 되어 있는 FBO 객체 (fbo)에 렌더버퍼 (depthbuffer)를 바인딩 하라.. 라는 말이다.

3. 렌더링 텍스쳐 추가하기

앞서 두 단계를 수행했지만 아직까지 FBO에 색상 버퍼를 추가하지 않았다. 이제 색상 버퍼를 추가 해야 하는데 이 색상 버퍼가 사실상 '내 눈에 보여질' 놈이다. 

이 색상 버퍼를 추가하는 방법은 두가지가 있다.
1. 색상 렌더버퍼를 FBO의 색상버퍼로 연결
2. 일반 텍스쳐를 FBO의 색상버퍼로 연결

첫번째 방법도 사용하기는 하지만 우리는 두번째 방법을 사용할 것이다. (앞서 2번에서 설명했듯이 렌더버퍼는 기본 RGB/RGBA를 저장하기 위한 버퍼로도 사용가능하다고 했다. 하지만 분명 일반 텍스쳐 보다는 범용성이 떨어질 듯하다.)

텍스쳐를 FBO에 연결하기 위해서는 당연히 텍스쳐 생성부터 해야 한다.
GLuint img;
glGenTextures(1, &img);
glBindTexture(GL_TEXTURE_2D, img);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8,  width, height, 0, GL_RGBA, 
             GL_UNSIGNED_BYTE, NULL);
이 과정은 일반적인 텍스쳐 생성 과정이다. 이때 주의 해야 할 한 가지 포인트는 폭과 넓이가 앞서 만든 깊이 렌더버퍼의 크기와 같아야 한다는 매우 상식적인 것이다. 

자, 이제 색상 버퍼로 사용될 텍스쳐를 만들었으므로 우리가 앞서 만든 FBO 객체에 연결하자.
glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, 
                          GL_TEXTURE_2D, img, 0);
이 명령이 어렵게 보여도 실제로는 그닥 어렵지 않다. 우선 굵은 글자로 표시된 GL_COLOR_ATTACHMENT0_EXT가 좀 어렵게 보일지 모르나 이것은 OpenGL에게 어떤 color attachment point에 연결할 지를 명시해주는 인자일 뿐이다. FBO는 동시에 여러개의 색상 버퍼를 가질 수 있기 때문에 어떤 포인트에 연결할 지를 명시해야 한다. 하나의 색상 버퍼만 사용한다면 저 숫자 0은 당신에게 아무런 중요성이 없는 단순한 표식일 뿐이다. 그리고 GL_TEXTURE_2D는 우리가 2D 텍스쳐를 사용한다는 말이고 img는 사용할 텍스쳐의 핸들, 그리고 마지막 0은 mipmap level 이다. 

이제 FBO 완성을 위해 마지막으로 해야 할 일은 FBO가 잘 만들어 졌는지 체크하는 일이다. 다음 함수로 한방에 체크하자.
GLenum status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
모든 셋팅이 제대로 통과하였다면 GL_FRAMEBUFFER_COMPLETE_EXT를 리턴할 것이고 만약 에러가 있다면 다른 값을 리턴한다. 

4. 텍스쳐로 렌더링하기

이제 색상버퍼로 연결시켜 놓은 텍스쳐 (img)로 렌더링을 수행하는 방법을 알아볼 차례. 이건 상당히 쉬운데 걍 glBindFramebufferEXT() 함수만 호출하면 땡이다. 셋업이 조금 어려워서 그렇지 사용 자체는 굉장히 쉽다.

FBO로 렌더링 하기 위해서는 glBindFrameBufferEXT()를 호출하면 되고 렌더링을 멈추기 위해서는 같은 함수를 호출하되 인자값을 0으로 주면 된다.
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo); // FBO 바인딩
glPushAttrib(GL_VIEWPORT_BIT);                      
glViewport(0,0,width, height);                 // 여기서 부터는 텍스쳐에 렌더링한다.
// Render as normal here // output goes to the FBO and it’s attached buffers glPopAttrib(); glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); // 언바인딩. FBO에 렌더링하기 중지
자, FBO를 바인딩함으로써 차후 수행되는 모든 그리기 명령은 우리가 설정한 FBO의 색상 버퍼, 깊이 버퍼, 스텐실 버퍼에 적용된다. 그리고 glPushAttrib() 와 glPopAttrib() 함수를 이용해 이전의 3차원 매트릭스 값들을 신속히 복구 및 적용할 수 있게 했다. 

중요한 것은, 다시한번 강조하지만, glBindFramebufferEXT() 함수 하나로 간단하게 FBO로 렌더링을 할 수 있다는 점이다. 앞서 셋업에서 수행했던 몇몇 버퍼 바인딩 및 연결과정은 더이상 필요없다. 

5. 렌더링 된 FBO의 색상 버퍼를 텍스쳐로 사용하기

이제 우리의 궁극적인 목표에 가까워 졌다. 왜 우리는 FBO에 렌더링을 수행했던가? 바로 이 단계를 위해서다. FBO 색상 버퍼에 그려진 그림은 곧바로 텍스쳐로 사용 가능하다. 즉, 집안에 있는 TV 속의 화면을 우리는 방금 FBO를 이용해서 그려낸 것이다. 이제 TV 안의 화면은 그렸으니 집을 그릴때 만들어 놓은 TV 속의 화면을 텍스쳐로 입히자.

알다시피 img 핸들에 FBO의 색상 버퍼가 연결되어 있으므로 이 텍스쳐를 사용하기 위해서는 우선 바인딩 부터 한다.
glBindTexture(GL_TEXTURE_2D, img);
그리고 FBO 밉맵 자동 생성 함수인 glGenerateMipmapEXT() 함수를 호출하자.
glGenerateMipmapEXT(GL_TEXTURE_2D);
한가지 주의할 것은 만약 내가 임의의 밉맵 필터 (GL_LINEAR_MIPMAP_LINEAR와 같은)를 사용할 것이라면 FBO에 연결될 색상 버퍼 생성시 반드시 glGenerateMipmapEXT() 함수를 호출해서 가능한지 여부를 체크해야 한다. 

이 경우, 아래의 명령대로 FBO의 색상버퍼를 생성하면 된다.
glGenTextures(1, &img);
glBindTexture(GL_TEXTURE_2D, img);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8,  width, height, 0, GL_RGBA, 
             GL_UNSIGNED_BYTE, NULL);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glGenerateMipmapEXT(GL_TEXTURE_2D);
img를 바인딩 했다면 이제 할 일은 바인딩한 텍스쳐를 원하는 곳에 그려넣기인데 소스 코드를 보면서 설명해 보면...

void display(void)   
{
// FBO 바인딩 - FBO에 렌더링 하겠다고 알려줌
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo);
// 각종 화면 관련 속성 저장해 놓기 - FBO에 렌더링 끝나면 이전값으로 복귀하기 위해서
glPushAttrib(GL_VIEWPORT_BIT);
glViewport(0,0,width,height);

// 이제 부터 회전하는 작은 큐브를 FBO에 그린다.
glClearColor(0.0f, 0.0f, 0.0f, 0.5f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glLoadIdentity();

glTranslatef(0.0f,0.0f,-2.0f);
glRotatef(xrot,1.0f,0.0f,0.0f);
glRotatef(yrot,0.0f,1.0f,0.0f);

glBegin(GL_QUADS);
// Front Face
glColor4f(0.0f,1.0f,0.0f,1.0f);
glVertex3f(-0.5f, -0.5,  0.5);
glVertex3f( 0.5, -0.5,  0.5);
glVertex3f( 0.5,  0.5,  0.5);
glVertex3f(-0.5,  0.5,  0.5);
// Back Face
glColor4f(1.0f,0.0f,0.0f,1.0f);
glVertex3f(-0.5, -0.5, -0.5);
glVertex3f(-0.5,  0.5, -0.5);
glVertex3f( 0.5,  0.5, -0.5);
glVertex3f( 0.5, -0.5, -0.5);
// Top Face
glColor4f(0.0f,0.0f,1.0f,1.0f);
glVertex3f(-0.5,  0.5, -0.5);
glVertex3f(-0.5,  0.5,  0.5);
glVertex3f( 0.5,  0.5,  0.5);
glVertex3f( 0.5,  0.5, -0.5);
// Bottom Face
glColor4f(0.0f,1.0f,1.0f,1.0f);
glVertex3f(-0.5, -0.5, -0.5);
glVertex3f( 0.5, -0.5, -0.5);
glVertex3f( 0.5, -0.5,  0.5);
glVertex3f(-0.5, -0.5,  0.5);
// Right face
glColor4f(1.0f,1.0f,0.0f,1.0f);
glVertex3f( 0.5, -0.5, -0.5);
glVertex3f( 0.5,  0.5, -0.5);
glVertex3f( 0.5,  0.5,  0.5);
glVertex3f( 0.5, -0.5,  0.5);
// Left Face
glColor4f(1.0f,1.0f,1.0f,1.0f);
glVertex3f(-0.5, -0.5, -0.5);
glVertex3f(-0.5, -0.5,  0.5);
glVertex3f(-0.5,  0.5,  0.5);
glVertex3f(-0.5,  0.5, -0.5);
glEnd();

// 앞서 저장했던 화면 관련 값을 복구하고 FBO 사용 해제
glPopAttrib();
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);

        // 이제부터 그리는 것들은 스크린에 보여질 직접적인 것들
glClearColor(0.0f, 0.0f, 0.2f, 0.5f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glLoadIdentity();
// 여기서 FBO에 그려진 회전하는 규브 (색상 버퍼)를 바인딩한다
glBindTexture(GL_TEXTURE_2D, img);
// 여기서는 밉맵 옵션이 사용되지 않지만 만약 당신이 밉맵을 사용한다면 아래의 주석을 제거하고 텍스쳐 생성시에 
// glGenerateMipmapEXT() 함수를 호출하라.
// glGenerateMipmapEXT(GL_TEXTURE_2D);
glEnable(GL_TEXTURE_2D);

glTranslatef(0.0f,0.0f,-2.0f);
glRotatef(-xrot,1.0f,0.0f,0.0f);
glRotatef(-yrot,0.0f,1.0f,0.0f);

glColor4f(1.0f,1.0f,1.0f,1.0f);

// 이제 FBO의 색상 버퍼를 텍스쳐로 사용하는 또다른 규브 그리기
glBegin(GL_QUADS);
// Front Face
glNormal3f( 0.0f, 0.0f, 1.0);
glTexCoord2f(0.0f, 1.0f); glVertex3f(-0.5f, -0.5,  0.5);
glTexCoord2f(1.0f, 1.0f); glVertex3f( 0.5, -0.5,  0.5);
glTexCoord2f(1.0f, 0.0f); glVertex3f( 0.5,  0.5,  0.5);
glTexCoord2f(0.0f, 0.0f); glVertex3f(-0.5,  0.5,  0.5);
// Back Face
glNormal3f( 0.0f, 0.0f,-1.0);
glTexCoord2f(1.0f, 0.0f); glVertex3f(-0.5, -0.5, -0.5);
glTexCoord2f(1.0f, 1.0f); glVertex3f(-0.5,  0.5, -0.5);
glTexCoord2f(0.0f, 1.0f); glVertex3f( 0.5,  0.5, -0.5);
glTexCoord2f(0.0f, 0.0f); glVertex3f( 0.5, -0.5, -0.5);
// Top Face
glNormal3f( 0.0f, 1.0, 0.0f);
glTexCoord2f(0.0f, 1.0f); glVertex3f(-0.5,  0.5, -0.5);
glTexCoord2f(0.0f, 0.0f); glVertex3f(-0.5,  0.5,  0.5);
glTexCoord2f(1.0f, 0.0f); glVertex3f( 0.5,  0.5,  0.5);
glTexCoord2f(1.0f, 1.0f); glVertex3f( 0.5,  0.5, -0.5);
// Bottom Face
glNormal3f( 0.0f,-1.0, 0.0f);
glTexCoord2f(1.0f, 1.0f); glVertex3f(-0.5, -0.5, -0.5);
glTexCoord2f(0.0f, 1.0f); glVertex3f( 0.5, -0.5, -0.5);
glTexCoord2f(0.0f, 0.0f); glVertex3f( 0.5, -0.5,  0.5);
glTexCoord2f(1.0f, 0.0f); glVertex3f(-0.5, -0.5,  0.5);
// Right face
glNormal3f( 1.0, 0.0f, 0.0f);
glTexCoord2f(1.0f, 0.0f); glVertex3f( 0.5, -0.5, -0.5);
glTexCoord2f(1.0f, 1.0f); glVertex3f( 0.5,  0.5, -0.5);
glTexCoord2f(0.0f, 1.0f); glVertex3f( 0.5,  0.5,  0.5);
glTexCoord2f(0.0f, 0.0f); glVertex3f( 0.5, -0.5,  0.5);
// Left Face
glNormal3f(-1.0, 0.0f, 0.0f);
glTexCoord2f(0.0f, 0.0f); glVertex3f(-0.5, -0.5, -0.5);
glTexCoord2f(1.0f, 0.0f); glVertex3f(-0.5, -0.5,  0.5);
glTexCoord2f(1.0f, 1.0f); glVertex3f(-0.5,  0.5,  0.5);
glTexCoord2f(0.0f, 1.0f); glVertex3f(-0.5,  0.5, -0.5);
glEnd();

glDisable(GL_TEXTURE_2D);
xrot+=xspeed;
yrot+=yspeed;

glutSwapBuffers ( );
// 버퍼 스왑 (출력하기)
}

위 프로그램을 돌려보면 일단 두개의 큐브가 나오는데 바깥쪽의 큰 큐브의 각 면은 FBO에 그려진 또 다른 회전하는 작은 큐브를 끊임없이 보여준다. (아래 그림 처럼)


6. 치우기

다 사용했다면 프로그램 종료전에 치우기를 수행해야 한다. (메모리 해제) 뭐 이것도 간단하다.
glDeleteFramebuffersEXT(1, &fbo);
위 명령은 fbo 를 삭제하라는 명령이고 앞서 만든 렌더버퍼를 날려보내기 위해서는 다음 명령을 수행한다.
glDeleteRenderbuffersEXT(1, &depthbuffer);
뭐 가운데 한 단어만 다를뿐 같은 명령이다.

마지막으로 글쓴이는 몇가지 당부를 하고 있는데, FBO를 생성했다 지웠다를 프로그램 실행중 반복하지 말라는 부탁과 렌더링된 FBO의 색상 버퍼를 glTexImage 함수를 이용해서 변경하거나 추가하는 행위를 피하는 것이 좋다고 한다. 이는 당연한 이야기로 이렇게 하면 Performance가 나빠질 것은 불을 보듯 뻔하다.

그리고 ATI를 사용하는 사람들에게는 위 예제가 제대로 동작하지 않을 수도 있단다. ATI 드라이버를 사용할때  FBO의 깊이 버퍼를 렌더버퍼로 연결하는 과정에 버그가 있다고 하는데 아마도 지금이 다 고쳐 졌겠지? 

소스 파일을 글에 첨부하였는데 Visual Studio 솔루션 파일이고 추가로 필요한 프로그램은 FreeGLUT이다. 이 FreeGLUT는 http://freeglut.sourceforge.net/에 가면 다운 받을수 있고 압축을 푼후 VisualStudio2008 폴더에 있는 프로젝트 파일을 열어서 컴파일 하게 되면 freeglut.lib와 freeglut.dll 파일이 생성된다. freeglut.dll은 윈도우즈 폴더에 System32 폴더안에 던져 넣어 버리고 lib 파일은 FBO_Example 프로젝트 폴더 안에 넣거나 FBO_Example/Release 폴더 및 FBO_Example/Debug 폴더에 던져 넣어버리면 문제 없이 컴파일 될것이다. 



posted by 대갈장군
2010. 2. 24. 06:01 OpenGL
Convex Hull은 볼록 껍데기로써 임의의 점으로 구성된 다각형에서 두 점을 연결했을때 이 두 점이 이루는 직선이 다각형의 외곽선을 교차하지 않는 것을 의미한다. 바로 다음 그림처럼..


반대로 임의의 두점이 외곽선을 관통하게 되면 (아래 그림처럼) 이것을 오목 껍데기 혹은 Concave Hull 이라고 한다. 중요한 것은 OpenGL에서는 오직 Convex Hull 로만 폴리곤을 그릴수 있다는 것... :) 


posted by 대갈장군
2010. 2. 24. 03:30 OpenGL
OpenGL에서 블렌딩은 예상외로 조금 복잡하다. 일단 함수의 인자부터 헷갈린다. glBlendFunc() 함수가 입력으로 들어오는 화소와 프레임 버퍼에 이미 그려져 있는 화소의 블렌딩 비율을 지정하는 놈인데 이 놈의 인자들을 살펴보면, 첫번째 인자가 source이고 두번째가 destination 이다. 

이거 마치 source가 원래 그려져 있던 화소 같이 들리지만 그 반대다. source인 첫번째 인자가 들어오는 화소고 두번째 인자인 destination이 목적지인 그려질 위치에 있던 화소를 말한다.

아래에 두 가지 대표적인 블렌딩의 예를 그림으로 표현했는데 우선 첫번째 인자가 GL_SRC_ALPHA이고 두번째 인자가 GL_ONE_MINUS_SRC_ALPHA 이면 입력으로 들어오는 화소의 알파값이 입력 화소의 블렌딩 비율이되고 1에서 입력으로 들어오는 화소의 알파값을 뺀 나머지 값이 원래 버퍼에 들어있던 화소의 블렌딩 비율이 되게 하라는 말이다.

고로 소스로 들어온 사각형의 알파값이 아래 그림 첫번째 처럼 50% 였다면, 1 - 0.5를 뺀 값인 0.5가 원래 있던 화소들인 빨강색 삼각형에 적용되어 둘이 합쳐져 오른쪽의 그림처럼 스크린에 나타난다.

만약 입력으로 들어오는 사각형의 알파값이 20%라면 삼각형은 80% (100 - 20)로 블렌딩 되어 그림 내부의 아래 그림 처럼 블렌딩 된다.


것을 코드로 표현하자면 아래와 같다. 우선 블렌딩을 활성화하고 사각형을 그리는데 블렌딩 옵션을 입력으로 들어오는 화소만 그려지라는 의미의 GL_ONE, GL_ZERO 로 주고 삼각형을 그린다. 그리고나서 블렌딩 옵션을 바꿔서 사각형을 그리면 된다.


  glEnable(GL_BLEND);  // 블렌딩활성화

  glBlendFunc(GL_ONE, GL_ZERO);  // 소스(Incoming) 만그리기

 

  glBegin(GL_TRIANGLES);  // Drawing Using Triangles

  glColor4f(1.0f, 0.0f, 0.0f, 1.0f);

  glVertex4f( 0.0f, 1.2f, 0.0f, 1.0f); 

  glVertex4f(-1.2f,-1.0f, 0.0f, 1.0f); 

  glVertex4f( 1.2f,-1.0f, 0.0f, 1.0f); 

  glEnd(); 


  glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);  

 

  glBegin(GL_QUADS);  // Draw A Quad

  glColor4f(0.0f, 0.0f, 1.0f, 0.5f);

  glVertex4f(-1.0f, 1.0f, 0.0f, 1.0f); 

  glVertex4f( 1.0f, 1.0f, 0.0f, 1.0f); 

  glVertex4f( 1.0f,-1.0f, 0.0f, 1.0f); 

  glVertex4f(-1.0f,-1.0f, 0.0f, 1.0f); 

  glEnd(); 

 

또 다른 예로는 3개의 물체를 균일한 값으로 블렌딩 하는 것인데 여기서는 3개의 물체 (원, 사각형, 삼각형)가 나온다. 이것도 위와 유사한 방법이지만 glBlendFunc() 함수의 인자를 GL_SRC_ALPHA와 GL_ONE으로 준다. 소스로 들어오는 화소의 블렌딩은 들어오는 알파값을 그대로 사용하되 먼저 그려져 있던 화소는 GL_ONE을 줌으로써 가지고 있던 알파값을 그대로 유지하게 한다. 결국 이 의미는 '중첩'시키라는 의미다. 고로 33%씩 세번 중첩 하면 100% 하얀 부분이 3개의 물체가 겹치는 부분에 나타나야 한다. 근데 아래 그림은 좀 약하다... 파워 포인트로 작업한지라 제대로 중첩이 안되고 있다.. -_-  

그래서 프로그램을 돌려 나온 결과를 스크린 캡쳐하여 첨부하였다. 아래 그림을 보면 아... 세개의 도형이 겹치는 부분은 100% 흰색이구나 하는 느낌이 올 것이다. 


소스 코드는 다음과 같다.

  glEnable(GL_BLEND);  // 블렌딩활성화

  glBlendFunc(GL_SRC_ALPHA,GL_ONE); 

 

  glBegin(GL_TRIANGLES);  // Drawing Using Triangles

  glColor4f(1.0f, 1.0f, 1.0f, 0.33333f);

  glVertex4f( 0.0f, 1.2f, 0.0f, 1.0f); 

  glVertex4f(-1.2f,-1.0f, 0.0f, 1.0f); 

  glVertex4f( 1.2f,-1.0f, 0.0f, 1.0f); 

  glEnd(); 

 

  glBegin(GL_QUADS);  // Draw A Quad

  glColor4f(1.0f, 1.0f, 1.0f, 0.33333f);

  glVertex4f(-1.0f, 1.0f, 0.0f, 1.0f); 

  glVertex4f( 1.0f, 1.0f, 0.0f, 1.0f); 

  glVertex4f( 1.0f,-1.0f, 0.0f, 1.0f); 

  glVertex4f(-1.0f,-1.0f, 0.0f, 1.0f); 

  glEnd(); 


  glColor4f(1.0f, 1.0f, 1.0f, 0.33333f);

  glutSolidSphere(1.0f, 36, 36);

posted by 대갈장군
2010. 2. 23. 01:37 OpenGL
OpenGL에서 어쩌면 가장 중요한 부분이 바로 Viewing 이 아닌가 싶다. 이 파트는 어떻게 보여지는 가를 설정하는 모든 명령에 관련되어 있는데 이걸 못하면 아무리 멎진 그림을 그려놔도 볼수가 없으니 말이다.

Red Book을 보면 참 설명이 잘 되어 있는데 문제는 영어로 되어 있다. -_-; 암튼, Red Book을 근거로 하여 설명하자면 OpenGL에서 Viewing을 설정하는 것은 카메라로 사진을 찍는 것과 유사하다고 말한다.

옆의 그림이 그것을 설명하는데 우선 차례 차례 보자. 제일 먼저 해야 하는 것이 카메라 설치이다. 이것은 첫번째 그림이 의미하는 바인데 OpenGL에서는 이것을 Viewing Transformation 이라고 한다. 

두번째로 해야 할 일은 사진찍을 물체를 적당한 위치에 놓는 것이다. (두번째 그림) 이것을 OpenGL에서는 Modeling Transformation 이라고 한다. 

세번째는 적당한 렌즈로 초점을 맞추는 일이다. OpenGL에서는 이 과정을 Projection Transformation 이라고 한다. 사실 이 파트의 설명은 조금 어색하긴 하다. OpenGL에서 이 과정은 사실 '잘라내기'에 가깝다. 왜냐면 이 과정에서 보여질 깊이를 설정하여 필요없는 부분은 잘라내 버리기 때문이다. 물론 카메라의 초점 조절이 어떤 깊이에 있는 물체를 '깨끗하게' 잡을 것인가를 정의한다는 면에서 비슷하기는 하지만 '잘라내기'를 하지 않는다는 점에서 조금 다르다. 

마지막으로 네번째가 '현상하기'인데 OpenGL에서의 사진 현상은 모니터에 그려내기와 같다. 이 과정을 Viewport Transformation 이라고 한다. 

이 일련의 과정들에서 주의해야 할 것들이 몇가지 있다. 

1. Viewing Transformation은 반드시 Modeling Transformation 보다 우선적으로 선언되어야 한다. 이 말은 카메라를 먼저 놓고 물체를 그리라는 말인데 정확한 이유는 설명되어 있지 않지만 내 생각에는 물체를 옮기고 변형하는 과정에서 계산된 매트릭스가 카메라를 놓는 Viewing Transformation에 영향을 미치면 예상치 못한 결과가 나올수 있기 때문이 아닐까 싶다. 상식적으로 생각해도 카메라를 먼저 놓고 물체를 놓는게 OpenGL의 세계에서는 합당한듯 하다. 

2. 뭔가를 그리기 전에는 항상 glLoadIdentity() 함수로 현재의 매트릭스를 초기화 한다. 이것은 매우 중요한 부분인데 먼저 이해해야 할 것이 OpenGL은 똑똑하지 않다. 렌더링 과정에서 사용되는 매트릭스가 있는데 이 매트릭스는 C의 static 과 같은 속성을 가진다. 즉, 한번 초기화된 후 사용자가 다른 값을 넣으면 계속 그 값을 유지하게 된다는 것이다. 렌더링을 할때 마다 그 값이 초기화 되지 않고 이전의 값을 유지하므로 적절한 시기에 초기화 하지 않으면 앞에서 계산된 값이 계속 적용되어 완전 예상 밖의 결과가 나온다. 고로 매번의 렌더링 마다 적절한 곳에서 초기화가 필요하다.

3. glMatrixMode() 함수를 이용하여 매트릭스 선택하기. 앞서 2번에서 말한 렌더링 과정에 사용되는 매트릭스는 두 가지가 존재한다. 하나는 GL_MODELVIEW고 다른 하나는 GL_PROJECTION 이다. GL_MODELVIEW는 Modeling Transformation과 Viewing Transformation에 관련된 매트릭스라는 의미고 GL_PROJECTION은 Projection Transformation에 관련된 매트릭스라는 의미다. glMatrixMode() 함수에 GL_MODELVIEW 인자를 넣게 되면 Modeling Transformation과 Viewing Transformation에 관련된 연산을 하는 매트릭스를 현재의 매트릭스로 선택하라는 의미고 마찬가지로 GL_PROJECTION 은 Projection Transformation에 관한 매트릭스를 현재의 매트릭스로 선택하라는 의미다. 

참 답답하게도 OpenGL은 현재의 매트릭스를 하나만 정해 사용할 수 있는 고집쟁이다. (아마도 최고의 스피드를 내기 위해 그런듯) 그래서 위의 명령을 이용해 다음 명령들에 의해 영향을 받을 매트릭스를 지정해 준다. 고로, glMatrixMode(GL_MODELVIEW)를 호출한 다음 glLoadIdentity()를 호출하면 물체를 놓고 움직이는 것에 관련된 매트릭스를 초기화하게 되는 것이다.

이제 Red Book에 나오는 예제를 이용해서 설명을 해보겠다.

void display(void)
{
glClear(GL_COLOR_BUFFER_BIT);
glColor3f(1.0, 1.0, 1.0);
glLoadIdentity();         // 6. 매트릭스 초기화

/* Modeling Transformation & Viewing Transformation */
gluLookat(0.0, 0.0, 5.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);  // 7. Viewing Transformation
glScalef(1.0, 2.0, 1.0);                                        // 8. Modeling Transformation
glutWireCube(1.0);                                            // 9. 렌더링
glFlush();
}

void reshape(int w, int h)
{
glViewport(0, 0, (GLsizei)w, (GLsizei)h);             // 1. Viewport Transformation
glMatrixMode(GL_PROJECTION);                        // 2. GL_PROJECTION 매트릭스 선택
glLoadIdentity();                                                // 3. 매트릭스 초기화
glFrustum(-1.0, 1.0, -1.0, 1.0, 1.5, 20.0);               // 4. Projection Transformation 
glMatrixMode(GL_MODELVIEW);                         // 5. GL_MODELVIEW 매트릭스 선택
}

위의 코드는 일부만 나타낸 것이지만 렌더링에 관련된 모든 부분이다. 번호가 매겨진 순서대로 렌더링이 일어난다고 생각하면 된다. reshape 함수는 최초 윈도우 생성시 및 윈도우 크기 변경등이 발생할때 호출되는 함수로 우선적으로 호출된다고 보면 된다.

제일 먼제 Viewport Transformation을 통해 보여질 화면을 스크린의 좌표계에서 어디서 어디까지인지 정의하였다. 이 함수는 매우 간단하다. 현재 나의 스크린이 1024 x 768이라고 하고 이 모든 영역에 OpenGL 렌더링을 하고 싶다면 걍 glViewport(0, 0, 1024, 768) 하면 그만이다. 입력으로 들어오는 w와 h값을 이용하는데 이것은 일반적인 프로그래밍의 경우 새로 변경된 윈도우의 크기가 될 가능성이 매우 높다. 

2번에서 GL_PROJECTION 매트릭스를 선택했다. 그리고 바로 3번에서 초기화 했고 (이전의 값 삭제) 4번에서 glFrustum() 함수를 이용해서 렌즈조절 (보여질 영역설정)을 했다. 이 함수는 perspective transformation을 하는데 원근법에 따라 멀리 있는 물체를 작게 보여준다는 의미다. 이와 반대로 직교법 (Orthographic transformation)을 이용하는 함수는 glOrtho() 함수가 있다. 

그리고 탈출하기 전에 선택된 매트릭스를 GL_MODELVIEW로 변경시켰다. 이제 다음은 직접적인 렌더링을 하는 함수인 display 함수이다. 우선 6번을 통해 선택된 GL_MODELVIEW 매트릭스를 초기화 시키고 7번을 통해 카메라를 위치시키는 Viewing Transformation을 수행한다. gluLookAt( 눈의 위치 (x,y,z), 쳐다볼 위치(x,y,z), 카메라 위쪽 방향 (x,y,z))를 이용해 아주 간단하게 카메라를 위치시킨다. 그리고 8번은 물체를 그릴때 적용될 Transformation을 정의한 것인데 y 축방향의 좌표는 2배로 적용될 것이라는 의미다. 그리고 나서 렌더링에 들어간다...

쓰다보니 좀 부족한 설명이 있는데 각 함수들에 대한 설명은 OpenGL 매뉴얼이나 검색을 하면 쉽게 얻을 수 있어서 생략했다. 이런 과정을 통해서 렌더링이 일어난다는 것만 설명했다. 아무래도 가장 중요한건 OpenGL의 상태 변수는 Static 속성을 가진다는 것이 아닐까 싶다... 디버깅 프로그램을 이용해 현재 무슨 값을 가지는지 쉽게 볼 수 있다면 이것이 그리 중요하지 않을지 모르나 OpenGL에서는 현재의 상태를 확인하기 위해서는 별도의 함수를 매번 호출해야 하므로 쉽게 확인이 불가능하다. 고로 보여지지 않으므로 무슨 일이 벌어지는지 알기 어렵다. 그래서 새까만 화면이 나오더라도 뭐가 잘못된건지 알수가 없는 경우가 허다하다.

'OpenGL' 카테고리의 다른 글

Convex Hull, Concave Hull  (0) 2010.02.24
OpenGL 블렌딩  (3) 2010.02.24
glPolygonMode()와 glCullFace()  (0) 2010.02.19
OpenGL Rendering Pipeline  (0) 2010.02.18
OpenGL Shader를 이용해 YUV 420 포맷 색상을 RGB로 변경하기  (4) 2009.07.25
posted by 대갈장군
2010. 2. 19. 06:09 OpenGL
OpenGL에서 폴리곤 (다각형)은 앞면과 뒷면이 존재한다. glPolygonMode() 함수를 이용하면 앞면과 뒷면을 어떻게 그릴 것인지 설정할 수 있다.

glPolygonMode(GLenum face, GLenum mode); 
- face 인자로 올수 있는 것은 GL_FRONT_AND_BACK, GL_FRONT, GL_BACK 등이 있고 mode 인자로 올수 있는 것은 GL_POINT, GL_LINE, GL_FILL 등이 있다.

첫번째 인자로 앞면과 뒷면 혹은 앞뒤 둘 다에게 두번째 인자로 오는 mode를 적용하게 된다. 뭐, mode 인자로 올 수 있는 것들은 상식적으로 이해 가능한 놈들이다. 포인트로 그릴거냐 선으로 그릴거냐, 아니면 채워넣을 거냐...


 


첫번째 그림은 원뿔인데 정상적으로 보인다. 이 경우 glPolygonMode의 두번째 인자를 GL_FILL로 주었기 때문에 밑을 구성하는 원이 제대로 그려졌다. 헌데 만약 GL_LINE을 주게 되면 아래를 구성하는 원이 선으로 그려지게 되고 아래 그림처럼 뻥 뚫린것 처럼 보이게 된다.

또 다른 함수인 glCullFace는 앞면과 뒷면을 '파낼것' 인가 말것인가를 지정한다. '파낸다'라는 말이 우습게 들릴수도 있다... 나는 처음에 들었을때 코파냐... 라고 생각했었따. -_-

예를 들어 벽돌을 그린다고 해보자. 벽돌 안은 절대 보일 일이 없다. 고로 벽돌을 구성하는 6개의 폴리곤 (사각형) 들은 보여질 면만 그리면 된다. 안보여질 부분은 안그리는게 속도 향상에 도움이 된다. 고로 glCullFace 함수로 그걸 지정해준다 이말이다. 

다만 주의 할 것은 이 함수를 불러내서 사용하기 전에 반드시 glEnable(GL_CULL_FACE) 함수를 먼저 호출하여 '파내기' 상태를 YES로 변경해야 한다.

고로 예를 들어 보여지지 않을 후면을 모두 파내버리고 싶다면,
glEnable(GL_CULL_FACE);
glCullFace(GL_BACK);
이라 하면 오케 바리 바리 발바리..

한가지 짚고 넘어갈 점은 시계방향 (CW)와 반시계방향(CCW)에 대한 것인데 일반적으로 모든 폴리곤 삼각형은 반 시계 방향이 정면이다. 즉, CCW (Counterclock wise)가 Front Facing Polygon 이라는 말이다. 그래서 Vertex (점)을 공간상에 찍어서 사각형을 그려낼때 항상 반시계 방향으로 그려주게 된다.

하지만 '나는 시계 방향 주의자야!' 라고 하는 분들은 이것을 glFrontFace() 함수를 이용해 변경할 수 있으나, 필요하다면 사용하겠지만 최대한 피할 수 있다면 피하는게... 
posted by 대갈장군
2010. 2. 18. 05:21 OpenGL


OpenGL 렌더링 파이프 라인에 대해서...

1. Pixel Data / Vertex Data
3차원 공간의 임의의 점 X는 위치 정보 (x,y,z)를 가지고 색상 정보 (RGB)도 가질 것이다. 이것이 바로 Vertex Data와 Pixel Data다. 이 두 놈이 입력으로 들어간다.

2. Display Lists
Display Lists는 미리 그려 놓은 그림들의 목록이다. 자주 사용되는 물체 (그림)을 미리 그려놓고 필요할때 신속히 꺼내서 그려낼수 있는데 이것이 바로 Display List 다. 프로그래밍에서 가장 흔한 '저장시켜놨다가 빨리 불러오기' 기법인 셈이다. 

3. Evaluators
모든 도형들 (2차원 도형이건 3차원 도형이건) 은 점(vertex)으로 표현된다. 예를 들자면, 사각형이 점 4개로 표현되고 오각형은 6개 정육면체 (3차원 도형)도 점 8개로 표현된다. 곡선이나 평면들도 처음에는 몇 개의 점으로 표현되거나 함수로 표현되지만 Evaluator를 거치면서 이런 함수적인 표현을 '점의 표현'으로 바꿔준다. 뭐 결국 따지고 보면 두 개의 점 사이에 놓인 점들을 인터폴레이션을 이용해 생성해낸다는 의미 아닌가? 이때 적용하는 방법이 이른바 'Polynomial mapping' 인데 이것을 사용하면 표면의 Normal vector와 texture coordinates 그리고 색상등을 알수 있다.

4. Per-Vertex Operations
Vertex data 에 대한 다음 Step은 Per-Vertex Operation 인데 이놈은 Vertices를 Primitives로 바꿔주는 놈이다. 여기서 잘 보면 Vertices (여러개의 점들을) Primitives (직선 또는 삼각형 또는 사각형, 혹은 다각형) 으로 바꾼다는 의미다. 이때 일부 Vertex Data는  4 x 4 floating-point matrices를 이용해서 transformed 된다. 이 말이 어렵게 들릴지 모르나 사실은 쉬운 이야기다. 3차원 공간의 물체를 2차원 평면 (모니터 스크린)을 통해서 보려면 3차원 -> 2차원 변형이 필요한데 이때 사용되는 특별한 함수가 있다. 이 함수를 행렬로 표현하면 4 x 4 형태의 매트릭스가 된다. 고로 3차원 공간을 2차원 평면에 투영시키기 위한 일종의 함수 대입 과정일 뿐이다. 

이 단계에서 추가로 가능한 작업들이 몇가지 있는데 만약 텍스쳐가 사용되었다면 텍스쳐 좌표가 생성되고 투영되며 조명이 있다면 그것도 계산해야하고 물질 표면 속성이 설정되어 있다면 이것 역시 여기서 다 계산한다. 좀 많네...

5. Primitive Assembly
Clipping 은 Primitive Assembly의 주요 파트인데 이것은 안보이는 부분 잘라내기다. 이 과정이 끝나면 나오는 결과물은 완전한 Geometric Primitives 들인데 (3차원 도형들) 이 놈들은 2차원 평면에 표현되기 위해 적절하게 Transformed 된 상태이며 적절히 Clipped 된 상태이다. 게다가 각 포인트의 색상과 깊이 그리고 텍스쳐 좌표까지 가지고 있다. 고로 이놈들은 다음 단계인 Rasterization 과정에서 가이드 라인으로 사용된다.

6. Pixel Operation
자 Vertex Data 들이 3,4,5의 과정을 거치는 동안 Pixel Data 는 이른바 Pixel Operation 과정을 거치는데 시스템 메모리 상에 존재하는 Pixel들은 우선 Unpacking ( 풀어내는 과정)을 거치는데 이때 수많은 포맷들 중 하나의 형태로 풀어진다. 그런 다음 각종 처리 (Scaling, Biasing, Pixel mapping) 를 거치게 된다. 이 과정을 사용자가 모두 세부 설정을 할 수 있는 것들이다. 그런다음 그 결과물은 Texture Memory에 저장이 되거나 혹은 Rasterization Step 으로 보내지게 된다. 

7. Texture Assembly
이 과정은 말 그대로 텍스쳐 입히기이다. 뭐 별다른 설명은 지금으로서는 필요 없을 듯하다.

8. Rasterization
이 단어를 사전에서 찾아보면 '인쇄하기' 인데 대략 그 말이 맞다. OpenGL 적으로 표현하자면 지금까지 모은 도형 정보와 픽셀 정보를 Fragment로 인쇄한다. 이 Fragment는 하나의 픽셀 (화면상에 보여질 픽셀)에 대응되는 놈으로 각 Fragment는 색상과 깊이 값을 가진다. 

9. Fragment Operations
화면에 보여지기 위한 최종 단계로서 스크린과 같은 화면을 저장하는 Frame buffer에 저장하기에 앞서 여러 가지 계산 과정을 거쳐야 한다. 예를 들자면 Alpha 테스트 (투명도 테스트), Stencil 테스트 (스텐실 테스트), 깊이 테스트등등. 이런 일련의 테스트들을 사용자가 모두 정의하고 어떤 방식으로 계산할 것인지를 지정할 수 있다. 즉, 사용자 맘대로 출력 할 수 있다는 것이다.





posted by 대갈장군
2009. 7. 25. 03:00 OpenGL
일반 캠코더나 CCTV용 흑백 소형 카메라를 OpenGL에 그려주기 위해서는 YUV 포맷을 알아야 할 것이다. 나도 몰랐지만 일반적으로 캠코더나 CCTV용 소형 카메라는 YUV 420이라는 포맷을 이용해서 색상을 표현한다.

이 YUV 포맷은 네이버에서 검색해보면 참 잘 설명해준 사이트가 있으니 바로 http://cafe.naver.com/camuser.cafe?iframe_url=/ArticleRead.nhn%3Farticleid=234 다.

네이버에서 YUV 치면 링크가 뜰것이다. 아무튼 이 놈은 Y라는 흑/백 데이터와 U와 V의 색상정보로 이루어 지는데 이 U와 V라는 놈은 특이하게도 Y가 가지는 데이터의 1/4 크기이다. 즉, Y에 대해서 1/2 높이와 1/2 넓이를 가지는 정보다.

그래서 단순히 생각하기에도 이놈들은 분명 압축전송에 유리할 것이다. 왜냐면 RGB는 R, G, B를 표현하기 위해 모두 같은 크기의 데이터 영역을 사용하지만 YUV는 절반만으로도 그것을 표현하기 때문이다... 

자, 이 YUV 420을 OpenGL Shader를 이용하여 RGB 포멧으로 렌더링 하고자 한다면 우선 구글에서 YUV to RGB shader라고 쳐보자.. 그럼 분명히 http://www.fourcc.org/fccyvrgb.php가 뜰것이다. 

글을 차례 대로 읽다보면 Code for a Sample Program이라는 링크가 있고 그 링크가 바로 여기다. http://www.fourcc.org/source/YUV420P-OpenGL-GLSLang.c 

이것이 바로 Shader를 이용한 YUV 420 을 RGB로 바꾸는 것인데... 몇가지 내가 변경한 것들이 있다. 우선 Shader Program 자체를 살펴보면,
char *FProgram= "uniform sampler2DRect Ytex;\n" "uniform sampler2DRect Utex,Vtex;\n" "void main(void) {\n" " float nx,ny,r,g,b,y,u,v;\n" " vec4 txl,ux,vx;" " nx=gl_TexCoord[0].x;\n" " ny=576.0-gl_TexCoord[0].y;\n" " y=texture2DRect(Ytex,vec2(nx,ny)).r;\n" " u=texture2DRect(Utex,vec2(nx/2.0,ny/2.0)).r;\n" " v=texture2DRect(Vtex,vec2(nx/2.0,ny/2.0)).r;\n" " y=1.1643*(y-0.0625);\n" " u=u-0.5;\n" " v=v-0.5;\n" " r=y+1.5958*v;\n" " g=y-0.39173*u-0.81290*v;\n" " b=y+2.017*u;\n" " gl_FragColor=vec4(r,g,b,1.0);\n" "}\n";
위 코드에서 주의 해야 할 부분은 빨간색으로 표시된 576.0 부분이다. 바로 저 값은 일종의 Magic Number인데 바로 입력으로 들어가는 YUV 그림의 높이 값이다.
문제는 이 높이 값은 입력으로 들어오는 신호가 고정된 값이면 상관없지만 사용자가 임의로 결정하는 경우에는 반드시 바뀌어야 한다. 나의 경우에는 입력으로 들어오는 비디오 신호가 실시간 인데다가 NTSC와 PAL 모두를 고려해야 했으므로 높이값이 일정하지 않았다.
그래서 추가로 shader 내부에 변수값을 하나 추가하여 shader 외부에서 값을 입력 신호의 비디오 높이에 따라서 적당히 변경하였다.
김형준님의 홈페이지에 가면 참 좋은 정보를 많이 얻을수 있다.. 특히 요즘 쉐이더에 대한 강의가 많이 올라와 있어 도움이 많이 되었다. http://www.gisdeveloper.co.kr/category/Programming/OpenGL?page=3
나머지 코드를 살펴보면, 우선 쉐이더 프로그램을 초기화하고 불러들여 컴파일 하는 과정이 있다.
/* 쉐이더 프로그램 핸들 생성 */ PHandle=glCreateProgramObjectARB(); FSHandle=glCreateShaderObjectARB(GL_FRAGMENT_SHADER_ARB); /* 쉐이터 컴파일 */ glShaderSourceARB(FSHandle,1,&FProgram,NULL); glCompileShaderARB(FSHandle); /* 에러가 있으면 출력해서 보여주기 */ glGetObjectParameterivARB(FSHandle,GL_OBJECT_COMPILE_STATUS_ARB,&i); s=malloc(32768); glGetInfoLogARB(FSHandle,32768,NULL,s); printf("Compile Log: %s\n", s); free(s); /* 프로그램 오브젝트 생성 */ glAttachObjectARB(PHandle,FSHandle); glLinkProgramARB(PHandle); /* 링크시 에러있으면 보여주기 */ s=malloc(32768); glGetInfoLogARB(PHandle,32768,NULL,s); printf("Link Log: %s\n", s); free(s); /* 이제 쉐이더 사용하기 */ glUseProgramObjectARB(PHandle);

.... (렌더링 하기 by 쉐이더)

/* 쉐이더 분리하기 */
glUseProgramObjectARB(0);

사실 중요한 부분이 생략되어 있는데 이런 모든 확장 기능을 사용하기 전에 OpenGL 버전 체크 및 확장 버전 체크 부분이 생략되어 있다. 이것은 여러 사이트에서 쉽게 찾을 수 있으니 일단은 넘어가지만 나도 추가로 나중에 이와 관련해 문서를 하나 만들어 두는 것이 좋을 듯 하다.
아무튼, 위 내용은 초기화 및 컴파일 그리고 사용 그리고 마지막으로 연결 해제 순서로 되어 있어 이해하기 쉽다. 결국 쉐이더도 객체 지향성을 띄기 때문에 참 사용이 간편하고 용이하다.
이제 마지막으로 렌더링을 어떻게 하느냐 파트만 남았다. 이부분이 어쩌면 제일 중요한것 같다.. ^^
/* This might not be required, but should not hurt. */ glEnable(GL_TEXTURE_2D); /* Select texture unit 1 as the active unit and bind the U texture. */ glActiveTexture(GL_TEXTURE1); i=glGetUniformLocationARB(PHandle,"Utex"); glUniform1iARB(i,1); /* Bind Utex to texture unit 1 */ glBindTexture(GL_TEXTURE_RECTANGLE_NV,1); glTexParameteri(GL_TEXTURE_RECTANGLE_NV,GL_TEXTURE_MAG_FILTER,GL_LINEAR); glTexParameteri(GL_TEXTURE_RECTANGLE_NV,GL_TEXTURE_MIN_FILTER,GL_LINEAR); glTexEnvf(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_DECAL); glTexImage2D(GL_TEXTURE_RECTANGLE_NV,0,GL_LUMINANCE,376,288,0,GL_LUMINANCE,GL_UNSIGNED_BYTE,Utex); /* Select texture unit 2 as the active unit and bind the V texture. */ glActiveTexture(GL_TEXTURE2); i=glGetUniformLocationARB(PHandle,"Vtex"); glBindTexture(GL_TEXTURE_RECTANGLE_NV,2); glUniform1iARB(i,2); /* Bind Vtext to texture unit 2 */ glTexParameteri(GL_TEXTURE_RECTANGLE_NV,GL_TEXTURE_MAG_FILTER,GL_LINEAR); glTexParameteri(GL_TEXTURE_RECTANGLE_NV,GL_TEXTURE_MIN_FILTER,GL_LINEAR); glTexEnvf(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_DECAL); glTexImage2D(GL_TEXTURE_RECTANGLE_NV,0,GL_LUMINANCE,376,288,0,GL_LUMINANCE,GL_UNSIGNED_BYTE,Vtex); /* Select texture unit 0 as the active unit and bind the Y texture. */ glActiveTexture(GL_TEXTURE0); i=glGetUniformLocationARB(PHandle,"Ytex"); glUniform1iARB(i,0); /* Bind Ytex to texture unit 0 */ glBindTexture(GL_TEXTURE_RECTANGLE_NV,3); glTexParameteri(GL_TEXTURE_RECTANGLE_NV,GL_TEXTURE_MAG_FILTER,GL_LINEAR); glTexParameteri(GL_TEXTURE_RECTANGLE_NV,GL_TEXTURE_MIN_FILTER,GL_LINEAR); glTexEnvf(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_DECAL); glTexImage2D(GL_TEXTURE_RECTANGLE_NV,0,GL_LUMINANCE,752,576,0,GL_LUMINANCE,GL_UNSIGNED_BYTE,Ytex);
i=glGetUniformLocationARB(PHandle,"Utex"); 는 PHandle이 가리키는 쉐이더 프로그램에서 Utex라는 변수를 찾아서 그 고유 값을 i로 돌려준다. i는 일종의 포인터인 셈이다.
멀티 텍스쳐링을 이용하여 YUV를 RGB로 바꾸기 때문에 3개의 텍스쳐가 필요한데 바로 Y, U, V 세가지 텍스쳐가 필요하다. 이 각각의 쉐이더 내부의 텍스쳐와 쉐이더 외부의 YUV 이미지의 링크 과정이 필요한데 바로 위의 과정이 그것을 보여주고 있다.
참고로 나의 경우에는 실시간 입력이기 때문에 위와 같이 텍스쳐를 생성하여 연결하지 않았고 미리 만들어둔 텍스쳐를 바인딩만 하였다. 물론 이 텍스쳐는 실시간으로 데이터가 갱신되므로 함부로 데이터를 건드리면 안된다. (Lock과 Unlock이 필요)
마지막으로 이상했던 점은 쉐이더 자체가 텍스쳐 좌표를 integer 범위 (576과 같은) 를 사용하였기 때문에 위와 같이 쉐이더를 통과한 후 3차원 공간에서 맵핑을 하고자 한다면 glTexCoord2i() 함수를 사용해야 한다는 점인것 같다.
나는 늘 사용하던 glTexCoord3f()를 생각없이 사용했었는데 그것을 사용하면 제대로 계산된 텍스쳐 좌표가 나오질 않는다는 점을 꼭 알아두자...
부족한 설명이 많았지만 일단 여기까지만 적어두고 까먹을만 하면 읽어봐야지.. ㅎㅎ








'OpenGL' 카테고리의 다른 글

glPolygonMode()와 glCullFace()  (0) 2010.02.19
OpenGL Rendering Pipeline  (0) 2010.02.18
OpenGL Rendering Context v.s. Windows Device Context  (0) 2009.03.12
Oh my god... 이게 사진이 아니라고?  (0) 2008.01.18
glRasterPos와 glWindowPos  (1) 2007.12.01
posted by 대갈장군
2009. 3. 12. 03:41 OpenGL
OpenGL을 Windows API 환경에서 작성하다 보면 Rendering Context 라는 말을 듣게 된다.

처음에 그 단어를 들었을때 '뭐여? 저것이?' 라고 생각했던 기억이 난다. 알고보니 이 Rendering Context 는 Windows API의 Device Context에 대응되는 OpenGL의 Device Context인 거였다.

바로 어제 작성한 글을 보면 Device Context가 무엇인지 말하고 있는데 DC는 특정한 하나의 윈도우의 다양한 그리기 설정을 저장하고 있는 구조체였다.

이런 구조체가 필요한 이유는 윈도우에서 윈도우 마다 서로 다른 형태의 그림과 글자, 선등을 그려줘야 하기 때문이라고 했다.

OpenGL을 Windows 환경에서 사용할 경우 주로 GLUT 를 이용해서 단 하나의 창으로 모든 렌더링을 보여주곤 하는데 이는 사실 여러개의 윈도우를 동시에 사용하는 멀티 태스킹의 윈도우 환경에는 조금 모순적이다.

고로, 지금 나의 경우처럼 Window API를 이용해 OpenGL 프로그램을 작성할 경우, 내가 렌더링한 OpenGL의 결과 화면 (매 프레임)을 특정한 윈도우에 그려야 하는데 이때 어떤 윈도우에 OpenGL을 그려 넣어야 하나 하는 문제가 생긴다.

따라서 Windows 환경에서는 OpenGL 명령들을 (그림 그려내는 명령들) 수행하기 전에 현재의 렌더링 창 (그려지는 놈이 보여질 창)을 지정하는 방법이 필요하다. 바로 그 정보를 저장하는 놈이 Rendering Context 이다. 결국 이 Rendering Context 는 Windows의 Device Context이고 다만 OpenGL에서는 조금 다른 형태의 구조체로 저장이 된다는 점이다. Rendering Context 는 OpenGL 렌더링을 위한 추가적인 멤버 변수들을 가지고 있을 것이다.

Windows 환경 위에서 작성하는 OpenGL은 반드시 하나 이상의 윈도우를 가져야 하므로 최소한 하나의 유효한 DC는 소유하고 있을 것이다.

이 유효한 (유효하다는 의미는 현재의 graphic device와 호환이 되는 pixel format 을 가진 이라는 의미) DC를 이용해서 OpenGL의 Rendering Context 를 얻어온다. 그 함수가 바로 wglCreateContext() 이다.

이 함수는 올바른 픽셀 포맷을 가진 윈도우 창의 DC를 입력으로 받아서 그와 호화되는 형식의 Rendering Context 를 출력으로 돌려준다.

HGLRC hRC;
HDC hDC;
hRC = wglCreateContext(hDC);


특별한 점은 일반적인 윈도우 어플리케이션의 경우에는 여러개의 Context를 가질수 있지만 OpenGL의 경우에는 작업을 할 목적지 윈도우를 정확하게 알아야 하므로 한개의 스레드당 하나의 Rendering Context만 가질수 있다는 점이다.

wglCreateContext() 함수를 이용해서 Rendering Context를 얻은다음 wglMakeCurrent(HDC hDC, HGLRC hRC); 함수를 호출하게 되면 입력 인자로 들어온 Device Context와 Rendering Context를 연결하게 되어 특정한 윈도우 창과 연결된다.

Rendering Context를 지우기 위해서는 wglMakeCurrent(hDC, NULL)을 호출해서 현재 선택된 Rendering Context를 해제하고 wglDeleteContext(hRC)를 호출해서 해제한 Rendering Context를 삭제하면 된다.

주의 할 것은 위의 함수를 호출해서 Rendering Context를 제거하기 전에 Display List와 Texture 개체를 삭제해야 한다.


posted by 대갈장군
2008. 1. 18. 06:42 OpenGL

방금 구글에서 3차원 물체를 검색하던중 아주 반가운 사진을 보았다. 바로 송혜교 사진이다.

그런데...... 두둥!!

이게 사진이 아니고 CG란다!! 허걱! 썌빨간 거짓말 입니다!!! 라고 순간 피식 했다...

허걱!!!! x 2
그런데 이 그림의 출처와 누가 이런 거짓말을 하는지 살펴보다 보니 이것이 정말로 CG로 만들어진 (MAX로 만듬) 것임을 알았다.... (충격 받음)

이 3차원 그래픽을 만든 사람은 인도네시아에 사는 사람인데 궁금하다면 아래 링크로 들어가보면 된다. 어떻게 만들었는지 간단하게 설명되어 있다.

http://www.cgarena.com/freestuff/tutorials/max/songhyekyo/index3.html

사용자 삽입 이미지


또봐도 사진같다... 후덜덜
posted by 대갈장군
2007. 12. 1. 01:20 OpenGL

glRasterPos 함수와 glWindowPos 두 함수는 얼핏 보기에는 비슷해 보인다. 하지만 큰 차이점이 있다.


먼저 내가 어떤 상황에서 두 함수를 사용할 일이 있었는지를 설명해 보자.


내가 현재 작성중인 프로그램에서는 수십장의 화면을 미리 만들어서 glReadPixels를 이용해서  메모리에 저장해


놓았다가 그것을 glDrawPixels를 이용해서 빠르게 보여주는 것이다.


뭐 물론 실시간을 보여주면 안되냐는 간단한 질문을 할 수가 있다..=.=


하지만 문제는 하나의 화면 렌더링에 걸리는 시간이 크기 때문에 그럴수 없다는 것이다.


그래서 glDrawPixels를 실행할때는 반드시 현재의 Raster 위치 (즉, 그림을 그려넣기 시작할 왼쪽 아래 시작 부분)이 어딘지를 확인해야 한다.


만약 그 값이 minus이거나 invalid 한 값이라면 화면에는 당연히 아무것도 그려지지 않는다.


처음에 나는 단순하게 glDrawPixels 명령 바로 위에 glRasterPos2i(0, 0)을 했다.


단순히 생각해서 Raster 위치를 window coordinates (사용자가 보는 window의 왼쪽 제일 구석 지점)의 0, 0으로 이동시키고자 했던 것이다.


하지만 중요한 점을 간과했으니 이놈은 화면에 존재하는 vertex처럼 matrix에 의해 변환이 된다는 것이다.


고로 내가 이전에 model_view matrix를 사용했고 loadidentity로 초기화 시켜 놓지 않았다면 이전에 남아있는 행렬 변환이 여기서 적용되게 되는 것이다.


그 결과 값이 터무니 없는 값으로 나오게 되었고 화면에는 깜깜한 배경외에는 아무것도 보이지 않았다.....


나중에 알았지만 이 함수를 대체할수 있는것이 OpenGL version 1.4이후에 나왔으니 바로, glWIndowPos 함수다.


이것은 행렬 변환, 치환 이런 것에 전혀 개의치 않고 현재의 윈도우 좌표계를 절대적인 값으로 적용하여


raster의 위치를 잡는다. 고로 이 함수로 glWindowPos3f(0.0, 0.0, 0.0)을 하면 현재 내 출력 윈도우의


좌표계에서 무조건 (model view matrix고 나발이고) 0, 0을 기준으로 그림을 그려 넣게 된다...


두개중에 어떤것을 사용해야 할지는 경우에 따라 다르겠지만 중요한 사실은 두 함수는 행렬 변환 matrix에 영향을 받느냐 혹은 안받느냐가 가장 큰 차이점이라는 것이다.... 명심하자.

'OpenGL' 카테고리의 다른 글

OpenGL Rendering Context v.s. Windows Device Context  (0) 2009.03.12
Oh my god... 이게 사진이 아니라고?  (0) 2008.01.18
OpenGL 에서 조명 설정에 관하여...  (0) 2007.11.16
Stencil Buffer의 사용법  (6) 2007.09.22
OpenGL  (0) 2007.09.19
posted by 대갈장군
2007. 11. 16. 01:21 OpenGL

OpenGL을 사용하면서 조명을 사용할 경우 알아둬야 할 몇가지 중요한 포인트가 있다.


단순히 예전에는 빛을 켜면 걍 물체의 현재 색상, 즉 파란공이면 파랗게 빨강공이면 빨간색으로 나올줄 알았다. 무식한 발상이 아닐 수 없다.


OpenGL에서는 물체가 어떤 색으로 나타나게 될지를 결정하는 두가지 중요한 요소가 있으니 바로 조명의 색상과 물체의 반사색 이다.


조명의 색상이 중요한 것은 당연지사. 파란불이 비추어지면 물체가 흰색이라해도 푸르스름할 것이고 만약 물체가 빨간색이라면 광학 원리상 물체는 빨간색 빛 이외에는 다 흡수하므로 파란 색 빛을 다 흡수하여 검은 물체로 보여질 것이다.


다음으로 물체의 반사색인데 내가 물체의 vertex 마다 어떤 색상을 지정했건 간에 glMaterial*() 함수를 이용해 물체의 반사색을 다른 걸로 설정하면 그 정의된 색으로 입사하는 빛을 반사한다는 것이다.


또한 OpenGL에는 3가지 빛의 속성을 설정 가능한데 우선 주변광 (ambient light)로써 이것은 우리 주위에 은근히 존재하는 빛을 말한다. 형광등에서 나온 빛이 온 방을 환하게 하는 그런 분위기의 빛이다.


다음으로 분산광인데 (diffuse light) 이것은 반사되어 퍼져나가는 색상을 말한다. 이 색상이 바로 물체의 색상을 결정짓는데 가장 큰 역활을 한다. 현실과 유사하게 만들기 위해서는 조명의 주변광은 적게 주고 조명의 분산광은 적당한 색으로 주고 (흰색에 가깝게)  물체의 분산광을 자신이 원하는 색상으로 만들면 물체가 자신이 원하는 색상으로 나타나게 만든다.


그리고 마지막 빛으로 반사광(specular light)인데 반사광이라기보단 highlighting 색상이 더 쉽게 이해될것 같다. 즉 물체에 입사하는 빛을 강하게 (분산광처럼 퍼져나가는 빛이 아니라) 반사하는 색상을 말한다. 은이나 금 같은 물체에 빛을 비추면 일부분이 강하게 반사되면서 눈이 부시게 되는 현상이 나타나는데 바로 그 부분의 색상을 결정하는 것이다. 이것은 당연히 들어오는 빛의 색을 반사하는 것이 현실과 유사하므로 주로 입사광의 색상을 따라간다.


마지막으로 한가지 중요한 사실은 내가 원래 설정한 물체의 색상, 즉 각 vertex마다 설정한 색상을 그대로 보여주기 위해서는 두가지 방법이 있다.


1) 빛을 설정하지 않는다. 조명을 설치하지 말라는 말. 그러면 원래 색상이 그대로 나옴

2) 만약 조명을 설치했다면 glEnable(GL_COLOR_MATERIAL)을 활성화 시켜서 물체의 색상이 나오도록 한다.

개인적으로 2번이 더 쉽다고 생각되는데 처리 시간이 1번보다 더 걸릴것으로 예상된다.



'OpenGL' 카테고리의 다른 글

OpenGL Rendering Context v.s. Windows Device Context  (0) 2009.03.12
Oh my god... 이게 사진이 아니라고?  (0) 2008.01.18
glRasterPos와 glWindowPos  (1) 2007.12.01
Stencil Buffer의 사용법  (6) 2007.09.22
OpenGL  (0) 2007.09.19
posted by 대갈장군
2007. 9. 22. 05:10 OpenGL

현재 내가 수행중이 프로젝트는 절묘한 Stencil Buffer 사용을 요구한다.

Framebuffer Object의 Stencil buffer를 사용하려고 하다보니 생각지 못한 문제들에 막힌적이 있었다.

우선 Framebuffer Object 사용시 Stencil Buffer의 세부 규약에 대해서 몰라서 어떻게 세팅하는지 몰랐고

다른 한가지는 Nvidia 그래픽 카드에서는 Nvidia에서만 허용되는 특수한 타입이 있는데 그것을 반드시 써야만 Framebuffer Object의 Stencil Buffer를 아무 에러없이 쓸 수 있다는 점이었다.

이것은 다음에 자세히 알아보도록 하고 오늘은 Stencil Buffer를 어떻게 정하고 또 어떻게 사용하는지만 이야기 해보자.

우선 Stencil Buffer를 정의하는 것부터 시작한다. Stencil Buffer를 어디서 정의해야 하는가 하는 의문부터 해결하자. Stencil Buffer라 함은 사용자가 원하는 부분만 그려내고 싶을때 사용하는 기술이다.

자동차 게임을 예로 들어보면 Need for speed에서 자동차 내부에서 보는 시점이 있다. 그 때에는 차 앞쪽과 측면 유리를 통해서만 외부의 세상이 보여질 뿐이다. 바로 이럴때 Stencil Buffer를 사용한다. Full Screen으로 렌더링 할 필요가 없고 사용자가 원하는 부분만 보여주고 싶을때 이다.

그렇다면 당연히 외부의 세상을 그리기 전에 Stencil Buffer를 정의해야 한다. 외부의 모든 물체를 다 그려놓고 보이지 않는 부분을 삭제하는 것과 보여지지 않는 부분을 빼고 그리는 것 둘중에 어떤것이 더 나을까 라고 스스로에게 질문해보자.

그래서 물체를 그리기 전에 다음과 같은 명령들을 내려줘야 한다.

  glColorMask(0, 0, 0, 0); // 1
  glEnable(GL_STENCIL_TEST); // 2
  glStencilFunc(GL_ALWAYS, 1, 1); // 3
  glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); // 4
  glDisable(GL_DEPTH_TEST); // 5

  glColor3f(0.0f, 1.0f, 0.0f); // 6
  glPushMatrix(); // 7
  glTranslatef(VP_x, VP_y, -10.0f); // 8
  glCallList(MakeMask); // 9
  glPopMatrix(); // 10
  glDisable(GL_STENCIL_TEST); // 11

1번은 Color Buffer(화면에 보여질 모습이 저장되는 버퍼)에 어떠한 색깔도 그리지 말라는 의미인데 이것을 하는 이유는 현재 우리는 Stencil Buffer에 그림을 그리고자 하는 것이지 Color Buffer에 그림을 그리고자 하는 것이 아니기 때문이다. Stencil Buffer에 그리고자 하는 그림이 화면에 그대로 보인다면 당연히 이상한 그림이 나올것이다.

2번과 5번 11번은 굳이 설명하지 않아도 알것이다. Stencil 기능을 켜고 끄며 혹은 Depth test를 끄는 기능들을 한다.

이제 3번과 4번이 중요한데 이거 상당히 복잡해서 나도 처음에 볼때는 많이 애먹었다. 3번 함수는 Stencil Buffer에 값을 쓸때 어떤 경우에 쓰고 또 어떤 값을 쓸것인가를 설정하는 함수다. 아시다시피 Stencil buffer는 단순히 생각해서 0과 1로 이루어진 bitplane이다. 즉 1이면 통과 0이면 통과 실패이다. 여기서 이해하고 넘어가야 할것은 stencil buffer를 정의할 때는 일일이 각각의 화소 위치에 대해서 1과 0을 세팅하는 것이 아니고 내가 원하는 위치에 물체를 그려 넣음으로써 그 부분은 1로 채워지게 만드는 것이다. 원래 Stencil Buffer는 모두 0으로 가득 차 있는데 내가 보여주고 싶은 부분에만 그림을 그려 넣으면 3번과 4번 함수에 의해서 그 부분의 Stencil buffer 값은 모두 1로 바뀌게 되는 것이다.

의미적으로만 설명을 했는데 3번과 4번 함수는 인터넷에서 man page를 찾아보면 금방 이해할 수 있을 것이다. 중요한 것은 이 단계에서는 Stencil buffer에 우리가 보여주고 싶은 부분만 그린다는 것이다.

6,7,8,9,10은 모두 물체를 그리는 명령이다. 다 설명하자면 너무 길고 어쨌든 줄여서 저 명령들은 내가 원하는 위치에 내가 원하는 물체를 그리는 것이다. 고로 그 위치의 Stencil Buffer 값은 모두 1로 변하게 된다. 자, 여기까지 하면 이제 비로서 Stencil Buffer를 사용할 수 있는 단계에 온것이다. 이제 고작 사용할 수 있는 단계에 온것이다. 그렇다면 어떻게 사용하느냐? 오히려 사용하는 것이 더 쉽다.

    glEnable(GL_STENCIL_TEST);
    glColorMask(1,1,1,1);
    glStencilFunc(GL_EQUAL, 1, 1);
    glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
 
    glPushMatrix();
    glCallList(MakeSpaceShip);
    glPopMatrix();
 
    glDisable(GL_STENCIL_TEST);

위의 일련의 명령이 사용하는 법이다. 우선 Stencil Test를 Enable한다음 glColorMask()함수를 이용해서 지금부터 그리는 물체를 color buffer에 넣으라는 명령을 내린다. 당연히 그래야 한다. 왜? 지금부터는 사용자에게 보여줄 화면을 그리는 것이기 때문이다. 그런다음 설정함수인 glStencilFunc와 glStencilOp가 나오는데 이것은 아까 본것과 조금 다를뿐 거의 같다. 함수에 대한 설명은 여기서 다 하기 힘드니 함수 설명을 찾아보고 읽으면 금방 이해가 갈것이다. 저 두함수가 하는 역활은 들어오는 각각의 화소에 대한 값을 stencil buffer의 해당 위치의 값과 비교해서 1이면 통과 0이면 통과 실패를 시키는 것이고 추가적으로 stencil buffer에 저장되어 있는 값은 변경하지 말라는 의미이다. 당연히 stencill buffer를 변경해서는 안된다.

그리고 그 다음 좌악 나오는 명령들은 이제 진짜 그림을 그리자는 명령들이다.

쉽게 쉽게 그리고 간단하게만 썼는데 나중에 내가 참고하려고 할때 다시 보면 이해가 잘 될지 모르겠다.ㅋㅋ
팍팍 줄여서 단계별로 설명하자면,
1. 스텐실 버퍼에 내가 보여주고자 하는 부분만 그린다.
2. 실제로 물체를 그리고자 할때에는 스텐실 버퍼 설정함수를 조작하여 스텐실 버퍼가 1의 값을 가지는 곳만 그림이 그려지도록 한다.

아주 간단하게 써보았다. :)


'OpenGL' 카테고리의 다른 글

OpenGL Rendering Context v.s. Windows Device Context  (0) 2009.03.12
Oh my god... 이게 사진이 아니라고?  (0) 2008.01.18
glRasterPos와 glWindowPos  (1) 2007.12.01
OpenGL 에서 조명 설정에 관하여...  (0) 2007.11.16
OpenGL  (0) 2007.09.19
posted by 대갈장군
2007. 9. 19. 04:15 OpenGL

OpenGL... 처음 들었을때는 심히 막연했던 단어가 아닌가 싶다.

프로그래밍에 대해 그닥 자신이 없는지라 더더욱 그랬다. 처음에는 OpenGL이 단순히 프로그래밍 언어라고 생각했었다. 하지만 알고보니 Library 였던 것이다. -_-

하지만... 차라리 프로그래밍 언어였으면 좋겠다라고 생각한 적이 있을 정도로 갑갑한 적도 있었다. 아예 프로그래밍 언어라면 여러가지 면에서 설명이나 구현이 더 일관성이 있어 질 것이라고 생각했기 때문이다.

뭐 지금은 그럴 필요가 왜 없는지 대충 감이 오긴 오지만 아직 공부를 더 해야....

오픈GL 입문서라 불리는 Red book을 읽다보니 제법 재미있다는 것을 알았다. '오 이거 신기한데~' 랄까..

나처럼 영상 분야를 공부하는 사람이라면 OpenGL이야 말로 나의 상상을 펼치게 만들어주는 날개와도 같다는 생각이 든다.

이론과 광학 장비로는 절대 풀수 없는 문제를 OpenGL을 통해 구현해 내는 것을 보면 신기하기 그지 없다.

그래서~ OpenGL 목록을 만들었고 내가 사용했던 혹은 사용할 유용한 프로그램 Tip이라든가 조그만 도움말정도의 글을 적어둘까 한다.

이제 겨우 시작이긴 하지만 재미있다고 느끼는 일이 생겨서 다행이다. :)

푸헐헐...

posted by 대갈장군
prev 1 next