[Learn OpenGL 번역] 5-2. 고급 OpenGL - Stencil testing
Stencil testing
고급 OpenGL/Stencil-testing
Fragment shader가 fragment를 처리하고 나면 depth test와 비슷한
Stencil buffer는 (일반적으로) 8
비트의 256
개의 값으로 나타내어집니다. 우리는 이 stencil 값을 설정하여 특정한 stencil 값을 가지고 있는 특정 fragment를 폐기할지 유지할지를 정할 수 있습니다.
Stencil buffer의 간단한 예제는 다음과 같습니다.
이 stencil buffer는 먼저 0으로 채워지고나서 속이 비어있는 사각형 모양의 1
을 설정합니다. 그러면 이 scene의 fragment들 중에서 stencil 값이 1
인 fragment들만 렌더링됩니다(다른 것들은 폐기됩니다).
Stencil buffer는 우리가 fragment를 렌더링해야할 곳에 특정 값을 설정할 수 있도록 합니다. 렌더링하는 도중에 stencil buffer를 수정함으로써 우리는 stencil buffer를 writing(작성)합니다. 동일한 렌더링 루프에서 우리는 특정 fragment들을 폐기하거나 유지하기위해서 이 값들을 read(읽다)할 수 있습니다. 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의 AND
연산을 시킬 bitmask를 설정할 수 있도록 해줍니다. 기본값으로 bitmask는 모두 1
로 설정되어 출력에 아무런 영향을 주지않습니다. 하지만 우리가 이것을 0x00
으로 설정하면 buffer에 작성되는 모든 stencil 값들은 0
이 됩니다. 이는 dpeth testing의
glStencilMask (0xFF); // 각 비트들은 stencil buffer 그대로 작성됩니다.
glStencilMask (0x00); // 각 비트들은 stencil buffer에 0으로 작성됩니다(작성 비활성화).
대부분의 경우에 여러분은 stencil mask를 0x00
나 0xFF
로 설정할 것입니다. 하지만 임의의 bitmask들을 설정할 수 있는 옵션들도 존재한다는 사실을 알아두세요.
Stencil 함수
Depth testing과 마찬가지로 stencil test를 통과시킬지 말지에 대한 기준을 설정할 수 있습니다. stencil testing을 설정할 수 있는 총 2개의 함수가 있습니다.
이
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를 통과시킨 후 렌더링하고 그렇지 않으면 폐기하라고 지시합니다.
하지만
sfail
: stencil test가 실패하였을 때 취할 행동dpfail
: stencil test가 통과했지만 depth test는 실패했을 때 취할 행동dppass
: stencil, depth test 모두 통과했을 때 취할 행동
각 옵션들에 대해 다음과 같은 행동들을 설정할 수 있습니다.
행동 | 설명 |
---|---|
GL_KEEP |
현재 저장된 stencil 값을 유지 |
GL_ZERO |
stencil 값을 0 으로 설정 |
GL_REPLACE |
stencil 값을 |
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)
이므로 test의 결과가 어떻든 stencil buffer의 값은 유지됩니다. 기본 행동은 stencil buffer를 수정하지 않는 것이므로 stencil buffer를 수정하고 있다면 옵션 중 하나라도 다른 행동으로 바꾸어야 합니다.
그래서
Object outlining
이전 섹션에서 stencil testing이 어떻게 작동하는지에 대해 완전히 이해가되지 않으실 것입니다. 그래서 stencil testing으로 구현될 수 있는 유용한 기능인
Object outlining은 말 그대로 입니다. 각 오브젝트(또는 오직 하나)에 대해 색이 입혀진 작은 외곽선을 생성합니다. 이는 예를 들면 전략 게임에서 유닛들을 선택하고 싶을 때 사용자가 어떠한 유닛들을 선택했는지 보여주어야 할 때 특히 유용한 효과입니다. 여러분의 오브젝트를 outlining하기 위한 과정은 다음과 같습니다.
- 오브젝트를 그리기 전에 stencil 함수를 GL_ALWAYS로 설정하고 오브젝트의 fragment가 렌더링될때마다 stencil buffer를
1
로 수정합니다. - 오브젝트를 렌더링합니다.
- stencil 작성과 depth testing을 비활성화합니다.
- 각 오브젝트들을 약간 확대합니다.
- 하나의 (외곽선) 컬러를 출력하는 별도의 fragment shader를 사용합니다.
- 오브젝트를 다시 그리지만 stencil 값이
1
과 같지 않은 fragment들만 그립니다. - 다시 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 값을 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 알고리즘의 결과는 다음과 같이 보여질 것입니다.
여기에서 전체 소스 코드를 확인하세요.
이 object outlining 알고리즘은 여러 게임에서 오브젝트의 선택을 시각화할 때(전략 게임)에 꽤 흔히 사용됩니다. 그리고 이러한 알고리즘은 model 클래스와 함게 쉽게 구현될 수 있습니다. 여러분은 간단히 외곽선을 그릴지 말지를 결정하는 boolean flag를 model 클래스에 생성할 수 있습니다. 좀 더 창의적이게 하고 싶다면 외곽선에 좀 더 자연스러운 효과를 넣기 위해 Gaussian Blur와 같은 전처리 필터의 도움을 사용할 수도 있습니다.
Stencil testing은 outlining 말고도 아주 많은 목적을 가지고 있습니다. 백미러의 텍스처를 그릴 때 거울의 모양에 맞게 그려야 하거나 실시간 그림자를 렌더링할 때