최상단 광고

2012년 4월 9일 월요일

초간단 perspective shadow map


인터넷 뒈지게 느리네 진짜...
..
..
..
음음...
깊이 버퍼 쉐도우 맵이라는거 정말 좋은 그림자 기법입니다.
첫째 구현하기 간단하고.. 둘째 간단함에도 불구 하고 자기그림자까지 표현하는 정교함까지..
그런데 장점이 좋은 만큼 단점도 눈에 확 들어오는데요.

1. Z Fighting 에 의한 Moire 발생.
2. 넓은 영역을 커버 할때 나타나는 엄청 거슬리는 깍뚜기(알리아싱)들.

그래서 여러방법이 나오는데
PSM (Perspective Shadow map), LisPSM(Light Space Perspective Shadow map), TSM(Trapezoidal Shadow Maps).. 등등등..
그래서 검색을 해보면 별 해괴 망측한 수식들과 그림들이 튀어나옵니다.
뭐 이런 그림들이지요. 저는 당최 모르겠습니다.
대학교에서 4년이나 물리학을 배웠는데도 도데체가 뭔소린지.. 뷰프러스텀 짜부려놓고 저게 무슨 논문이라고.. 지들은 알아보는건지..
남들은 잘정리된 논문이라고 하던데.. 난 머리가 나쁜것인지..

그래서 그냥 제가 만들기로 했더랬지요.
먼저 아주 전통적인 깊이버퍼 그림자를 구현 해보면..
이렇게 나옵니다.. 180m X 180m를 그림자 한장으로 덮었더니 이꼴이 났는데요.
그림자의 해상도는 자그마치 2048X2048 짜립니다.
예.. 멀리 있는건 볼만 하지만 앞에 있는 그림자는 정말 못봐주겠더랩니다.
2048이라는 해상도는 다 어따가 팽개친건지..
다음 그림이 어따가 팽개쳤는지 알려줍니다.
개략적으로 그린것이긴 하지만 대충 봐도 낭비가 엄청 심하다는것을 알수 있구요.
2048이면 뭐합니까 앞에서는 40~200 픽셀 정도 밖에 못쓰는데..
..
..
..
그럼 다음 그림을 보면..
오오.. 앞에 그림과 거의 같은 위치에서 보는 것이지만 틀려도 너무 틀립니다.
물론 해상도는 되려 작습니다. 1024X1024를 쓰고 있습니다.
어떻게 이런일이 가능해 버린걸까요?
깊이버퍼를 보면 다음과 같습니다.
역시 대충 그린 그림입니다. 하지만 뭔가 틀리지요?
네. 카메라와 가까운곳은 크게 그려주고 먼곳은 작게 그려주는것. 그게 포인트 입니다.
낭비되는 범위는 비슷하지만 그려지는 화질이 틀린것이지요.
PSM의 공통된 의도는 바로 그겁니다. 가까운곳에는 텍스처의 픽셀을 많이 할당하고 먼곳은 적게 할당한다.
간단하지요?
그런데 그 수식 구할라고 하면 아주 박터집니다. 도데체 뭔말인지도 모르겠고 수식은 뭔 기호가 그렇게 많은지..
물론 위에 제가 직접 사용한 방식은 PSM의 의도만을 가지고 순수하게 제가 그냥 코딩한 것이라서 내세우긴 좀 뭐합니다만....

저와 마찬가지로 그냥 PSM의 맛보기라도 하고싶다라는 분들이 있을꺼라 생각하고..
또 제가 알고 있는 이 방법이 의외로 다른 분들에게 도움이 될지도 모른다는 생각에 씁니다.

[시작.]
먼저 전통적인 깊이 버퍼 그림자의 순서입니다.
1. 깊이텍스처를 준비한다.
2. 렌더 타겟을 깊이텍스처로 전환
3. 광원의 위치로 View행렬을 세팅(단. Up Direction은 카메라가 보는 방향)
4. 광원의 FOV대로 Projection행렬 세팅
5. 깊이값을 렌더링
6. 렌더 타겟을 다시 주버퍼로 전환
7.  카메라의 위치로 View행렬 세팅
8. 카메라의 FOV대로 Projection 행렬 세팅
9. 3과4에서 쓰인 행렬식을 토대로 깊이 버퍼를 가져와 비교하여 그림자 채색.
- 2로 다시 반복.
...
이건데요.
제가 사용한 방법은 위의 전통적인 방법과 순서는 전혀 안틀립니다.
아니.. 아예 똑같습니다.
하지만 코드상에서 틀린부분은 바로 4번(광원의 FOV대로 Projection행렬 세팅) 인데요.
일단 똑깉이 구해놓고 행렬하나를 더 만들어서 곱한후에 세팅합니다.
그 행렬이 뭔고 하면 그림을 위에 보시는 것처럼 짜부러 뜨리는 행렬입니다. (아래는 넓게 위쪽은 좁게)

어떻게?
바로 우리가 알고 있는 카메라 프로젝션 행렬을 곱해주는 겁니다. 근데 그냥 곱하면 안되구요.
..
..
프로젝션 행렬을 통하고 나면 좌우 위아래는 -1~1 로 고정되구요 앞뒤는 0~1로 고정됩니다.
원래 프로젝션 행렬은 앞은 크게 뒤는 작게 그릴수 있게 해주는 것입니다.
-Z 축으로 왜곡

그런데 우리는 위는 좁게 아래는 크게 만들어줘야 합니다.
-Y 축으로 왜곡
감이 오시지요?
우리가 가장 많이 쓰는 프로젝션 행렬은 Dx에서 다음과 같이 정의 됩니다.
2*zn/w  0          0                   0
0           2*zn/h  0                   0
0           0          zf/(zf-zn)       1
0           0          zn*zf/(zn-zf)  0
w는 뷰 프러스텀의 폭
h는 뷰 프러스텀의 높이
zn은 근단면의 거리
zf는 원단면의 거리입니다.
앞에서 말한것과 같이 Z축 왜곡이니까 Y축 왜곡으로 바꿔볼잡시면
2*zn/w  0                     0            0
0           zf/(zf-zn)        0             1
0           0                    2*zn/h     0
0           zn*zf/(zn-zf)   0             0
이렇게 됩니다.
이걸 4번에서 구한 프로젝션 행렬에 곱해준다음 적용하는 겁니다.
간단하죠?
하다못해 셰이더 코드는 100% 똑같습니다.

w는 원래 같으면 다른 값을 쓰겠지만 1 이하가 됩니다. 이미 프로젝션 행렬을 통한 다음엔 최대 좌우 폭은 -1~1 2가 되기때문이지요.
w와 h는 근단면의 폭과 높이이기 때문에 2보다는 작아지니까요.
그리고 zn과 zf는 모두 0~1 사이값을 갖게 됩니다. zf는 1보다 더 커져도 큰 무리는 없지만 zn은 정말 세밀하게 조절 해야됩니다.
zn값 하나 때문에 퀄리티가 오르락 내리락 합니다. 너무 작으면 깍뚜기가 나오고 너무크면 가까운곳은 그림자가 사라집니다.
테스트를 거쳐야 나오는 값이라 뭐라 포스팅 할수가 없네요.

이 방법도 단점은 있습니다.
바로 라이트의 방향과 카메라의 방향의 각에 따라 퀄리티가 들쭉 날쭉 합니다.
이는 좀더 그림자맵을 그릴때 방향을 잘 잡아야 하는 문제니까 금방 잡힐꺼 같습니다.

최종적으로 모든게 적용된 스크린샷을 올려 볼께욤.









갑자기 만든거 치고는 괜찮아보여 혼자 만족중입니다. 하하핫!

댓글 없음: