Stencil Test

Date:     Updated:

카테고리:

태그:

Main Reference
- Learn OpenGL
- Rinthel Kwon - OpenGL Lecture


Stencil Buffer

stencil_buffer

ezgif com-crop

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

012332




맨 위로 이동하기

OpenGL 카테고리 내 다른 글 보러가기

댓글 남기기