Advanced OpenGL(2) - Stencil test

흑빡·2026년 6월 16일

그래픽스

목록 보기
36/40
post-thumbnail

stencil?

unity나 뭐 다른엔진 사용해보면
오브젝트의 특정 부분만 가리고 싶을때 mask라는걸 사용할거임

이렇게 특정 부분에서 fragment를 지워주는게 stencil임

이렇게 stencil buffer를 통해
screen space에 출력되어야할 fragment들을 지워버리는거지비 ㅇㅇ

이런 작업을 수행하는 연산을
stencil test라고 함

stencil test는 fragment shader이후에 실행되고,
early depth test가 아닌 경우에

fragment shader -> stencil test -> depth test순서로 실행됨

stencil buffer는 보통 8비트의 값을 가짐
따라서 픽셀당 282^8의 경우의 수를 가지므로 최대 총 256개의 서로다른 스텐실 값을 가지게 되는거지

stencil buffer 사용법

간단함

  1. stencil buffer를 만들어줌
  2. buffer를 쓰기형식으로 바꾸어, 원하는 값을 채워넣을 준비
  3. 값을 채워넣음
  4. buffer를 읽기형식으로 바꾸어, 수정이 안되도록 잠금
  5. buffer를 이용해서 masking

stencil mask

depth buffer처럼
glEnable을 통해 먼저 설정을 enable시켜주고
매 프레임마다 stencil buffer를 초기화해주면 됨

glEnable(GL_STENCIL_TEST); 
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

Advenced OpenGL(1) - Depth test

여기서 glDepthMask를 보면
GL_TRUE, GL_FALSE로 DepthBuffer에
쓰기, 읽기로 바꿔주는걸 확인할 수 있음

stencil test도 마찬가지임

스텐실 값과 AND연산을 통해 연산이 수행될 비트마스크를 설정할 수 있음

glStencilMask(0xFF);
glStencilMask(0x00);
  • 0xFF(255) : 쓰기 허용, 특정 값 n과 AND연산을 하면 n이 됨
  • 0x00(0) : 쓰기 차단, 어떠한 값 n과 AND연산을 해도 0이됨

glStencilFunc

stencil설정에 필요한 메서드임

glStencilFunc(GLenum func, GLint ref, GLuint mask);

이렇게 되어있음

  • func : glDepthFunc에 사용되는 값과 의미가 동일함
  • ref : stencil test에 이용될 stencil 참조 값임
  • mask : 지정된 ref 스텐실 값과 AND연산이 수행되는 마스크,
    기본값은 0xFF

glStencilFunc는 스텐실 버퍼에 따라 특정 fragment를 통과시킬지 없애버릴지 결정을 하는거임

glStencilOp

stencil test의 성공여부에 따라 스텐실 버퍼를 어떻게 업데이트 할건지 정할 수 있음

glStencilOp(GLenum sfail, GLenum dpfail, GLenum dppass)
  • sfail : stencil test실패시의 버퍼 업데이트
  • dpfail : stencil test는 통과했지만, depth test 실패시의 버퍼 업데이트
  • dppass : stencil test, depth test모두 성공시의 버퍼 업데이트

각각의 옵션에 사용할 수 있는 값은 다음과 같음

조치설명
GL_KEEP현재 저장된 스텐실 값을 유지
GL_ZERO스텐실 값을 0으로 설정
GL_REPLACE스텐실 값을 glStencilFunc로 설정된 참조 값으로 대체
GL_INCR스텐실 값이 최대값보다 낮으면 1을 증가
GL_INCR_WRAPGL_INCR과 동일하지만 최대값(기본 8비트 0xFF)을 초과하면 0으로 돌아옴
GL_DECR스텐실 값이 최소값보다 높으면 1을 감소
GL_DECR_WRAPGL_DECR과 동일하지만 0보다 낮아지면 최대값으로 돌아옴
GL_INVERT현재 스텐실 버퍼 값을 비트 단위로 반전

기본옵션은 순서대로
GL_KEEP, GL_KEEP, GL_KEEP

outline그리기

stencil 예시로 외곽선 그리는걸 해볼거임

이 배낭의 외곽선을 흰색으로 그려보도록 하게뜸

먼저 rendering 외부에 stencil 설정을 해줌

glEnable(GL_STENCIL_TEST);
glStencilFunc(GL_NOTEQUAL, 1, 0xFF);
glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);

전략은 다음과 같음

  1. stencil test활성화
  2. ref로 넘겨지는 값(1)을 0xFF와 비교한 후,
    도출된 stencil 결과 값이 저장된 stencil 값과 다르면
    (GL_NOTEQUAL) stencil test통과
  3. sfail시 stencil buffer유지,
    dpfail시 stencil buffer유지,
    stencil, depth test성공시 stencil buffer교체

그리고 외곽선을 위한 쉐이더를 만들음

//vsh
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec2 aTexCoords;

out vec2 TexCoords;

uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;

void main()
{
    TexCoords = aTexCoords;
    gl_Position = projection * view * model * vec4(aPos, 1.0f);
}

//----------------------------

//fsh
#version 330 core
out vec4 FragColor;

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

이제 while문에서 수정만 해주면 됨

	while (!glfwWindowShouldClose(window))
    {
        // ...

        // render
        // ------
        glClearColor(0.05f, 0.05f, 0.05f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

        //1. 기본배낭 shader 설정
        modelMatShader.use();
        glm::mat4 projection = glm::perspective(glm::radians(camera.Zoom), (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f);
        glm::mat4 view = camera.GetViewMatrix();
        glm::mat4 model = glm::mat4(1.0f);
        modelMatShader.setMat4("projection", projection);
        modelMatShader.setMat4("view", view);
        modelMatShader.setMat4("model", model);
        
        //1. 기본크기 배낭 렌더링
        glStencilFunc(GL_ALWAYS, 1, 0xFF);
        glStencilMask(0xFF);
        
        myModel.Draw(modelMatShader);
        
        //2. 약간 큰 크기 배낭 shader 설정
        float scale = 1.01f;
        stencilShader.use();
        model = glm::scale(model, glm::vec3(scale, scale, scale));
        stencilShader.setMat4("model", model);
        stencilShader.setMat4("view", view);
        stencilShader.setMat4("projection", projection);
        
        //2. 외곽선용 약간 큰 크기 배낭 렌더링
        glStencilFunc(GL_NOTEQUAL, 1, 0xFF);
        glStencilMask(0x00); 
        
        //3
        glDisable(GL_DEPTH_TEST);
        
        myModel.Draw(stencilShader);
        
        //mode redering logic...
        
        //4
        glStencilMask(0xFF);
        glStencilFunc(GL_ALWAYS, 0, 0xFF);
        glEnable(GL_DEPTH_TEST);
        
        //...
    }

1,2,3,4가 있지?
그걸 차례대로 설명해줌

		//1. 기본크기 배낭 렌더링
        glStencilFunc(GL_ALWAYS, 1, 0xFF);
        glStencilMask(0xFF);
  1. 그려질 모델에 대하여 stencil 설정하기
    1 & 0xFF를 한 값을 GL_ALWAYS전략을 이용해 stencil buffer 채우는 옵션 설정
    추가로 stencil 쓰기 허용을 함으로써 현재 그려질 모델을 이용해 stencil buffer 채우기~~
		//2. 외곽선용 약간 큰 크기 배낭 렌더링
        glStencilFunc(GL_NOTEQUAL, 1, 0xFF);
        glStencilMask(0x00);
  1. 그려질 모델에 대하여 stencil 설정하기
    1 & 0xFF를 한 값을 GL_NOTEQUAL전략을 이용해 stencil buffer 채우는 옵션 설정
    추가로 stencil 쓰기를 허용하지 않음으로써 현재 그려질 모델은 stencil buffer에 영향을 주지 않음
    • 현재 stencil buffer의 값과 다른 값에 대해서만 통과 설정
		//3
        glDisable(GL_DEPTH_TEST);
  1. depth test를 일시적으로 비활성화
    외곽선을 그리게 될 모델은 원본 모델과 비슷한 크기, 위치에 그려짐
    따라서 depth test가 수행되면 이상하게 모델링 될 가능성이 생김
		//4
        glStencilMask(0xFF);
        glStencilFunc(GL_ALWAYS, 0, 0xFF);
        glEnable(GL_DEPTH_TEST);
  1. 하나의 렌더링 프레임 종료 전 수행해야되는 옵션들임
    • glStencilMask : 0xFF로 돌려놓음으로써 다음 프레임에 버퍼를 clear할때, 성공적으로 쓰기가 되도록 만들어놓음
    • glStencilFunc : 매 프레임 초기에 buffer clear를 하기때문에 필요는 없음
    • glEnable : 다음 프레임 실행될때 depth test가 꺼져있으면 buffer clear를 하나마나 소용이 없어 이상하게 렌더링 됨

크으~~~~

profile
그래픽스 하는 퍼그

0개의 댓글