눈팅하는 게임개발자 블로그

OpenGL Advanced 5-2 Stencil testing 본문

공부한거/OpenGL

OpenGL Advanced 5-2 Stencil testing

Palamore 2020. 12. 8. 17:23

원문 사이트

learnopengl.com/Advanced-OpenGL/Stencil-testing

 

LearnOpenGL - Stencil testing

Stencil testing Advanced-OpenGL/Stencil-testing Once the fragment shader has processed the fragment a so called stencil test is executed that, just like the depth test, has the option to discard fragments. After that the remaining fragments are passed to t

learnopengl.com

번역 사이트

heinleinsgame.tistory.com/25?category=757483

 

[Learn OpenGL 번역] 5-2. 고급 OpenGL - Stencil testing

Stencil testing 고급 OpenGL/Stencil-testing Fragment shader가 fragment를 처리하고 나면 depth test와 비슷한 stencil test 라고 불리는 것이 수행됩니다. fragment가 폐기될지 안될지 테스트하는 것입니다...

heinleinsgame.tistory.com

 

Stencil testing

Fragment shader가 fragment를 처리하고 나면 depth test와 비슷한 stencil test라고 불리는 것이 수행된다.

fragment가 폐기될 것인지를 테스트한다.

이후 남아있는 fragment는 depth test에서 또 한번 폐기될 것인지를 테스트한다.

이 stencil test는 지금까지의 buffer와는 다른 stencil buffer라고 불리는 buffer를 기반으로 수행된다.

이 buffer는 흥미로운 효과를 내기 위해 렌더링 동안에 수정할 수 있다.

 

Stencil buffer는 8비트의 stencil value를 가지고 있고 이 값은 pixel/fragment 마다 256개의 값으로 나타내어진다.

이 stencil 값을 설정하여 특정한 stencil 값을 가지고 있는 특정 fragment를 폐기할 지 유지할 지를 정할 수 있다.

각 window 라이브러리들은 stencil buffer를 세팅해야만 작동한다. GLFW는 이를 자동적으로 해주기 때문에
GLFW에게 생성하라고 지시할 필요가 없지만
다른 window 라이브러리들은 기본 값으로 stencil을 생성하지 않을 수도 있으므로
라이브러리 문서를 제대로 확인할 필요가 있다.

Stencil buffer의 간단한 예제는 다음과 같다.

이 stencil buffer는 먼저 0으로 채워지고 나서 속이 비어있는 사각형 모양의 1을 설정한다.

그러면 이 scene의 fragment들 중에서 stencil 값이 1인 fragment들만 렌더링된다.(다른 것들은 모두 폐기된다.)

 

Stencil buffer는 fragment를 렌더링해야 할 곳에 특정 값을 설정할 수 있도록 한다.

렌더링하는 도중에 stencil buffer를 수정함으로써 stencil buffer를 작성할 수 있다.

동일한 렌더링 루프에서 특정 fragment들을 폐기하거나 유지하기 위해서 이 값들을 읽어낼 수 있다.

stencil buffer를 마음대로 사용할 수도 있지만 다음과 같은 일반적인 틀이 존재한다.

- stencil buffer 작성 활성화

- 오브젝트 렌더링, stencil buffer 수정

- stencil buffer 작성 비활성화

- stencil buffer를 기반으로 특정 fragment를 폐기하여 오브젝트 렌더링

Stencil buffer를 사용함으로써 scene에 그려진 다른 오브젝트의 fragment들을 기반으로 하여

특정 fragment를 폐기시킬 수 있다.

 

GL_STENCIL_TEST를 활성화하여 stencil testing을 활성화시킬 수 있다.

이후 시점부터 호출되는 모든 렌더링 명령은 stencil buffer의 영향을 받는다.

glEnable(GL_STENCIL_TEST);    

또한 color, depth buffer와 마찬가지로 매 렌더링 루프마다 stencil buffer를 비워주어야 한다는 것을 유념해야 한다.

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); 

또한 depth testing의 glDepthMask 함수처럼, stencil buffer에도 동일한 기능의 함수가 존재한다.

이 glStencilMask 함수는 곧 buffer에 작성될 stencil 값에 AND 연산을 시킬 bitmask를 설정할 수 있도록 해준다.

기본 값으로 bitmask는 모두 1로 설정되어 출력에 아무런 영향을 주지 않는다.

하지만 이것을 0x00으로 설정하면 buffer에 작성되는 모든 stencil 값들은 0이 된다.

이는 depth testing의 glDeptMask(GL_FALSE) 함수와 비슷하다.

glStencilMask(0xFF); // 각 비트들은 stencil buffer 그대로 작성됩니다.
glStencilMask(0x00); // 각 비트들은 stencil buffer에 0으로 작성됩니다(작성 비활성화).

대부분의 경우에 stencil mask를 0x00이나 0xFF로 설정할 것이다. 하지만 임의의 bitmask들을 설정할 수 있는

옵션들도 존재한다는 사실을 알아두어야 한다.

 

Stencil functions

Depth testing과 마찬가지로 stencil test를 통과시킬지 말지에 대한 기준을 설정할 수 있다.

stencil testing을 설정할 수 있는 총 2개의 함수가 있다.

glStencilFunc 함수와 glStencilOp 함수이다.

 

glStencilFunc(GLenum func, GLint ref, GLuint mask)

glStencilFunc 함수는 3개의 파라미터를 갖는다.

func : stencil test 함수를 설정한다, 이 test 함수는 저장된 stencil 값과 glStencilFunc 함수의 ref 값에 적용된다.

가능한 옵션은 GL_NEVER, GL_LESS, GL_LEQUAL, GL_GREATER, GL_GEQUAL, GL_EQUAL, GL_NOTEQUAL, GL_ALWAYS

가 있다. 이들은 Depth buffer의 함수들과 비슷하게 동작한다.

ref : stencil test에 대한 레퍼런스 값을 지정한다. stencil buffer의 내용은 이 값과 비교된다.

mask : 그들을 비교하기 전에 레퍼런스 값과 저장된 stencil 값 모두에 AND 연산이 수행되어질 mask를 지정한다.

초기값으로는 모두 1로 설정된다.

 

위의 간단한 stencil 예제의 경우에서는 다음과 같은 함수가 사용되었을 것이다.

glStencilFunc(GL_EQUAL, 1, 0xFF)

이는 OpenGL에게 fragment의 stencil 값이 레퍼런스 값인 1과 동일(GL_EQUAL)하다면 test를 통과시킨 후

렌더링하고 그렇지 않으면 폐기하라고 지시한다.

하지만 glStencilFunc 함수는 오직 OpenGL이 stencil buffer의 내용으로 무엇을 해야하는지에 대해서만 묘사하고

실제로 buffer를 수정할 수 있는 방법에 대해서는 다루고 있지 않다. 이는 glStencilOp 함수에서 다루어진다.

glStencilOp(GLenum sfail, GLenum dpfail, GLenum dppass)

glStencilOp 함수는 3개의 옵션을 가지고 있고 각 옵션들에 대해 취해질 액션들을 설정할 수 있다.

sfail : stencil test가 실패했을 경우 취할 행동.

dpfail : stencil test가 통과했지만 depth test는 실패했을 경우 취할 행동.

dppass : stencil, depth test를 모두 통과했을 경우 취할 행동.

각 옵션들에 대해 다음과 같은 행동들을 설정할 수 있다.

행동 설명
GL_KEEP 현재 저장된 stencil 값을 유지
GL_ZERO stencil 값을 0으로 설정
GL_REPLACE stencil 값을 glStencilFunc 함수에서 지정한 레퍼런스 값으로 설정
GL_INCR 최댓값보다 작다면 stencil 값을 1만큼 증가시킴
GL_INCR_WRAP GL_INCR과 같지만 최댓값을 초과하면 0으로 돌아옴.
GL_DECR 최솟값보다 크다면 stencil 값을 1만큼 감소시킴
GL_DECR_WRAP GL_DECR과 같지만 0보다 작다면 최댓값으로 설정함.
GL_INVERT 현재 stencil buffer 값의 비트를 뒤집는다.

glStencilOp 함수의 기본 값은(GL_KEEP, GL_KEEP, GL_KEEP)이므로 test의 결과가 어떻든 stencil buffer의 값은 유지된다.

기본 행동은 stencil buffer를 수정하지 않는 것이므로 stencil buffer를 수정하고 싶다면 옵션 중 하나라도 다른 행동으로

바꾸어야 한다.

 

그래서 glStencilFunc 함수와 glStencilOp 함수를 사용하면 언제 그리고 어떻게 stencil buffer를 수정해야 하는지를 정확히

지정할 수 있고 또한 언제 stencil test가 통과하거나 실패할지도 지정할 수 있다.

 

Object outlining

이전 섹션에서 stencil testing이 어떻게 작동하는지에 대해 완전히 이해하기 힘들었을 것이다.

stencil testing으로 구현될 수 있는 유용한 기능인 object outlining을 시연해보자.

Object outlining은 말 그대로의 기능이다. 각 오브젝트(또는 오직 하나)에 대해 색이 입혀진 작은 외곽선을 생성한다.

이는 예를 들면 전략 게임에서 유닛들을 선택하고 싶을 때 사용자가 어떠한 유닛들을 선택했는지를 보여줄 때

특히 유용한 효과이다.

오브젝트를 outlining하기 위한 과정은 다음과 같다.

1. 오브젝트를 그리기 전에 stencil 함수를 GL_ALWAYS로 설정하고 오브젝트의 fragment가 렌더링 될 때마다 stencil buffer를 1로 수정한다.

2. 오브젝트를 렌더링한다.

3. stencil 작성과 depth testing을 비활성화한다.

4. 각 오브젝트들을 약간 확대한다.

5. 하나의 외곽선 컬러를 출력하는 별도의 fragment shader를 사용한다.

6. 오브젝트를 다시 그리지만 stencil 값이 1과 같지 않은 fragment들만 그린다.

7. 다시 stencil 작성과 depth testing을 활성화한다.

이 과정은 각 오브젝트의 fragment들에 대해 stencil buffer의 내용을 1로 설정하고 외곽선을 그리고 싶을 때

오브젝트의 확대된 버전을 stencil test가 통과된 부분만 그려준다.

확대된 버전은 오브젝트의 외곽선으로 그려진다.

기본적으로 stencil buffer를 사용하여 확대된 버전과 원본 오브젝트가 겹치는 부분의 fragment는 폐기한다.

 

먼저 외곽선 컬러를 출력하는 아주 기본적인 fragment shader를 생성할 것이다.

간단히 컬러 값을 하드코딩하고 이 shader를 shaderSingleColor 라고 네이밍한다.

void main()
{
    FragColor = vec4(0.04, 0.28, 0.26, 1.0);
}

오직 두 개의 컨테이너에만 외곽선을 추가할 것이기 때문에 바닥은 그대로 둔다.

따라서 먼저 바닥을 그린 후(stencil buffer를 작성하면서) 두 개의 컨테이너를 그리고 (전에 그려진 컨테이너 fragment

들을 기반으로 fragment들을 폐기하면서) 확대된 컨테이너들을 그린다.

 

먼저 stencil testing을 활성화하고 test가 성공하거나 실패했을 때의 행동을 설정한다.

glEnable(GL_STENCIL_TEST);
glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);  

어떠한 테스트라도 실패했다면 아무 행동도 하지 않고 현재 저장된 값들을 유지시킨다.

하지만 stencil test와 depth test 모두 성공했다면 저장된 stencil 값을 glStencilFunc 함수를 통해 지정된 레퍼런스 값

(나중에 1로 설정할 것)으로 수정한다.

 

stencil buffer를 0으로 비우고 컨테이너들을 위해 각 그려진 fragment들에 대해 stencil buffer를 1로 수정한다.

glStencilFunc(GL_ALWAYS, 1, 0xFF); // 모든 fragment들은 stencil buffer를 수정해야합니다
glStencilMask(0xFF); // stencil buffer 작성 활성화
normalShader.use();
DrawTwoContainers();

GL_ALWAYS stencil testing 함수를 사용하여 컨테이너의 각 fragment들이 stencil buffer를 수정하여 stencil 값을

1로 만든다. 이 fragment 들은 stencil test에 항상 통과되기 때문에

stencil buffer는 이들이 그려질 때마다 레퍼런스 값으로 수정된다.

 

이 stencil buffer에 컨테이너가 그려진 자리가 1로 수정되었으면 확대된 컨테이너를 그려야 한다.

하지만 이번에는 stencil buffer 작성을 비활성화한다.

glStencilFunc(GL_NOTEQUAL, 1, 0xFF);
glStencilMask(0x00); // stencil buffer 작성 비활성화
glDisable(GL_DEPTH_TEST);
shaderSingleColor.use(); 
DrawTwoScaledUpContainers();

stencil 함수를 GL_NOTEQUAL로 설정하여 1과 같지 않은 부분만 그리도록 하여 이전에 그렸던 컨테이너의 바깥쪽 부분만

그리게 했다. 또한 depth testing을 비활성화하여 바닥에 의해 가려지지 않게 했다.

또한 수행하고 나면 depth buffer를 다시 활성화시킨다.

전체 object outlining 과정은 다음과 같다.

glEnable(GL_DEPTH_TEST);
glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);  
  
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); 

glStencilMask(0x00); // 바닥을 그리는 동안에는 stencil buffer를 수정하지 않습니다
normalShader.use();
DrawFloor()  
  
glStencilFunc(GL_ALWAYS, 1, 0xFF); 
glStencilMask(0xFF); 
DrawTwoContainers();
  
glStencilFunc(GL_NOTEQUAL, 1, 0xFF);
glStencilMask(0x00); 
glDisable(GL_DEPTH_TEST);
shaderSingleColor.use(); 
DrawTwoScaledUpContainers();
glStencilMask(0xFF);
glEnable(GL_DEPTH_TEST);  

stencil testing에 대한 개념을 이해한다면 해당 코드가 이해하기 어렵지는 않을 것이다.

이해하기 어렵다면 이전 섹션을 다시 주의깊게 읽어보고 각 함수들이 예제에서 어떻게 사용되어지고 있는지

완전히 이해하려고 노력해봐야 한다.

이 outlining 알고리즘의 결과는 다음과 같다.

두 컨테이너의 외곽선이 겹쳐지는 것을 확인할 수 있을 것이다.
이는 일반적으로 노렸던 효과이다.(전략 게임에서 복수의 유닛을 선택하게되는 경우)
오브젝트의 완벽한 외곽선을 원한다면 오브젝트마다 stencil buffer를 비우고 약간의 새로운 depth buffer를 생성해야 한다.

이 object outlining 알고리즘은 여러 게임에서 오브젝트의 선택을 시각화할 때에 꽤 흔히 사용된다.

그리고 이러한 알고리즘은 model 클래스와 함께 쉽게 구현될 수 있다.

간단히 외곽선을 그릴지 말지를 결정하는 boolean flag를 model 클래스에 생성할 수도 있다.

좀 더 창의적이게 하고 싶다면 외곽선에 좀 더 자연스러운 효과를 넣기 위해 Gaussian Blur와 같은 전처리 필터의

도움을 사용할 수도 있다.

 

Stencil testing은 outlining 말고도 아주 많은 목적을 가지고 있다.

백미러의 텍스처를 그릴 때 거울의 모양에 맞게 그려야 하거나

실시간 그림자를 렌더링 할 때 shadow volumes라고 불리는 stencil buffer 기술이 쓰이기도 한다.

 

 

'공부한거 > OpenGL' 카테고리의 다른 글

OpenGL Advanced 5-4 Face culling  (0) 2020.12.10
OpenGL Advanced 5-3 Blending  (0) 2020.12.09
OpenGL Advanced 5-1 Depth testing  (0) 2020.12.08
OpenGL Model Loading 4-3 Model  (0) 2020.12.07
OpenGL Model Loading 4-2 Mesh  (0) 2020.12.07