포스트

컴퓨터그래픽스 21. Stencil Buffer가 무엇인가


Stencil Buffer가 무엇인가?

Pasted image 20250612184214.png

특정 픽셀의 렌더링을 제한하거나, 제어할 수 있다. 이를 통해 마스킹 효과, 특수한 이미지 효과를 구현할 수 있다.

Stencil Buffer는 8비트(0~255) 정수형 데이터 타입으로 구성된다. 렌더링 여부만 파악하면 1비트만 있어도 되는거 아닌가? 왜 8비트나 사용할까? 여러 비트를 다른 목적으로 동시에 사용할 수 있기 때문이다.

  • 특정 비트는 그림자 계산용
  • 다른 비트는 반사 효과용
  • 또 다른 비트는 아웃라인 효과용

현재 픽셀이 Stencil 값이 몇인지 검사해서, 렌더링할지 특수효과를 적용할지를 검사하는 단계를 Stencil Test라고 한다. 이 단계는 Depth Test보다 먼저 수행되야 이득이다. Stencil Test에서 걸러지면, Depth Test를 안해도 되기 때문이다.

Stencil Buffer를 어떻게 사용하는가?

INIT에서 Stencil Test를 활성화한다.

1
glEnable(GL_STENCIL_TEST);

Depth Buffer와 같이 매 프레임마다 Stencil Buffer를 초기화해야 한다.

1
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

스탠실 버퍼 내의 값은 총 8비트를 사용할 수 있고, 이 8비트 중 각 비트를 사용자 입맛에 맞게 설정할 수 있다.

1
2
3
glStencilMask(0xFF); // 11111111 - 모든 비트 쓰기 가능 
glStencilMask(0x00); // 00000000 - 모든 비트 쓰기 금지 
glStencilMask(0x0F); // 00001111 - 하위 4비트만 쓰기 가능

위 함수로 Stencil Buffer의 특정 비트를 쓰게 하거나, 못쓰게 보호를 할 수 있다.

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

위 함수로 조건문을 만들어 해당 픽셀을 렌더링할지, 말지 결정할 수 있다. func는 비교 함수다. 다음 옵션이 가능하다.

함수의미통과 조건
GL_NEVER항상 실패어떤 경우에도 픽셀을 그리지 않음
GL_ALWAYS항상 통과모든 픽셀을 그림
GL_EQUAL같으면 통과(ref & mask) == (stencil & mask)
GL_NOTEQUAL다르면 통과(ref & mask) != (stencil & mask)
GL_LESS작으면 통과(ref & mask) < (stencil & mask)
GL_LEQUAL작거나 같으면 통과(ref & mask) <= (stencil & mask)
GL_GREATER크면 통과(ref & mask) > (stencil & mask)
GL_GEQUAL크거나 같으면 통과(ref & mask) >= (stencil & mask)

ref는 비교의 기준이 되는 값이다. 예를들어 ref = 1로 설정하고, GL_EQUAL로 설정하면 Stencil Buffer가 1인 값만 렌더링될 것이다.

mask는 비교에 사용할 비트를 선택한다. 11111111로 설정하면 모든 비트를 비교에 사용할 수 있다.

1
glStencilOp(GLenum sfail, GLenum dpfail, GLenum dppass);

위 함수로 스텐실 테스트 결과에 따라 스텐실 버퍼의 값을 어떻게 바꿀지 규칙을 설정할 수 있다. sfail는 스텐실 테스트가 실패시 동작, dpfail은 깊이 테스트가 실패시 동작, dppass는 둘다 통과시 동작한다. 각 매개변수에는 다음 선택지가 존재한다.

연산설명
GL_KEEP현재 스텐실 값을 유지 (기본값)
GL_ZERO스텐실 값을 0으로 설정
GL_REPLACEglStencilFunc에서 지정한 참조값으로 대체
GL_INCR스텐실 값을 1 증가 (최대값에서 고정)
GL_INCR_WRAP스텐실 값을 1 증가 (최대값에서 0으로 순환)
GL_DECR스텐실 값을 1 감소 (0에서 고정)
GL_DECR_WRAP스텐실 값을 1 감소 (0에서 최대값으로 순환)
GL_INVERT비트별 반전 연산 수행

즉 스탠실 버퍼는 마스킹하고, 스탠실 테스트 조건을 설정하고, 테스트 성공 또는 실패시 스탠실 버퍼의 값을 어떻게 바꿀지 설정해서 다양한 특수 효과를 구현할 수 있다.

그렇다면 특정 마스크 영역을 1로 설정하고, func로 equal 1일때만 test 통과하도록 하면 마스킹할 수 있다. 그럼 stencil buffer를 어떻게 초기화하는데? 마스킹 영역을 1로 초기화해야 하잖아.

스탠실 버퍼를 채우기 위해서, Stencil Buffer만 활성화 해서 Mask Object를 Draw하면 된다.

COLOR_BUFFER, DEPTH_BUFFER를 잠시 비활성화고 STENCIL_BUFFER만 활성화해둔다.

1
2
3
4
5
6
7
8
// 색상 버퍼 쓰기 비활성화 (마스크만 그리고 실제 색상은 안 그림) 
glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);

// 깊이 버퍼 쓰기 비활성화 (선택사항) 
glDepthMask(GL_FALSE); 

// 스텐실 버퍼 쓰기 활성화 
glStencilMask(0xFF);

그리고 스탠실 테스트를 항상 통과하도록 하고, 통과하면 버퍼의 값이 1로 대체되도록 만든다.

1
2
glStencilFunc(GL_ALWAYS, 1, 0xFF);
glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);

이후 마스크로 설정할 도형을 Draw하면, 그 도형 모양이 Stencil Buffer에 채워진다. 이런 원리로 Stencil Buffer를 채울 수 있다.

예제 코드는 다음과 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void renderWithCircularMask() {
    // 1. 초기화
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
    glEnable(GL_STENCIL_TEST);
    
    // 2. 마스크 영역 설정
    glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
    glStencilMask(0xFF);
    glStencilFunc(GL_ALWAYS, 1, 0xFF);
    glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
    
    drawCircle();  // 원형 마스크 영역 그리기
    
    // 3. 실제 렌더링 설정
    glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
    glStencilMask(0x00);  // 스텐실 쓰기 금지
    glStencilFunc(GL_EQUAL, 1, 0xFF);   // 스텐실 값이 1인 곳만
    glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);  // 스텐실 값 유지
    
    // 4. 최종 객체 렌더링
    drawBackground();  // 원형 영역에만 배경이 그려짐
    drawObjects();     // 원형 영역에만 객체들이 그려짐
}

Fragment Pipeline의 Test들..

Pasted image 20250612191023.png

프래그먼트는 위와 같은 여러 테스트를 거쳐 최종적으로 Frame Buffer에 기록된다.