陰付けは不要とのことですので、それを前提に方法を調査してみました。
ですので、陰付けも一緒に行うLIGHTING_COORDS
やLIGHT_ATTENUATION
は使用せず、影用のマクロを用いることにしました。
現行バージョンでは、自身の上に落ちる影は下記3つのマクロが面倒を見てくれるようです。
UNITY_SHADOW_COORDS(idx1)
...v2f
定義内で使う。バーテックスシェーダーからフラグメントシェーダーへの情報伝達のための変数を追加する。引数に番号を指定することで、データ伝達に何番のテクスチャ座標用セマンティクスを使うか選べる。
UNITY_TRANSFER_SHADOW(a, coord)
...バーテックスシェーダーで使う。a
はv2f
構造体で、それにフラグメントシェーダーに伝達するべき情報が書き込まれる。GI対応のため、coord
にライトマップ用UV座標を渡してやる必要がある。
UNITY_SHADOW_ATTENUATION(a, worldPos)
...フラグメントシェーダーで使う。a
にはバーテックスシェーダーから送られてきたv2f
構造体を、worldPos
にはフラグメントのワールド座標を渡す。すると影係数が返ってくるので、これを出力色に掛けてやれば影の部分が暗くなる。
これらを使って下記のようにしてみましたが、あちらを立てればこちらが立たずといったジレンマが随所にあって、どこかしらに不満の残る結果になってしまいました。
半透明を正しく描画するためにキューを「Transparent」にしたかったが、そうすると影描画ロジックが動かない
キューを「AlphaTest」にすれば影描画が行われるが、プレイモードではスカイボックス描画がAlphaTestよりも後に配置されているため、スカイボックスを透かして見る構図になると描画が破綻(背景がSolid Colorか不透明オブジェクトなら大丈夫そうです)するので対策が必要
影描画はデプスシャドウ技法に基づくロジックになっているが、半透明をうまく扱うのは難しそう(シャドウマップ作成時にディザリングを行ってみたものの、ノイズが気になる...ノイズ除去フィルタを加えればマシになるかもしれませんが未実験です)
HLSL
1 Shader "Unlit/CastShadowTest1"
2 {
3 Properties
4 {
5 _Color ( "Main Color" , Color ) = ( 1 , 1 , 1 , 1 )
6 _MainTex ( "Base (RGB)" , 2D ) = "white" { }
7 _ShadowIntensity ( "Shadow Intensity" , Range ( 0.0 , 1.0 ) ) = 0.5 // 大きくするほど影が暗くなる
8 // _Cutoff ("Alpha cutoff", Range(0, 1)) = 0.5 // 半透明物体の場合、アルファカットオフを可変にはせずに...
9 [ Toggle ( UNITY_UI_ALPHACLIP ) ] _UseUIAlphaClip ( "Use Alpha Clip" , Float ) = 0 // カットオフをトグルで切り替え、オンならば固定値0.001でカットオフするケースが多いようです
10 }
11 CGINCLUDE
12 // 影係数の取得および緩和に使う
13 // 1 - (1 - 影係数) * _ShadowIntensity を最終的な影係数とすることで、影が暗くなりすぎるのを防ぐ
14 # define FADED_SHADOW_ATTENUATION ( i ) ( 1.0 - ( 1.0 - UNITY_SHADOW_ATTENUATION ( i , i . worldPos ) ) * _ShadowIntensity )
15 ENDCG
16 SubShader
17 {
18 // Queueが「AlphaTest」になっていましたが、おそらく妥当な方法だと思います
19 // 本来は「AlphaTest」はアルファを基に完全透明・完全不透明を切り替えるタイプの不透明オブジェクトに
20 // 用いるもので、半透明オブジェクトは「Transparent」を使うべきかと思いますが、そうしてしまうと
21 // 影機能も使えなくなってしまうようです
22 // 半透明オブジェクトを正しくアルファ合成するには他の不透明オブジェクトよりも後に描画させたいところですが
23 // 「AlphaTest」なら一般的な不透明オブジェクトの「Geometry」よりは後になるそうなので、描画不具合も
24 // 起こりにくいのではないでしょうか
25 Tags { "Queue" = "AlphaTest" "RenderType" = "TransparentCutout" }
26 // シャドウキャスティングパス
27 // LightModeが「ShadowCaster」のパスがないと、影を落とす・受けるのいずれも行われなくなるようです
28 // コード末尾にFallbackを記述しておくことで他のシェーダーに処理を代替させることもできますが、
29 // 今回は半透明の特殊なケースですので、自前で用意することにしました
30 // なお、旧バージョンでは「ShadowCollector」パスが影を受ける処理を担当していたそうですが
31 // 現在は廃止されたようです
32 Pass
33 {
34 Name "ShadowCaster"
35 Tags { "LightMode" = "ShadowCaster" }
36 ZWrite On ZTest LEqual
37 CGPROGRAM
38 # define UNITY_STANDARD_USE_DITHER_MASK
39 # define UNITY_STANDARD_USE_SHADOW_UVS
40 # pragma target 3.0
41 # pragma vertex vert
42 # pragma fragment frag
43 # include "UnityStandardShadow.cginc"
44 struct VertexOutput
45 {
46 V2F_SHADOW_CASTER_NOPOS
47 float2 tex : TEXCOORD1 ;
48 } ;
49 void vert ( VertexInput v , out VertexOutput o , out float4 opos : SV_POSITION )
50 {
51 TRANSFER_SHADOW_CASTER_NOPOS ( o , opos )
52 o . tex = v . uv0 ;
53 }
54 half4 frag ( VertexOutput i , UNITY_VPOS_TYPE vpos : VPOS ) : SV_Target
55 {
56 // シャドウマップへの描画は原理的に完全透明か完全不透明でないとなりません
57 // そこで、Unity組み込みのディザリング用テクスチャをアルファに基づいて参照することで
58 // アルファが小さいほど高確率で透明、大きいほど不透明となるような動作をさせることができます
59 // できあがるシャドウマップにはディザリングによるノイズが乗っていますが、ソフトシャドウモードなら
60 // 後で近傍比率フィルタリングがかかるので、影に関してはさほど見た目は悪くないと思われます
61 // 半透明部分のエッジにノイズが目立つかもしれませんが、これはポストエフェクトなどでごまかす...?
62 half alpha = tex2D ( _MainTex , i . tex . xy ) . a * _Color . a ;
63 alpha = tex3D ( _DitherMaskLOD , float3 ( vpos . xy * 0.25 , alpha * 0.9375 ) ) . a ;
64 // 場合によっては下記1行をコメントアウトして、Quad全面を完全描画させた方が良好な結果になるかと思います
65 clip ( alpha - 0.01 ) ; // ディザリングの結果アルファ0.01未満になればフラグメントを破棄
66 SHADOW_CASTER_FRAGMENT ( i )
67 }
68
69 ENDCG
70 }
71 // フォワードレンダリングベースパス
72 Pass
73 {
74 Name "ForwardBase"
75 Tags { "LightMode" = "ForwardBase" }
76 Blend SrcAlpha OneMinusSrcAlpha // 一般的なアルファ合成方式を使用
77 ZWrite On ZTest LEqual
78 CGPROGRAM
79
80 # pragma vertex vert
81 # pragma fragment frag
82 # pragma fragmentoption ARB_fog_exp2
83 # pragma fragmentoption ARB_precision_hint_fastest
84 # pragma multi_compile_fwdbase
85 # pragma multi_compile _ UNITY_UI_ALPHACLIP // アルファクリップがオン・オフの場合でマルチコンパイル
86 # include "UnityCG.cginc"
87 # include "AutoLight.cginc"
88 sampler2D _MainTex ;
89 fixed4 _Color ;
90 float _ShadowIntensity ;
91 struct v2f
92 {
93 float4 pos : SV_POSITION ; // UNITY_TRANSFER_SHADOW内ではクリッピング座標の名前が「pos」であることが前提になっているため、そのようにする(ご質問者さんのコードでは、すでにそうなっていました)
94 float2 uv : TEXCOORD0 ;
95 UNITY_SHADOW_COORDS ( 1 ) // TEXCOORD1を影情報伝達に使用する
96 float3 worldPos : TEXCOORD2 ; // TEXCOORD2をワールド座標伝達に使用する
97 } ;
98 v2f vert ( appdata_full v ) // 頂点入力データの形式として、appdata_tanに代わってappdata_fullを使う
99 {
100 v2f o ;
101 o . pos = UnityObjectToClipPos ( v . vertex ) ;
102 o . uv = v . texcoord . xy ;
103 UNITY_TRANSFER_SHADOW ( o , v . texcoord1 ) ; // texcoord1にライトマップ用UVが格納されているので、それを使って影情報の計算を行う
104 o . worldPos = mul ( unity_ObjectToWorld , v . vertex ) ; // worldPosに頂点のワールド座標をセットする
105 return o ;
106 }
107 fixed4 frag ( v2f i ) : SV_Target
108 {
109 fixed4 color = tex2D ( _MainTex , i . uv ) * _Color ;
110 # ifdef UNITY_UI_ALPHACLIP
111 clip ( color . a - 0.001 ) ; // アルファ0.001未満は完全透明と見なし、フラグメントを破棄してレンダリングしない
112 # endif
113 color . rgb *= FADED_SHADOW_ATTENUATION ( i ) ; // 影係数はアルファには作用させない
114 return color ;
115 }
116 ENDCG
117
118 }
119
120 // フォワードレンダリング加算パス
121 Pass
122 {
123 Name "ForwardAdd"
124 Tags { "LightMode" = "ForwardAdd" }
125 Blend DstColor Zero // 普通は加算パスでは照明効果をどんどん加算していくかと思いますが、今回は影をどんどん乗算していくことにしました
126 ZWrite Off
127 CGPROGRAM
128
129 # pragma vertex vert
130 # pragma fragment frag
131 # pragma fragmentoption ARB_fog_exp2
132 # pragma fragmentoption ARB_precision_hint_fastest
133 # pragma multi_compile_fwdadd_fullshadows
134 # pragma multi_compile _ UNITY_UI_ALPHACLIP
135 # include "UnityCG.cginc"
136 # include "AutoLight.cginc"
137 sampler2D _MainTex ;
138 fixed4 _Color ;
139 float _ShadowIntensity ;
140 // v2f、vertはベースパスと同じ(CGINCLUDEやcgincを使って共通コードをまとめることも可能かと思います)
141 struct v2f
142 {
143 float4 pos : SV_POSITION ;
144 float2 uv : TEXCOORD0 ;
145 UNITY_SHADOW_COORDS ( 1 )
146 float3 worldPos : TEXCOORD2 ;
147 } ;
148 v2f vert ( appdata_full v )
149 {
150 v2f o ;
151 o . pos = UnityObjectToClipPos ( v . vertex ) ;
152 o . uv = v . texcoord . xy ;
153 UNITY_TRANSFER_SHADOW ( o , v . texcoord1 ) ;
154 o . worldPos = mul ( unity_ObjectToWorld , v . vertex ) ;
155 return o ;
156 }
157 fixed4 frag ( v2f i ) : SV_Target
158 {
159 float alpha = tex2D ( _MainTex , i . uv ) . a * _Color . a ;
160 # ifdef UNITY_UI_ALPHACLIP
161 clip ( alpha - 0.001 ) ; // アルファ0.001未満は完全透明と見なし、フラグメントを破棄してレンダリングしない
162 # endif
163 float attenuation = FADED_SHADOW_ATTENUATION ( i ) ;
164 return fixed4 ( attenuation , attenuation , attenuation , 1.0 ) ;
165 }
166 ENDCG
167
168 }
169 }
170 }
描画結果1
半透明部分にディザリングによるノイズが目立ちます。また、スカイボックス描画順問題の対策をしていないので、ゲームビューではスカイボックスの半透明部分を透かして見た部分が描画されていません。シーンビューではスカイボックスが最初にレンダリングされるらしく、ちゃんと透けています。
描画結果2
上記コードのシャドウキャスティングパスでclip(alpha - 0.01);
をコメントアウトするとこうなります。デプスが単なる四角い板と同じになるので、落とす影が四角くなったり、前後関係を正しく表現できなかったりするかもしれませんが、ノイズは現れないのでこの方が好都合な場合もあるでしょう。
描画結果3
スカイボックス問題の対策として、
カメラを複製する。複製したカメラは元のカメラの子にして、カメラを動かせば追従して動くようにする。
元のカメラの「Clear Flags」を「Don't Clear」とし、背景描画をさせなくする。
複製したカメラの「Culling Mask」を「Nothing」にし、背景だけを描画させる。
複製したカメラの「Depth」を元のカメラより小さくし(例えば-2)、複製したカメラの方を先に描画させるようにする。
とした場合です。とりあえずゲームビューでも背景の破綻は起こらなくなりました。