Stencil testing
Fragment shader가 fragment를 처리하고 나면, depth test와 비슷한
stencil test
라고 불리는 것이 수행된다.- fragment가 폐기될지 안될지 테스트하는 것이다.
- 이를 수행한 후 depth test로 또한번 폐기되는지 테스트한다.
stencil buffer
라는 버퍼에 의해 수행된다.- 이 버퍼는 렌더링 동안에 수정할 수 있다.
stencil buffer
는 일반적으로8
비트의 stencil value를 가지고 있음.- 이 값은 pixel/fragment마다
256
개의 값으로 나타내어진다. - 이 stencil 값을 가지고 있는 특정 fragment를 테스트할 수 있다.
- 이 값은 pixel/fragment마다
각 windowing library들은 stencil buffer를 세팅해야만 작동한다. GLFW는 이를 자동적으로 해준다. 하지만 다른 라이브러리들은 기본적으로 생성하지 않을 수 있으니, 문서를 확인하자
간단한 예제는 아래와 같다. (pixels not-to-scale):
이 stencil buffer는 먼저 0으로 채워지고나서 속이 비어있는 사각형 모양의
1
을 설정한다.- 이 scene의 fragment 중에서 stencil 값이
1
인 fragment들만 렌더링된다.(다른 것들은 폐기된다.)
- 이 scene의 fragment 중에서 stencil 값이
stencil buffer는 fragment를 렌더링할 때마다 버퍼를 특정 값으로 설정할 수 있도록 허용한다.
- 같은 프레임에서 이러한 값을 읽어 특정 frag를 폐기하거나 전달할 수 있다.
- 스텐실 버퍼를 마음대로 사용할 수 있지만, 일반적인 아웃라인은 다음과 같다.
- 스텐실 버퍼 쓰기 활성화
- 스텐실 버퍼의 내용을 업데이트하여 오브젝트 렌더링.
- 스텐실 버퍼에 쓰기 비활성화
- 스텐실 버퍼를 기반으로 특정 fragment를 폐기하여 (other)오브젝트 렌더링
stencil buffer를 사용함으로써 scene에 그려진 다른 오브젝트의 fragment들을 기반으로하여 특정 fragment를 폐기할 수 있음.
GL_STENCIL_TEST
를 활성화하여stencil testing
을 활성화시킬 수 있다.- 이 시점부터 호출되는 모든 렌더링 호출은 어떤 방식으로든 stencil buffer에 영향을 미친다.
1
glEnable(GL_STENCIL_TEST);
- 또한 color, depth buffer와 마찬가지로, 매 렌더링 루프마다
stencil buffer
를 비워주어야 한다.
1
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
glStencilMask
glDepthMask
함수처럼 동일한 기능을 수행함.- 곧 버퍼에 작성될 stencil 값에
AND
연산을 시킬 bitmask를 설정함 - 기본적으로 bitmask는 모두
1
로 설정됨, 출력에 영향주지 않음 - 이것을
0x00
으로 설정하면 buffer에 작성되는 모든 stencil 값들은0
이 된다. - 이는 depth testing의 glDepthMask(GL_FALSE) 함수와 비슷하다.
1
2
glStencilMask(0xFF); // each bit is written to the stencil buffer as is
glStencilMask(0x00); // each bit ends up as 0 in the stencil buffer (disabling writes)
- 대부분 위처럼
0x00
이나0xFF
로 설정한다.- 다른 커스텀 비트마스크 옵션들도 존재하긴 함.
Stencil functions
stencil test를 통과시킬지 말지에 대한 기준을 설정한다.
- 2개의 함수가 있음
glStencilFunc
glStencilOp
두 함스를 사용하면, 언제 어떻게 버퍼를 수정해야하는지를 정확히 지정할 수 있고, 언제 테스트가 통과하거나 실패할지도 지정할 수 있다.
glStencilFunc
glStencilFunc(GLenum func, GLint ref, GLuint mask)
함수는 3개의 파라미터를 가지고 있다.- func: stencil test 함수 설정, 이 test 함수는 저장된 stencil 값과
glStencilFunc
함수의 ref 값에 적용된다.- 가능한 옵션은
GL_NEVER
,GL_LESS
,GL_LEQUAL
,GL_GREATER
,GL_GEQUAL
,GL_EQUAL
,GL_NOTEQUAL
,GL_ALWAYS
- 가능한 옵션은
- ref: stencil test에 대한 레퍼런스 값을 지정
- stencil buffer의 내용은 이 값과 비교됨.
- mask: 비교하기 전에 참조 값과 저장된 스텐실 값 모두에 AND 연산이 수행되어질 마스크를 지정한다. 초기값은 모두 1.
- func: stencil test 함수 설정, 이 test 함수는 저장된 stencil 값과
1
glStencilFunc(GL_EQUAL, 1, 0xFF)
위 코드는, OpenGL에게 fragment의 stencil값이 레퍼런스 값인
1
과 동일 하다면, stencil test를 통과시킨 후 그리고, 그렇지 않으면 폐기한다.하지만 이 함수는 오직 OpenGL이 stencil buffer의 내용물읠 기반으로 fragment들이 폐기될지 패스될지만 기술한다.
- 실제로 buffer를 수정할 수 있는 방법에 대한 기술은 없음.
glStencilOp
` glStencilOp(GLenum sfail, GLenum dpfail, GLenum dppass)`
- 3개의 옵션
sfail
: stencil test가 실패하였을 때 취할 행동dpfail
: stencil test가 통과했지만 depth test는 실패했을 때 취할 행동dppass
: stencil, depth test 모두 통과했을 때 취할 행동
각 옵션들에 대해 다음을 설정할 수 있다.
GL_KEEP
: 기존 값(현재 저장된 stencil)을 그대로 유지GL_ZERO
: stencil 값을 0으로 설정GL_REPLACE
: stencil 값을glStencilFunc
함수에서 지정한 ref 값으로 설정GL_INCR
: 최댓값보다 작다면, stencil값을 1만큼 증가시킴GL_INCR_WRAP
:GL_INCR
와 같고, 최댓값을 초과하면 0으로 돌아옴GL_DECR
: 최솟값보다 크다면, stencil 값을 1만큼 감소시킴GL_DECR_WRAP
:GL_DECR
와 같고, 0보다 작으면 최댓값으로 설정GL_INVERT
: 현재 stencil buffer 값의 비트를 뒤집음
기본값 ==
(GL_KEEP, GL_KEEP, GL_KEEP)
- 결과가 어떻든 버퍼의 값 유지됨
- 기본 행동 == 버퍼를 수정하지 않는것.
- 버퍼를 덮어쓰고 싶다면 이 옵션을 변경해야함
Object outlining
- 스텐실 테스트로 구현될 수 있는 유용한 기능중 하나.
각 오브젝트에 대해 색이 입혀진 작은 외곽선을 생성한다.
- 유닛을 선택했을 때 해당 유닛이 선택됬는지 보여주는 기능을 구현할 때 유용하다.
과정은 다음과 같다.
- stencil 쓰기 활성화
- 외곽선이 그려질 오브젝트를 그리기 전에 stencil 함수를
GL_ALWAYS
로 설정, 오브젝트의 fragment가 렌더링될때마다stencil buffer
를 1로 업데이트 - 오브젝트 렌더링
- stencil 쓰기와 depth testing을 비활성화
- 각 오브젝트들을 약간 확대
- 하나의 (외곽선) 컬러를 출력하는 별도의 fragment shader를 사용.
- 오브젝트를 다시 그리고, stencil 값이 1과 같지 않은 fragment들만 그립니다.
- 다시 stencil 작성과 depth testing을 활성화합니다.
이 과정은 각 오브젝트의 fragment들에 대해 버퍼의 내용을 1로 설정하고, 외곽선을 그리고 싶을 때, 오브젝트의 확대된 버전을 stencil test가 통과된 부분만 그린다.
- 확대된 버전은 오브젝트의 외곽선으로 그려진다.
- stencil buffer를 사용하여 겹치는 부분의 fragment는 폐기한다.
예제
- 먼저 외곽선 컬러 fragment shader를 작성해야한다. (간단히 하드코딩)
1
2
3
4
void main()
{
FragColor = vec4(0.04, 0.28, 0.26, 1.0);
}
depth test의 앞선 예제를 사용하여, 두 컨테이너에 외곽선을 추가할것이다.
- 먼저 바닥을 그린후
- stencil을 쓰면서 두 컨테이너를 그린다.
- 그리고 스케일업된 두 컨테이너를 그린다.(곂치는 부분의 frag를 폐기하면서)
먼저 stencil test를 활성화하자
1
glEnable(GL_STENCIL_TEST);
- 그다음에 각 프레임마다 수행하기를 원하는 행동을 정의하자.
- 모든 테스트가 성공할 때, 지정된 ref값으로 수정하게 설정
- stencil buffer를
0
으로 비우고 - 컨테이너들을 위해 각 그려진 fragment들에 대해 stencil buffer를
1
로 업데이트한다.
1
2
3
4
5
glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
glStencilFunc(GL_ALWAYS, 1, 0xFF); // all fragments should pass the stencil test
glStencilMask(0xFF); // enable writing to the stencil buffer
normalShader.use();
DrawTwoContainers();
GL_ALWAYS
로 각 fragment들이 버퍼를 수정하여 stencil 값을1
로 만든다.- 항상 통과, 그려지는곳에 버퍼가 ref값 즉 1로 수정
- 그 후, 확대된 컨테이너를 그려야함.
- 이번에는 버퍼 쓰기를 비활성화한다.
- 그리고 1과 같지않은 부분만 그리도록한다.(NOTEQUAL) => 외곽선
- 그리고 depth test를 비활성화하여, 바닥에 의해 덮어쓰여지지 않도록했다.(?)
1
2
3
4
5
glStencilFunc(GL_NOTEQUAL, 1, 0xFF);
glStencilMask(0x00); // disable writing to the stencil buffer
glDisable(GL_DEPTH_TEST);
shaderSingleColor.use();
DrawTwoScaledUpContainers();
- 전체 과정은 다음과 같다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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); // make sure we don't update the stencil buffer while drawing the floor
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);
glStencilFunc(GL_ALWAYS, 1, 0xFF);
glEnable(GL_DEPTH_TEST);
- 이
outlining
알고리즘의 결과는 다음과 같다.
두 컨테이너의 외곽선이 겹쳐짐. 이처럼 합치는것은 일반적으로 작은 유닛을 선택할 때 유용하다.
- 완벽한 외곽선을 원한다면, 오브젝트마다 stencil buffer를 비우고, 약간 창의적으로
depth buffer
를 사용해야한다.
- 완벽한 외곽선을 원한다면, 오브젝트마다 stencil buffer를 비우고, 약간 창의적으로
이런 외곽선은 여러 게임(특히 전략게임)에서 오브젝트를 선택을 시각화할 때 흔히 사용된다.
- 이러한 알고리즘은 model 클래스와 함께 쉽게 구현 가능하다.
- 간단히 외곽선을 그릴지 말지를 결정하는 boolean flag를 model 클래스에 생성할 수 있다.
- 좀 더 창의적이게 자연스러운 효롸를 넣고 싶다면,
Gaussian Blur
와 같은 전처리 필터를 사용할 수 있다.
stencil testing은 다른 목적으로 사용가능하다.
- 백미러의 텍스처를 그릴 때, 거울의 모양에 맞게 그리거나
- 실시간 그림자를 렌더링할 때 shadow volumes라는 stencil buffer 기술이 쓰임.