- URL : http://catlikecoding.com/unity/tutorials/rendering/part-7/
Investigate how Unity renders shadows.
Cast directional shadows.
Receive directional shadows.
Add support for spotlight and point light shadows.
When rendering, casting a shadow is a good thing.
우리 조명 쉐이더는 지금까지 상당히 현실적인 결과를 만들어 내고 있습니다 만, 각 표면의 조각을 분리하여 평가합니다. 모든 광원으로부터의 광선이 결국 모든 조각에 해당한다고 가정합니다. 그러나 이것은 그 광선이 뭔가에 차단되지 않은 경우에만 적용됩니다.
Some light rays get blocked.
광원과 다른 물체 사이에 물체가 놓여지면, 광선의 일부 또는 전부가 다른 물체에 도달하는 것을 방지 할 수있다. 제 1 대상물을 비추는 광선은 더 이상 제 2의 대상물을 조명 할 수 없다. 결과적으로, 제 2 개체는 적어도 부분적으로 꺼져있다. 점등하지 않는 영역은 첫 번째 개체의 그림자에 있습니다. 이것을 설명하기 위해, 우리는 종종 개체가 두 번째 개체에 그림자를 던진다 고 말한다.
사실, 반 그림자로 알려진 완전히 조명 된 공간과 완전히 그늘진 공간 사이의 전이 영역이 존재한다. 이것은 모든 광원에 볼륨이 있기 때문에 존재합니다. 그 결과, 광원의 일부만이 가시 인 영역이 부분적으로 그림자를 붙일 수있는 것을 의미한다. 광원이 크고 표면이 그림자 캐스터에서 멀리 떨어져있을수록이 영역은 커집니다
Shadow with penumbra.
유니티는 반영을 지원하지 않습니다. 유니티는 부드러운 그림자를 지원하고 있습니다 만, 그것은 그림자 필터링 기법이며, 반 그림자의 시뮬레이션이 없습니다
그림자가 없으면 객체 사이의 공간적 관계를 보는 것은 어렵습니다. 이를 설명하기 위해 몇 가지의 큐브가 성장 간단한 장면을 만들었습니다. 나는이 입방체에 4 열 공을 배치했습니다. 가운데 열은 공의 부동 소수점 수, 외부 칼럼은 원통을 통해 그 아래의 큐브에 연결됩니다.
개체는 Unity의 기본입니다. 장면에는 2 개의 지향성 라이트 기본 지향성 라이트 약간 약한 황색 표시등이 있습니다. 이들은 이전 튜토리얼에서 사용 된 것과 동일합니다.
현재 프로젝트 전체에 그림자가 비활성화되어 있습니다. 이전 튜토리얼에서 작업을 수행했습니다. 주위의 밝기는 제로로 설정되어 있기 때문에 그림자가 선명합니다
Two directional lights, no shadows, no ambient light.
그림자는 프로젝트 전체의 품질 설정의 일부이며 편집 / 프로젝트 설정 / 품질에 의해 발견됩니다. 우리는 그들을 고품질 수준에서 허용하는 것입니다. 이것은 고해상도 안정된 맞는 투영 150 거리 및 4 개의 폭포를 사용하여 하드 및 소프트 그림자를 모두 지원하는 것을 의미합니다
Shadow quality settings.
두 빛이 부드러운 그림자를 캐스팅하도록 설정되어 있는지 확인하십시오. 그 해상도는 품질 설정에 의존해야합니다
Shadow settings per light.
양방향 빛이 그림자를 던질 때 모든 객체 사이의 공간적 관계가 더 명확 해집니다. 전체 장면을보다 리얼하게 보는 것이 재미가 있습니다.
Scene with shadows.
Unity는 이러한 그림자를 장면에 어떻게 추가 하시겠습니까? 표준 쉐이더는 레이가 차단되어 있는지 여부를 확인하는 방법이있는 것 같습니다.
당신은 그림자가 그림자인지 여부를 현장에서 광선을 표면 조각에 투사하여이를 파악할 수 있습니다. 그 광선이 조각에 도달하기 전에 뭔가에 당첨되면, 그것은 블록됩니다. 이는 물리 엔진이 할 수있는 일이지만, 각 조각, 조명마다 이렇게하는 것은 매우 실용적 없습니다. 그리고 당신은 어떻게 든 GPU에 결과를 얻어야 할 것이다
실시간 그림자를 지원하는 방법은 여러 가지가 있습니다. 각각 장단점이 있습니다. Unity는 오늘 그림자 매핑이다 가장 일반적인 방법을 사용하고 있습니다. 이것은 Unity 어떻게 든 그림자 정보를 텍스처에 저장하는 것을 의미합니다. 여기에서 그 구조를 조사합니다.
창 / 프레임 디버거를 통해 프레임 디버거를 열고 사용하여 렌더링 단계의 계층 구조를 확인합니다. 그림자가 유효한 프레임과 그림자가 유효한 프레임의 차이를보세요.
Rendering process without vs. with shadows.
그림자를 비활성화하면 모든 개체가 정상적으로 렌더링됩니다. 우리는 이미이 과정을 잘 알고있었습니다. 그러나 그림자가 활성화되면 프로세스는 더 복잡해집니다. 렌더링 단계가 좀 있고, 꽤 많은 자본 콜 있습니다.
방향성 그림자가 활성화되어있는 경우, Unity는 렌더링 프로세스의 심도 패스를 개시합니다. 결과는 화면의 해상도와 일치하는 텍스처에 넣을 수 있습니다. 이 경로는 전체 장면을 렌더링하지만, 각 조각의 깊이 정보를 기록합니다. 이것은 조각이 이전에 렌더링 된 조각의 위 또는 아래로 끝날지 어떨지를 판단하기 위해 GPU가 사용하는 것과 동일한 정보입니다.
이 데이터는 클립 공간의 조각의 Z 좌표에 대응합니다. 이것은 카메라가 볼 수있는 영역을 정의하는 공간입니다. 깊이 정보는 0-1 사이의 값으로 저장됩니다. 텍스처를 표시하면 가까운 텍셀이 어둡게 표시됩니다. 텍셀가 멀수록 더 가벼워집니다.
Depth texture, with camera near plane set to 5.
이 정보는 실제로 그림자과는 직접 관계 없지만, Unity는 나중에 사용합니다.
Unity이 렌더링하는 것은 첫 번째 빛의 그림자 맵입니다. 조금 후 두 번째 빛의 그림자 맵도 렌더링됩니다.
다시 장면이 렌더링되어 다시 깊이 정보 만 텍스처에 저장된다. 그러나 이번에는 광원의 시점에서 렌더링됩니다. 효과적으로 빛은 카메라로 작동합니다. 즉, 심도 값은 광선이 뭔가에 해당 전에 어느 정도까지 광선이 이동했는지를 보여줍니다. 이것은 뭔가 그늘이되어 있는지를 확인하는 데 사용할 수 있습니다!
우리는 지향성 라이트를 사용하고 있기 때문에 카메라는 정면 투영입니다. 따라서 투시 투영 아니라 라이트 카메라의 정확한 위치는 중요하지 않습니다. Unity는 카메라를 배치하여 일반 카메라의 시야에있는 모든 개체를 표시합니다.
Two shadow maps, each with four viewpoints.
사실, Unity가 라이트마다 장면을 렌더링 할뿐만 아니라는 것을 알 수 있습니다. 장면은 빛 당 4 번 렌더링됩니다! 텍스처는 4 개의 사분면으로 나누어 각각 다른 시점에서 렌더링됩니다. 이것은 4 개의 그림자 폭포를 사용하는 것을 선택했기 때문에 발생합니다. 2 개의 직렬로 전환하면 장면은 라이트마다 2 회 렌더링됩니다. 계단식없이, 그것은 빛마다 한 번만 렌더링되지 않습니다. 그림자의 질을 보면, Unity가 이것을 왜 실시하는지 알 수 있습니다.
카메라 시점에서 장면의 깊이 정보를 가지고 있습니다. 우리는 또한 각 빛의 관점에서이 정보를 가지고 있습니다. 물론,이 데이터는 다른 클립 공간에 저장되지만, 이러한 공간의 상대적인 위치와 방향을 알 수 있습니다. 그래서 우리는있는 공간에서 다른 공간으로 변환 할 수 있습니다. 그러면 두 관점에서 심도 측정 값을 비교할 수 있습니다. 개념적으로 동일한 점에 끝나는 것 두 벡터가 있습니다. 만약 그렇다면, 카메라와 라이트 모두가 그 점을 볼 수 있기 때문에 점등됩니다. 빛의 벡터가 포인트에 도달하기 전에 종료하면 빛이 차단됩니다. 이 지점이 섀도우되는 것을 의미합니다.
Screen-space shadows, per light.
Unity는 뷰 전체를 커버하는 단일 쿼드를 렌더링함으로써 이러한 텍스처를 만듭니다. 이 경로는 숨기기 / 내부 ScreenSpaceShadows 쉐이더를 사용합니다. 각 조각은 장면과 빛의 깊이 텍스처에서 샘플을 추출하여 비교하고 최종 음영 값을 스크린 공간 그림자 맵에 렌더링합니다. Lit 텍셀은 1로 설정되어 그림자 텍셀은 0으로 설정됩니다. 이 시점에서 Unity는 필터링을 수행하고 부드러운 그림자를 만들 수 있습니다.
마지막으로, Unity는 그림자의 렌더링을 종료합니다. 이제 장면이 하나의 변화에서 제대로 렌더링됩니다. 밝은 색상은 그림자 맵에 저장된 값을 곱합니다. 이는 빛을 차단할 필요가 없습니다.
렌더링되는 모든 조각은 그림자 맵을 샘플링합니다. 또한 후 렌더링되는 다른 개체 뒤에 숨어 버리는 단편도 있습니다. 그래서 이러한 조각은 결국 그들을 숨길 될 개체의 그림자를 받게됩니다. 이것은 프레임 디버거를 단계별로 실행할 때 표시됩니다. 실제로 캐스트 개체 앞에 그림자가 나타날 수 있습니다. 물론 이러한 실수는 프레임을 렌더링 할 때만 나타납니다. 그것이 끝나면 이미지 맞습니다.
Patially rendered frame, containing strange shadows.
장면이 라이트의 관점에서 표현 될 때, 그 방향은 장면 카메라와 일치하지 않습니다. 따라서 그림자 맵의 텍셀은 최종 이미지의 텍셀과 일치하지 않습니다. 또한 그림자 맵의 해상도도 달라집니다. 최종 이미지의 해상도는 디스플레이 설정에 의해 결정됩니다. 그림자 맵의 해상도는 그림자 품질 설정에 의해 결정됩니다.
쉐도우 맵의 텍셀이 최종 이미지의 텍셀보다 크게 렌더링되면 그들은 눈에 띄게된다. 그림자의 가장자리에 앨리어싱이 적용됩니다. 이것은 하드 그림자를 사용할 때 가장 분명합니다.
Hard vs. soft shadows.
이것을 가능한 한 분명하게하려면 그림자의 품질 설정을 변경하여 최저 해상도에서 계단식없이 하드 섀도우만을 가져옵니다.
Low quality shadows.
그림자 텍스처 인 것은 매우 분명하다. 또한 그림자 부분은 장소에 나타나 있습니다. 그것에 대해 나중에 살펴 보겠습니다.
그림자가 장면 카메라에 가까울수록 텍셀은 커집니다. 그림자 맵은 현재 장면 카메라에 표시되는 전체 영역을 커버하고 있기 때문입니다. 그림자에 의해 덮여 영역을 품질 설정에 따라 줄임으로써 카메라 근처의 품질을 높일 수 있습니다.
Shadow distance reduced to 25.
장면 카메라에 가까운 영역에 그림자를 한정하여 더 작은 영역을 커버하기 위해 같은 그림자 맵을 사용할 수 있습니다. 결과적으로 더 나은 그림자를 얻을 수 있습니다. 그러나 우리는 더 멀리있는 그림자를 잃게됩니다. 최대 거리에 가까워 질수록 그림자가 사라집니다.
이상적으로는 먼 그림자를 유지하면서 고품질의 그림자를 접근합니다. 멀리있는 그림자는 더 작은 화면 영역에 렌더링되기 때문에 저해상도 그림자 맵에서 할 수 있습니다. 이것이 그림자 캐스케이드 기능입니다. 사용하면 여러 그림자 맵이 같은 텍스처에 렌더링됩니다. 각지도의 거리에서 사용하기위한 것입니다.
Low resolution textures, with four cascades.
4 개의 폭포를 사용하면 같은 텍스처 해상도를 사용하더라도 결과는 더 잘 보입니다. 우리는 더 효율적으로 텍셀을 사용하고 있습니다. 단점은 장면을 3 번 이상 렌더링 할 필요가있는 것입니다.
스크린 공간 그림자 맵에 렌더링 할 때, Unity는 올바른 캐스케이드에서 샘플링을 처리합니다. 그림자 텍셀의 크기가 갑자기 변경되는 것을 찾는 것으로, 캐스케이드의 종료 위치와 종료 위치를 찾을 수 있습니다.
그림자 디스턴스의 일부로 계단식 밴드의 범위를 품질 설정에서 제어 할 수 있습니다. 음영 모드를 변경하여 장면 뷰에서 시각화 할 수 있습니다. 다만 음영 대신 기타 / 음영 폭포를 사용하십시오. 이는 계단식 색깔이 장면에 렌더링됩니다.
Cascade regions, adjusted to show three bands.
계단식 밴드의 모양은 그림자 투영 품질 설정에 따라 달라집니다. 기본값은 Stable Fit입니다. 이 모드에서는 밴드는 카메라의 위치까지의 거리에 따라 선택됩니다. 또 다른 옵션은 카메라의 깊이를 대신 사용하는 "대략 맞는 '입니다. 그러면 카메라의 시선 방향에 직사각형의 밴드가 생성됩니다.
Close fit.
이 구성은 그림자 텍스처를보다 효율적으로 사용할 수 있으며, 고품질의 그림자를 얻을 수 있습니다. 그러나 그림자 투영 현재 위치 및 방향 또는 카메라에 의존한다. 따라서 카메라가 움직이거나 회전 할 때 그림자 맵도 변화합니다. 그림자 텍셀을 볼 수 있다면, 그들이 움직이는 것을 알 수 있습니다. 이 효과는 그림자 가장자리 수영이라고 매우 분명합니다. 따라서 다른 모드가 기본입니다.
낮은 품질의 하드 섀도우를 사용했을 때, 우리는 그림자 부분이 보이지 않는 곳에 나타나는 것을 보았다. 불행히도, 이것은 품질 설정에 관계없이 발생합니다.
그림자 맵의 각 텍셀은 광선이 표면에 해당하는 점을 나타냅니다. 그러나 텍셀은 단일 지점에서하지 않습니다. 그들은 더 넓은 영역을 커버 해 버린다. 그리고 그들은 빛의 방향으로 정렬되어 있습니다. 겉으로는 없습니다. 따라서 어두운 조각 같은 얼굴에 꽂히는 수 있습니다. 텍셀의 일부가 그림자를 던지는 표면에서 돌출하면 표면 자체가 그림자를 붙이고있는 것 같습니다. 이것은 그림자 여드름으로 알려져 있습니다.
Shadow map causes acne.
Another source of shadow acne is numerical precision limitations. These limitations can cause incorrect result when very small distances are involved.
Severe acne, when using no biases at all.
이 문제를 방지 한 가지 방법은 섀도우 맵을 렌더링 할 때 심도 오프셋을 추가하는 것입니다. 이 편견은 빛으로부터 그림자 캐스트면까지의 거리에 적용된 그림자를 표면에 밀어 넣으십시오.
Biased shadow map.
그림자 편견은 빛에 대해 설정된 기본값은 0.05로 설정되어 있습니다.
Shadow settings per light.
낮은 바이어스는 그림자 여드름을 생성 할 수 있지만, 큰 바이어스는 다른 문제를 일으 킵니다. 그림자 캐스팅 개체가 라이트에서 밀린다는 섀도 섀도됩니다. 결과적으로 음영은 개체와 완벽하게 정렬하지 않습니다. 작은 바이어스를 사용하면 이것은 나쁘지 않습니다. 그러나, 바이어스가 너무 크면 그림자를 던지는 개체에서 분리 된 것처럼 보일 수 있습니다. 이 효과는 피터팬로 알려져 있습니다
Large bias causes peter panning.
이 거리 편견 이외에 일반적 편견도 있습니다. 이것은 그림자 캐스터 조정합니다. 이 바이어스는 그림자 캐스터의 정점을 그 법선을 따라 안쪽으로 밀어 넣으십시오. 이것은 또한 자기 그림자를 줄이고 있지만 섀도우를 줄여 섀도우 홀을 볼 수 있습니다.
품질 설정에서 안티 앨리어싱을 사용하고 있습니까? 만약 당신이 있으면 그림자 맵의 다른 문제를 발견 할 수 있습니다. 표준 앤티 앨리어싱 기법과 혼동하지 않습니다.
Aliasing when using anti-aliasing..
품질 설정에서 안티 앨리어싱을 활성화하면 Unity는 멀티 샘플링 안티 앨리어싱 MSAA를 사용합니다. 이러한 가장자리를 따라 몇 가지 슈퍼 샘플링을 수행하여 삼각형의 가장자리에서 앨리어싱을 제거합니다. 자세한 내용은 여기에서 중요하지 않습니다. 중요한 것은 Unity가 스크린 공간 그림자 맵을 렌더링 할 때 뷰 전체를 커버하는 단일 쿼드를 사용하는 것입니다. 그 결과, 삼각형의 모서리가 존재하지 않기 때문에 MSAA는 스크린 공간 그림자 맵에 영향을주지 않습니다. MSAA는 최종 이미지를 처리하지만, 음영 값은 스크린 공간 그림자 맵에서 직접 검색됩니다. 이것은 어두운 표면 옆의 라이트 표면이 그림자 될 때 매우 분명합니다. 명암의 형상 사이의 가장자리는 앤티 앨리어싱 된 그림자 가장자리는 별칭 처리되지 않습니다.
No AA vs. MSAA 4× vs. FXAA.
FXAA와 같은 이미지 후 처리에 의존하는 앤티 방법은 장면이 렌더링 된 후에 적용되기 때문에이 문제는 없습니다.
No shadows are cast by our material.
Unity는 방향성 그림자 위해 장면을 여러 번 렌더링하는 것을 알고 있습니다. 각 그림자 맵의 계단식 대해 심도 경로에 1 회, 라이트마다 한 번. 스크린 공간 쉐도우 맵은 화면 공간 효과이며, 우리는 관계 없습니다.
관련된 모든 경로를 지원하려면 조명 모드를 ShadowCaster로 설정하여 쉐이더에 경로를 추가해야합니다. 우리는 깊이 값만큼 관심이 있기 때문에, 다른 경로보다 훨씬 쉽습니다.
SubShader { Pass { Tags { "LightMode" = "ForwardBase" } … } Pass { Tags { "LightMode" = "ForwardAdd" } … } Pass { Tags { "LightMode" = "ShadowCaster" } CGPROGRAM #pragma target 3.0 #pragma vertex MyShadowVertexProgram #pragma fragment MyShadowFragmentProgram #include "My Shadows.cginc" ENDCG } }
그림자 프로그램에 자신의 파일을 포함 My Shadows.cginc을합시다. 그들은 매우 간단합니다. 정점 프로그램은 정상적으로 오브젝트 공간에서 클립 공간으로 위치를 변환 해, 그 이외는 아무것도하지 않습니다. 조각 프로그램은 실제로 아무것도 할 필요가 없기 때문에 0을 돌려 줄뿐입니다. GPU는 우리의 깊이 값을 기록합니다.
#if !defined(MY_SHADOWS_INCLUDED)#define MY_SHADOWS_INCLUDED#include "UnityCG.cginc"struct VertexData { float4 position : POSITION;};float4 MyShadowVertexProgram (VertexData v) : SV_POSITION { return mul(UNITY_MATRIX_MVP, v.position);}half4 MyShadowFragmentProgram () : SV_TARGET { return 0;}#endif
This is already enough to cast shadows directional.
Casting shadows.
또한 그림자 바이어스를 지원해야합니다. 심도 경로 중 편견 제로이지만, 그림자 맵을 렌더링 할 때 편견 라이트의 설정에 해당합니다. 이것은 클립 공간 버텍스 쉐이더의 위치에 깊이 바이어스를 적용하여 실시합니다.
깊이 바이어스를 지원하기 위해 UnityCG에 정의되어있는 UnityApplyLinearShadowBias 함수를 사용할 수 있습니다.
float4 MyShadowVertexProgram (VertexData v) : SV_POSITION { float4 position = mul(UNITY_MATRIX_MVP, v.position); return UnityApplyLinearShadowBias(position); }
또한 그림자 바이어스를 지원해야합니다. 심도 경로 중 편견 제로이지만, 그림자 맵을 렌더링 할 때 편견 라이트의 설정에 해당합니다. 이것은 클립 공간 버텍스 쉐이더의 위치에 깊이 바이어스를 적용하여 실시합니다.
깊이 바이어스를 지원하기 위해 UnityCG에 정의되어있는 UnityApplyLinearShadowBias 함수를 사용할 수 있습니다.
struct VertexData { float4 position : POSITION; float3 normal : NORMAL; }; float4 MyShadowVertexProgram (VertexData v) : SV_POSITION { float4 position = UnityClipSpaceShadowCasterPos(v.position.xyz, v.normal); return UnityApplyLinearShadowBias(position); }
Our shader is now a fully functional shadow caster.
No shadows are received, when only using our material.
주요 지향성 빛 그림자 만 조심합시다. 이 라이트는 기본 경로에 포함되어 있기 때문에 조정해야합니다.
메인 지향성 라이트가 그림자를 던질 때 Unity는 SHADOWS_SCREEN 키워드가 활성화되어있는 쉐이더 변형을 찾습니다. 그래서 우리는 기본 경로의 두 가지 변종을 작성해야합니다. 이것은 VERTEXLIGHT_ON 키워드와 동일하게 작동합니다.
#pragma multi_compile _ SHADOWS_SCREEN #pragma multi_compile _ VERTEXLIGHT_ON
경로에는 2 개의 멀티 컴파일 지령이 있으며 각각 하나의 키워드 용입니다. 그 결과, 4 개의 가능한 변형이 존재한다. 하나는 키워드없이 하나는 키워드별로 다른 하나는 두 키워드 기능입니다.
// Snippet #0 platforms ffffffff: SHADOWS_SCREEN VERTEXLIGHT_ON 4 keyword variants used in scene: <no keywords defined> VERTEXLIGHT_ON SHADOWS_SCREEN SHADOWS_SCREEN VERTEXLIGHT_ON
멀티 컴파일 pragma를 추가 한 후 쉬어 업체 컴파일러는 존재하지 않는 _ShadowCoord 대해 불평합니다. 이것은 그림자가 재생되는 때 UNITY_LIGHT_ATTENUATION 매크로 동작이 다르기 때문에 발생합니다. 이를 즉시 수정하려면 My Lighting.cginc 파일을 열고 그림자가있을 때 감쇠를 1로 설정합니다.
UnityLight CreateLight (Interpolators i) { UnityLight light; #if defined(POINT) || defined(POINT_COOKIE) || defined(SPOT) light.dir = normalize(_WorldSpaceLightPos0.xyz - i.worldPos); #else light.dir = _WorldSpaceLightPos0.xyz; #endif #if defined(SHADOWS_SCREEN) float attenuation = 1; #else UNITY_LIGHT_ATTENUATION(attenuation, 0, i.worldPos); #endif light.color = _LightColor0.rgb * attenuation; light.ndotl = DotClamped(i.normal, light.dir); return light; }
음영을 얻으려면 스크린 공간 쉐도우 맵을 샘플링해야합니다. 이렇게 스크린 공간의 텍스처 좌표를 알 필요가 있습니다. 다른 텍스처 좌표뿐만 아니라 그들을 버텍스 쉐이더에서 조각 셰이더에 전달합니다. 따라서 그림자를 지원하기 위해 보간 회로를 추가해야합니다. 이종 클립 공간 위치를 단순히 전달하는 것부터 시작합니다. 따라서 float4이 필요합니다.
struct Interpolators { … #if defined(SHADOWS_SCREEN) float4 shadowCoordinates : TEXCOORD5; #endif #if defined(VERTEXLIGHT_ON) float3 vertexLightColor : TEXCOORD6; #endif }; … Interpolators MyVertexProgram (VertexData v) { … #if defined(SHADOWS_SCREEN) i.shadowCoordinates = i.position; #endif ComputeVertexLightColor(i); return i; }
_ShadowMapTexture를 통해 스크린 공간 그림자에 액세스 할 수 있습니다. 이것은 필요에 따라 AutoLight에서 정의됩니다. 간단한 방법은 단순히이 텍스처를 샘플링하기 위해 조각 클립 공간 XY 좌표를 사용하는 것입니다.
UnityLight CreateLight (Interpolators i) { … #if defined(SHADOWS_SCREEN) float attenuation = tex2D(_ShadowMapTexture, i.shadowCoordinates.xy); #else UNITY_LIGHT_ATTENUATION(attenuation, 0, i.worldPos); #endif … }
Sampling shadows.
현재 그림자를 샘플링하고 있지만 스크린 공간 좌표 대신 클립 공간 좌표를 사용하고 있습니다. 우리는 그늘을 얻을 수 있지만, 화면 중앙의 작은 공간에 압축되어 버립니다. 창 전체를 덮도록 그들을 뻗어 있어야합니다.
클립 공간에서는 모든 가시 XY 좌표는 -1-1 범위 내에있어, 스크린 공간의 범위는 0-1입니다. 이를 해결하기위한 첫 걸음은 XY입니다. 그런 다음 화면의 왼쪽 하단에 좌표가 제로가되도록 좌표를 오프셋해야합니다. 원근 변환을 다루고 있기 때문에 좌표를 얼마나 상쇄 할 수 있을지는 그것들이 얼마나 떨어져 있느냐에 따라 결정됩니다. 이 경우 오프셋은 반으로 이전의 제 4의 동차 좌표와 같다.
#if defined(SHADOWS_SCREEN) i.shadowCoordinates.xy = (i.position.xy + i.position.w) * 0.5; i.shadowCoordinates.zw = i.position.zw; #endif
Shadows in the bottom-left corner.
균등 한 좌표를 사용하고 있기 때문에, 투영 아직 올바르지 않습니다. X와 Y를 W로 나누어 스크린 공간 좌표로 변환해야합니다.
i.shadowCoordinates.xy = (i.position.xy + i.position.w) * 0.5 / i.position.w;
Incorrect conversion.
결과는 왜곡. 그림자 뻗어 구부러져있다. 이것은 보간 전에 나눗셈을 할 때 발생합니다. 이것은 잘못된 것입니다. 좌표는 분할 전에 독립적으로 보간해야합니다. 그래서 우리는 조각 쉐이더에 사업부를 이동해야합니다.
Interpolators MyVertexProgram (VertexData v) { … #if defined(SHADOWS_SCREEN) i.shadowCoordinates.xy = (i.position.xy + i.position.w) * 0.5; // / i.position.w; i.shadowCoordinates.zw = i.position.zw; #endif … } UnityLight CreateLight (Interpolators i) { … #if defined(SHADOWS_SCREEN) float attenuation = tex2D( _ShadowMapTexture, i.shadowCoordinates.xy / i.shadowCoordinates.w ); #else UNITY_LIGHT_ATTENUATION(attenuation, 0, i.worldPos); #endif … }
Correctly sampled shadows.
이 시점에서 당신의 그림자가 올바른지 거꾸로 표시됩니다. 그들이 반전되는 경우는 그래픽 API (Direct3D) 스크린 공간의 Y 좌표가 상향이 아니라 0부터 아래로된다는 것을 의미합니다. 이와 동기화하려면 정점의 Y 좌표를 반전시킵니다.
i.shadowCoordinates.xy = (float2(i.position.x, -i.position.y) + i.position.w) * 0.5;
Flipped shadows.
Unity 포함 파일은 그림자를 샘플하는 데 도움이 함수와 매크로의 컬렉션을 제공합니다. API의 차이와 플랫폼의 제한을 처리합니다. 예를 들어, UnityCG의 ComputeScreenPos 함수를 사용할 수 있습니다.
#if defined(SHADOWS_SCREEN) i.shadowCoordinates = ComputeScreenPos(i.position); #endif
AutoLight 포함 파일에는 3 개의 유용한 매크로가 정의되어 있습니다. 그들은 SHADOW_COORDS, TRANSFER_SHADOW 및 SHADOW_ATTENUATION입니다. 그림자가 활성화되어있는 경우 이러한 매크로는 이전과 동일한 작업을 수행합니다. 그림자가없는 경우는 아무것도하지 않습니다.
SHADOW_COORDS 필요에 따라 그림자 좌표 보간을 정의합니다. 나는 _ShadowCoordname을 사용하고 있습니다. 이것은 컴파일러가 이전에 불만을 제기 한 것이 었습니다.
struct Interpolators { … // #if defined(SHADOWS_SCREEN)// float4 shadowCoordinates : TEXCOORD5;// #endif SHADOW_COORDS(5) … };
TRANSFER_SHADOW는 정점 프로그램에서 이러한 좌표를 채 웁니다.
Interpolators MyVertexProgram (VertexData v) { … // #if defined(SHADOWS_SCREEN)// i.shadowCoordinates = i.position;// #endif TRANSFER_SHADOW(i); … }
그리고 SHADOW_ATTENUATION는 좌표를 사용하여 조각 프로그램의 그림자 맵을 샘플링합니다.
UnityLight CreateLight (Interpolators i) { … #if defined(SHADOWS_SCREEN) float attenuation = SHADOW_ATTENUATION(i); #else UNITY_LIGHT_ATTENUATION(attenuation, 0, i.worldPos); #endif … }
사실, UNITY_LIGHT_ATTENUATION 매크로는 이미 SHADOW_ATTENUATION을 사용하고 있습니다. 그래서 우리는 그 컴파일러 오류를 받았습니다. 따라서 매크로 충분합니다. 단 하나의 변화는 이전에 제로를 사용하고 있던 반면, 을 두 번째 인수로 사용할 필요가있는 것입니다.
// #if defined(SHADOWS_SCREEN)// float attenuation = SHADOW_ATTENUATION(i);// #else UNITY_LIGHT_ATTENUATION(attenuation, i, i.worldPos); // #endif
이러한 매크로를 사용하도록 코드를 다시 작성 후 새 컴파일 오류가 발생합니다. 이것은 Unity 매크로 불행히도 정점 데이터 보간 구조에 대한 가정을 할 때 발생합니다. 먼저 정점의 위치를 정점으로 그 정점의 이름을 position합니다. 제 2 보간 기의 위치는 pos라고 명명하고 있습니다 만, 그것을 position라는합니다.
실제,이 이름도 채용하자. 어쨌든 어떤 장소에서만 사용되지 않기 때문에 그다지 변경할 필요가 없습니다.
struct VertexData { float4 vertex : POSITION; … }; struct Interpolators { float4 pos : SV_POSITION; … }; … Interpolators MyVertexProgram (VertexData v) { Interpolators i; i.pos = mul(UNITY_MATRIX_MVP, v.vertex); i.worldPos = mul(unity_ObjectToWorld, v.vertex); … }
우리의 그림자는 다시 동작한다. 이번에는 Unity가 지원하는 플랫폼만큼 많은 플랫폼에서 작동합니다.
메인 지향성 라이트는 현재 그림자를 캐스팅하고 있지만, 제 2의 방향성 라이트는 아직 그림자가되지 않습니다. 이것은 첨가제 경로 SHADOWS_SCREEN를 아직 정의하지 않기 때문입니다. 멀티 컴파일 문을 추가 할 수 있지만 SHADOWS_SCREEN은 지향성 빛에서만 작동합니다. 키워드의 올바른 조합을 얻으려면 기존의 멀티 컴파일 문을 그림자도 포함 문으로 변경합니다.
#pragma multi_compile_fwdadd_fullshadows
이로 인해 다양한 조명 유형을 지원하기 위해 4 개의 추가 키워드가 믹스에 추가됩니다
// ----------------------------------------- // Snippet #1 platforms ffffffff: DIRECTIONAL DIRECTIONAL_COOKIE POINT POINT_COOKIE SHADOWS_CUBE SHADOWS_DEPTH SHADOWS_SCREEN SHADOWS_SOFT SPOT 13 keyword variants used in scene: POINT DIRECTIONAL SPOT POINT_COOKIE DIRECTIONAL_COOKIE SHADOWS_DEPTH SPOT DIRECTIONAL SHADOWS_SCREEN DIRECTIONAL_COOKIE SHADOWS_SCREEN POINT SHADOWS_CUBE POINT_COOKIE SHADOWS_CUBE SHADOWS_DEPTH SHADOWS_SOFT SPOT POINT SHADOWS_CUBE SHADOWS_SOFT POINT_COOKIE SHADOWS_CUBE SHADOWS_SOFT
Two directional lights casting shadows.
지향성 조명을 다룬 때문에 스포트라이트 알아 보자. 지향성 조명을 사용하지 않고 장면에 그림자 스포트 라이트를 추가합니다. 놀람! Unity 매크로 덕분에 스포트라이트 그림자는 이미 기능하고 있습니다.
Two spotlights with shadows.
프레임 디버거를 보면, Unity가 스포트라이트의 그늘에 너무 작동하지 않는다는 것을 알 수 있습니다. 다른 심도 경로가 아닌 스크린 공간 그림자는 통과하지 않습니다. 그림자 맵 만 렌더링됩니다.
Rendering with spotlight shadows.
그림자 맵은 지향성 라이트처럼 작동합니다. 그들은 빛의 시점에서 그려진 깊이 맵입니다. 그러나 지향성 조명 및 조명에 큰 차이가 있습니다. 스포트라이트는 실제 위치를 가져, 그 광선은 평행하지 않습니다. 따라서 스포트라이트 카메라는 원근 관점을 가지고 모든 주위를 볼 수 없습니다. 그 결과,이 빛은 그림자 폭포를 지원하지 않습니다.
Shadow map, near plane set to 4.
카메라의 설정은 다르지만, 그림자 캐스팅 코드는 두 라이트 타입으로 동일합니다. 일반적인 편견은 방향성 그림자에서만 지원되고 있습니다 만, 다른 라이트는 제로로 설정되어 있습니다.
스포트라이트는 스크린 공간 섀도우를 사용하지 않기 때문에, 샘플링 코드는 달라야합니다. 그러나 Unity 매크로는이 차이를 우리에게 숨기고 있습니다.
우리는 단순히 스크린 공간 쉐도우 맵을 샘플링하여 방향성 그림자를 발견했다. 그 맵을 만들 때 Unity는 그림자 필터를 처리하고 있었으므로, 그것에 대해 걱정할 필요가 없습니다. 그러나 스포트라이트는 스크린 공간 섀도우를 사용하지 않습니다. 그래서 부드러운 그림자를 사용하려면 조각 프로그램에서 필터링을 수행해야합니다.
그럼 SHADOW_ATTENUATION 매크로 UnitySampleShadowmap 함수를 사용하여 그림자 맵을 샘플링합니다. 이 함수는 AutoShight에 포함 된 UnityShadowLibrary에 정의되어 있습니다. 하드 그림자를 사용하는 경우,이 함수는 그림자 맵을 한 번 샘플링합니다. 부드러운 그림자를 사용하는 경우, 맵을 4 회 샘플링하여 평균합니다. 결과는 스크린 공간의 그림자에 사용되는 필터링만큼 좋지 않지만 훨씬 빠릅니다.
Hard vs. soft spotlight shadows.
#include "UnityPBSLighting.cginc"#include "AutoLight.cginc" //#include "UnityPBSLighting.cginc"
그것은 컴파일되지만, 빛의 범위에있는 모든 개체는 검게됩니다. 그림자 맵에 문제가 있습니다.
An incorrect shadow map.
프레임 디버거를 통해 그림자 맵을 검사하면 라이트마다 1 개가 아닌 6 개의 맵이 렌더링되는 것을 알 수 있습니다. 이것은 포인트 라이트가 모든 방향으로 빛나는 때문에 발생합니다. 결과적으로, 쉐도우 맵은 큐브 맵이어야한다. 큐브 맵은 카메라가 6 개의 서로 다른 방향을 가리키는 장면을 큐브의 각면에 대해 한 번 렌더링하여 작성됩니다. 따라서 포인트 라이트의 그림자는 비싸다.
불행히도, 단위 깊이 큐브 맵을 사용하지 않습니다. 분명히 그들을 지원하는 플랫폼이 부족하다는 것은 분명하다. 그래서 My Shadows에서 조각의 깊이 값에 의존 할 수 없습니다. 대신, 조각 프로그램의 결과로 조각의 거리를 출력해야합니다.
포인트 라이트 그림자 맵을 렌더링 할 때 Unity는 SHADOWS_CUBE 키워드가 정의 된 그림자 캐스터 변형을 찾습니다. SHADOWS_DEPTH 키워드는 방향 및 스포트라이트의 그림자에 사용됩니다. 이를 지원하려면 쉐도우 캐스터의 특별한 멀티 컴파일 지령을 경로에 추가합니다.
Pass { Tags { "LightMode" = "ShadowCaster" } CGPROGRAM #pragma target 3.0 #pragma multi_compile_shadowcaster #pragma vertex MyShadowVertexProgram #pragma fragment MyShadowFragmentProgram #include "My Shadows.cginc" ENDCG }
그러면 필요한 변형이 추가됩니다
// Snippet #2 platforms ffffffff: SHADOWS_CUBE SHADOWS_DEPTH 2 keyword variants used in scene: SHADOWS_DEPTH SHADOWS_CUBE
포인트 라이트는 이러한 다른 접근 방식을 필요로하기 때문에 필요한 변형에 대해 별도의 프로그램 함수를 작성합시다.
#if defined(SHADOWS_CUBE)#else float4 MyShadowVertexProgram (VertexData v) : SV_POSITION { float4 position = UnityClipSpaceShadowCasterPos(v.position.xyz, v.normal); return UnityApplyLinearShadowBias(position); } half4 MyShadowFragmentProgram () : SV_TARGET { return 0; } #endif
조각의 빛으로부터의 거리를 확인하려면 라이트에서 조각까지 월드 공간 벡터를 구축해야합니다. 정점마다 이러한 벡터를 생성하고이를 보간함으로써 그렇게 할 수 있습니다. 여기에는 추가 보간 장치가 필요합니다.
#if defined(SHADOWS_CUBE) struct Interpolators { float4 position : SV_POSITION; float3 lightVec : TEXCOORD0; }; Interpolators MyShadowVertexProgram (VertexData v) { Interpolators i; i.position = UnityObjectToClipPos(v.position); i.lightVec = mul(unity_ObjectToWorld, v.position).xyz - _LightPositionRange.xyz; return i; } float4 MyShadowFragmentProgram (Interpolators i) : SV_TARGET { return 0; }#else
조각 프로그램에서는 빛 벡터의 길이를 취하고 그것에 편견을 추가합니다. 그렇다면 그것을 0-1의 범위에 맞추기 위해 빛의 범위로 나눕니다. _LightPositionRange.w 변수는 범위의 역수가 포함되어 있기 때문에이 값을 걸어야합니다. 결과는 부동 소수점 값으로 출력됩니다.
float4 MyShadowFragmentProgram (Interpolators i) : SV_TARGET { float depth = length(i.lightVec) + unity_LightShadowBias.x; depth *= _LightPositionRange.w; return UnityEncodeCubeShadowDepth(depth); }
Correct shadow map.
우리의 그림자 맵이 올바른 때문에, 포인트 조명 그림자가 표시됩니다. Unity 매크로는 그 맵의 샘플링을 처리합니다.
Shadowed point lights.
스포트라이트 그림자의 경우와 마찬가지로, 그림자 맵은 하드 그림자의 경우 1 회, 부드러운 그림자의 경우는 4 회 샘플링됩니다. 큰 차이는 Unity가 그림자 큐브 맵 필터링을 지원하지 않는 것입니다. 그 결과, 그림자의 가장자리는 더 나빠집니다. 그래서, 포인트 조명 그림자는 비용 별명이 붙어 있습니다.
Hard vs. soft point light shadows.