Stencil testing
fragment shader가 fragment를 처리하기만 한다면, 소위 stencil test가 실행된다. 그것은 depth test처럼, fragment를 버리는 가능성을 가진다. 그러고나서 남아있는 fragments는 가능하게 심지어 더 많은 fragments들을 버릴 수 있는 depth test에 넘겨지게 된다. 그 stencil test는 stencil buffer라고 불려지는 다른 버퍼의 내용을 기반으로 한다. 우리는 렌더링 하는 동안 흥미로운 효과를 얻기위해 그 버퍼를 업데이트 하는 것이 허용된다.
stencil 버퍼는 (보통) pixle/fragment마다 256개의 다른 stencil values로 구성되는 stencil value당 8비트를 포함한다. 우리는 그러고나서 이러한 stencil values 들을 우리가 좋아하는 값으로 설정할 수 있고, 그러고나서 우리는 특정한 fragment가 특정한 stencil value를 가질 때 마다 fragments들을 버리거나 유지할 수 있다.
Green Box
각 windowing library는 너를 위해 stencil buffer를 설정할 필요가 있다. GLFW는 이것을 자동적으로 한다. 그래서, 우리는 GLFW에게 하나를 만들라고 할 필요가 없지만, 다른 windowing libraries들은 기본적으로 stencil library를 만들지 않을지도 모른다. 그래서 너의 라이브러리의 문서를 참고하도록 해라.
stencil buffer의 간단한 예는 아래에서 보여진다:
stencil buffer는 처음에 0으로 clear되고, 그리고나서 1로된 open rectangle이 stencil buffer에서 설정된다. 그 fragment의 stencil value가 1을 포함하는 곳에서 (다른 것들은 버려지고)그 scene의 fragments들은 그러고나서야만 렌더링 된다.
Stencil 버퍼 연산은 우리가 우리가 fragments를 렌더링하는 곳 어디든 특정한 값들을 stencil buffer에 설정할 수 있도록 해준다. 렌더링 하는 동안 stencil buffer의 내용을 바꾸어서, 우리는 stencil buffer에 writing을 한다. 같은 (또는 다음의) 렌더 반복에서, 우리는 어떤 fragments들을 버리거나 넘겨주기 위해 이러한 값들을 read할 수 있다. stencil buffers를 사용할 때, 너는 너가 좋아하는 만큼 열광할 수 있지만, 일반적인 개요는 보통 다음과 같다:
- stencil buffer에 쓰기 활성화
- stencil buffer의 내용을 업데이트 하여 오브젝트를 렌더링
- stencil buffer에 쓰기 비활성화
- stencil buffer의 내용을 기반으로 어떤 fragments를 이번에 버려서 (다른) 오브젝트들 렌더링
stencil buffer를 사용하여, 우리는 따라서 그 scene에서 다른 그려진 오브젝트들의 fragments들을 기반으로 어떤 fragments를 버릴 수 있다.
너는 GL_STENCIL_TEST를 활성화하여 stencil testing을 활성화 할 수 있다. 그 시점으로 부터, 모든 렌더링 호출은 한 방향 또는 다른 방향으로 stencil buffer에 영향을 준다.
glEnable(GL_STENCIL_TEST);
너는 또한 color와 depth buffer처럼 매 반복마다 stencil buffer를 clear할 필요가 있는 것에 유의해라.
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
또한, depth testing의 glDepthMask 함수 처럼, stencil buffer에 대해서 동일한 함수가 있다. 그 함수 glStencilMask는 우리가 버퍼에 쓰여지기 위해 stencil value와 함께 AND 연산 될 bitmask를 설정하도록 해준다. 기본적으로 이것은 output에 영향을 미치지 않는 모두 1로 구성된 비트마스크로 설정된다. 그러나 만약 우리가 이것은 0x00으로 설정한다면, 버퍼에 쓰여지는 모든 stencil values는 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 mask로서 쓸 것이지만, custom 비트마스크를 설정하는 옵션이 있다는 것을 아는것이 좋다.
Stencil functions
depth testing처럼, 우리는 stencil test가 언제 pass하거나 fail할지에 대해 그리고 어떻게 그것이 stencil buffer에 영향을 미칠지에 대해 어느정도 통제할 수 있다. stencil testing을 설정하기 위해 우리가 사용할 수 있는 총 두 개의 함수들이 있다: glStencilFunc and glStencilOp.
그 glStencilFunc(Glenum func, Glint ref, Gluint mask)는 세 개의 인자를 갖는다:
그 glStencilFunc(Glenum func, Glint ref, Gluint mask)는 세 개의 인자를 갖는다:
- func : stencil test 함수를 설정한다. 이 테스트 함수는 저장된 stencil value과 glStencilFunc의 ref value에 적용된다. 가능한 옵션들은 : GL_NEVER, GL_LESS, GL_LEQUAL, GL_GREATER, GL_GEQUAL, GLEQUAL, GLNOTEQUAL, and GL_ALWAYS. 이러한 것들의 의미는 depth buffer의 함수들과 유사하다.
- ref : stencil test에 대하 참조 값을 명시한다. 그 stencil buffer의 내용은 이 값과 비교된다.
- mask : 테스트가 값들을 비교하기 전에 reference value와 저장된 stencil value 둘 다에 AND 연산될 마스크를 명시한다.
그래서, 간단한 stencil example의 경우에, 우리는 시작에서 그 함수가 다음과 같이 설정되는 것을 보여준다 :
glStencilFunc(GL_EQUAL, 1, 0xFF)
이것은 OpenGL에게 한 fragment의 stencil value가 reference value 1과 동일할 때(GL_EQUAL), 그 fragment가 테스트를 통과하고, 그려지고, 그렇지 않으면 버려진다.
그러나 glStencilFunc는 오직 OpenGL이 stencil buffer의 내용과 해야만 하는 것을 묘사했지만, 우리가 실제로 버퍼를 어떻게 업데이트 하는지는 묘사하지 않는다. 이것이 glStencilOp가 오는 지점이다.
glStencilOp(GLenum sfail, GLenum dpfail, GLenum dppass) 는 우리가 각 옵션에 대해 무슨 행동을 취할 지를 명시하는 세 가지 옵션을 포함한다:
- sfail : 만약 stencil test가 실패한다면 취할 행동
- dpfail : 만약 stencil test는 통과했지만, depth test가 실패하면 취할 행동
- dppass : stencil과 depth test 둘 다 통과하면 취할 행동
그러고나서 그 옵션들 각각에 대해, 너는 다음의 행동들 중 어떤 것이든 취할 수 있다:
- GL_KEEP : 현재 저장된 stencil value를 유지한다.
- GL_ZERO : stencil value는 0으로 설정한다.
- GL_REPLACE : 그 stencil value는 glStencilFunc로 설정된 reference value로 교체된다.
- GL_INCR : 만약 stencil value가 최대값보다 더 작다면, 1 증가 시킨다.
- GL_INCR_WRAP : GL_INCR과 비슷하지만, MAXIMUM VALUE를 초과하자마자 그것을 0으로 만든다.
- GL_DECR : 만약 그것이 최소값 보다 더 크다면, 1 감소 시킨다.
- GL_DECR_WRAP : GL_DECR과 같지만, 그것이 0보다 더 작게 되면, 최대값으로 wrap 한다.
- GL_INVERT : 현재 stencil buffer value를 비트연산으로 invert한다.
기본적으롷 glStencilOp 함수는 (GL_KEEP, GL_KEEP, GL_KEEP)으로 설정되어 있다. 그래서 그 테스트의 어떤 결과가 나오든, 그 stencil buffer는 그것의 값을 유지한다. 기본 행동은 stencil buffer를 유지하지 않는다. 그래서 만약 너가 stencil buffer에 쓰기를 하고싶다면, 너는 적어도 그 옵션들 중 어떤 것에 대해 한 가지 다른 action을 명시할 필요가 있다.
그래서 glStencilFunc와 g;lStencilOp를 사용하여, 우리는 정확히 언제, 어떻게 우리가 stencil buffer를 업데이트 할 지를 명시할 수 있고, 그리고 또한 그 stencil test가 언제 통과해야하고 그렇지 않을지를 명시할 수 있다. 예를들어, fragments가 버려져야 할 때.
Object outlining
만약 너가 stencil testing이 어떻게 작동하는지를 이전 섹션으로부터 완전히 이해했다면 그것은 그럴 가능성이 없다. 그래서 우리는 object outlining이라고 불려지는 stencil testing만을 가지고 구현될 수 있는 특별하고 실용적인 특징을 보여줄 것이다.
Object outlining는 정확히 그것이 말한 그것을 한다. 각 오브젝트에 대해 (또는 오직 하나에 대해) 우리는 그 (결합된) 오브젝트의 주변에 작은 색이 칠해진 영역을 만들 것이다. 이것은 예를들어 너가 전략 게임에서 유닛들을 선택하길 원하고, 사용자에게 어떤 유닛들이 선택되었는지를 보여줄 필요가 있을 때, 특별힌 유용한 효과이다. 너의 오브젝트들을 윤곽잡는 것의 루틴은 다음과 같다:
- 그 (윤곽이 그려질) 오브젝트를 그리기 전에 stencil func를 GL_ALWAYS로 설정해라. 그리고 그 오브젝트의 fragments가 렌더링 되는 곳에서 stencil buffer를 1로 업데이트 해라.
- 그 오브젝트를 렌더링 해라
- stencil writing과 depth testing을 비활성화 해라.
- 그 오브젝트 각각을 작은 양으로 스케일링 해라.
- 하나의 (영역) 컬러를 만들어내는 다른 fragment shader를 사용해라.
- 그 오브젝트를 다시 그려라, 그러나 오직 그것들의 fragments의 stencil values들이 1과 동일하지 않은 경우에만.
- stencil writing과 depth testing을 다시 활성화 시켜라
이 프로세스는 그 오브젝트의 fragments 각각에 대해 stencil buffer의 내용을 1로 설정한다. 그리고 우리가 그 영역을 그리길 원할 때, 우리는 기본적으로 그 오브젝트의 스케일이 커진 버전을 그린다. 그리고 그 stencil test를 통과하는 곳 어디든, 스케일 업된 버전들이 그려지고, 그 버전이 그 오브젝트의 테두리가 된다. 우리는 기본적으로 stencil buffer를 사용하여 원래 오브젝트의 fragments의 부분이 scale-up 버전들의 모든 fragments를 버린다.
그래서 우리는 border color를 만들어내는 매우 기본적인 fragment를 만들어 낼 것이다. 우리는 간단히 하드코딩된 컬러 값을 설정하고, 그 쉐이더를 shaderSingleColor라고 부른다:
void main() { FragCoor = vec4(0.04, 0.28, 0.26, 1.0); }
우리는 오직 두 컨테이너들에 대해 object outlining을 추가할 것이다. 그래서 우리는 그것에서 바닥은 내버려 둘 것이다. 우리는 따라서 처음에 그 바닥을 그리기 원한다; 그러고나서 그 두 컨테이너들을 그리고 (stencil buffer에 쓰는 동안), 그리고나서 scaled-up container들을 그린다 (이전에 그려진 container fragments에 덮여 씌워지는 fragments들을 버리는 동안)
우리는 처음에 stencil testing을 활성화 시키길 원하고, tests중 어떤 것이든 성공하거나 실패 할 때 마다 취해질 행동을 설정한다:
glEnable(GL_STENCIL_TEST);
glStencilOp(GL_KEEP, GL_KEEP, GL_REPLCAE);
만약 테스트들 중 어떤 것이라도 실패한다면, 우리는 아무것도 하지 않고, 우리는 간단히 stencil buffer에 있는 현재 저장된 값을 유지한다. 만약 stencil test와 depth test 둘 다 성공한다면, 그러나, 우리는 그 저장된 값을 glStencilFunc를 통해 우리가 나중에 1로 설정한 참조 값으로 대체하길 원한다.
우리는 stencil buffer를 0으로 clear하고, 컨테이너에 대해, 우리는 그려진 각 fragment에 대해 stencil buffer를 1로 업데이트 한다:
우리는 처음에 stencil testing을 활성화 시키길 원하고, tests중 어떤 것이든 성공하거나 실패 할 때 마다 취해질 행동을 설정한다:
glEnable(GL_STENCIL_TEST);
glStencilOp(GL_KEEP, GL_KEEP, GL_REPLCAE);
만약 테스트들 중 어떤 것이라도 실패한다면, 우리는 아무것도 하지 않고, 우리는 간단히 stencil buffer에 있는 현재 저장된 값을 유지한다. 만약 stencil test와 depth test 둘 다 성공한다면, 그러나, 우리는 그 저장된 값을 glStencilFunc를 통해 우리가 나중에 1로 설정한 참조 값으로 대체하길 원한다.
우리는 stencil buffer를 0으로 clear하고, 컨테이너에 대해, 우리는 그려진 각 fragment에 대해 stencil buffer를 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 stencil testing 함수를 사용하여, 우리는 그 컨테이너들의 fragments 각각이 stencil buffer를 1의 값으로 업데이트 하도록 한다. fragments는 항상 stencil test를 패스하기 때문에, stencil buffer는 우리가 그것들을 그리는 ㄳ에서 어디서든 참조 값으로 업데이트 된다.
컨테이너가 그려진 곳에서 stencil buffer가 1로 업데이트 되었으니, 우리는 업스케일드 된 컨테이너들을 그릴 것이지만, 이번에 stencil buffer에 쓰는 것을 비활성화 시킬 것이다:
glStencilFunc(GL_NOTEQUAL, 1, 0xFF); glStencilMask(0x00); // disable writing to the stencil buffer glDisable(GL_DEPTH_TEST); shaderSingleColor.use(); DrawTwoScaleUpContainers();
우리는 stencil function을 stencil value가 1이 아닌 컨테이너의 부분만을 그리도록 하는 GL_NOTEQUAL로 설정한다. 따라서 오직 이전에 그려진 컨테이너들의 밖에있는 컨테이너의 부분을 그린다. 우리는 또한 depth testing을 비활성화 했다는 것에 유의해라. 그래서 그 영역들은 바닥에 의해 덮여씌워지지 않는다.
또한 일이 처리되면 depth buffer를 다시 활성화 하도록 해라.
우리의 scene을 위한 전체 object outlining routine은 이것과 같아 보일 것이다:
glEnable(GL_DEPTH_TEST); glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); glClearColor(0.11f, 0.11f, 0.11f, 1.0f); 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); // all fragments should update the stencil buffer glStencilMask(0xFF); // enable writing to the stencil buffer DrawTwoContainers(); glStencilFunc(GL_NOTEQUAL, 1, 0xFF); glStencilMask(0x00); glDisable(GL_DEPTH_TEST); shaderSingleColor.use(); DrawTwoScaleUpContainers(); glStencilMask(0xFF); glEnable(GL_DEPTH_TEST);
너가 stencil testing 뒤에 있는 일반적인 아이디어를 이해하는 한, 이 코드 조각은 이해하기에 너무 어렵지 않을 것이다. 만약 그렇지 않다면, 세심히 이전 섹션을 다시 읽도록 하고, 그 함수들이 각각 무엇을 하는지 이해하려고 해라. 너가 그것의 사용의 예제들을 봐왔으니까.
depth testing tutorial에 있는 scene에서 이 outlining algorithm의 결과는 이것과 같다:
(이렇게 했을 떄, CPU 19%, GPU 11%, MEMORY 50MB정도 쓴다.)
object outlining algorithm의 완전하 코드를 보기위해서 여기에서 소스코드를 체크해라.
Green Box
너는 두 컨테이너 사이에서 경계가 중첩되는 것을 볼 수 있다. 이것은 보통 우리가 원하는 효과이다. (전략 게임을 생각해라. 거기에서 우리는 10개의 유닛들을 선택하고 싶어한다; 경계를 합치는 것은 보통 우리가 원하는 것이다.) 만약 너가 오브젝트 마다 완전한 경계를 원한다면, 너는 stencil buffer를 오브젝트마다 clear 해야할 것이고, depth buffer에 대해 좀 더 창의적이여야 할 것이다.
너가 보아온 object outlining algorithm은 꽤 흔히 선택된 오브젝트들을 시각화 하기 위해 몇 가지 게임들에서 사용된다. (전략 게임을 생각해보아라) 그리고 그러한 한 알고리즘은 쉽게 model class 내에서 구현되어질 수 있다. 너는 그러고나서 간단히 model class 내에서 테두리를 가진채 그릴지 없애고 그릴지를 위해 boolean flag를 설정할 수 있다. 만약 너가 창의적이고 싶다면, 너는 심지어 Gaussian Blur와 같은 후처리 필터의 도움으로 좀 더 자연수러운 테두리를 줄 수 있다.
오브젝트의 윤곽을 잡는 것 외에도 Stencil testing은 좀 더 많은 목적을 가지고 있다. 예를들어, 후방-뷰 거울 안에서 텍스쳐를 그리기 같은. 그래서 그것은 깔끔히 mirro shape에 들어맞다. 또는 shawdow volumes이라고 불려지는 stencil buffer와 함께 실시간 그림자를 렌더링하는 예도 있다. Stencil buffers는 광범위한 OpenGL toolkit에서 우리에게 아직 다른 좋은 도구들을 제공한다.
==================================
나중에 이 object outlining은 게임에 무조건 쓰일 것 같으니, 기억해 놓고 그 때 개발하자.
또한 Gaussian Blur도 Fast 버전이 있으니 그것으로 구현해서, 게임에서 좀 더 자연스러운 비쥬얼을 구성하자.
댓글 없음:
댓글 쓰기