【UnityShader】オブジェクトの影を受け取る #108
前回の成果
オブジェクトの影を落とした。
今回やること
前回のコードのままですと、他のオブジェクトからの影を受け取ることができません。
なので、その部分を修正します。
影を受け取れていない
手前のオブジェクトが前回、影を落としたオブジェクトになります。
奥のオブジェクトはUnityのデフォルトのCubeになります。
事前準備
オブジェクトを配置し、配置したオブジェクトの影が当たる位置にオブジェクトを配置します。
影が当たる位置のオブジェクトに今回制作するマテリアルをアタッチします。
ソースコード
Shader "Unlit/ShadowDefault" { Properties { _MainTex ("Texture", 2D) = "white" {} } SubShader { // 通常の描画 Pass { Tags { "RenderType"="Opaque" "LightMode"="ForwardBase" } CGPROGRAM #pragma vertex vert #pragma fragment frag #pragma multi_compile_fwdbase nolightmap nodirlightmap nodynlightmap novertexlights #include "UnityCG.cginc" #include "Lighting.cginc" #include "AutoLight.cginc" struct appdata { float4 vertex : POSITION; float3 normal : NORMAL; float2 uv : TEXCOORD0; }; struct v2f { float2 uv : TEXCOORD0; fixed4 diff : COLOR0; float4 pos : SV_POSITION; SHADOW_COORDS(1) }; sampler2D _MainTex; float4 _MainTex_ST; v2f vert (appdata v) { v2f o; o.pos = UnityObjectToClipPos(v.vertex); o.uv = TRANSFORM_TEX(v.uv, _MainTex); half3 worldNormal = UnityObjectToWorldNormal(v.normal); half NdotL = saturate(dot(worldNormal, _WorldSpaceLightPos0.xyz)); o.diff = NdotL * _LightColor0; TRANSFER_SHADOW(o) return o; } fixed4 frag (v2f i) : SV_Target { fixed4 col = tex2D(_MainTex, i.uv); fixed4 shadow = SHADOW_ATTENUATION(i); return col * i.diff * shadow; } ENDCG } // 影の描画 Pass { Tags { "LightMode"="ShadowCaster" } CGPROGRAM #pragma vertex vert #pragma fragment frag #pragma multi_compile_shadowcaster #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; float3 normal : NORMAL; float2 uv : TEXCOORD0; }; struct v2f { // 深度テクスチャではないキューブマップの場合 // float3 vec : TEXCOORD0; // float4 pos : SV_POSITION // それ以外の場合 // float4 pos : SV_POSITION V2F_SHADOW_CASTER; }; v2f vert (appdata v) { v2f o; // 深度テクスチャではないキューブマップの場合 // o.vec = mul(unity_ObjectToWorld, v.vertex).xyz - _LightPositionRange.xyz; // o.pos = UnityObjectToClipPos(v.vertex); // それ以外の場合 // o.pos = UnityClipSpaceShadowCasterPos(v.vertex, v.normal); // o.pos = UnityApplyLinearShadowBias(o.pos); TRANSFER_SHADOW_CASTER_NORMALOFFSET(o); return o; } fixed4 frag (v2f i) : SV_Target { // 深度テクスチャではないキューブマップの場合 // UnityEncodeCubeShadowDepth ((length(i.vec) + unity_LightShadowBias.x) * _LightPositionRange.w); // それ以外の場合 // return 0 SHADOW_CASTER_FRAGMENT(i) } ENDCG } } }
LightMode Tag
Tags { "RenderType"="Opaque" "LightMode"="ForwardBase" }
今回はForwardBase
を指定しています。
このキーワードはアンビエント、メインのDirectionalLight、Vertex/SHLight、ライトマップを適応するものになります。
詳しくは以下の記事を参考にしてください。
シェーダーバリアント
#pragma multi_compile_fwdbase nolightmap nodirlightmap nodynlightmap novertexlights
シェーダーバリアントとは
コンパイル時に複数のシェーダーコードを自動的に生成したい命令をコンパイラに伝えるものになります。
また、multi_compile_fwdbase
のようなmulti_compile
で始まるものは複数のシェーダーバリアントをコンパイルしています。
今回のmulti_compile_fwdbase
では、コンパイラにフォワードレンダリングベースに必要な全てのバリアントをコンパイルしたいことを伝えています。
multi_compileの例
名前 | コンパイルするバリアント |
---|---|
multi_compile_fwdbase | フォワードレンダリングのベース処理 |
multi_compile_fwdadd | フォワードレンダリングのライト加算処理 |
multi_compile_fwdadd_fullshadows | multi_compile_fwdaddとライトのリアルタイムシャドウ |
multi_compile_fog | フォグの処理 |
使用されているバリアントの確認
UnityのInspectorから、使用されているバリアントを確認することができます。
シェーダーを選択肢、Inspectorから
Compile code -> ▼ -> Show
の手順で確認できます。
今回のシェーダーですと、8つのバリアントが使用されているようです。
// Total snippets: 2 // ----------------------------------------- // Snippet #0 platforms ffffffff: Builtin keywords used: DIRECTIONAL LIGHTPROBE_SH SHADOWS_SHADOWMASK SHADOWS_SCREEN LIGHTMAP_SHADOW_MIXING VERTEXLIGHT_ON 8 keyword variants used in scene: DIRECTIONAL DIRECTIONAL LIGHTPROBE_SH DIRECTIONAL SHADOWS_SCREEN DIRECTIONAL LIGHTPROBE_SH SHADOWS_SCREEN DIRECTIONAL VERTEXLIGHT_ON DIRECTIONAL LIGHTPROBE_SH VERTEXLIGHT_ON DIRECTIONAL SHADOWS_SCREEN VERTEXLIGHT_ON DIRECTIONAL LIGHTPROBE_SH SHADOWS_SCREEN VERTEXLIGHT_ON // ----------------------------------------- // Snippet #1 platforms ffffffff: Builtin keywords used: SHADOWS_DEPTH SHADOWS_CUBE 2 keyword variants used in scene: SHADOWS_DEPTH SHADOWS_CUBE
シェーダーバリアントのコード生成オプション
フォワードレンダリングのバリアントを指定したあとに、いくつかのキーワードが指定されています。
#pragma multi_compile_fwdbase nolightmap nodirlightmap nodynlightmap novertexlights
これは、コード生成オプションと呼ばれるものになります。
multi_compileでは複数のバリアントをコンパイルしてしまうため、不要なものが出てきてしまう可能性があります。
予め、不要なものがわかっている場合は、コード生成オプションを指定することで対象のコンパイルをスキップすることができます。
今回使用しているコード生成オプション
名前 | 意味 |
---|---|
nolightmap | このシェーダーの全てのライトマッピングのサポートを無効にする |
nodirlightmap | このシェーダーのDirectional Lightmapのサポートを無効にする |
nodynlightmap | このシェーダーのDynamic GIを無効にする |
novertexlights | フォワードレンダリングで、ライトプローブと頂点ライティングを適応しない |
他にもいくつかあります。
詳しくは、公式のリファレンスを参照してください。
組み込みシェーダーのinclude
#include "Lighting.cginc" #include "AutoLight.cginc"
Lighting.cginc
サーフェスシェーダーにおける標準的な照明モデルになります。
サーフェスシェーダーを書く時には自動的に含まれます。
今回の場合、_LightColor0
を使用するためにincludeしています。
AutoLight.cginc
ライティングとシャドウの機能になります。
サーフェスシェーダーの場合、この組み込みシェーダーを内部的に使用しています。
今回の場合、SHADOW_COORDS
、TRANSFER_SHADOW
、SHADOW_ATTENUATION
を使用するためにincludeしています。
v2f構造体のSV_POSITION
struct v2f { // 省略 float4 pos : SV_POSITION; };
影を使用する際には、SV_POSITION名をpos
にしないとエラーが出てしまいます。
これはvertexからfragmentへ変換を行う際のマクロでpos
が決め打ちとなっているからになります。
今回ですとTRANSFER_SHADOW
でposが決め打ちとなっています。
SHADOW_COORDS
struct v2f { // 省略 SHADOW_COORDS(1) };
SHADOW_COORDSの定義
#define SHADOW_COORDS(idx1) unityShadowCoord4 _ShadowCoord : TEXCOORD##idx1;
引数に応じたunityShadowCoord4
型のTEXCOORDを定義しています。
すでに使用しているTEXCOORDは使用できないので注意してください。
今回の場合、uvでTEXCOORD0を指定しているので、SHADOW_COORDSの引数は1にしています。
unityShadowCoord4の定義
#define unityShadowCoord4 float4
float4型となっており、ここに影の情報を入れています。
TRANSFER_SHADOW
v2f vert (appdata v) {
// 省略
TRANSFER_SHADOW(o)
}
TRANSFER_SHADOWの定義
#define TRANSFER_SHADOW(a) a._ShadowCoord = ComputeScreenPos(a.pos);
SHADOW_COORDS
で定義した、_ShadowCoord
にスクリーンスペースの座標を入れています。
ここでposが決め打ちとなっているので、v2f構造体のSV_POSITIONの名前はposである必要があります。
SHADOW_ATTENUATION
fixed4 frag (v2f i) : SV_Target {
// 省略
fixed4 shadow = SHADOW_ATTENUATION(i);
}
SHADOW_ATTENUATIONの定義
#define SHADOW_ATTENUATION(a) unitySampleShadow(a._ShadowCoord)
バーテックスシェーダーでTRANSFER_SHADOW
マクロを用いてスクリーンスペース座標を計算した_ShadowCoord
を引数としていれています。
unitySampleShadowの定義
inline fixed unitySampleShadow (float4 shadowCoord) { fixed shadow = tex2Dproj( _ShadowMapTexture, UNITY_PROJ_COORD(shadowCoord) ).r; return shadow; }
tex2Dproj
を使用して、オブジェクトにシャドウマップのテクセルを参照しています。
R値を返しているのは、シャドウマップはR値で深度を参照するからになります。
シャドウマップの例
【Unity】シャドウマップを自前で作成する(ライトからの深度をRenderTextureに描画する) - LIGHT11:より引用
tex2Dproj
tex2Dprojは、投影テクスチャマッピングの際に必要なtex2D処理となります。
これは仮想視点での射影変換をしているので、wでの除算が必要になるためになります。
// 元定義 fixed shadow = tex2Dproj( _ShadowMapTexture, UNITY_PROJ_COORD(shadowCoord) ).r; return shadow; // tex2Dprojの置き換え float2 uv = shadowCoord.xy / shadowCoord.w; fixed shadow = tex2D(_ShadowMapTexture, uv).r return shadow
UNITY_PROJ_COORD
#if defined(SHADER_API_PSP2) #define UNITY_BUGGY_TEX2DPROJ4 #define UNITY_PROJ_COORD(a) (a).xyw #else #define UNITY_PROJ_COORD(a) a #endif
SHADER_API_PSP2
はPlayStation Vitaかを判断するマクロになります。
ですので、ほとんどのプラットフォームの場合、aをそのまま返すだけになります。
シェーダーにコメントを付与
まとめとして、コメントを記載しました。
Shader "Unlit/ShadowDefault" { Properties { _MainTex ("Texture", 2D) = "white" {} } SubShader { // 通常の描画 Pass { Tags { "RenderType"="Opaque" "LightMode"="ForwardBase" } CGPROGRAM #pragma vertex vert #pragma fragment frag #pragma multi_compile_fwdbase nolightmap nodirlightmap nodynlightmap novertexlights #include "UnityCG.cginc" #include "Lighting.cginc" #include "AutoLight.cginc" struct appdata { float4 vertex : POSITION; float3 normal : NORMAL; float2 uv : TEXCOORD0; }; struct v2f { float2 uv : TEXCOORD0; fixed4 diff : COLOR0; // 変数名は"pos"固定 float4 pos : SV_POSITION; // unityShadowCoord4型(float4)を定義 // 影の情報を入れる SHADOW_COORDS(1) }; sampler2D _MainTex; float4 _MainTex_ST; v2f vert (appdata v) { v2f o; o.pos = UnityObjectToClipPos(v.vertex); o.uv = TRANSFORM_TEX(v.uv, _MainTex); half3 worldNormal = UnityObjectToWorldNormal(v.normal); half NdotL = saturate(dot(worldNormal, _WorldSpaceLightPos0.xyz)); o.diff = NdotL * _LightColor0; // SHADOW_COORDSにスクリーンスペース座標を入れる TRANSFER_SHADOW(o) return o; } fixed4 frag (v2f i) : SV_Target { fixed4 col = tex2D(_MainTex, i.uv); // シャドウマップのテクセルを反映 fixed4 shadow = SHADOW_ATTENUATION(i); return col * i.diff * shadow; } ENDCG } // 影の描画 Pass { Tags { "LightMode"="ShadowCaster" } CGPROGRAM #pragma vertex vert #pragma fragment frag #pragma multi_compile_shadowcaster #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; float3 normal : NORMAL; float2 uv : TEXCOORD0; }; struct v2f { // 深度テクスチャではないキューブマップの場合 // float3 vec : TEXCOORD0; // float4 pos : SV_POSITION // それ以外の場合 // float4 pos : SV_POSITION V2F_SHADOW_CASTER; }; v2f vert (appdata v) { v2f o; // 深度テクスチャではないキューブマップの場合 // o.vec = mul(unity_ObjectToWorld, v.vertex).xyz - _LightPositionRange.xyz; // o.pos = UnityObjectToClipPos(v.vertex); // それ以外の場合 // o.pos = UnityClipSpaceShadowCasterPos(v.vertex, v.normal); // o.pos = UnityApplyLinearShadowBias(o.pos); TRANSFER_SHADOW_CASTER_NORMALOFFSET(o); return o; } fixed4 frag (v2f i) : SV_Target { // 深度テクスチャではないキューブマップの場合 // UnityEncodeCubeShadowDepth ((length(i.vec) + unity_LightShadowBias.x) * _LightPositionRange.w); // それ以外の場合 // return 0 SHADOW_CASTER_FRAGMENT(i) } ENDCG } } }
結果
手前が今回制作したシェーダーのオブジェクト
奥がUnityのデフォルトシェーダーのオブジェクトになります。
手前のオブジェクトにも影が落ちるようになりました。
今回は以上となります。
ここまでご視聴ありがとうございました。