【Unity】SpriteRendererで3D空間上に影を投影する【URP】
概要
SpriteRendererは2D向けの機能なので、3D空間上に影を落とす機能が標準で搭載されていません。しかし、所謂HD2Dのような表現をしたいときなど、SpriteRendererでも影を描画したいケースがあります。
今回は、そういったケースのために、SpriteRendererで影を描画する方法を紹介します。
目標
このような形で、SpriteRendererから3D空間上に影を投影することを目標にします。
影を受けることはこの記事では取り扱いません。
SpriteRendererの設定を変更する
STEP1 InspectorウィンドウをDebugモードにする
まずは、SpriteRendererの設定を変更します。
SpriteRendererの3D用の設定は、通常の表示モードだと隠れているので、
Inspectorウィンドウ右上のその他 (⋮) メニューから、
表示モードをDebugモードにします。
STEP2 SpriteRendererのCastShadowsをOnにする
InspectorウィンドウをDebugモードにすると、詳細なオプションが確認できます。
影を投影したいので、ここのCast ShadowsをOnに設定してください。
変更後は、InspectorビューをNormalモードに戻して大丈夫です。
ここで、影はまだ描画されないことに注意してください。
これは、標準のシェーダーが影を投影するパスを含んでいないからです。
3D用のシェーダーのマテリアルを適用すれば影が描画されるようになりますが、描画順がDepthバッファ基準になるため、Zファイティングが発生してしまいます。1枚の画像ならそれでも大丈夫ですが、SpriteRendererの各種機能(Sprite Skin, flipなど)を利用することができません。
そこで、シェーダーを複製/改変して影を描画するようにします。
シェーダーを改変する
STEP3 シェーダーを複製する
まずはデフォルトのSpriteシェーダーをコピーします。
URPの場合は、Projectビューの
Packages/Universal RP/Shaders/2D/Sprite-Unlit-Default
をコピーしてください。
Sprite-Lit-Default
には2D光源の影響を受ける処理が含まれているので、3D空間で使う際はUnlitを使用するのがいいと思います。
実際のファイルは以下のパスにあります。(バージョンは適宜置き換えてください)
Library\PackageCache\com.unity.render-pipelines.universal@15.0.6\Shaders\2D\Sprite-Unlit-Default.shader
STEP4 シェーダーの名前を変更する
シェーダーはこのような構造になっています。
まずは最上部のShaderの名前を変更します。
Shader "Universal Render Pipeline/2D/Sprite-Lit-Default" // <-ここを変更する
{
Properties
{
// ...
}
SubShader
{
// ...
Pass
{
// ...
}
// ...
}
Fallback "Sprites/Default"
}
STEP5 影用のパスを追加する
次に、SubShaderブロックの末尾に、影用のパスを追加します。
Shader "任意の名前"
{
Properties
{
// ...
}
SubShader
{
// ...
Pass
{
// ...
}
// ...
// <-ここに挿入
}
Fallback "Sprites/Default"
}
挿入する影用のパス(URP)は以下の通りです。
Pass
{
Tags { "LightMode" = "ShadowCaster" }
ZWrite On
ZTest LEqual
ColorMask 0
HLSLPROGRAM
#pragma target 2.0
// -------------------------------------
// Shader Stages
#pragma vertex ShadowPassVertex
#pragma fragment ShadowPassFragment
//--------------------------------------
// GPU Instancing
#pragma multi_compile_instancing
#include_with_pragmas "Packages/com.unity.render-pipelines.universal/ShaderLibrary/DOTS.hlsl"
// -------------------------------------
// Universal Pipeline keywords
// -------------------------------------
// This is used during shadow map generation to differentiate between directional and punctual light shadows, as they use different formulas to apply Normal Bias
#pragma multi_compile_vertex _ _CASTING_PUNCTUAL_LIGHT_SHADOW
// -------------------------------------
// Includes
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/CommonMaterial.hlsl"
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Shadows.hlsl"
#include "Packages/com.unity.render-pipelines.universal/Shaders/2D/Include/Core2D.hlsl"
// Shadow Casting Light geometric parameters. These variables are used when applying the shadow Normal Bias and are set by UnityEngine.Rendering.Universal.ShadowUtils.SetupShadowCasterConstantBuffer in com.unity.render-pipelines.universal/Runtime/ShadowUtils.cs
// For Directional lights, _LightDirection is used when applying shadow Normal Bias.
// For Spot lights and Point lights, _LightPosition is used to compute the actual light direction because it is different at each shadow caster geometry vertex.
float3 _LightDirection;
float3 _LightPosition;
struct Attributes
{
float3 positionOS : POSITION;
float3 normalOS : NORMAL;
UNITY_SKINNED_VERTEX_INPUTS
UNITY_VERTEX_INPUT_INSTANCE_ID
};
struct Varyings
{
float4 positionCS : SV_POSITION;
UNITY_VERTEX_OUTPUT_STEREO
};
float4 GetShadowPositionHClip(Attributes input)
{
float3 positionWS = TransformObjectToWorld(input.positionOS.xyz);
float3 normalWS = TransformObjectToWorldNormal(input.normalOS);
#if _CASTING_PUNCTUAL_LIGHT_SHADOW
float3 lightDirectionWS = normalize(_LightPosition - positionWS);
#else
float3 lightDirectionWS = _LightDirection;
#endif
float4 positionCS = TransformWorldToHClip(ApplyShadowBias(positionWS, normalWS, lightDirectionWS));
#if UNITY_REVERSED_Z
positionCS.z = min(positionCS.z, UNITY_NEAR_CLIP_VALUE);
#else
positionCS.z = max(positionCS.z, UNITY_NEAR_CLIP_VALUE);
#endif
return positionCS;
}
Varyings ShadowPassVertex(Attributes attributes)
{
Varyings o = (Varyings)0;
UNITY_SETUP_INSTANCE_ID(attributes);
UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);
UNITY_SKINNED_VERTEX_COMPUTE(attributes);
attributes.positionOS = UnityFlipSprite(attributes.positionOS, unity_SpriteProps.xy);
o.positionCS = GetShadowPositionHClip(attributes);
return o;
}
half4 ShadowPassFragment(Varyings i) : SV_TARGET
{
return 0;
}
ENDHLSL
}
これはURPのLitシェーダー(3D用のシェーダー)のShadowCasterパスとSprite-Unlit-Defaultシェーダーを切り貼りしたものです。LODやAlphaClip関係の処理を削除し、VertexShaderをSpirte-Unlit-Defaultシェーダーのものに差し替えました。
しかし、これらの対応をせずそのままLitシェーダーのShadowCasterパスをコピペしても何故か正常に描画されたため(Transparentなため?)、そちらでも問題ないかもしれません。
正直よくわかっていない部分なので、もし何かご存じの方がいらっしゃればご教示いただけると嬉しいです。
一応、AlphaClip対応バージョンも用意したので、gistに上げておきます。
他のレンダーパイプラインを使用する場合は、それぞれのレンダーパイプラインの標準シェーダーからコピーしてください。
Tags { "LightMode" = "ShadowCaster" }
を目印に、ShadowCasterで検索を掛けると見つかりやすいと思います。
マテリアルを適用する
STEP6 マテリアルを作成する
あとはマテリアルを適用するだけです。
Projectビューで右クリック->Create/Materialからマテリアルを作成し、
Inspectorビュー上部の赤枠の部分をクリックし、先ほど変更した名前のシェーダーに変更してください。
STEP7 SpriteRendererにマテリアルを設定する
最後に、SpriteRendererのMaterialスロットに先ほど作成したマテリアルを設定します。
完成
以上で影が描画されるはずです。
これでも影が描画されない場合は、各種グラフィックス設定を確認してください。
3Dのプリミティブをいくつか作成して影が落ちるか確認するといいかもしれません。
参考
Discussion