投影テクスチャシャドウを実作してみた

##はじめに
この記事の作成は[「いけにえと雪のセツナ」グラフィック解説（第2回・グラフィック効果編）](http://logicalbeat.jp/blog/dev-case/373/)を参考させていただきます

非常に勉強になりました

今回は参考記事内容の一つ、**投影テクスチャシャドウ**について実作してみたいと思います


##シャドウマップ作成
まず、シャドウカメラを生成して、光源と同じ方向を設置する(光視点と考えればいい
Culling Maskを弄って、被写体だけ写せるように
そのあとシャドウカメラのtargetTextureを指定する。
その指定されたRenderTextureは、後に投影テクスチャシャドウが参照するシャドウマップです

![image.png](https://qiita-image-store.s3.amazonaws.com/0/235561/b99b289a-9d62-0e12-f5e6-720f2dfd1db2.png)


##SetReplacementShader
シャドウマップに描き込むカラーは影の色だけで
そのため、一々被写体のmaterialを切り替えるのも面倒な作業

なにかいい方法ありますかと調べたら
SetReplacementShader まんま願いをかなえてくれそうです

[SetReplacementShader](https://docs.unity3d.com/jp/540/ScriptReference/Camera.SetReplacementShader.html)
SetReplacementShaderをシンプルに考えると、shader versionのoverrideみたいなもの


SetReplacementShader第2引数が指定したタグを判断条件
差し替えるShaderとmaterial指定したShader　タグの値が同じなら
そのまま置き換える

例として　シーンの中にCubeとSphereがいる
それぞれ別のshaderを使用する

DemoForShaderReplace.cginc
<div class="code-frame" data-lang="text"><div class="highlight"><pre><span></span>
 #ifndef _DEMO_FOR_SHADERREPLACE
 #define _DEMO_FOR_SHADERREPLACE
 #include "UnityCG.cginc"
 struct appdata
 {
	float4 vertex : POSITION;
	float2 uv : TEXCOORD0;
 };
 struct v2f
 {
	float2 uv : TEXCOORD0;
	float4 vertex : SV_POSITION;
 };
 sampler2D _MainTex;
 float4 _MainTex_ST;			
 v2f vert (appdata v)
 {
	v2f o;
	o.vertex = UnityObjectToClipPos(v.vertex);
	o.uv = TRANSFORM_TEX(v.uv, _MainTex);
	return o;
 }			
 fixed4 frag (v2f i) : SV_Target
 {
	return tex2D(_MainTex, i.uv);
 }
 #endif //_DEMO_FOR_SHADERREPLACE</pre></div></div>

DemoSphere.shader
<div class="code-frame" data-lang="text"><div class="highlight"><pre><span></span>Shader "Unlit/DemoSphere"
{
	Properties
	{
		_MainTex ("Texture", 2D) = "white" {}
	}
	SubShader
	{
		Tags { "RenderType"="Opaque" "Name"="Sphere"</color> }
		LOD 100

		Pass
		{
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag

			#include "DemoForShaderReplace.cginc"
			ENDCG
		}
	}
</pre></div></div>


DemoCube.shader
<div class="code-frame" data-lang="text"><div class="highlight"><pre><span></span>Shader "Unlit/DemoCube"
{
	Properties
	{
		_MainTex ("Texture", 2D) = "white" {}
	}
	SubShader
	{
		Tags { "RenderType"="Opaque" "Name"="Cube" }
		LOD 100

		    Pass
		    {
			    CGPROGRAM
			    #pragma vertex vert
			    #pragma fragment frag

			    #include "DemoForShaderReplace.cginc"
			    ENDCG
		    }
	    }
}
</pre></div></div>


![image.png](https://qiita-image-store.s3.amazonaws.com/0/235561/6e10d83b-5b30-b3e3-9e20-6ac4a892953e.png)


二つのshaderはほぼ同じですが、唯一の差別はカスタムタグ Name の値が違う


次は差し替えるshader

DemoReplace.shader
<div class="code-frame" data-lang="text"><div class="highlight"><pre><span></span>Shader "Unlit/DemoReplace"
{
	SubShader
	{
		Tags { "Name"="Cube" }

		    Pass
		    {
			    CGPROGRAM
			    #pragma vertex vert
			    #pragma fragment frag_Replace

			    #include "DemoForShaderReplace.cginc"

			    fixed4 frag_Replace (v2f i) : SV_Target
			    {
				    return fixed4(1,0,0,1);
			    }
			    ENDCG
		    }  
	    }

	    SubShader
	    {
		    Tags { "Name"="Sphere" }

		    Pass
		    {
			     CGPROGRAM
			     #pragma vertex vert
			     #pragma fragment frag_Replace

			     #include "DemoForShaderReplace.cginc"

			     fixed4 frag_Replace (v2f i) : SV_Target
			     {
				    return fixed4(0,1,0,1);
			     }
			     ENDCG
		    }
	    }
}
</pre></div></div>


差し替えるshaderはカスタムタグ Name ごとに
別々の差し替えたい描画を定義する
例はCubeを赤に　Sphereを緑に

最後は　camera.SetReplacementShader 呼び出す


<div class="code-frame" data-lang="text"><div class="highlight"><pre><span></span>
Camera.main.SetReplacementShader (Shader.Find("Unlit/DemoReplace"), "Name");
</pre></div></div>

結果
![image.png](https://qiita-image-store.s3.amazonaws.com/0/235561/99d33ff4-1f20-3720-b3be-7a87e3cf12db.png)


##シャドウマップ参照
床に影を映る、そのため
先ずシャドウマップのどこに参照すればいいを知ることが必要

シャドウマップはシャドウカメラ(光視点)で写せる結果
なら床もシャドウカメラ(光視点)で写せ、その描画された位置を参照すればいい

メインカメラでシャドウカメラに描画された位置を求めるには
シャドウカメラのビュー行列とプロジェクション行列が必要

<div class="code-frame" data-lang="text"><div class="highlight"><pre><span></span>
Matrix4x4 view = shadowCamera.worldToCameraMatrix;
Matrix4x4 proj = GL.GetGPUProjectionMatrix (shadowCamera.projectionMatrix, true);
Shader.SetGlobalMatrix("_ProjectionViewProj", proj*view);
</pre></div></div>

二つの行列を掛ける その結果をshaderに伝えて
最後にshaderで計算


<div class="code-frame" data-lang="text"><div class="highlight"><pre><span></span>Shader "Shadow/ProjectionShadowGround"
{
	Properties
	{
		_MainTex ("Texture", 2D) = "white" {}
	}
	SubShader
	{
		Tags { "RenderType"="Opaque" }
		LOD 100

		Pass
		{
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			
			#include "UnityCG.cginc"

			struct appdata
			{
				float4 vertex : POSITION;
				float2 uv : TEXCOORD0;
			};

			struct v2f
			{
				float2 uv : TEXCOORD0;
				float4 uv_shadow : TEXCOORD1;
				float4 vertex : SV_POSITION;
			};

			sampler2D _MainTex;
			float4 _MainTex_ST;

			sampler2D _ProjectionShadowMap;
			float4x4  _ProjectionViewProj;
			
			v2f vert (appdata v)
			{
				v2f o;
				o.vertex = UnityObjectToClipPos(v.vertex);
				o.uv = TRANSFORM_TEX(v.uv, _MainTex);
				
				float4 world = mul (unity_ObjectToWorld, v.vertex);
				float4 pos = mul (_ProjectionViewProj, world);
				o.uv_shadow = ComputeScreenPos (pos);
				return o;
			}
			
			fixed4 frag (v2f i) : SV_Target
			{
				fixed4 col = tex2D(_MainTex, i.uv);
				
				fixed4 shadow = tex2D(_ProjectionShadowMap, (i.uv_shadow/i.uv_shadow.w).xy);
				
				return lerp(col, shadow, shadow.a);
			}
			ENDCG
		}
	}
}</pre></div></div>

結果
![image.png](https://qiita-image-store.s3.amazonaws.com/0/235561/b3498b0d-c160-2cf9-30d0-682eb7dab077.png)


##リポジトリ
[github](https://github.com/sigmalin/Institute/tree/master/Assets/ProjectionShadow)