T.TAO
ブログへ戻る
/6 min read/Technical Art

Unity Shader #3 URP へのアップグレード

#Unity#Shader#TechnicalArt

このノートでは Built-in RP の Shader ファイルを URP の書き方へ移行する方法をまとめます。ネット上の Shader 解説はいまだに Built-in 前提のものが多いので、この対応表は思った以上に出番があります。

構造

SubShader

Tags {} にレンダーパイプラインのタグを追加します。これがないと URP はこの SubShader を候補にすら入れません。

Plain TextSubShader {
    Tags {
        "RenderPipeline" = "UniversalPipeline"
    }
}

LightMode の値も変わりました。Built-in の ForwardBase / ForwardAddUniversalForward ひとつに統合されています。URP のフォワードレンダラーは単一の Pass 内ですべてのライトを処理し、追加ライトごとにジオメトリを描き直さないためです。実際に使うのは次のとおり。

用途Built-inURP
メインライティング PassForwardBaseUniversalForward
追加ライトForwardAddUniversalForward に統合)
影の投射ShadowCasterShadowCaster
深度プリパスDepthOnly / DepthNormals
非ライティングの追加 Pass任意SRPDefaultUnlit

URP が認識できない LightMode の Pass は黙って破棄されます。エラーも出ず、描画もされません。アウトラインのような追加 Pass が出てこないとき、原因は十中八九これです。

CG マクロ

CGPROGRAM / ENDCGHLSLPROGRAM / ENDHLSL に、CGINCLUDEHLSLINCLUDE に置き換えます。

#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.hlslCommon.hlslSpaceTransforms.hlsl などをまとめて読み込むので、多くの場合これひとつで足ります。

組み込みヘルパー関数の置き換え

頂点変換

Built-inURP
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;

移行チェックリスト

  1. RenderPipeline タグは入っているか?
  2. LightMode は URP が認識する値か?
  3. CGPROGRAMHLSLPROGRAM になっているか?
  4. include は URP の ShaderLibrary を指しているか?
  5. 変換とサンプリングのマクロはすべて置き換えたか?
  6. マテリアルプロパティは UnityPerMaterial の中か?Inspector で SRP Batcher 互換を確認。
  7. 影が必要なら multi_compile キーワードを宣言したか?
  8. 深度やポストプロセスがこのオブジェクトを必要とするなら、DepthOnly Pass を追加したか?

最初の四つを飛ばすと Shader はマゼンタになるか、そもそも描画されません。後半の四つを飛ばしても動きはしますが、パフォーマンスか見た目が静かに劣化します。見つけにくいのは、むしろこちらの類です。

このシリーズの記事

Unity Shader
  1. 01Unity Shader #0 数学の基礎
  2. 02Unity Shader #1 レンダーパイプライン
  3. 03Unity Shader #2 コードの基礎
  4. 04Unity Shader #2.1 基本コマンド
  5. 05Unity Shader #3 URP へのアップグレード
  6. 06Unity Shader #4 UV 上での図形描画
  7. 07Unity Shader #5 リムライト
  8. 08Unity Shader #6 スキャンとホログラム
  9. 09Unity Shader #7 アニメトゥーンシェーダー
  10. 10Unity Shader #8 ディゾルブ
  11. 11Unity Shader #9 レンズの雨滴
  12. 12Unity Shader #13 ポストプロセス
  13. 13URP #1 Universal Lit と URP ShaderLab