/* ---- 스텐실 테스트를 활성화 시킨 후부터는 Frame Buffer 에 쓰는 작업이 Stencil Buffer 에도 그대로 반영된다 ---- */
// 스텐실 버퍼내의 값과 두번째 인자와 세번째 인자를 And 연산한 값을 비교값을 비교해 첫번째 인자인 조건에 맞춰 스텐실 테스트를 통과시킬지 지정
glStencilFunc(GL_NEVER, 0, 0);
/* GL_NEVER : 어떠한 Fragment(pixel)도 Test를 통과하지 못함. 두번째 세번째 매개변수 값이 뭐가 되던지 아무것도 그려지지 않게 됨
GL_ALWAYS : 모든 값 통과. 즉 두번째 세번째 매개변수와 상관없이 화면에 그리고자 하는 그림이 그대로 보임.
GL_LESS
GL_LEQUAL
GL_EQUAL
GL_NOTEQUAL : 스텐실 버퍼내의 값과 비교값이 같지 않을 경우 통과. 여기서는 (GL_NOTEQUAL, 0, 0) 도 같은 의미가 된다. 초기값으로 스텐실 버퍼를 모두 0으로 채웠기 때문에 0이 아닌 경우가 없어 모든 스텐실 테스팅이 실패하기 때문이다.
*/
// 스텐실 버퍼에 접근해 버퍼내의 값을 변경할 조건을 설정하는 부분
glStencilOp(GL_INCR, GL_INCR, GL_INCR);
/* 첫번째 인자는 스텐실 테스트가 실패 => 스텐실 버퍼의 다음 동작 정의
두번째 인자는 스텐실 테스트 성공, depth 테스트 실패 => 스텐실 버퍼의 다음 동작 정의
세번째 인자는 스텐실 테스트, depth 테스트 모두 성공 => 스텐실 버퍼의 다음 동작 정의
GL_KEEP : 스텐실 버퍼 내의 값을 그대로 유지
GL_ZERO
GL_REPLACE
GL_INCR : 스텐실 버퍼에 있는 값을 하나 증가시킨다. 여기서는 GL_NEVER,0,0 으로 했기 때문에 모든 스텐실 테스트는 실패하므로 첫번째 인자인 GL_INCR이 동작하고, 화면에 보이지는 않지만 해당 위치의 버퍼 내용만 0에서 1로 바뀌게 된다. (종이에 구멍이 뚫린다는 개념??)
위 예제는 Stencil Test 를 이용해 특정 부분을 제외한 채 Rendering하는 예제다.
하여간 차근차근 살펴보자.
glClearStencil(0.0f)함수를 통해 Clear 할 Stencil Buffer의 초기값을 지정해주고
glClear(GL_COLOR_BUFFER | GL_STENCIL_BUFFER_BIT)함수를 호출함으로써 실제로
위에서 지정한 값으로 Stencil Buffer를 채운다.
그리고 나서 Stencil TEST를 활성화 시킨다. ->glEnable(GL_STENCIL_TEST)
다음은 Stencil Test 방법을 지정하는것인데
여기서 알아야 할 중요한 부분은
Stencil Test를 활성화 시킨 후 부터는 Frame Buffer 에 쓰는 작업이
Stencil Buffer에도그대로 반영된다는 것이다.
일단 그렇게 알고 계속 가보자.
glStencilFunc(GL_NEVER,0x0,0x0)
glStencilOp(GL_INCR , GL_INCR , GL_INCR);
glStencilFunc(...)함수는 Stencil Buffer 내의 값과 비교값(두번째 인자)를 비교해
조건(첫번째 인자)과 맞다면 Stencil Test를 통과하게끔 하기 위해 조건과 비교값을 지정한다.
그러나 조건 방식인GL_NEVER이므로어떠한 Fragment(pixel)도 Test를 통과 하지 못한다.
그런데 아래 함수 glStencilOp의 첫번째 인자, 즉 Stencil Test를 통과하지 못했을때
Stencil Buffer에 적용되는 변경 방법은 GL_INCR, Buffer내의 해당 값을 증가 시키는 것이다.
이게 뭔 말이냐면 일반적인 Rendering 즉 Frame Buffer에 뭔가를 그릴려 시도한다해도
현 상태에서 무조건 Stencil TEST가 실패로 간주되므로 Frame Buffer에 아무 것도 그려지지
않고 그 Frame Buffer의 대응되는 동일 위치의 Stencil Buffer내의 값이 증감된다는 말이다.
glBegin(GL_LINE_STRIP) 과 glEnd() 사이의 Loop 는 서로 지름이 다른 동심원을 그린다.
이때 위의 조건에 의해 Frame Buffer에는 어떤 Pixel(Fragment)도 써지지 못하지만
Stencil Buffer에는 아마도 Frame Buffer에 쓰려던 형태(값은 다르다. 0x1 )가 그대로 써질 것이다.
바로 여기까지가 Stencil Pattern 즉, Mask를 만든것이다.
<Frame Buffer><Stencil Buffer>
<A. glClear() 함수를 통해 초기화된 상태>
<B. 동심원을 Rendering 한 후 >
<C. 사각형을 Rendring 한 후 >
여기서 다시 Stencil TEST 방식이 변경됐는데 이번엔
참조 방식이GL_NOTEQUAL이고 비교 값이0x1이됐다.
풀어서 얘기하자면 Frame Buffer에 무언가를 그릴시에
Stencil Buffer내에 0x1이 아닌 위치에 Pixel들만 Test가 통과 되게 설정했다는것이다.
이런 상태에서 Rectf(...) 함수를 이용해 사각형을 그리면 Stencil Buffer에 그려진
동심원의 선 부분을 제외한 곳이 그려질것이다.
반대로 Stencil Buffer에 정의된 부분만 Frame Buffer에 그리고자 한다면
마지막 glStencilFunc(...)의 첫번째 인자를 GL_EQUAL 로 바꾸면 될것이다.
하여간 정리하자면
Stencil Buffer를 이용해 특정 부분만 Rendering 하고자 하는 방법은 다음과 같다.
1. Stencil TEST 를 활성화 시킨다.
2. 일단 모든 Stencil TEST 실패하게 한상태에서 Mask 형태 Rendering 한다.
->결국, 이것은 Frame Buffer가 아닌 Stencil Buffer에 그려진다.
3. Stencil Test Option을 변경한뒤 객체를 Rendering 한다.
=======================
=======================
=======================
출처: http://202psj.tistory.com/1033
I'm working on a game for the iPhone that has a drawing/paint mechanic involved and I'm having problems trying to create a tool that would erase things already painted.
The main problem is that the background being painted on is not a solid color but a static image or animation. I've tried using different blending options and logical operations in drawing but nothing seemed to work. I'm new to OpenGL so I must be missing something.
Any tips?
EDIT: To give a little more information, I'm using textures for my brushes and using glVertexPointer() and glDrawArrays() to render them. For example:
EDIT 2: Unfortunately, stencil buffers are not available on the iPhone. : (
EDIT 3: Framebuffer objects are available on the iPhone and that is the road I took. I haven't fully implemented it yet, but so far it looks like it works the way I wanted it to. Thanks everyone!
Draw a full-screen textured quad over your scene. When the user draws a brush stroke, use glTexSubImage2D to update the your texture.
glReadPixels/glDrawPixels is slow.
Using FrameBufferObjects is even better, but I doubt this extention is available on the iPhone (then again, I don't know for sure, so maybe try it). FBO's allow you to draw directly into a texture as if it were another rendering context.
Stencil Buffer is the best way to approach this for sure...You'll save time, cpu, and potential problems..,They are available on Iphone, you just have to created an OpenGlES 2.0 surface (not gles 1.0 or 1.1).
//Turn off writing to the Color Buffer and Depth Buffer //We want to draw to the Stencil Buffer only glColorMask(false, false, false, false); glDepthMask(false); //Enable the Stencil Buffer glEnable(GL_STENCIL_TEST); //Set 1 into the stencil buffer glStencilFunc(GL_ALWAYS, 1, 0xFFFFFFFF); glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE); //CALL YOUR DRAWING METHOD HERE //Turn on Color Buffer and Depth Buffer glColorMask(true, true, true, true); glDepthMask(true); //Only write to the Stencil Buffer where 1 is set glStencilFunc(GL_EQUAL, 1, 0xFFFFFFFF); //Keep the content of the Stencil Buffer glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); //CALL OUR DRAWING METHOD AGAIN
With drawing->ImageData being the buffer. What you could do is have a separate background buffer and draw it first. Then the erase tool will simply white-out the the drawing buffer(turning all the values, including alpha, all the way up).
For this solution to work you'd have to turn on blending and turn off depth testing.
You could use the stencil buffer for this operation. The stencil buffer is a special buffer which holds information for every pixel, similiar to the depth buffer. Unlike the depth buffer, you decide how the stencil buffer is altered while drawing and how it influences the decision to draw into the color buffer or not. To do so, you can set the specific states before any drawing operation. Here is what you do:
At the time the users erases, draw the "erased" region into the stencil buffer (using StencilOp, see below). You can use any GL drawing function for that.
In every rendering pass, first draw the background (if you want, you can use a negative stencil test here to only draw the "erased" part of the background - may increase performance).
Then, enable the stencil test for all other drawing operations. OpenGL will then only draw to the "non-erased" regions.
The function to set how the stencil buffer should influence drawing (or not): glStencilFunc()
The function to set how the stencil buffer itself is influenced by drawing: glStencilOp()
// Now, allow drawing, except where the stencil pattern is 0x1
// and do not make any further changes to the stencil buffer
glStencilFunc(GL_NOTEQUAL, 0x1, 0x1);
glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
// Now draw red bouncing square
// (x and y) are modified by a timer function
glColor3f(1.0f, 0.0f, 0.0f);
glRectf(x, y, x + rsize, y - rsize);
// All done, do the buffer swap
glutSwapBuffers();
}
아무것도 수정하지 않은 상태의 결과 화면은 아래 그림이다.
그럼, 이제부터 설명..
glClearStencil(0.0f);
스텐실 버퍼의 값을 모두 0으로 채운다는 뜻이다. glClearColor(0.0f, 0.0f, 1.0f) 함수를 호출한 다음 glClear(GL_COLOR_BUFFER_BIT) 함수를 실행하면 컬러 버퍼의 값이 모두 파란색 컬러 값으로 채워지듯이 마찬가지로 스텐실 버퍼를 0 으로 채운다는 개념으로 이해하면 된다. 책 본문의 소스는 0.0f 값을 줬는데 사실 glClearStencil() 함수의 매개변수는GLint 값으로 들어가야 한다. 타입 캐스팅 돼서 GLint 형으로 변경될 것이고 돌아가는데 문제는 없지만 바람직한 코드는 아니다. 따라서 glClearStencil(0) 과 같은 식으로 코드를 수정하는 것이 올바른 코드가 되겠다.
glEnable(GL_STENCIL_TEST);
스텐실 테스팅 기능을 사용할거라고 선언하는 부분이다. 이거 안 넣으면 나선원이던 붉은 사각형이던 모두 화면에 그냥 그려진다.
glStencilFunc(GL_NEVER, 0x0, 0x0);
이 함수의 첫번째 parameter 는 몇 가지가 있다. GL_ALWAYS, GL_LESS 등등이 있다. 그런데 여기서는 GL_NEVER 를 쓰고 있다. 즉, 스텐실 버퍼가 어떤 값이 들어가 있던지 무엇을 그리던지 아무것도 그려지지 않게 하겠다는 뜻이다. 그러므로 GL_NEVER 가 첫번째 값이면 두번째와 세번째 매개 변수가 의미가 없게 된다. 비슷한 의미로 GL_ALWAYS 로 설정되면 모든 값을 통과 시킨다는 뜻. 즉 화면에 그리고자 하는 그림이 그대로 보인다는 뜻이다.GL_ALWAYS 로 설정하고 테스트 해 보면 두번째 세번째 매개 변수의 값이 뭐가 되던지 아래 처럼 화면에 그려진다.
여기서는 glStencilFunc(GL_NOTEQUAL, 0x0, 0x0); 도 glStencilFunc(GL_NEVER, 0x0, 0x0); 와 같은 의미가 된다. 왜냐면 스텐실 버퍼의 값이 모두 0 으로 채워져 있기 때문에 0 이 아닌 경우가 없어 모든 스텐실 테스팅이 실패하기 때문이다.
glStencilOp(GL_INCR, GL_INCR, GL_INCR);
교재에도 나와 있듯이 첫번째 매개 변수는 스텐실 테스트가 실패했을 때 그 다음 동작을 두번째 매개 변수는 스텐실 테스트가 성공하고 depth 테스트가 실패했을 때 세번째는 스텐실 테스트와 깊이 테스트가 모두 성공했을 때 그 다음 동작을 정의하는 것이다. 첫번째 매개변수가 GL_INCR 로 돼 있으므로 스텐실 테스트가 실패했을 때 스텐실 버퍼에 있는 값을 하나 증가시킨다는 뜻이다. 바로 위 함수에서 glStencilFunc(GL_NVER, 0, 0) 으로 했기 때문에 화면에 어떤 그림을 그리던지 모든 스텐실 테스트가 실패한다. 즉, 화면에 아무것도 보이지 않게 된다. 스텐실 테스트가 성공해야 그 부분이 화면에 보이게 되는데 일단은 모든 테스트가 실패하도록 설정했기 때문에 화면에 아무것도 보이지 않는 것이고 그렇기 때문에 첫번째 옵션인 GL_INCR 이 동작하게 된다.그래서 결국 glClearStencil(0.0f)을 실행해서 0 으로 채워진 스텐실 버퍼의 내용이 1로 바뀌게 된다. 화면에 뭔가를 그리고자 했을 때 보이지는 않지만 해당 위치의 버퍼 내용만 1로 바뀌게 되는 것이다(종이에 구멍이 뚫린다는 개념으로 이해하면 된다). 버퍼의 나머지 부분은 그대로 0 으로 유지된다.
glBegin(GL_LINE_STRIP);
화면에 나선형의 그림을 그리는 부분이다. 스텐실 테스트 기능을 꺼 버린다면 - glEnable(GL_STENCIL_TEST) 함수를 실행하지 않는다면 – 서로 이어지는 나선형(?) 원들이 화면에 보일 것이다. 그런데 스텐실 테스트 기능이 ON 상태이고 glStencilFunc(GL_NEVER, 0x0, 0x0) 함수를 실행했기 때문에 화면에 아무것도 보이지 않는 상태인 것이다. 하지만 변화된 내용이 있는데 그것은 glStencilOp(GL_INCR, GL_INCR, GL_INCR) 를 실행하면서 나선원이 그려지는 부분에 있는 스텐실 버퍼의 값이 1 로 변경된다는 것이다. (일단 스텐실 버퍼의 위치와 화면 픽셀이 1:1 매핑이 된다고 생각하면 이해하기 편하다)
glStencilFunc(GL_NOTEQUAL, 0x1, 0x1);
두번째 param 과 세번째 param 을 And 연산한 값이 스텐실 버퍼에 있는 값과 같지 않을 때 통과 시키겠다는 뜻. 나선원을 그린 후에 스텐실 버퍼는 그리고자 하는 위치에 있는 버퍼의 값이 1로 변경됐는데 이 함수를 실행하면 1과 같지 않은 것만 통과 시키겠다는 뜻이다. 즉 화면에 뭔가를 그린다면 나선원 부분만 제외하고 그려질 것이다. 만약 glStencilFunc(GL_NOTEQUAL, 0x2, 0x2); 이렇게 하면 어떻게 그려질까? 0x2 & 0x2 = 0x2 이고 스텐실 버퍼에 있는 값 중에서 0x2 가 아닌 것은 모두 통과 시키겠다는 뜻이므로 결국 화면에 뭘 그리면 그대로 모두 그려지게 된다. 그럼 glStencilFunc(GL_NOTEQUAL, 0x2, 0x1); 요렇게 하면? 0x2 & 0x1 = 0x0 이므로 0 이 아닌 것만 통과 시키게다는 뜻. 결국 나선원 부분만 그려지게 된다. 직접 실행해 보면 아래처럼 보인다.
glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
첫 번째 매개변수는 스텐실 테스팅이 실패했을 경우 다음 동작이라고 설명했다. 나선원이 그려진 부분의 스텐실 버퍼값이 1 이고 나머지 부분은 모두 0 이기 때문에 glStencilFunc(GL_NOTEQUAL, 0x1, 0x1); 함수를 실행하면 나선원이 있는 버퍼의 내용과 스텐실 테스트했을 때는 실패, 나머지 부분에서는 모두 성공이 나온다. 왜냐면 0x1 & 0x1 = 0x1 이 아닌 값은 모두 통과 시킨다고 했는데 나선원이 그려진 부분만 1 이기 때문이다. 그래서 0x1 인 버퍼와 테스트했을 때는 실패이므로 첫번째 매개변수인 GL_KEEP을 실행하고 0x0 인 스텐실 버퍼값과 테스트했을 때는 성공이므로 두번째 매개변수인 GL_KEEP 이 실행된다. 만약 두번째 매개변수를 GL_INCR로 하면 어떻게 될까나? 그러면 0 의 값이 들어가 있던 스텐실 버퍼의 내용이 모두1로 되므로 두번째 프레임 부터는 화면에 아무것도 그려지지 않을 것이다라고 예상할 수 있으나 사실은 glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);함수 실행은 이 예제에서 의미가 없는 코드다. 왜냐면 glStencilFunc(GL_NOTEQUAL, 0x1, 0x1); 를 실행하면서 어떤 값들을 통과 시킬지 이미 결정이 나 있는 상태이기 때문에 glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); 를 실행한다고 해도 RenderScene() 함수가 다시 호출되면서 스텐실 버퍼의 값이 모두 0 으로 초기화 되기 때문이다.만약 첫번째 glStencilOp(GL_ INCR, GL
_
INCR, GL_ INCR); 함수를 실행할 때 glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); 와 같은 식으로 실행했다면 스텐실 버퍼의 내용이 변하는 게 없어서 결국 glStencilFunc(GL_NOTEQUAL, 0x1, 0x1); 실행하면서 화면에 그리고자 하는 내용이 모두 그려지게 된다.
glColor3f(1.0f, 0.0f, 0.0f);
glRectf(x, y, x + rsize, y - rsize);
스텐실 테스트와 관련된 함수들을 차례대로 실행하면서 나선원이 그려진 부분은 스텐실 테스팅이 실패하고 나머지 부분에서는 모두 성공할 것이라는 세팅이 돼 있기 때문에 붉은 사각형을 그리면 나선원 부분만 보이지 않고 나머지는 모두 그려진다.
OpenGL은 Stencil buffer를 가지고 stencil 하는데 사용한다. Stencil의 기본 기능은, 구멍뚫린 마스크(stencil)를 만들어서 구멍뚫린 부분의 각 pixel들을 비교해서 함수처리(같거나 다르거나 등등)를 해서 동작(색깔을 덧입히거나, 지우거나 등등)을 수행한다.
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); //3개의 buffer를 초기화 한다. 즉 색상버퍼, 깊이버퍼, 스텐실 버퍼를 초기화 한다.
glClearStencil(GLint s); //stencil buffer 초기화시에 초기화 값으로 사용될 값을 지정한다.
glStencilFunc(GLenum 함수, GLint 참조, GLuint mask);
함수는 비교에 사용되는 기능들 중 하나를 지정하는데, GL_NEVER,GL_ALWAYS,GL_LESS,GL_EQUAL,GL_GEQUAL,GL_GREATER,GL_NOTEQUAL 이 있다. 참조는 비교시에 사용되는 참조 값이다. mask는 비교전에 AND 연산을 수행한다.
즉, (해당bits AND mask) 값이 (참조bits AND mask)와 비교해서 같은지,다른지 확인한다.
glStencilOp(GLenum 실패, GLenum z실패, GLenum z성공);
비교시 값이 거짓인경우, "실패"를 수행하고, 그리고 비교값이 참인경우에 깊이 test가 실패했을때 "z실패"를 수행하고, 깊이 test가 성공시에는 "z성공"을 수행한다.
즉, 1. 비교값이 거짓 : 실패 수행. 2. 비교값이 참: 2.1 깊이 test가 실패: z실패 수행. 2.2 깊이 test가 성공: z성공 수행.
수행되는 값은 GL_KEEP,GL_ZERO, GL_REPLACE, GL_INCR, GL_DECR,GL_INVERT,GL_INCR_WRAP,GL_DECR_WRAP 등이 있다
[Sample code] 어떤 object를 화면의 특정 부분에서만 그리고 싶을 때 stencil buffer를 활용한 예.
void DrawScreen(void) {
...
glClear(GL_STENCIL_BUFFER_BIT); glStencilFunc(GL_NEVER, 0x0, 0x0); // 이후에 나오는 drawing 명령은 모두 stencil test를 pass하지 못하므로 실제로 그려지지 않는다. glStencilOp(GL_INCR, GL_INCR, GL_INCR); // test후 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를 정의해야 한다. 외부의 모든 물체를 다 그려놓고 보이지 않는 부분을 삭제하는 것과 보여지지 않는 부분을 빼고 그리는 것 둘중에 어떤것이 더 나을까 라고 스스로에게 질문해보자.
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를 사용할 수 있는 단계에 온것이다. 이제 고작 사용할 수 있는 단계에 온것이다. 그렇다면 어떻게 사용하느냐? 오히려 사용하는 것이 더 쉽다.
위의 일련의 명령이 사용하는 법이다. 우선 Stencil Test를 Enable한다음 glColorMask()함수를 이용해서 지금부터 그리는 물체를 color buffer에 넣으라는 명령을 내린다. 당연히 그래야 한다. 왜? 지금부터는 사용자에게 보여줄 화면을 그리는 것이기 때문이다. 그런다음 설정함수인 glStencilFunc와 glStencilOp가 나오는데 이것은 아까 본것과 조금 다를뿐 거의 같다. 함수에 대한 설명은 여기서 다 하기 힘드니 함수 설명을 찾아보고 읽으면 금방 이해가 갈것이다. 저 두함수가 하는 역활은 들어오는 각각의 화소에 대한 값을 stencil buffer의 해당 위치의 값과 비교해서 1이면 통과 0이면 통과 실패를 시키는 것이고 추가적으로 stencil buffer에 저장되어 있는 값은 변경하지 말라는 의미이다. 당연히 stencill buffer를 변경해서는 안된다.
그리고 그 다음 좌악 나오는 명령들은 이제 진짜 그림을 그리자는 명령들이다.
쉽게 쉽게 그리고 간단하게만 썼는데 나중에 내가 참고하려고 할때 다시 보면 이해가 잘 될지 모르겠다.ㅋㅋ 팍팍 줄여서 단계별로 설명하자면, 1. 스텐실 버퍼에 내가 보여주고자 하는 부분만 그린다. 2. 실제로 물체를 그리고자 할때에는 스텐실 버퍼 설정함수를 조작하여 스텐실 버퍼가 1의 값을 가지는 곳만 그림이 그려지도록 한다.
위 링크 내용의 중간 쯤에 있는 코드 블록을 참조하여 아래와 같이 Stencil기능을 쓰는 주요 부분에 대한 코드를 구현하였다.
// hud.cpp { glEnable(GL_STENCIL_TEST);
// draw occlusion regions, such as airspeed box, to the stencil buffer drawMode=Draw_Stencil; glStencilFunc(GL_NEVER,1,0xFF);// only occlusion regions will be written glStencilOp(GL_KEEP,GL_KEEP,GL_REPLACE); glStencilMask(0xFF);// write to stencil buffer glColorMask(false,false,false,false); glClear(GL_STENCIL_BUFFER_BIT);
// first pass to draw stencil renderHudScreen();
// draw actual symbols drawMode=Draw_Normal; glStencilFunc(GL_EQUAL,0,0xFF);// pass symbols outside occlusion regions // texts inside occlusion regions are written if 1 glStencilMask(0x00);// don't write to stencil buffer glColorMask(true,true,true,true);
// last pass to render symbols renderHudScreen();
glDisable(GL_STENCIL_TEST); }
원래 HUD 심볼을 그리던 부분은 renderHudScreen() 이고, 한 프레임에 한번만 실행하면 되었으나, Stenciling을 위해서는 두 번 실행이 필요했고, 이에 따라 Stencil과 관련 없는 심볼들의 실행을 최소화 하기 위해 전역 변수 drawMode를 추가하였다.
이 변수는 다음과 같이 정의 되었다.
// hud.h // Occulusion control typedefenumHudDrawMode{ Draw_Stencil, Draw_Normal, TotalHudDrawMode }HudDrawMode;
externHudDrawModedrawMode;
첫번 Pass를 통해 Occlusion 영역을 Stencil Buffer에 기록하기 위한 세팅 부분을 보면, drawMode를 Draw_Stencil로 하여 renderHudScreen()의 심볼들 중 Stencil에 필요한 부분만 선택적으로 불리게 하였다.
그러나 나머지 심볼들을 모두 if-문 처리하기가 곤란했기 때문에 glStencilFunc의 디폴트 파라미터를 GL_NEVER로 하여 관련 없는 심볼들이 Stencil Buffer에 기록되는 것을 원천 차단했다.
renderHudSymbol() 함수에서 가려지는 영역 (Occlusion)을 그리는 부분은 다음과 같이 drawMode가 Draw_Stencil일 때 glStencilFunc를 GL_ALWAYS로 하여 Stencil Buffer에 기록 되도록 했다. 이 부분은 예전 코드에서 검은색으로 채워 넣던 부분이다.
// occlusion box { if(drawMode==Draw_Stencil) { glStencilFunc(GL_ALWAYS,1,0xFF);// was glColor4d(0,0,0,1) drawOcclusionBox(); glStencilFunc(GL_NEVER,1,0xFF);// was glColor4d(0,1,0,1) } }
이어서 실제 HUD심볼을 그리는 두번째 pass에서는 drawMode를 Draw_Normal로 놓았고, 이를 이용하여 Occlusion 영역 위에 디스플레이할 숫자 값 등을 그리게 되었다. 이 부분은 다음과 같이 drawMode가 Draw_Normal일 때 glStencilFunc을 GL_ALWAYS로 하여 화면에 그려지도록 했다.
Stenciling을 위해 HUD 심볼을 그리는 renderHudScreen()을 두 번 실행하는 것에 성능 저하를 우려하였으나, 예상외로 큰 차이가 없이 거의 완벽하게 원하는 효과를 이룰 수 있었다.
아직 움직이는 심볼들 중 Occlusion이 필요한 것들 일부를 시험해보지 않았으나, 맨 위의 코드에 보인 바와 같이 첫번 Pass를 실행하기 전에 glClear(GL_STENCIL_BUFFER_BIT)을 실행하여 Stencil Buffer를 매번 새로 설정하기 때문에 아무 문제 없을 것으로 예상된다.
현재 진행되고 있는 프로젝트에서 HUD는 OpenSceneGraph (OSG) 기반 영상시스템 안에서 실행되므로 OSG 관련 몇가지 추가적인 세팅이 필요했다 (관련 포스팅).