유니티는 '쉐도우 맵' 기법으로 그림자를 표현한다.
쉐도우 맵?
https://en.wikipedia.org/wiki/Shadow_mapping
위 글을 보며 대략적으로 이해했다.
중점적으로 살펴본 문구는 다음과 같다.
광원에서 밖을 내다보면 볼 수 있는 모든 물체가 빛으로 나타납니다.
그러나 그 물체 뒤에 있는 모든 것은 그림자에 있습니다.
이것은 그림자 맵을 만드는 데 사용되는 기본 원리입니다. 라이트의 뷰가 렌더링되어 보이는 모든 표면의 깊이(그림자 맵)를 저장합니다.
다음으로, (눈이 아닌 빛에 의해 보이는 것처럼) 그려진 모든 점의 깊이를 이 깊이 맵과 비교하여 일반 장면이 렌더링됩니다.
카메라에 렌더링될 실제 표면의 깊이 맵(뎁스 텍스처)과, 빛에 의해 보이는 표면의 깊이 맵을 비교하여
빛에 의해 보이는 깊이 맵이 더 얕다면, 그림자가 드리우는 곳으로 판단하여 그림자를 렌더링한다는 아이디어로 판단했다.
쉐도우 맵은 픽셀 쉐이더에서 일어나며,
유니티에선 넓은 영역을 커버하기 위해 구역을 cascade하거나,
계단 현상을 막기 위한 샘플링을 하는 추가 기능들이 들어간다고 함.
Screen Space 쉐도우 맵이라 불리는 유니티의 그림자 처리 과정은 포워드 렌더링 기준 다음과 같다.
1. 뎁스 텍스처 생성
카메라를 통해 현재 씬을 렌더링한다.
하지만, 컬러 대신 카메라로부터 픽셀의 위치까지의 거리를 렌더링한다. (가까울 수록 검음)
2. 쉐도우 맵 생성
별도의 버퍼에 광원에서 바라보는 오브젝트 픽셀의 깊이를 저장한다.
3. 실제 그림자를 그려야 하는 영역을 시나리오
뎁스 텍스처의 픽셀이 쉐도우 맵에 저장된 거리보다 멀리 있으면 그림자로 판단한다.
뎁스 텍스처, 쉐도우 맵을 추가로 렌더링하기 위해 추가 드로우 콜이 필요하고, (CPU 오버헤드)
위 대조 과정이 픽셀 쉐이더에 부하를 주기 때문에 (GPU 오버헤드)
많이 부담이 되는 과정이다.
그럼 어떻게?
1. 그림자 적용이 반드시 필요한 곳에만 적용한다.
Shadows Only는 본인은 렌더링되지 않지만, 그림자를 만드는 오브젝트가 된다.
> 그림자의 영향을 받아 렌더링될 오브젝트는 고품질의 오브젝트로, 본인은 저품질의 메시를 사용하는 방법으로
성능을 절약할 수도 있다.
Two Sided는 오브젝트의 컬링을 끈다.
> 평면 또는 쿼드와 같은 단면 개체를 빛이 뒤에 있더라도 그림자를 드리운다.
2. Light의 퀄리티를 적절히 선택한다.
해당 옵션의 품질은 픽셀 쉐이더의 영향을 받는다.
3. Light 쉐도우 맵의 해상도를 적절히 선택한다.
4. Light 쉐도우 맵 렌더링 기준 거리를 줄이고 늘인다.
수치가 낮을 수록 쉐도우 맵에 렌더링하는 범위가 줄어들어서,
오브젝트의 픽셀이 차지하는 비중이 커져서 시각적인 해상도가 올라간다고 한다.
카메라가 오브젝트를 가까이서 비추는 게임이라면, 낮추어도 좋을 것 같음
5. Cascade 쉐도우 맵을 고려한다.
쉐도우 맵의 해상도가 높으면 GPU 메모리를 많이 잡아먹기 때문에, 구역별로 나누어 해상도를 조절하도록 하는 쉐도우 맵
카메라가 Perspective Projection을 사용하고 있는 경우, 뷰 프러스텀 공간은 사다리꼴 모양이 되어
카메라와 가까이 있는 오브젝트일 수록 적은 픽셀이 렌더링되기 때문에, 해상도의 영향을 크게 받게 됩니다.
전체 쉐도우 맵의 퀄리티를 올리기보단 구역별로 쉐도우 맵의 퀄리티를 다르게 하는 옵션이다.
나눈 만큼 드로우 콜을 여러 번 해야되긴 하지만...
그래서 모바일은 기본적으로 사용하지 않는 기능이다.
위 영역에서 적용시켜도 동작되지 않고, Project Setting > Graphics의 모바일 티어 세팅을 조작하여야 한다.
메시 평면 그림자 기법?
버텍스 쉐이더로 메시를 빛의 방향으로 평면에 투영시켜 그리면 되는 기법이다.
(평면에만 쓰는 아주 저렴한 기법임)
코드로 옮긴 경우 다음과 같다.
// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'
#include "UnityCG.cginc"
// User-specified uniforms
uniform float4 _ShadowColor;
uniform float _PlaneHeight = 0;
struct vsOut
{
float4 pos : SV_POSITION;
};
vsOut vertPlanarShadow( appdata_base v)
{
vsOut o;
float4 vPosWorld = mul( unity_ObjectToWorld, v.vertex);
float4 lightDirection = -normalize(_WorldSpaceLightPos0);
float opposite = vPosWorld.y - _PlaneHeight;
float cosTheta = -lightDirection.y; // = lightDirection dot (0,-1,0)
float hypotenuse = opposite / cosTheta;
float3 vPos = vPosWorld.xyz + ( lightDirection * hypotenuse );
o.pos = mul (UNITY_MATRIX_VP, float4(vPos.x, _PlaneHeight, vPos.z ,1));
return o;
}
float4 fragPlanarShadow( vsOut i)
{
return _ShadowColor;
}
다음 쉐이더를 메시에 적용시키면 바로 적용된다.
출처)
https://github.com/ozlael/plannarshadowforunity
'Unity' 카테고리의 다른 글
[Unity] 텍스처 (그래픽스 최적화 스타트업) (0) | 2022.08.27 |
---|---|
[Unity] Global Illumination (그래픽스 최적화 스타트업) (0) | 2022.08.17 |
[Unity] 포워드, 디퍼드 렌더링 (0) | 2022.08.01 |
[Unity] 드로우 콜과 배칭 (그래픽스 최적화 스타트업) (0) | 2022.07.25 |
[유니티] sRGB, Linear, Gamma (0) | 2022.07.20 |