はじめに
ScriptableRenderFeature の勉強に、フェードアウトする床反射を作ってみました。
謝辞
この記事の内容は 【Unity】URPを拡張して水面反射を作ってみた を参考・ベースにさせて頂いています。
「反射する3Dオブジェクトの描画」部分はほぼそのままなので、詳しくは参考記事をご覧ください。
Git Repository
環境
- Unity 2021.3.16f1 (LTS)
- Universal RP 12.1.8
Render 設定方法
HorizontalPlaneReflectionTextureRenderFeature
を UnivarsalRenderData に追加して使用します。
Property 詳細
Property | Description |
---|---|
Culling Mask | レンダリング対象とするレイヤー |
Plane Height | 鏡面の高さ |
Plane Thickness | 鏡面の厚み 鏡像のレンダリング位置が厚みの分だけ下がります。 |
Fade Base Height | フェードアウトが始まる高さ |
Fade Range | フェードアウトしきるまでの幅 |
Draw Opaque | 不透明オブジェクトを描画するか |
Draw Transparent | 透明オブジェクトを描画するか |
Render Pass Event | Render Pass のタイミング 鏡面オブジェクトを Opaque で描画することを想定して、デフォルト値は Before Rendering Opaques. |
Render Texture Scale | Render Texture の大きさ倍率 |
Filter Mode | Render Texture の Filter Mode |
Render Texture Format | Render Texture のフォーマット HDR を使用する際は RGBAHalf などのアルファチャンネルが使えるフォーマットを指定することを推奨。 |
Render Texture Debug View | Render Texture を表示する(デバッグ用) |
フロー概要
- RenderTexture に特定の高さで反転した3Dオブジェクトをフェードアウト状態でレンダリングする
- フェードアウト描画用に専用の Pass を用意
- 不透明パスは Depth 描画 → Color 描画の2ステップを踏むことで、前後関係の破綻を回避する
- 上記 RenderTexture をサンプリングした床オブジェクトを描画する
- AlphaBlend だけではなく Additive にも対応したいため、 P-MAP ブレンド手法を採用
- ※ P-MAP ブレンド手法について詳しくは CEDEC2015「加算合成コストが0になる!?すぐに使えるP-MAPブレンドテクニック」 をご覧ください。
実装
鏡像を描画する設定
鏡像の描画部分は 参考記事(【Unity】URPを拡張して水面反射を作ってみた) そのまま利用させて頂いています。
非常に丁寧に解説してくれているので、詳細はそちらをご覧ください。
フェードアウト描画
Plane Height などのパラメータを ShaderGlobalParameter で渡して、専用パスで描画します。
ScriptableRenderPass では LightMode を指定して描画できるため、 Opaque と Transparent にそれぞれ独自の LightMode の Tag を付けます。
- Opaeque : HorizontalPlaneReflectionOpaque
- Transpaernt : - Opaeque : HorizontalPlaneReflectionTransparent
Render Pass実装
cmd.SetGlobalFloat(FadeRangeId, Settings.fadeRange);
cmd.SetGlobalFloat(FadeBaseHeightId, Settings.fadeBaseHeight);
// 高さぴったりにすると床にピッタリ着く面も消してしまうため、少し余裕を持たせる
cmd.SetGlobalFloat(PlaneHeightId, Settings.planeHeight - 0.01f);
// Opaque Pass は Depth だけ先に描画する
context.DrawRenderers(cullingResults, ref depthDrawingSettings, ref FilteringSettings);
// Color を描画
context.DrawRenderers(cullingResults, ref drawingSettings, ref FilteringSettings);
Shader 実装
Opaque Shader
// URP Lit Shader に Pass を追加
Pass
{
Name "HorizontalPlaneReflectionOpaque"
Tags{"LightMode" = "HorizontalPlaneReflectionOpaque"}
Blend SrcAlpha OneMinusSrcAlpha
Cull [_Cull]
ZTest Equal // 事前に Depth を描いているので Equal で描画
ZWrite Off
...
float GetReflectionAlpha(float heightWS)
{
half reflectionFadeAlpha = smoothstep(
_ReflectionFadeBaseHeight + _ReflectionFadeRange,
_ReflectionFadeBaseHeight,
heightWS);
reflectionFadeAlpha *= step(_ReflectionPlaneHeight, heightWS);
return reflectionFadeAlpha;
}
half4 ReflectionLitPassFragment(Varyings input) : SV_Target
{
half4 outColor = LitPassFragment(input);
half reflectionFadeAlpha = GetReflectionAlpha(input.positionWS.y);
outColor.a *= reflectionFadeAlpha;
return outColor;
}
}
Alpha Blend Shader
Pass
{
Name "HorizontalPlaneReflectionTransparent"
Tags { "LightMode"="HorizontalPlaneReflectionTransparent" }
Blend SrcAlpha OneMinusSrcAlpha
Cull [_CullMode]
ZTest LEqual
ZWrite Off
HLSLPROGRAM
#pragma vertex vert
#pragma fragment fragReflection
...
half4 fragReflection (Varyings input) : SV_Target
{
// sample the texture
half4 outColor = frag(input);
half reflectionFadeAlpha = GetReflectionAlpha(input.positionWS.y);
outColor.a *= reflectionFadeAlpha;
return outColor;
}
ENDHLSL
}
Additive Shader
Pass
{
Name "HorizontalPlaneReflectionTransparent"
Tags { "LightMode"="HorizontalPlaneReflectionTransparent" }
Blend One One, Zero One // アルファチャンネルは書き込まない
Cull [_CullMode]
ZTest LEqual
ZWrite Off
HLSLPROGRAM
#pragma vertex vert
#pragma fragment fragReflection
...
half4 fragReflection (Varyings input) : SV_Target
{
half4 outColor = frag(input);
half reflectionFadeAlpha = GetReflectionAlpha(input.positionWS.y);
outColor.rgb *= reflectionFadeAlpha;
return outColor;
}
ENDHLSL
}
RenderTexture を鏡面に合成
RenderTexture を P-MAP ブレンド手法で合成します。
Pass
{
Name "ForwardUnlit"
Tags { }
ZWrite On
HLSLPROGRAM
#pragma vertex vert
#pragma fragment frag
// Includes
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/UnityInput.hlsl"
#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Color.hlsl"
struct Attributes
{
float4 positionOS : POSITION;
float2 texcoord : TEXCOORD0;
};
struct Varyings
{
float4 positionCS : SV_POSITION;
float2 uv : TEXCOORD0;
float4 positionSS : TEXCOORD1;
};
TEXTURE2D(_MainTex); SAMPLER(sampler_MainTex);
TEXTURE2D(_HorizontalPlaneReflectionTexture); SAMPLER(sampler_HorizontalPlaneReflectionTexture);
CBUFFER_START(UnityPerMaterial)
half4 _BaseColor;
float4 _MainTex_ST;
float _ReflectionTextureIntensity;
CBUFFER_END
Varyings vert (Attributes input)
{
Varyings output = (Varyings)0;
output.positionCS = TransformObjectToHClip(input.positionOS.xyz);
output.uv = TRANSFORM_TEX(input.texcoord, _MainTex);
// Screen Space Position
output.positionSS = ComputeScreenPos(output.positionCS);
return output;
}
half4 frag (Varyings input) : SV_Target
{
half4 outColor = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, input.uv);
outColor *= _BaseColor;
half4 reflectionColor = SAMPLE_TEXTURE2D(
_HorizontalPlaneReflectionTexture,
sampler_HorizontalPlaneReflectionTexture,
input.positionSS.xy / input.positionSS.w);
// SrcColor + DstColor * (1 - SrcAlpha) で合成
outColor.rgb = reflectionColor.rgb * _ReflectionTextureIntensity
+ outColor.rgb * (1.0 - reflectionColor.a* _ReflectionTextureIntensity);
return outColor;
}
ENDHLSL
}
サンプルでは Unlit ですが、もちろん PBR などの Lit Shader にも合成できます。
課題:ビルボードと相性が悪い
反転後のビルボードメッシュの向きがカメラ方向を向かない課題があります。
ScriptableRenderPass からメッシュの向きを調整できれば良いのですが、良い方法が思いついてない……
ユニティちゃんライセンス
この記事はユニティちゃんライセンス条項の元に提供されています。