본문 바로가기

Game/Graphics

Learn OpenGL - Advanced OpenGL : Stencil testing

link : https://learnopengl.com/Advanced-OpenGL/Stencil-testing


Stencil testing


 프래그먼트 쉐이더가 프래그먼트를 처리하고 나면, 깊이 테스트와 마찬가지로 조각을 파기 할 수 있는 스텐실 테스트가 실행된다.


그런 다음 남은 조각들은 더 많은 파편을 버릴 수 있는 깊이 테스트로 넘어간다. 스텐실 테스트는 스텐실 버퍼라는 또 다른 버퍼의


내용을 기반으로 한다. 스텐실 버퍼는 렌더링 중에 업데이트해 흥미로운 효과를 얻을 수 있다.



 스텐실 버퍼는 스텐실 값당 8비트를 포함하며 pixel / fragment 당 총 256개의 스텐실 값을 갖는다. 그런 다음 이러한 스텐실 값을


원하는 값으로 설정한 다음 특정 조각에 특정 스텐실 값이 있을 때마다 조각을 버리거나 보관할 수 있다.

 각 윈도우 라이브러리는 스텐실 버퍼를 설정해야한다. GLFW는 이 작업을 자동으로 수행하므로 GLFW에서 작성하도록 지시 할 필요는 없지만 다른 윈도우 라이브러리는 기본적으로 스텐실 라이브러리를 만들지 않으므로 라이브러리 설명서를 확인해야 한다.

 스텐실 버퍼의 간단한 예제는 다음과 같다:


A simple demonstration of a stencil buffer

 스텐실 버퍼는 먼저 0으로 지워지고 스텐실 버퍼에 1초의 열린 사각형이 설정된다. 그런 다음 조각의 스텐실 값에 1이 포함되어 있으면


장면의 조각만 렌더링된다. (나머지는 무시한다)



 스텐실 버퍼 작업을 사용하면 조각을 렌더링할 때마다 특정 값으로 스텐실 버퍼를 설정할 수 있다. 렌더링하는 동안 스텐실 버퍼의


내용을 변경해 스텐실 버퍼에 쓰고 있다. 같은 렌더링 반복에서 우리는 이 값을 읽고 특정 조각을 버리거나 전달할 수 있다.


스텐실 버퍼를 사용할 때 너가 원하는대로 crazy 할 수 있다. (해석 이상)


일반적인 개요는 다음과 같다:


    - 스텐실 버퍼 쓰기를 Enable한다.


    - 스텐실 버퍼의 내용을 업데이트해 객체를 렌더링한다.


    - 스텐실 버퍼에 쓸 수 없다.


    - 이번에는 다른 객체를 렌더링해 스텐실 버퍼의 내용을 기반으로 특정 조각을 버린다.


 스텐실 버퍼를 사용해 장면의 다른 그려진 오브젝트의 조각을 기반으로 특정 조각을 버릴 수 있다.



 GL_STENCIL_TEST를 활성화해 스텐실 테스트를 활성화 할 수 있다. 이 시점부터는 모든 렌더링 호출이 스텐실 버퍼에 영향을 미친다.

glEnable(GL_STENCIL_TEST);   

 색상 버퍼와 깊이 버퍼처럼 스텐실 버퍼를 반복할 때마다 반복해야한다:

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); 

 또한, 깊이 테스트의 glDepthMask 함수와 마찬가지로 스텐실 버퍼에 해당하는 함수가 있다. glStencilMask 함수는 스텐실 값과


AND 연산된 비트 마스크를 버퍼에 기록하도록 설정할 수 있다. 기본적으로 이 값은 출력에 영향을 주지 않는 모든 1의 비트 마스크로


설정되지만, 이 값을 0x00으로 설정하면 버퍼에 쓰여지는 모든 스텐실 값은 0으로 끝난다. 이는 깊이 테스트의 glDepthMask(GL_FALSE)와 동일하다:

glStencilMask(0xFF); // each bit is written to the stencil buffer as is
glStencilMask(0x00); // each bit ends up as 0 in the stencil buffer (disabling writes)

 대부분의 경우 스텐실 마스크로 0x00 또는 0xFF를 쓰겠지만 사용자 정의 비트 마스크를 설정하는 옵션이 있다는 것을 알고 있는 것이 좋다.






Stencil functions


 깊이 테스트와 마찬가지로 스텐실 테스트가 통과 또는 실패해야하는지, 스텐실 버퍼가 스텐실 버퍼에 영향을 미칠지 여부를 제어할 수 있다.


스텐실 테스트를 구성하는데 사용할 수 있는 두 가지 함수는 glStencilFunc 및 glStencilOp이다.



 glStencilFunc (GLenum func, GLint ref, GLuint mask)에는 세 개의 매개 변수가 있다.


    - func : 스텐실 테스트 기능을 설정한다. 이 테스트 함수는 저장된 스텐실 값과 glStencilFunc의 참조 값에 적용된다.


   가능한 옵션은 GL_NEVER, GL_LESS, GL_LEQUAL, GL_GREATER, GL_GEQUAL, GL_EQUAL, GL_NOTEQUAL, GL_ALWAYS이다.


    - ref : 스텐실 테스트의 참조 값을 지정한다. 스텐실 버퍼의 내용이 이 값과 비교된다.


    - mask : 테스트가 비교하기 전에 참조 값과 저장된 스텐실 값 모두와 ANDed 된 마스크를 지정한다. 초기에는 모두 1로 설정된다.


그래서 우리는 처음에 보여준 간단한 스텐실 예제의 경우 함수는 다음과 같이 설정된다:

glStencilFunc(GL_EQUAL, 1, 0xFF)

 이것은 OpenGL에게 프래그먼트의 스텐실 값이 참조 값 1과 같을 때 (조각이 테스트를 통과해 그려지거나 그렇지 않으면 삭제됨)를 알려준다.



 그러나 glStencilFunc는 OpenGL이 스텐실 버퍼의 내용으로 수행해야하는 작업만을 설명했지만 실제로 버퍼를 업데이트하는 방법은 설명하지


않았다. 그것이 glStencilOp가 들어오는 곳이다.



 glStencilOp (GLenum sfail, GLenum dpfail, GLenum dppass)에는 각 옵션에 대해 수행할 작업을 지정할 수 있는 세가지 옵션이 있다.


    - sfail : 스텐실 테스트가 실패할 경우 수행할 작업이다.


    - dpfail : 스텐실 테스트가 통과했지만 깊이 테스트가 실패할 때 수행할 작업이다.


    - dppass : 스텐실과 깊이 테스트가 모두 통과하면 수행할 작업이다.


 그런 다음 각 옵션에 대해 다음 작업 중 하나를 수행할 수 있다:

ActionDescription
GL_KEEPThe currently stored stencil value is kept.
GL_ZEROThe stencil value is set to 0.
GL_REPLACEThe stencil value is replaced with the reference value set with glStencilFunc.
GL_INCRThe stencil value is increased by 1 if it is lower than the maximum value.
GL_INCR_WRAPSame as GL_INCR, but wraps it back to 0 as soon as the maximum value is exceeded.
GL_DECRThe stencil value is decreased by 1 if it is higher than the minimum value.
GL_DECR_WRAPSame as GL_DECR, but wraps it to the maximum value if it ends up lower than 0.
GL_INVERTBitwise inverts the current stencil buffer value.


 기본적으로 glStencilOp 함수는 (GL_KEEP, GL_KEEP, GL_KEEP)로 설정되므로 테스트의 결과에 관계없이 스텐실 버퍼는 값을 유지한다.


기본 동작은 스텐실 버퍼를 업데이트하지 않으므로 스텐실 버퍼에 쓰려면 옵션에 대해 적어도 하나의 다른 동작을 지정해야한다.



 따라서 glStencilFunc와 glStencilOp를 사용해 스텐실 버퍼를 업데이트할 시기와 방법을 정확하게 지정할 수 있으며,


파편을 삭제해야 할 때 스텐실 테스트가 통과해야하는지 여부도 지정할 수 있다.







Object outlining


 스텐실 테스팅이 이전 섹션들에서 어떻게 작동하는지 완벽하게 이해한다면 스텐실 테스팅만으로 구현할 수 있는 유용한 기능을


설명할 것이다.


An object outlined using stencil testing/buffer


 객체 개요는 그것이 말하는대로 정확하게 수행한다. 각 오브젝트(또는 하나만)에 대해 (결합된) 오브젝트 주위에 작은 컬러


테두리를 만든다. 예를 들어 전략 게임에서 유닛을 선택하고, 어떤 유닛을 선택했는지 사용자에게 보여줄 때 유용하다.


개체의 윤곽을 그리는 루틴은 다음과 같다:


1. 스텐실 함수를 GL_ALWAYS로 설정한 다음 객체를 그리기 전에 스텐실 버퍼를 1로 업데이트한다.


2. 개체를 랜더링한다.


3. 스텐실 쓰기 및 깊이 테스트를 비활성화해라.


4. 각 개체의 크기를 약간 조정해라.


5. 단일 색상을 출력하는 다른 조각 쉐이더를 사용해라.


6. 조각의 스텐실 값이 1이 아닌 경우에만 오브젝트를 다시 그린다.


7. 스텐실 쓰기 및 깊이 테스트를 다시 활성화해라.


 이 프로세스는 각 조각의 스텐실 버퍼 내용을 1로 설정하고 테두리를 그릴 때 기본적으로 개체의 크기 조절된 버전을 그리며


스텐실 테스트가 통과 할 때마다 오브젝트의 경계선 주위에 확대된 버전이 그려진다. 우리는 기본적으로 스텐실 버퍼를 사용해 


원본 객체의 조각인 스케일된 버전의 모든 조각을 삭제한다.



 그래서 우리는 경계색을 출력하는 매우 기본적인 프래그먼트 쉐이더를 생성하려고 한다. 하드코드된 색상 값을 설정하고


쉐이더 shaderSingleColor를 호출하면된다:

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

 두 개의 컨테이너에 윤곽선을 추가해 바닥을 떠날 것이다. 따라서 먼저 바닥을 그린 다음 두 컨테이너를 그리고 나서 이전에


그려진 컨테이너 조각을 덮어 쓰는 조각을 버리는 동안 크기를 조정한 컨테이너를 그린다.



 먼저 스텐실 테스트를 활성화하고 테스트가 성공하거나 실패할 때마다 수행할 작업을 설정하려고 한다:

glEnable(GL_STENCIL_TEST);
glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);  

 테스트가 실패해도 아무것도 하지 않으면 스텐실 버퍼에 있는 현재 저장된 값을 그대로 유지한다. 그러나 스텐실 테스트와


깊이 테스트가 모두 성공하면 저장된 스텐실 값을 glStencilFunc를 통해 설정된 참조 값으로 바꾸고, 나중에 glStencilFunc를 1로 설정한다.



 스텐실 버퍼를 0으로 초기화하고 컨테이너의 경우 드로잉된 각 조각에 대해 스텐실 버퍼를 1로 업데이트한다:

glStencilFunc(GL_ALWAYS, 1, 0xFF); // all fragments should update the stencil buffer
glStencilMask(0xFF); // enable writing to the stencil buffer
normalShader.use();
DrawTwoContainers();

 GL_ALWAYS 스텐실 테스트 함수를 사용해 컨테이너의 각 조각이 스텐실 값 1로 스텐실 버퍼를 업데이트하는지 확인한다.


파편은 항상 스텐실 테스트를 통과하기 때문에 스텐실 버퍼는 참조 값으로 업데이트된다.



 이제 스텐실 버퍼가 컨테이너가 그려진 1s로 업데이트되었으므로 업 스케일된 컨테이너를 그려야하지만 이번에는 스텐실 버퍼에 쓰지 못하게된다:

glStencilFunc(GL_NOTEQUAL, 1, 0xFF);
glStencilMask(0x00); // disable writing to the stencil buffer
glDisable(GL_DEPTH_TEST);
shaderSingleColor.use(); 
DrawTwoScaledUpContainers();

 우리는 스텐실 함수를 GL_NOTEQUAL로 설정한다. GL_NOTEQUAL은 1과 같지 않은 컨테이너의 일부만 그리기 때문에 이전에 그려진 컨테이너


밖에 있는 컨테이너의 부분만 그린다. 깊이 테스트를 사용 중지해 크기가 조정된 컨테이너만 표시되도록해라. (테두리가 바닥에 겹쳐 쓰이지 않도록)



 또한, 일단 완료되면 심도 버퍼를 다시 활성화해라.



 장면에 대한 전체 객체 개요 루틴은 다음과 같다:

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); // make sure we don't update the stencil buffer while drawing the floor
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);  

 스텐실 테스팅의 일반적인 아이디어를 이해한다면 이 코드 조각을 너무 이해하기 어려워서는 안된다. 그렇지 않으면 이전 섹션을 다시 읽고


각 기능이 무엇을 하는지 완전히 이해하려고 시도해라.



 깊이 테스트 튜토리얼의 장면에서 이 개요 알고리즘의 결과는 다음과 같다:


3D scene with object outlining using a stencil buffer


 일반적으로 우리가 원하는 효과인 두 컨테이너 사이에 테두리가 겹쳐 있음을 볼 수 있다. 오브젝트마다 경계를 완벽하게 하려면 오브젝트당 스텐실 버퍼를 지워야하고 깊이 버퍼로 조금 창의적이어야한다.

 여러분이 보았던 오브젝트 개요 알고리즘은 여러 겡미에서 선택된 오브젝트를 시각화하기 위해 일반적으로 사용되며 이러한 알고리즘은


모델 클래스 내에서 쉽게 구현될 수 있다. 그런 다음 모델 클래스 내에 부울값을 설정해 테두리 없이 그릴 수 있다. 창의성을 발휘하려면


Gaussian Blur와 같은 사후 처리 필터를 사용해 테두리를 보다 자연스럽게 보이게 할 수 있다.



 스텐실 테스팅은 rear-viwe mirror 내부에 텍스처를 그리는 것과 같이 윤곽선이 있는 오브젝트 옆에 더 많은 목적을 가지고 있어


미러 모양에 깔끔하게 맞추거나 그림자 볼륨이라는 스텐실 버퍼 기술로 실시간 그림자를 렌더링한다. 스텐실 버퍼는 이미 광범위한


OpenGL 툴킷에서 또 다른 멋진 도구를 제공한다.






** ** ** ** ** ** ** ** ** **


오늘은 집중해서 두 챕터를 끝냈다. '물리 게임 엔진 개발' 한국 번역판 책을 주문했는데 화요일에 도착한다.


화요일에 도착한 후에는 OpenGL & Graphic와 물리 엔진 공부를 병행해야한다. 힘내자!