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"
Cull Back
Blend One Zero
ZTest LEqual
ZWrite On
HLSLPROGRAM
#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を使う方が楽と思う