URP10.2.2 URPシェーダーをShaderLabで書いてみる
URPのシェーダーを作ろうとすると、UNITYからは全部ShaderGraphになる
ので、テキストエディタでShaderを書く事は推奨されていないようだし
実際に仕事でも、アーティストと会話するために、ノードエディタのShaderGraphを使う方が良いと思うが
勉強のためにあえてShaderLabで書いてみる
URP UnlitShaderを調査
まずは、最も簡単なはずのUnlitから書いてみる
テンプレートからはShaderLabは作れないので、ShaderGraphのUnlitを新規で作成します
床に適用してみた。テクスチャも影もなくなった
作成されたUnlitShaderGraphからShaderLabのコードを取得するには、Inspectorから ViewGeneratedShader を選択するとコードが表示される
パスは、通常パス、ShadowCaster、DepthOnlyの3つで、それがTransparent、Opaqueの両方あるので合計6個のパスがあります
おそらく、SRP Batchingのために、TransparentとOpaqueの両方のシェーダをまとめているんだと思う
Feature関連
// Pragmas #pragma target 2.0 #pragma only_renderers gles gles3 glcore #pragma multi_compile_instancing #pragma multi_compile_fog #pragma vertex vert #pragma fragment frag // DotsInstancingOptions: <None> // HybridV1InjectedBuiltinProperties: <None> // Keywords #pragma multi_compile _ LIGHTMAP_ON #pragma multi_compile _ DIRLIGHTMAP_COMBINED #pragma shader_feature _ _SAMPLE_GI // GraphKeywords: <None> // Defines #define ATTRIBUTES_NEED_NORMAL #define ATTRIBUTES_NEED_TANGENT #define FEATURES_GRAPH_VERTEX /* WARNING: $splice Could not find named fragment 'PassInstancing' */ #define SHADERPASS SHADERPASS_UNLIT /* WARNING: $splice Could not find named fragment 'DotsInstancingVars' */
色々と書いてあるが、おおざっぱに
Instancing、Fog、dots instancing、ライトマップ系、アトリビュート、そしてshaderpass unlit
等設定しています
dots instancingとか、最近のUNITYの高速化、Data-Oriented Technology Stackの事でしょう
今度ちゃんと調べてみたいです
Includes
#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Color.hlsl" #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl" #include "Packages/com.unity.render-pipelines.core/ShaderLibrary/TextureStack.hlsl" #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/ShaderGraphFunctions.hlsl"
これらのコードをIncludeしています
BuiltIn-Shaderではなく、URPのシェーダーを見に行ってますね
入出力
struct Attributes { float3 positionOS : POSITION; float3 normalOS : NORMAL; float4 tangentOS : TANGENT; #if UNITY_ANY_INSTANCING_ENABLED uint instanceID : INSTANCEID_SEMANTIC; #endif }; struct Varyings { float4 positionCS : SV_POSITION; #if UNITY_ANY_INSTANCING_ENABLED uint instanceID : CUSTOM_INSTANCE_ID; #endif #if (defined(UNITY_STEREO_MULTIVIEW_ENABLED)) || (defined(UNITY_STEREO_INSTANCING_ENABLED) && (defined(SHADER_API_GLES3) || defined(SHADER_API_GLCORE))) uint stereoTargetEyeIndexAsBlendIdx0 : BLENDINDICES0; #endif #if (defined(UNITY_STEREO_INSTANCING_ENABLED)) uint stereoTargetEyeIndexAsRTArrayIdx : SV_RenderTargetArrayIndex; #endif #if defined(SHADER_STAGE_FRAGMENT) && defined(VARYINGS_NEED_CULLFACE) FRONT_FACE_TYPE cullFace : FRONT_FACE_SEMANTIC; #endif };
おそらく入力パラメータAttributesは #defineで有効にしたものが入ってくるのだろう
それにInstancing用のID
Varyingsは、InstancingやVR等で条件コンパイルされてるが、ポジション程度しかない
それらの入力値を使い、データを抽象化しているのか、シェーダーライブラリ内で使う構造体に変換しているのだと思われる
SurfaceDescriptionInputs、VertexDescriptionInputs、PackedVaryingsと変換関数がある
実際の処理
#include "Packages/com.unity.render-pipelines.universal/Editor/ShaderGraph/Includes/ShaderPass.hlsl" #include "Packages/com.unity.render-pipelines.universal/Editor/ShaderGraph/Includes/Varyings.hlsl" #include "Packages/com.unity.render-pipelines.universal/Editor/ShaderGraph/Includes/UnlitPass.hlsl"
実際の処理はシェーダーライブラリの中だ
その中でも実際にシェーダーの中身は下記になる
PackedVaryings vert(Attributes input) { Varyings output = (Varyings)0; output = BuildVaryings(input); PackedVaryings packedOutput = PackVaryings(output); return packedOutput; } half4 frag(PackedVaryings packedInput) : SV_TARGET { Varyings unpacked = UnpackVaryings(packedInput); UNITY_SETUP_INSTANCE_ID(unpacked); UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(unpacked); SurfaceDescriptionInputs surfaceDescriptionInputs = BuildSurfaceDescriptionInputs(unpacked); SurfaceDescription surfaceDescription = SurfaceDescriptionFunction(surfaceDescriptionInputs); #if _AlphaClip half alpha = surfaceDescription.Alpha; clip(alpha - surfaceDescription.AlphaClipThreshold); #elif _SURFACE_TYPE_TRANSPARENT half alpha = surfaceDescription.Alpha; #else half alpha = 1; #endif #ifdef _ALPHAPREMULTIPLY_ON surfaceDescription.BaseColor *= surfaceDescription.Alpha; #endif return half4(surfaceDescription.BaseColor, alpha); }
まあ、ここだけ見ると単純です
シンプルなUnlitシェーダー
白く塗りつぶすだけの非常に簡単なシェーダーを作った
Shader "MyShader/URPUnlit" { SubShader { Tags { "RenderPipeline" = "UniversalPipeline" "RenderType" = "Opaque" "UniversalMaterialType" = "Unlit" "Queue" = "Geometry" } Pass { Name "Pass" // Render State Cull Back Blend One Zero ZTest LEqual ZWrite On HLSLPROGRAM // Pragmas #pragma target 4.5 #pragma exclude_renderers gles gles3 glcore #pragma vertex vert #pragma fragment frag #include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Common.hlsl" #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/UnityInput.hlsl" struct Attributes { float3 positionOS : POSITION; float3 normalOS : NORMAL; float4 tangentOS : TANGENT; }; struct Varyings { float4 positionCS : SV_POSITION; }; float3 TransformObjectToWorld(float3 positionOS) { return mul(unity_ObjectToWorld, float4(positionOS, 1.0)).xyz; } float4 TransformWorldToHClip(float3 positionWS) { return mul(unity_MatrixVP, float4(positionWS, 1.0)); } Varyings vert(Attributes input) { Varyings output = (Varyings)0; output.positionCS = TransformWorldToHClip(TransformObjectToWorld(input.positionOS)); return output; } half4 frag(Varyings input) : SV_TARGET { return half4(1,1,1,1); } ENDHLSL } } }
includeは2個あるが、UNITYのシェーダーのCORE的なものと、RPから入ってくるUNITY定数の宣言
思ったより簡単に書けたが、色々な処理を無視しているし
影とかは難しそうな気配・・
PS,XBOX,Switch等の対応とかも考えると、ShaderGraphで書く方が楽だろうなあ・・・
テクスチャマッピングする
これは非常に簡単だった
今までのBuiltIn-Shaderの作法にのっとり
Texture2DをPropertiesに出し、Texture2DとSamplerを宣言し
AttributesとVaryingsに TEXCOORD0を入れて
SAMPLE_TEXTURE2Dでマッピングした結果をリターンすればいい
意外と素直
影を落とす
"Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
をIncludeすることにした
そして今回はメインライトからの影のみを取るので
pragma multi_compile _ _MAIN_LIGHT_SHADOWS
をつけた
shadowAttenuationを計算するのだが、ShadowMapをSampleするには、ワールド座標のPositionが必要なので
positionWSを計算することに
TransformObjectToWorld(input.positionOS)
で簡単に取得できた
ワールド座標系のオブジェクトポジションを使い
TransformWorldToShadowCoord(input.positionWS)
をすると、ShadowCoordが取得できるので、そこから
GetMainLightを使い、メインライトの情報、影の情報が取れる
今回はライトカラーなどは使わずshadowAttenuationだけを使い、テクスチャにそのまま乗算した
とりあえず影はできた
今回の完成シェーダコード
Shader "MyShader/URPUnlit" { Properties { [NoScaleOffset] MainTex("_MainTex", 2D) = "white" {} [HideInInspector][NoScaleOffset]unity_Lightmaps("unity_Lightmaps", 2DArray) = "" {} [HideInInspector][NoScaleOffset]unity_LightmapsInd("unity_LightmapsInd", 2DArray) = "" {} [HideInInspector][NoScaleOffset]unity_ShadowMasks("unity_ShadowMasks", 2DArray) = "" {} } SubShader { Tags { "RenderPipeline" = "UniversalPipeline" "RenderType" = "Opaque" "UniversalMaterialType" = "Unlit" "Queue" = "Geometry" } Pass { Name "Pass" // Render State Cull Back Blend One Zero ZTest LEqual ZWrite On HLSLPROGRAM // Pragmas #pragma target 4.5 #pragma exclude_renderers gles gles3 glcore #pragma vertex vert #pragma fragment frag #pragma multi_compile _ _MAIN_LIGHT_SHADOWS #include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Common.hlsl" #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Input.hlsl" #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/UnityInput.hlsl" #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl" TEXTURE2D(MainTex); SAMPLER(samplerMainTex); struct Attributes { float3 positionOS : POSITION; float3 normalOS : NORMAL; float4 tangentOS : TANGENT; float4 uv0 : TEXCOORD0; }; struct Varyings { float4 positionCS : SV_POSITION; float4 texCoord0 : TEXCOORD0; float3 positionWS: TEXCOORD1; }; Varyings vert(Attributes input) { Varyings output = (Varyings)0; output.positionCS = TransformWorldToHClip(TransformObjectToWorld(input.positionOS)); output.texCoord0 = input.uv0; output.positionWS = TransformObjectToWorld(input.positionOS); return output; } half4 frag(Varyings input) : SV_TARGET { half4 o = SAMPLE_TEXTURE2D(MainTex, samplerMainTex, input.texCoord0.xy); Light l = GetMainLight(TransformWorldToShadowCoord(input.positionWS)); return o*l.shadowAttenuation; } ENDHLSL } } }
Instancingやメイン以外のライト等、いろいろすっ飛ばしているので実用性はないが
URPのシェーダーは、こんな感じで書く事は可能だ
ShadowCasterやDepthOnlyパスは消してしまったが同じように簡単に作る事ができそう
ただし、URPはバージョンアップで色々変わってしまうので
ShaderGraphを使う方が楽と思う