Stencil Test
카테고리: OpenGL
태그: Graphics
Main Reference
- Learn OpenGL
- Rinthel Kwon - OpenGL Lecture
Stencil Buffer
Rasterize State가 끝나고 오브젝트의 위치가 픽셀 단위로 나타나게 되면, Depth/Stencil Test가 진행된다. 둘 다 해당 픽셀이 fragment에 사용될지 폐기될지 결정하는 단계인데, 순서로는 Stencil 테스트가 먼저 진행되고, 그 다음 Depth 테스트가 진행된다. Stencil Buffer는 프레임 버퍼와 사이즈가 동일하며 8비트(0~255) 정수형 데이터로 이루어져 있다. 대표적인 예시로는 화면의 일정 부분만 렌더링 하는 거울 효과라던가, 아래 예시에 등장하는 오브젝트의 outline을 그리는데에도 아주 유용하다. Stencil Test의 사용 방법은 Depth Test와 비슷한데, 훨씬 더 다양한 연산을 제공한다.
Stecntil 관련 함수에 항상 mask값이 들어가는데 이게 어떤 역할을 하는지 한참을 고민했다. 예전 DX11을 공부했을 때 정리한 내용을 찾아보니 32bit 버퍼로 depth-stencil test를 진행하는데, 앞에 24bit는 stencil test용, 남은 8bit는 depth test용으로 사용했었다. 이를 바탕으로 생각해보면 비용이 많이 드는 stencil buffer 생성은 한 번만 하고, mask값으로 특정 비트들만을 사용해 여러가지 테스트를 진행할 수 있기 때문에 mask를 사용하는 듯하다.
Stencil Test
glEnable(GL_STENCIL_TEST);
glStencilMask(0xFF);
glEnable(GL_STENCIL_TEST)
: Stencil Test 활성화glStencilMask(0xFF)
: stencil buffer 작성 시 AND연산으로 사용할 마스크 값 지정- clear값으로 사용하거나
- 0x00 값을 통해 특정 오브젝트는 stencil buffer 건드리지 못하게 할 수도 있음
glStencilFunc(GLenum func, GLint ref, GLuint mask)
- ref값과 비교하여 func 연산이 true를 리턴하면 stencil test 통과, false면 실패
- ex)
glStencilFunc(GL_EQUAL, 1, 0xFF)
: 각 픽셀별로 (stencil AND 0xFF) 값과 (1 AND 0xFF) 값이 같으면 테스트 통과 - func 연산은 depth test와 동일하게
- GL_NEVER, GL_LESS, GL_LEQUAL, GL_GREATER, GL_GEQUAL, GL_EQUAL, GL_NOTEQUAL and GL_ALWAYS
glStencilOp(GLenum sfail, GLenum dpfail, GLenum dppass)
Method | Description |
---|---|
GL_KEEP | stencil 값 유지 |
GL_ZERO | stencil 값 0으로 설정 |
GL_REPLACE | stencil 값 glStencilFunc 함수에서 지정한 레퍼런스 값으로 설정 |
GL_INCR | stencil 값 1만큼 증가 (최대값이면 유지) |
GL_INCR_WRAP | stencil 값 1만큼 증가 (최대값이면 0으로 변경) |
GL_DECR | stencil 값 1만큼 감소 (최소값이면 유지) |
GL_DECR_WRAP | stencil 값 1만큼 감소 (최소값이면 최대값으로 변경) |
GL_INVERT | 비트 뒤집기 |
sfail
: stencil test가 실패했을 때 동작dpfail
: stencil test 통과 & depth test 실패 시 동작dppass
: stencil test 통과 & depth test 통과 시 동작
Example
// Render()...
glEnable(GL_STENCIL_TEST);
glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
glStencilFunc(GL_ALWAYS, 1, 0xFF);
glStencilMask(0xFF);
modelTransform =
glm::translate(glm::mat4(1.0f), glm::vec3(0.0f, 0.75f, 2.0f)) *
glm::rotate(glm::mat4(1.0f), glm::radians(20.0f), glm::vec3(0.0f, 1.0f, 0.0f)) *
glm::scale(glm::mat4(1.0f), glm::vec3(1.5f, 1.5f, 1.5f));
transform = projection * view * modelTransform;
m_program->SetUniform("transform", transform);
m_program->SetUniform("modelTransform", modelTransform);
m_box2Material->SetToProgram(m_program.get());
m_box->Draw(m_program.get());
glStencilFunc(GL_NOTEQUAL, 1, 0xFF);
glStencilMask(0x00);
glDisable(GL_DEPTH_TEST);
m_simpleProgram->Use();
m_simpleProgram->SetUniform("color", glm::vec4(1.0f, 1.0f, 0.5f, 1.0f));
m_simpleProgram->SetUniform("transform", transform * glm::scale(glm::mat4(1.0f), glm::vec3(1.05f, 1.05f, 1.05f)));
m_box->Draw(m_simpleProgram.get());
glEnable(GL_DEPTH_TEST);
glDisable(GL_STENCIL_TEST);
glStencilMask(0xFF);
glStencilFunc(GL_ALWAYS, 1, 0xFF);
: box 오브젝트가 그려지는 픽셀들은 무조건 stencil test 통과glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
: stencil test & depth test 모두 통과하면 ref값 1로 채움- box 렌더링
- stencil buffer는 box 위치 전부 1로 채워진 상태
glStencilFunc(GL_NOTEQUAL, 1, 0xFF);
: stencil 값이 1이 아닌 픽셀들만 stencil test 통과glStencilMask(0x00);
: stencil buffer는 건드리지 않음(stencil test랑은 다른 개념)glDisable(GL_DEPTH_TEST);
: depth를 끄는 이유는.. 보통 내가 선택한 캐릭이 벽 뒤에 있어도 테두리가 표시되어야 하기 때문- 지정된 컬러로만 렌더링하는 simpleProgram 사용
- transform matrix 넘겨줄 때 살짝 키워서(scaling) 전달
- box 렌더링
- 이때 새로운 박스는 기존 박스보다 약간 큰 박스
- 기존 박스 부분은 stencil test에 의해 폐기
- 결과적으로 남은 부분, 즉 테두리 영역만 렌더링
glclear()
를 이용해 stencil buffer를 초기화해야 하는데 이때 사용할 mask값 지정
Result
댓글 남기기