KoreanFoodie's Study

[OpenGL ES] 15강 : 쉐도우 매핑(Shadow Mapping), PCR(Percentage Closer Filtering), Hard Shadow vs Soft Shadow 본문

Game Dev/OpenGL ES

[OpenGL ES] 15강 : 쉐도우 매핑(Shadow Mapping), PCR(Percentage Closer Filtering), Hard Shadow vs Soft Shadow

GoldGiver 2023. 4. 27. 14:42

이 강의는 유투브에 무료로 공개되어 있는 한정현 교수님의 컴퓨터 그래픽스 강좌를 정리한 글입니다. 자세한 내용은 강의를 직접 들으시거나 을 구입하셔서 확인해 보세요. 강의 자료는 깃헙 링크에 올라와 있습니다.

요약 :

1. 쉐도우 매핑(Shadow Mapping) 은 그림자를 처리하기 위해 Vertex Shader 에서 light source 로부터 각 surface 점까지의 깊이값과 visibility 값을 저장하는 기법을 의미한다.

2. Pass 1 에서는 각 표면까지의 깊이값 z 를, Pass 2 에서는 실제 점까지의 거리 d 를 저장하여, 이를 비교해서 Shadow 여부를 결정한다. Nearest Sampling 과 Bilinear Interpolation 둘 다 별 도움은 되지 않으므로, PCR(Percentage Closer Filtering) 기법을 사용하여 Visibility 를 수치값으로 표현한다.

3. Vertex Shader 에서 만든 Shadow Map 은 Output Merger 가 Z-buffering 을 하는 과정에서 활용되어 그림자가 출력되게 된다. 이 과정에서 Fragment Shader 는 아무 일도 하지 않는다!

쉐도우 매핑(Shadow Mapping)

그림자는 실제 게임에서 매우 중요한 요소이다!

 

그림자를 만들 때는 Two-pass 알고리즘을 사용한다.

일단 Pass 1 에선, light source 가 닿을 수 있는 점들을 렌더링한다. shadow map 에는 light source 에 대한 깊이값만 저장한다! 이때의 값이 z 이다 😄

 

이제 Pass 2 에서는 카메라 관점에서 렌더링을 실제로 진행해 보자.

각 픽셀마다, light source 까지의 거리를 d 라고 하면... d > z 일때, shadow 가 생길 것이다! d1 과 z1 의 관계를 보면 이해가 쉬울 것이다 😉

 

위의 알고리즘을 사용하면 위와 같은 화면이 나오는데... 아직 좀 이상해 보인다. 특히 그림자 부분을 보면...

빛을 받는 것으로 판정이 되야하는 픽셀에 shadow 가 맺히고 있다! (특히 울퉁불퉁한 부분)

 

문제가 뭘까? lit 되어야 하는 표면에서, shadow 가 공존하고 있어 이런 문제가 생긴다.

그 이유는... 실제로 모든 점에 대해 렌더링이 되는 게 아니라, 일정 단위(크기)간격으로 shadow maping 이 이루어지기 때문이다!

즉, q1 과 q2 점을 보면, nearest sampling 을 했을 때, q1 은 shadow 가 생기고 q2 는 lit 이 되게 된다.

 

해법은 간단하다. bias 값이라는... offset 을 만들어서, d1 - bias > z1 인지를 판정하여 shadow 를 맺히게 하면 된다!

d1 - bias = d1' 이므로, 이제 d1' < z1 일때 lit 이 될 것이다 😆

 

bias 를 어떻게 조절하느냐에 따라, 위처럼 그림자의 크기가 달라진다 😅

 

그럼 bias 를 어떻게 적절하게 설정할 수 있을까?

빨간색 점은 Pass 1 에서, 파란색 점들은 Pass 2 에서 생기는 점들이다. 

위처럼, q3 와 q4 점이 있고, 해당 점에 대해 nearest sampling 을 하면, 위처럼 결과값에서 각진 부분이 생기게 된다 😥

 

이를 해결하기 위해서, texturing - mipmap 에서 사용했듯이, bilinear interpolation 을 활용해보자. 그런데 픽셀은 완전히 lit 이거나 완전히 shadowed 되는 경우밖에 없으므로, 왼쪽 그림처럼 별로 도움이 되지 않는다 😮

해결책으로는... 인접한 4개의 텍셀에 대해 픽셀의 visibility 를 먼저 결정한 후, visibility 를 보간하는 방법이 있다. 이 값은 'degree of being lit' 이라고 부른다. 그럼 각 픽셀의 shawdow 에 대한 visibility 는 lit 이냐 shadowed 냐가 아닌, 'percentage' 값으로 표현될 것이다. 0 은 shadow, 1 은 lit 이다. 이 기법을 PCF(Percentage Closer Filtering) 이라고 한다! 😎

 

실제 구현은 위와 같다. 먼저 Pass 1 부터! View Matrix 를 이용하면, World Space 의 vertex 를 light space 로 변환할 수 있다. 위에서의 position 은 뭐.. 그냥 object space 의 한 점일 것이다.

light space 는 빛을 중심으로 한 카메라 공간이라고 생각하면 된다. fovy, aspect, n, f 이 있으면, view frustum 도 만들 수 있다. 

 

위에서 식을 보면... Fragment Shader 의 main 함수가 텅 비어있다.

왜 그럴까? 왜냐하면 Vertex Shader 의 경우, gl_Position 을 제외하면 딱히 보간해서 넘겨줄 값이 없다. Color 를 처리하는 것도 아니기 때문이다...!

실제로 Vertex Shader 가 만들어낸 Shadow Map 에 대한 정보는, 깊이값에 해당한다. 따라서, 이는 Output Merger 에게 넘어가게 되고, Output Merger 가 z-buffering 을 수행하는 과정해서 해당 깊이값을 활용하게 된다!

 

마지막으로... Hard Shadow 와 Soft Shadow 가 있다.

 

두 가지 모두 실시간으로 Shadow 를 처리하나, Hard Shadow 는 외곽선을 부드럽게 처리하지는 않고, Soft Shadow 는 외곽선을 부드럽게 처리해 준다. 대신 많은 부하를 준다! 😅

Comments