⚡
【URP】3Dモデルの頂点を引き伸ばして、シャドウボリュームを描画してみる
はじめに
シャドウボリュームを描画してみました。
3Dモデルの頂点を引き伸ばすことで、影のように見せています。
環境
Unity 2021.3.16f1
Universal RP 12.1.8
考え方
3Dモデルの陰になっている部分に注目します。
陰の部分の頂点を、光の進む方向へ引き伸ばすことで、シャドウボリュームになります。
シャドウボリュームを描画するための設定
Universal Renderer Data にRender Objects
を追加します。
Render Objects
は以下のように設定します。
- Event は AfterRenderingTransparentsに設定
- LightMode Tags に ShadowForward を設定
これにより、"LightMode" = "ShadowForward"
というTagが指定されたシェーダーのパスが、半透明描画後に実行されるようになります。
シェーダーの実装
シェーダーには、2つのパスを持たせます。
- 3Dモデルを描画するパス
- シャドウボリュームを描画するパス
BasePass.hlsl
3Dモデル本体を描画するパス用のシェーダー関数(vertやfrag)をBasePass.hlslに定義しました。
BasePass.hlsl
#ifndef BASE_PASS_INCLUDED
#define BASE_PASS_INCLUDED
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
struct BaseVertexInput
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct BaseVertexOutput
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
float4 _MainTex_ST;
BaseVertexOutput vert (BaseVertexInput v)
{
BaseVertexOutput o;
o.vertex = TransformObjectToHClip(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
return o;
}
half4 frag (BaseVertexOutput i) : SV_Target
{
half4 col = tex2D(_MainTex, i.uv);
return col;
}
#endif
ShadowPass.hlsl
シャドウボリュームを描画するパス用のシェーダー関数は ShadowPass.hlsl に定義しました。
ShadowPass.hlsl
#ifndef SHADOW_PASS_INCLUDED
#define SHADOW_PASS_INCLUDED
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
struct ShadowVertexInput
{
float4 vertex : POSITION;
float4 normal : NORMAL;
};
struct ShadowVertexOutput
{
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
float4 _MainTex_ST;
float _ShadowLength;
half4 _ShadowColor;
ShadowVertexOutput vert (ShadowVertexInput v)
{
ShadowVertexOutput o;
// ライトの取得
Light light = GetMainLight();
// 法線をワールド空間に変換
float3 worldNormal = TransformObjectToWorld(v.normal);
// マスクの作成 (陰になっている部分は1.0になる)
float nDotL = dot(worldNormal, light.direction);
float mask = step(nDotL, 0.0);
// 頂点を押し出す
float3 vertex2 = v.vertex + TransformWorldToObjectDir(light.direction) * _ShadowLength;
o.vertex = TransformObjectToHClip(lerp(v.vertex, vertex2, mask));
}
half4 frag (ShadowVertexOutput i) : SV_Target
{
return _ShadowColor;
}
#endif
MyShader.shader
シェーダーの実装は以下のようになります。
BaseパスとShadowパスの二つで構成されています。
MyShader.shader
Shader "Unlit/MyShader"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_ShadowLength ("Shadow Length", Float) = 0.5
_ShadowColor ("Shadow Color", COlor) = (0, 0, 0, 0.5)
}
SubShader
{
LOD 100
Tags { "RenderType"="Opaque" }
Pass
{
// 3dモデル本体を描画するパス
Name "Base"
Tags { "LightMode" = "UniversalForward" }
HLSLPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "BasePass.hlsl"
ENDHLSL
}
Pass
{
// シャドウボリュームを描画するパス
Name "Shadow"
Tags { "LightMode" = "ShadowForward" }
Blend SrcAlpha OneMinusSrcAlpha
ZWrite Off
Cull Off
HLSLPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "ShadowPass.hlsl"
ENDHLSL
}
}
}
このシェーダーを3Dモデルにアタッチすることで、シャドウボリュームが設定されます。
影を1回だけ描画する
今回の方法だと、影が重なる部分が出てしまい、半透明のオーバードローが発生してしまいます。
以下のようなステンシルブロックを記述することで、影が1回だけ描画されるようにでき、オーバードローを回避できます。
Stencil
{
Ref 1
Comp Greater
Pass IncrSat
}
Shadowパスを以下のように書き換えます。
MyShader.shader
Pass
{
Name "Shadow"
Tags { "RenderType"="Opaque" }
+ Stencil
+ {
+ Ref 1
+ Comp Greater
+ Pass IncrSat
+ }
Blend SrcAlpha OneMinusSrcAlpha
ZWrite Off
Cull Off
HLSLPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "ShadowPass.hlsl"
ENDHLSL
}
Discussion