深度图也是会粉的。
这次笔记没什么美术含量,算是一个代码解析,如果有错误的地方欢迎勘误。
UnityURPshader光照部分学习分析(下)-ShadowCaster和Depthnormals Lit.shader中的Subshader结构 之前一直忘记提到shader中的结构了,由于默认的Lit.shader是一个单pass实现的效果,所以Lit中把大部分的光照计算都放在了”ForwardLit”这个pass中,这个pass里依靠properties变量来决定Blend、ZWrite、Cull的命令,并且在下面首先有一行图形API版本需求的声明,然后是大量的keywords,这些keyword是否被定义决定了在光照时的计算方式(在前两篇的代码解释中已经提及),比如我们常用的multi_compile_fragment系列。
然后就是直接使用顶点和片元着色器进行光照。
而下面的几个pass”ShadowCaster”、”GBuffer”、”DepthOnly”、”DepthNormals”、”Meta”、”Universal2D”等,则是其它产生作用的pass,我们通常会用到ShaderCaster和DepthNormals两个pass,它们的作用分别是向shadowmap中写入阴影和在深度法线图中写入深度和法线。
代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 Pass { // Lightmode matches the ShaderPassName set in UniversalRenderPipeline.cs. SRPDefaultUnlit and passes with // no LightMode tag are also rendered by Universal Render Pipeline Name "ForwardLit" Tags{"LightMode" = "UniversalForward"} Blend[_SrcBlend][_DstBlend] ZWrite[_ZWrite] Cull[_Cull] HLSLPROGRAM #pragma exclude_renderers gles gles3 glcore #pragma target 4.5 // ------------------------------------- // Material Keywords #pragma shader_feature_local _NORMALMAP #pragma shader_feature_local _PARALLAXMAP #pragma shader_feature_local _RECEIVE_SHADOWS_OFF #pragma shader_feature_local _ _DETAIL_MULX2 _DETAIL_SCALED #pragma shader_feature_local_fragment _SURFACE_TYPE_TRANSPARENT #pragma shader_feature_local_fragment _ALPHATEST_ON #pragma shader_feature_local_fragment _ALPHAPREMULTIPLY_ON #pragma shader_feature_local_fragment _EMISSION #pragma shader_feature_local_fragment _METALLICSPECGLOSSMAP #pragma shader_feature_local_fragment _SMOOTHNESS_TEXTURE_ALBEDO_CHANNEL_A #pragma shader_feature_local_fragment _OCCLUSIONMAP #pragma shader_feature_local_fragment _SPECULARHIGHLIGHTS_OFF #pragma shader_feature_local_fragment _ENVIRONMENTREFLECTIONS_OFF #pragma shader_feature_local_fragment _SPECULAR_SETUP // ------------------------------------- // Universal Pipeline keywords #pragma multi_compile _ _MAIN_LIGHT_SHADOWS _MAIN_LIGHT_SHADOWS_CASCADE _MAIN_LIGHT_SHADOWS_SCREEN #pragma multi_compile _ _ADDITIONAL_LIGHTS_VERTEX _ADDITIONAL_LIGHTS #pragma multi_compile_fragment _ _ADDITIONAL_LIGHT_SHADOWS #pragma multi_compile_fragment _ _REFLECTION_PROBE_BLENDING #pragma multi_compile_fragment _ _REFLECTION_PROBE_BOX_PROJECTION #pragma multi_compile_fragment _ _SHADOWS_SOFT #pragma multi_compile_fragment _ _SCREEN_SPACE_OCCLUSION #pragma multi_compile_fragment _ _DBUFFER_MRT1 _DBUFFER_MRT2 _DBUFFER_MRT3 #pragma multi_compile_fragment _ _LIGHT_LAYERS #pragma multi_compile_fragment _ _LIGHT_COOKIES #pragma multi_compile _ _CLUSTERED_RENDERING // ------------------------------------- // Unity defined keywords #pragma multi_compile _ LIGHTMAP_SHADOW_MIXING #pragma multi_compile _ SHADOWS_SHADOWMASK #pragma multi_compile _ DIRLIGHTMAP_COMBINED #pragma multi_compile _ LIGHTMAP_ON #pragma multi_compile _ DYNAMICLIGHTMAP_ON #pragma multi_compile_fog #pragma multi_compile_fragment _ DEBUG_DISPLAY //-------------------------------------- // GPU Instancing #pragma multi_compile_instancing #pragma instancing_options renderinglayer #pragma multi_compile _ DOTS_INSTANCING_ON #pragma vertex LitPassVertex #pragma fragment LitPassFragment #include "Packages/com.unity.render-pipelines.universal/Shaders/LitInput.hlsl" #include "Packages/com.unity.render-pipelines.universal/Shaders/LitForwardPass.hlsl" ENDHLSL } Pass { Name "ShadowCaster" Tags{"LightMode" = "ShadowCaster"} ZWrite On ZTest LEqual ColorMask 0 Cull[_Cull] HLSLPROGRAM #pragma exclude_renderers gles gles3 glcore #pragma target 4.5 // ------------------------------------- // Material Keywords #pragma shader_feature_local_fragment _ALPHATEST_ON #pragma shader_feature_local_fragment _SMOOTHNESS_TEXTURE_ALBEDO_CHANNEL_A //-------------------------------------- // GPU Instancing #pragma multi_compile_instancing #pragma multi_compile _ DOTS_INSTANCING_ON // ------------------------------------- // Universal Pipeline keywords // This is used during shadow map generation to differentiate between directional and punctual light shadows, as they use different formulas to apply Normal Bias #pragma multi_compile_vertex _ _CASTING_PUNCTUAL_LIGHT_SHADOW #pragma vertex ShadowPassVertex #pragma fragment ShadowPassFragment #include "Packages/com.unity.render-pipelines.universal/Shaders/LitInput.hlsl" #include "Packages/com.unity.render-pipelines.universal/Shaders/ShadowCasterPass.hlsl" ENDHLSL } Pass { // Lightmode matches the ShaderPassName set in UniversalRenderPipeline.cs. SRPDefaultUnlit and passes with // no LightMode tag are also rendered by Universal Render Pipeline Name "GBuffer" Tags{"LightMode" = "UniversalGBuffer"} ZWrite[_ZWrite] ZTest LEqual Cull[_Cull] HLSLPROGRAM #pragma exclude_renderers gles gles3 glcore #pragma target 4.5 // ------------------------------------- // Material Keywords #pragma shader_feature_local _NORMALMAP #pragma shader_feature_local_fragment _ALPHATEST_ON //#pragma shader_feature_local_fragment _ALPHAPREMULTIPLY_ON #pragma shader_feature_local_fragment _EMISSION #pragma shader_feature_local_fragment _METALLICSPECGLOSSMAP #pragma shader_feature_local_fragment _SMOOTHNESS_TEXTURE_ALBEDO_CHANNEL_A #pragma shader_feature_local_fragment _OCCLUSIONMAP #pragma shader_feature_local _PARALLAXMAP #pragma shader_feature_local _ _DETAIL_MULX2 _DETAIL_SCALED #pragma shader_feature_local_fragment _SPECULARHIGHLIGHTS_OFF #pragma shader_feature_local_fragment _ENVIRONMENTREFLECTIONS_OFF #pragma shader_feature_local_fragment _SPECULAR_SETUP #pragma shader_feature_local _RECEIVE_SHADOWS_OFF // ------------------------------------- // Universal Pipeline keywords #pragma multi_compile _ _MAIN_LIGHT_SHADOWS _MAIN_LIGHT_SHADOWS_CASCADE _MAIN_LIGHT_SHADOWS_SCREEN //#pragma multi_compile _ _ADDITIONAL_LIGHTS_VERTEX _ADDITIONAL_LIGHTS //#pragma multi_compile _ _ADDITIONAL_LIGHT_SHADOWS #pragma multi_compile_fragment _ _REFLECTION_PROBE_BLENDING #pragma multi_compile_fragment _ _REFLECTION_PROBE_BOX_PROJECTION #pragma multi_compile_fragment _ _SHADOWS_SOFT #pragma multi_compile_fragment _ _DBUFFER_MRT1 _DBUFFER_MRT2 _DBUFFER_MRT3 #pragma multi_compile_fragment _ _LIGHT_LAYERS #pragma multi_compile_fragment _ _RENDER_PASS_ENABLED // ------------------------------------- // Unity defined keywords #pragma multi_compile _ LIGHTMAP_SHADOW_MIXING #pragma multi_compile _ SHADOWS_SHADOWMASK #pragma multi_compile _ DIRLIGHTMAP_COMBINED #pragma multi_compile _ LIGHTMAP_ON #pragma multi_compile _ DYNAMICLIGHTMAP_ON #pragma multi_compile_fragment _ _GBUFFER_NORMALS_OCT //-------------------------------------- // GPU Instancing #pragma multi_compile_instancing #pragma instancing_options renderinglayer #pragma multi_compile _ DOTS_INSTANCING_ON #pragma vertex LitGBufferPassVertex #pragma fragment LitGBufferPassFragment #include "Packages/com.unity.render-pipelines.universal/Shaders/LitInput.hlsl" #include "Packages/com.unity.render-pipelines.universal/Shaders/LitGBufferPass.hlsl" ENDHLSL } Pass { Name "DepthOnly" Tags{"LightMode" = "DepthOnly"} ZWrite On ColorMask 0 Cull[_Cull] HLSLPROGRAM #pragma exclude_renderers gles gles3 glcore #pragma target 4.5 #pragma vertex DepthOnlyVertex #pragma fragment DepthOnlyFragment // ------------------------------------- // Material Keywords #pragma shader_feature_local_fragment _ALPHATEST_ON #pragma shader_feature_local_fragment _SMOOTHNESS_TEXTURE_ALBEDO_CHANNEL_A //-------------------------------------- // GPU Instancing #pragma multi_compile_instancing #pragma multi_compile _ DOTS_INSTANCING_ON #include "Packages/com.unity.render-pipelines.universal/Shaders/LitInput.hlsl" #include "Packages/com.unity.render-pipelines.universal/Shaders/DepthOnlyPass.hlsl" ENDHLSL } // This pass is used when drawing to a _CameraNormalsTexture texture Pass { Name "DepthNormals" Tags{"LightMode" = "DepthNormals"} ZWrite On Cull[_Cull] HLSLPROGRAM #pragma exclude_renderers gles gles3 glcore #pragma target 4.5 #pragma vertex DepthNormalsVertex #pragma fragment DepthNormalsFragment // ------------------------------------- // Material Keywords #pragma shader_feature_local _NORMALMAP #pragma shader_feature_local _PARALLAXMAP #pragma shader_feature_local _ _DETAIL_MULX2 _DETAIL_SCALED #pragma shader_feature_local_fragment _ALPHATEST_ON #pragma shader_feature_local_fragment _SMOOTHNESS_TEXTURE_ALBEDO_CHANNEL_A //-------------------------------------- // GPU Instancing #pragma multi_compile_instancing #pragma multi_compile _ DOTS_INSTANCING_ON #include "Packages/com.unity.render-pipelines.universal/Shaders/LitInput.hlsl" #include "Packages/com.unity.render-pipelines.universal/Shaders/LitDepthNormalsPass.hlsl" ENDHLSL } // This pass it not used during regular rendering, only for lightmap baking. Pass { Name "Meta" Tags{"LightMode" = "Meta"} Cull Off HLSLPROGRAM #pragma exclude_renderers gles gles3 glcore #pragma target 4.5 #pragma vertex UniversalVertexMeta #pragma fragment UniversalFragmentMetaLit #pragma shader_feature EDITOR_VISUALIZATION #pragma shader_feature_local_fragment _SPECULAR_SETUP #pragma shader_feature_local_fragment _EMISSION #pragma shader_feature_local_fragment _METALLICSPECGLOSSMAP #pragma shader_feature_local_fragment _ALPHATEST_ON #pragma shader_feature_local_fragment _ _SMOOTHNESS_TEXTURE_ALBEDO_CHANNEL_A #pragma shader_feature_local _ _DETAIL_MULX2 _DETAIL_SCALED #pragma shader_feature_local_fragment _SPECGLOSSMAP #include "Packages/com.unity.render-pipelines.universal/Shaders/LitInput.hlsl" #include "Packages/com.unity.render-pipelines.universal/Shaders/LitMetaPass.hlsl" ENDHLSL } Pass { Name "Universal2D" Tags{ "LightMode" = "Universal2D" } Blend[_SrcBlend][_DstBlend] ZWrite[_ZWrite] Cull[_Cull] HLSLPROGRAM #pragma exclude_renderers gles gles3 glcore #pragma target 4.5 #pragma vertex vert #pragma fragment frag #pragma shader_feature_local_fragment _ALPHATEST_ON #pragma shader_feature_local_fragment _ALPHAPREMULTIPLY_ON #include "Packages/com.unity.render-pipelines.universal/Shaders/LitInput.hlsl" #include "Packages/com.unity.render-pipelines.universal/Shaders/Utils/Universal2D.hlsl" ENDHLSL }
ShadowCaster shadowcaster pass的结构非常简单,它写入深度、并且进行深度测试,但不写入颜色,使用了一个宏来进行直接光和点光之间的转换,然后就是两个函数,这两个函数在ShadowCaster.hlsl中。
代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 #ifndef UNIVERSAL_SHADOW_CASTER_PASS_INCLUDED #define UNIVERSAL_SHADOW_CASTER_PASS_INCLUDED #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Shadows.hlsl" // Shadow Casting Light geometric parameters. These variables are used when applying the shadow Normal Bias and are set by UnityEngine.Rendering.Universal.ShadowUtils.SetupShadowCasterConstantBuffer in com.unity.render-pipelines.universal/Runtime/ShadowUtils.cs // For Directional lights, _LightDirection is used when applying shadow Normal Bias. // For Spot lights and Point lights, _LightPosition is used to compute the actual light direction because it is different at each shadow caster geometry vertex. float3 _LightDirection; float3 _LightPosition; struct Attributes { float4 positionOS : POSITION; float3 normalOS : NORMAL; float2 texcoord : TEXCOORD0; UNITY_VERTEX_INPUT_INSTANCE_ID }; struct Varyings { float2 uv : TEXCOORD0; float4 positionCS : SV_POSITION; }; float4 GetShadowPositionHClip(Attributes input) { float3 positionWS = TransformObjectToWorld(input.positionOS.xyz); float3 normalWS = TransformObjectToWorldNormal(input.normalOS); #if _CASTING_PUNCTUAL_LIGHT_SHADOW float3 lightDirectionWS = normalize(_LightPosition - positionWS); #else float3 lightDirectionWS = _LightDirection; #endif float4 positionCS = TransformWorldToHClip(ApplyShadowBias(positionWS, normalWS, lightDirectionWS)); #if UNITY_REVERSED_Z positionCS.z = min(positionCS.z, UNITY_NEAR_CLIP_VALUE); #else positionCS.z = max(positionCS.z, UNITY_NEAR_CLIP_VALUE); #endif return positionCS; } Varyings ShadowPassVertex(Attributes input) { Varyings output; UNITY_SETUP_INSTANCE_ID(input); output.uv = TRANSFORM_TEX(input.texcoord, _BaseMap); output.positionCS = GetShadowPositionHClip(input); return output; } half4 ShadowPassFragment(Varyings input) : SV_TARGET { Alpha(SampleAlbedoAlpha(input.uv, TEXTURE2D_ARGS(_BaseMap, sampler_BaseMap)).a, _BaseColor, _Cutoff); return 0; } #endif
这个函数写的就是一个普通的unlit函数做的事情,但是在vertex中用GetShadowPositionHClip()来替换了原本的TransformXXXToHClip(),而这个函数它调用了一个叫AppyShadowBias()的函数来计算裁剪空间坐标,这个函数在Shadows.hlsl中。
代码 1 2 3 4 5 6 7 8 9 10 11 12 float3 ApplyShadowBias(float3 positionWS, float3 normalWS, float3 lightDirection) { float invNdotL = 1.0 - saturate(dot(lightDirection, normalWS)); float scale = invNdotL * _ShadowBias.y; // normal bias is negative since we want to apply an inset normal offset positionWS = lightDirection * _ShadowBias.xxx + positionWS; positionWS = normalWS * scale.xxx + positionWS; return positionWS; }
出人意料地,这个函数并没有再调用别的函数,它的作用是将原本片元的positionWS沿着lightingDirection和normalWS分别偏移了一段距离,大家可以自己试验一下加和不加这个偏移造成的结果有什么差距。
而片元着色器中用到的Alpha()和SampleAlbedoAlpha()函数在SurfaceInput.hlsl中。
代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 half Alpha(half albedoAlpha, half4 color, half cutoff) { #if !defined(_SMOOTHNESS_TEXTURE_ALBEDO_CHANNEL_A) && !defined(_GLOSSINESS_FROM_BASE_ALPHA) half alpha = albedoAlpha * color.a; #else half alpha = color.a; #endif #if defined(_ALPHATEST_ON) clip(alpha - cutoff); #endif return alpha; } half4 SampleAlbedoAlpha(float2 uv, TEXTURE2D_PARAM(albedoAlphaMap, sampler_albedoAlphaMap)) { return half4(SAMPLE_TEXTURE2D(albedoAlphaMap, sampler_albedoAlphaMap, uv)); } half3 SampleNormal(float2 uv, TEXTURE2D_PARAM(bumpMap, sampler_bumpMap), half scale = half(1.0)) { #ifdef _NORMALMAP half4 n = SAMPLE_TEXTURE2D(bumpMap, sampler_bumpMap, uv); #if BUMP_SCALE_NOT_SUPPORTED return UnpackNormal(n); #else return UnpackNormalScale(n, scale); #endif #else return half3(0.0h, 0.0h, 1.0h); #endif } half3 SampleEmission(float2 uv, half3 emissionColor, TEXTURE2D_PARAM(emissionMap, sampler_emissionMap)) { #ifndef _EMISSION return 0; #else return SAMPLE_TEXTURE2D(emissionMap, sampler_emissionMap, uv).rgb * emissionColor; #endif }
这个Alpha()函数很容易理解,它就是在用color的a通道去做一个AlphaTest,clip掉一定阈值下的颜色,而SampleAlbedoAlpha()也就是一个普通的采样2D贴图来得到alpha通道。
自定义ShadowCaster 可以看出,整个shadowmap的写入并没有开放给我们。shadowcaster其实只完成了这样一件事情——做一个带偏移的世界坐标to齐次裁剪坐标的变换,然后做一个alphatest,其它都没有开放,但其实这些实现大部分的设计思路也足够了。
通常需要做的自定义阴影投影多半也就是改变一下阴影的形状、颜色和clip等等,显然,shadowCaster是不支持改变阴影颜色的,形状的改变可以依靠顶点着色器里面进行位移来实现,不过也比较复杂,完全不如在读取阴影的时候直接进行处理。
因此大部分我们需要做的也就是clip一下吧。
DepthNormals 首先依然是查看DepthNormalsPass.hlsl的代码。
代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 #ifndef UNIVERSAL_DEPTH_NORMALS_PASS_INCLUDED #define UNIVERSAL_DEPTH_NORMALS_PASS_INCLUDED #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" struct Attributes { float4 positionOS : POSITION; float4 tangentOS : TANGENT; float2 texcoord : TEXCOORD0; float3 normal : NORMAL; UNITY_VERTEX_INPUT_INSTANCE_ID }; struct Varyings { float4 positionCS : SV_POSITION; float2 uv : TEXCOORD1; float3 normalWS : TEXCOORD2; UNITY_VERTEX_INPUT_INSTANCE_ID UNITY_VERTEX_OUTPUT_STEREO }; Varyings DepthNormalsVertex(Attributes input) { Varyings output = (Varyings)0; UNITY_SETUP_INSTANCE_ID(input); UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(output); output.uv = TRANSFORM_TEX(input.texcoord, _BaseMap); output.positionCS = TransformObjectToHClip(input.positionOS.xyz); VertexNormalInputs normalInput = GetVertexNormalInputs(input.normal, input.tangentOS); output.normalWS = NormalizeNormalPerVertex(normalInput.normalWS); return output; } half4 DepthNormalsFragment(Varyings input) : SV_TARGET { UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(input); Alpha(SampleAlbedoAlpha(input.uv, TEXTURE2D_ARGS(_BaseMap, sampler_BaseMap)).a, _BaseColor, _Cutoff); #if defined(_GBUFFER_NORMALS_OCT) float3 normalWS = normalize(input.normalWS); float2 octNormalWS = PackNormalOctQuadEncode(normalWS); // values between [-1, +1], must use fp32 on some platforms. float2 remappedOctNormalWS = saturate(octNormalWS * 0.5 + 0.5); // values between [ 0, 1] half3 packedNormalWS = PackFloat2To888(remappedOctNormalWS); // values between [ 0, 1] return half4(packedNormalWS, 0.0); #else float3 normalWS = NormalizeNormalPerPixel(input.normalWS); return half4(normalWS, 0.0); #endif } #endif
代码依然很短,顶点着色器中只是做了个很正常的变换,并且根据设置的渲染质量来判断是否需要归一化法线(NormalizeNormalPerVertex()函数,这个函数没找到在哪,谷歌了一下有什么用……)
片元着色器中,首先依然是进行一个alphatest,然后直接就输出法线了,和shadowcaster一下,具体的映射方式依然没有开放给我们,我们能进行操作的只有改变法线的方向以及进行clip的部分。
值得一提的是,很多朋友都会在发现在使用DepthOnly这个pass的时候没法正确地向深度图写入深度,而对比下来我们会发现这两个pass的函数写得没有任何区别,只是depthNormals多了一计算法线的过程,而DepthOnlyFragment()直接在alphatest之后输出了0.
于是回看Lit.shader,发现这两个Pass唯一的区别只有DepthNormals并没有开启ColorMask 0,而且DepthNormalsFragment()如果同样只输出0的话可以发现,它确实是会输出颜色的。
URP,你做的好啊,我完全无法参透这个里面的逻辑。
结语 到这里其实我们已经可以在URP的框架上进行改动来创造一个属于自己的前向shader,并且尽量使用URP的现有代码来减少代码量了,如果说是给这几天的学习交个报告的话,那就用URP框架来写一个完整的NPR Shader吧。