블로그 이미지
대갈장군

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
31

Notice

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 대갈장군