このノートでは Built-in RP の Shader ファイルを URP の書き方へ移行する方法をまとめます。ネット上の Shader 解説はいまだに Built-in 前提のものが多いので、この対応表は思った以上に出番があります。
構造
SubShader
Tags {} にレンダーパイプラインのタグを追加します。これがないと URP はこの SubShader を候補にすら入れません。
Plain TextSubShader {
Tags {
"RenderPipeline" = "UniversalPipeline"
}
}
LightMode の値も変わりました。Built-in の ForwardBase / ForwardAdd は UniversalForward ひとつに統合されています。URP のフォワードレンダラーは単一の Pass 内ですべてのライトを処理し、追加ライトごとにジオメトリを描き直さないためです。実際に使うのは次のとおり。
| 用途 | Built-in | URP |
|---|---|---|
| メインライティング Pass | ForwardBase | UniversalForward |
| 追加ライト | ForwardAdd | (UniversalForward に統合) |
| 影の投射 | ShadowCaster | ShadowCaster |
| 深度プリパス | — | DepthOnly / DepthNormals |
| 非ライティングの追加 Pass | 任意 | SRPDefaultUnlit |
URP が認識できない LightMode の Pass は黙って破棄されます。エラーも出ず、描画もされません。アウトラインのような追加 Pass が出てこないとき、原因は十中八九これです。
CG マクロ
CGPROGRAM / ENDCG を HLSLPROGRAM / ENDHLSL に、CGINCLUDE を HLSLINCLUDE に置き換えます。
#include の変更
よく使う include は次のように対応します。
Plain Text#include "UnityCG.cginc"
=> #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
#include "Lighting.cginc"
=> #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
#include "AutoLight.cginc"
=> #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Shadows.hlsl"
Core.hlsl は Common.hlsl や SpaceTransforms.hlsl などをまとめて読み込むので、多くの場合これひとつで足ります。
組み込みヘルパー関数の置き換え
頂点変換
| Built-in | URP |
|---|---|
UnityObjectToClipPos(v) | TransformObjectToHClip(v) |
UnityObjectToWorldDir(v) | TransformObjectToWorldDir(v) |
UnityObjectToWorldNormal(v) | TransformObjectToWorldNormal(v) |
mul(unity_ObjectToWorld, v) | TransformObjectToWorld(v.xyz) |
UnityObjectToViewPos(v) | TransformWorldToView(TransformObjectToWorld(v)) |
UnityWorldSpaceViewDir(p) | GetWorldSpaceViewDir(p) |
URP には VertexPositionInputs という構造体もあり、object / world / view / clip の各空間の位置を一度に計算してくれます。手作業で個別に変換するより記述量も命令数も減ります。
HLSLVertexPositionInputs positions = GetVertexPositionInputs(IN.positionOS.xyz);
VertexNormalInputs normals = GetVertexNormalInputs(IN.normalOS, IN.tangentOS);
OUT.positionCS = positions.positionCS;
OUT.positionWS = positions.positionWS;
OUT.normalWS = normals.normalWS;
テクスチャサンプリング
Built-in の sampler2D + tex2D は、URP では明示的なテクスチャ/サンプラーの組に置き換わります。これにより一つのサンプラーを複数のテクスチャで共有でき、数の限られたサンプラースロットを節約できます。
HLSL// Built-in
sampler2D _MainTex;
half4 c = tex2D(_MainTex, uv);
// URP
TEXTURE2D(_MainTex);
SAMPLER(sampler_MainTex);
half4 c = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, uv);
ライティング
_LightColor0 と _WorldSpaceLightPos0 は廃止されました。代わりに URP からライトオブジェクトを受け取ります。
HLSLLight mainLight = GetMainLight(shadowCoord);
half3 lightDir = mainLight.direction;
half3 lightColor = mainLight.color;
half atten = mainLight.distanceAttenuation * mainLight.shadowAttenuation;
// 追加ライトは自分でループする必要がある
uint count = GetAdditionalLightsCount();
for (uint i = 0; i < count; i++) {
Light light = GetAdditionalLight(i, positionWS);
// ... 加算
}
SRP Batcher と CBUFFER
これは文法上の要求ではありませんが、パフォーマンスを直接左右します。マテリアル単位のプロパティはすべて UnityPerMaterial 定数バッファに入れる必要があります。さもないとこの Shader は SRP Batcher の対象外となり、マテリアルごとの SetPass に戻ってしまいます。
HLSLCBUFFER_START(UnityPerMaterial)
float4 _MainTex_ST;
half4 _BaseColor;
half _Smoothness;
CBUFFER_END
条件は厳格です。CBUFFER 内の宣言順と型はすべての Pass で完全に一致していなければならず、テクスチャ本体を含めることはできません(_MainTex_ST のような付随プロパティのみ)。作業後に Inspector で Shader を展開すると、ヘッダーに "SRP Batcher: compatible" と表示されます。そこで初めて合格です。
影の受け取り
URP の影はキーワードを明示的に有効化しないと、サンプル結果が常に 1(完全に明るい)になります。
HLSL#pragma multi_compile _ _MAIN_LIGHT_SHADOWS
#pragma multi_compile _ _MAIN_LIGHT_SHADOWS_CASCADE
#pragma multi_compile _ _SHADOWS_SOFT
#pragma multi_compile _ _ADDITIONAL_LIGHTS
そのうえでフラグメント段階でシャドウ座標を取得します。
HLSLfloat4 shadowCoord = TransformWorldToShadowCoord(positionWS);
Light mainLight = GetMainLight(shadowCoord);
half shadow = mainLight.shadowAttenuation;
移行チェックリスト
RenderPipelineタグは入っているか?LightModeは URP が認識する値か?CGPROGRAM→HLSLPROGRAMになっているか?- include は URP の ShaderLibrary を指しているか?
- 変換とサンプリングのマクロはすべて置き換えたか?
- マテリアルプロパティは
UnityPerMaterialの中か?Inspector で SRP Batcher 互換を確認。 - 影が必要なら
multi_compileキーワードを宣言したか? - 深度やポストプロセスがこのオブジェクトを必要とするなら、
DepthOnlyPass を追加したか?
最初の四つを飛ばすと Shader はマゼンタになるか、そもそも描画されません。後半の四つを飛ばしても動きはしますが、パフォーマンスか見た目が静かに劣化します。見つけにくいのは、むしろこちらの類です。