TANotes

💡 学点儿丨URP Shader编辑指北[2] - Unlit Shader详细拆解

by ERIN.Z, 2022-05-31


靠北,这篇写了超级长结果被草稿覆盖了.....我是真的很emo😇😇😇😇😇

就极精简地复述一遍....好难过😭😭

文件结构

URP管线的Shader都位于Packages/Universal RP/Shaders目录,是Shader的主要框架和相关的Pass。通用的渲染方法函数一般位于Packages/Universal RP/Shaders Library目录,这是一个为SRP提供的函数包,可以为我们省去许多重复性的工作。另外涉及到Material面板的文件位于Packages/Universal RP/Editor/Shader GUI. Directory 本文代码块的第一行表示代码所在的目录位置,就不多重复了~ 有关拓展阅读:[URP]内置shader解析笔记_1

Properties

[Universal RP/Shaders/Unlit.shader]
    Properties
    {
        [MainTexture] _BaseMap("Texture", 2D) = "white" {}
        [MainColor] _BaseColor("Color", Color) = (1, 1, 1, 1)
        _Cutoff("AlphaCutout", Range(0.0, 1.0)) = 0.5

        // BlendMode
        _Surface("__surface", Float) = 0.0
        _Blend("__mode", Float) = 0.0
        _Cull("__cull", Float) = 2.0
        [ToggleUI] _AlphaClip("__clip", Float) = 0.0
        [HideInInspector] _BlendOp("__blendop", Float) = 0.0
        [HideInInspector] _SrcBlend("__src", Float) = 1.0
        [HideInInspector] _DstBlend("__dst", Float) = 0.0
        [HideInInspector] _ZWrite("__zw", Float) = 1.0

        // Editmode props
        _QueueOffset("Queue offset", Float) = 0.0

        // ObsoleteProperties
        [HideInInspector] _MainTex("BaseMap", 2D) = "white" {}
        [HideInInspector] _Color("Base Color", Color) = (0.5, 0.5, 0.5, 1)
        [HideInInspector] _SampleGI("SampleGI", float) = 0.0 // needed from bakedlit
    }

URP的Shader都使用了Custom Editor,可以看出Properties的声明和面板样式并不相同。其具体样式可见最后一行的索引:

[Universal RP/Shaders/Unlit.shader]
    CustomEditor "UnityEditor.Rendering.Universal.ShaderGUI.UnlitShader"

这个文件位于Universal RP/Editor/ShaderGUI/ShadersUnityEditor.Rendering.Universal.ShaderGUI是它所在的命名空间。这些脚本文件都继承于BaseShaderGUI类。在BaseShaderGUI中,定义了有关属性的面板和更新关键字的方法。

ShaderGUI

GUI BaseShaderGUI继承于ShaderGUI,需要使用UnityEditor命名空间。 它包含了六个代码块:

  • EnumsAndClasses 在这个代码块中,定义了各个选项有关的枚举量,例如SurfaceType,BlendMode等。并定义了一个类Styles。 在Styles中,包含了以static readonly stings[]的形式记录了上述的枚举关键字,和以static readonly GUIContent的方式记录了GUI文本和鼠标悬停时的提示信息。例如:
    [Universal RP/Editor/ShaderGUI/BaseShaderGUI.cs]
            public static readonly GUIContent SurfaceOptions =
                EditorGUIUtility.TrTextContent("Surface Options", 
                "Controls how URP Renders the material on screen.");
  • Variables 这个代码块主要是对变量的声明。 首先是一个MaterialEditor类的面板,是承载后续所有内容的主体。 然后是15个MaterialProperty的变量,既包含渲染器的处理方式(如Blendmode、alphaclip等),也包含基础的表面输入(如basemap、basecolor等)。 布尔量m_FirstTimeApply用于在第一次访问时运行初始化。 接下来MaterialHeaderScopeList用于绘制面板,这里在构造时就声明了其默认展开方法,其中Expandable变量在声明时就是以二进制为单位的,这里就可以通过位运算表示是否展开。

    [Universal RP/Editor/ShaderGUI/BaseShaderGUI.cs]
        // By default, everything is expanded, except advanced
        readonly MaterialHeaderScopeList m_MaterialScopeList = new MaterialHeaderScopeList(uint.MaxValue & ~(uint)Expandable.Advanced);
  • General Functions 既然是对材质面板的定义,那就先从OnGUI()开始看。

    [Universal RP/Editor/ShaderGUI/BaseShaderGUI.cs]
        public override void OnGUI(MaterialEditor materialEditorIn, MaterialProperty[] properties)
        {
            if (materialEditorIn == null)
                throw new ArgumentNullException("materialEditorIn");
    
            materialEditor = materialEditorIn;
            Material material = materialEditor.target as Material;
    
            FindProperties(properties);   // MaterialProperties can be animated so we do not cache them but fetch them every event to ensure animated values are updated correctly
    
            // Make sure that needed setup (ie keywords/renderqueue) are set up if we're switching some existing
            // material to a universal shader.
            if (m_FirstTimeApply)
            {
                OnOpenGUI(material, materialEditorIn);
                m_FirstTimeApply = false;
            }
    
            ShaderPropertiesGUI(material);
        }

    ShaderGUI的OnGUI方法需要两个输入:面板MaterialEditor,和属性们MaterialProperty[],如果找不到输入会抛出报错,如果输入正常,则会定义目标材质。 为了呈现才材质的动态效果,每一帧都会重新获取材质属性:

    [Universal RP/Editor/ShaderGUI/BaseShaderGUI.cs]
        public virtual void FindProperties(MaterialProperty[] properties)
        {
            var material = materialEditor?.target as Material;
            if (material == null)
                return;
    
            surfaceTypeProp = FindProperty(Property.SurfaceType, properties, false);
            blendModeProp = FindProperty(Property.BlendMode, properties, false);
            cullingProp = FindProperty(Property.CullMode, properties, false);
            zwriteProp = FindProperty(Property.ZWriteControl, properties, false);
            ztestProp = FindProperty(Property.ZTest, properties, false);
            alphaClipProp = FindProperty(Property.AlphaClip, properties, false);
    
            // ShaderGraph Lit and Unlit Subtargets only
            castShadowsProp = FindProperty(Property.CastShadows, properties, false);
            queueControlProp = FindProperty(Property.QueueControl, properties, false);
    
            // ShaderGraph Lit, and Lit.shader
            receiveShadowsProp = FindProperty(Property.ReceiveShadows, properties, false);
    
            // The following are not mandatory for shadergraphs (it's up to the user to add them to their graph)
            alphaCutoffProp = FindProperty("_Cutoff", properties, false);
            baseMapProp = FindProperty("_BaseMap", properties, false);
            baseColorProp = FindProperty("_BaseColor", properties, false);
            emissionMapProp = FindProperty(Property.EmissionMap, properties, false);
            emissionColorProp = FindProperty(Property.EmissionColor, properties, false);
            queueOffsetProp = FindProperty(Property.QueueOffset, properties, false);
        }

    属性的赋值使用的是ShaderGUI的FindProperty()方法,会根据名称从MaterialProperty[]中查找属性值,若未找到则返回null;第三个变量是是否强制要求变量存在,如果设置为true,没找到变量时会抛出异常。 注意,URP中统一使用的变量名是_BaseMap_BaseColor如果希望管线自动识别,最好也使用这个变量名。

前文提到,使用布尔值m_FirstTimeApply来记录是否初始化:

public virtual void OnOpenGUI(Material material, MaterialEditor materialEditor)
        {
            var filter = (Expandable)materialFilter;

            // Generate the foldouts
            if (filter.HasFlag(Expandable.SurfaceOptions))
                m_MaterialScopeList.RegisterHeaderScope(Styles.SurfaceOptions, (uint)Expandable.SurfaceOptions, DrawSurfaceOptions);

            if (filter.HasFlag(Expandable.SurfaceInputs))
                m_MaterialScopeList.RegisterHeaderScope(Styles.SurfaceInputs, (uint)Expandable.SurfaceInputs, DrawSurfaceInputs);

            if (filter.HasFlag(Expandable.Details))
                FillAdditionalFoldouts(m_MaterialScopeList);

            if (filter.HasFlag(Expandable.Advanced))
                m_MaterialScopeList.RegisterHeaderScope(Styles.AdvancedLabel, (uint)Expandable.Advanced, DrawAdvancedOptions);
        }

初始化的工作主要是向MaterialHeaderScopeList写入信息,当展开标题栏时,会调用Action的方法。

[UnityEditor.Rendering]
public void RegisterHeaderScope<TEnum>(GUIContent title, TEnum expandable, Action<Material> action)
    where TEnum : struct, IConvertible

由于每个Header自己根据展开与否绘制自己的属性,统一的绘图指令就只要绘制这些Header就可以了,所以OnGUI中最后一个绘制函数非常简单:

[Universal RP/Editor/ShaderGUI/BaseShaderGUI.cs]
        public void ShaderPropertiesGUI(Material material)
        {
            m_MaterialScopeList.DrawHeaders(materialEditor, material);
        }

在这个代码块中还包含一个检测属性是否更改的方法,调用ShaderGUI的ValidateMaterial()方法:

[Universal RP/Editor/ShaderGUI/BaseShaderGUI.cs]
[Obsolete("MaterialChanged has been renamed ValidateMaterial", false)]
        public virtual void MaterialChanged(Material material)
        {
            ValidateMaterial(material);
        }
  • Material Data Functions 这个代码块中声明了一些方法函数,主要用于写入材质的关键字信息。对于Unlit Shader而言,其实只用到了其中的一小部分。

前文提到ValidateMaterial()方法,用于保证Shader和Pass的关键字在面板更改时一并更改。这个方法在BaseShaderGUI没定义,UnlitShader.cs子类中对它进行了override:

[Universal RP/Editor/ShaderGUI/shaders/UnlitShader.cs]
        // material changed check
        public override void ValidateMaterial(Material material)
        {
            SetMaterialKeywords(material);
        }

这里SetMaterialKeywords()就来自Material Data Functions代码块:

[Universal RP/Editor/ShaderGUI/BaseShaderGUI.cs]
 // this is the function used by Lit.shader, Unlit.shader GUIs
        public static void SetMaterialKeywords(Material material, Action<Material> shadingModelFunc = null, Action<Material> shaderFunc = null)
        {
            UpdateMaterialSurfaceOptions(material, automaticRenderQueue: true);

            // Setup double sided GI based on Cull state
            if (material.HasProperty(Property.CullMode))
                material.doubleSidedGI = (RenderFace)material.GetFloat(Property.CullMode) != RenderFace.Front;

            // Temporary fix for lightmapping. TODO: to be replaced with attribute tag.
            if (material.HasProperty("_MainTex"))
            {
                material.SetTexture("_MainTex", material.GetTexture("_BaseMap"));
                material.SetTextureScale("_MainTex", material.GetTextureScale("_BaseMap"));
                material.SetTextureOffset("_MainTex", material.GetTextureOffset("_BaseMap"));
            }
            if (material.HasProperty("_Color"))
                material.SetColor("_Color", material.GetColor("_BaseColor"));

            // Emission
            if (material.HasProperty(Property.EmissionColor))
                MaterialEditor.FixupEmissiveFlag(material);

            bool shouldEmissionBeEnabled =
                (material.globalIlluminationFlags & MaterialGlobalIlluminationFlags.EmissiveIsBlack) == 0;

            // Not sure what this is used for, I don't see this property declared by any Unity shader in our repo...
            // I'm guessing it is some kind of legacy material upgrade support thing?  Or maybe just dead code now...
            if (material.HasProperty("_EmissionEnabled") && !shouldEmissionBeEnabled)
                shouldEmissionBeEnabled = material.GetFloat("_EmissionEnabled") >= 0.5f;

            CoreUtils.SetKeyword(material, ShaderKeywordStrings._EMISSION, shouldEmissionBeEnabled);

            // Normal Map
            if (material.HasProperty("_BumpMap"))
                CoreUtils.SetKeyword(material, ShaderKeywordStrings._NORMALMAP, material.GetTexture("_BumpMap"));

            // Shader specific keyword functions
            shadingModelFunc?.Invoke(material);
            shaderFunc?.Invoke(material);
        }

前文提到URP使用的变量名是_BaseMap和_BaseColor,为了兼容老版本的_MainTex和_Color,这里其实是将面板上的信息进行了转移(x)。 (还有程序员自己都不知道为什么存在的变量,笑死

  • Drawing Functions 主要就是各种属性的详细绘制方法,于上述MaterialHeaderScopeList或子类来调用,就不详细展开了。
  • Helper Functions 一些压缩面板用的工具函数,也不详细展开了。

UnlitShaderGUI

UnlitShaderGUI基本上就是继承自BaseMapGUI。AssignNewShaderToMaterial()使从别的Shader切换至UnlitShader时可以自动识别一些基础变量。

using System;
using UnityEngine;

namespace UnityEditor.Rendering.Universal.ShaderGUI
{
    internal class UnlitShader : BaseShaderGUI
    {
        // material changed check
        public override void ValidateMaterial(Material material)
        {
            SetMaterialKeywords(material);
        }

        // material main surface options
        public override void DrawSurfaceOptions(Material material)
        {
            if (material == null)
                throw new ArgumentNullException("material");

            // Use default labelWidth
            EditorGUIUtility.labelWidth = 0f;

            base.DrawSurfaceOptions(material);
        }

        // material main surface inputs
        public override void DrawSurfaceInputs(Material material)
        {
            base.DrawSurfaceInputs(material);
            DrawTileOffset(materialEditor, baseMapProp);
        }

        public override void AssignNewShaderToMaterial(Material material, Shader oldShader, Shader newShader)
        {
            if (material == null)
                throw new ArgumentNullException("material");

            // _Emission property is lost after assigning Standard shader to the material
            // thus transfer it before assigning the new shader
            if (material.HasProperty("_Emission"))
            {
                material.SetColor("_EmissionColor", material.GetColor("_Emission"));
            }

            base.AssignNewShaderToMaterial(material, oldShader, newShader);

            if (oldShader == null || !oldShader.name.Contains("Legacy Shaders/"))
            {
                SetupMaterialBlendMode(material);
                return;
            }

            SurfaceType surfaceType = SurfaceType.Opaque;
            BlendMode blendMode = BlendMode.Alpha;
            if (oldShader.name.Contains("/Transparent/Cutout/"))
            {
                surfaceType = SurfaceType.Opaque;
                material.SetFloat("_AlphaClip", 1);
            }
            else if (oldShader.name.Contains("/Transparent/"))
            {
                // NOTE: legacy shaders did not provide physically based transparency
                // therefore Fade mode
                surfaceType = SurfaceType.Transparent;
                blendMode = BlendMode.Alpha;
            }
            material.SetFloat("_Blend", (float)blendMode);

            material.SetFloat("_Surface", (float)surfaceType);
            if (surfaceType == SurfaceType.Opaque)
            {
                material.DisableKeyword("_SURFACE_TYPE_TRANSPARENT");
            }
            else
            {
                material.EnableKeyword("_SURFACE_TYPE_TRANSPARENT");
            }
        }
    }
}

SubShader

面板说了一大堆,让我们回到Shader本身!Unlit有两个SubShader,面向不同的TargetShaderModel。2.0支持所有平台,4.5只支持DirectX11+,Unlit的两个SubShader差别不大,本篇主要关注4.5版本。

  • Shader Model 4.5
    SubShader
    {
        Tags {... "ShaderModel"="4.5"}
        ...
        Pass
        {
            Name "Unlit"
            HLSLPROGRAM
            #pragma exclude_renderers gles gles3 glcore
            #pragma target 4.5
            ...
            ENDHLSL
        }
        ...
     }
  • Shader Model 2.0

    SubShader
    {
        Tags {... "ShaderModel"="2.0"}
        ...
        Pass
        {
            Name "Unlit"
            HLSLPROGRAM
            #pragma only_renderers gles gles3 glcore d3d11
            #pragma target 2.0
            ...
            ENDHLSL
        }
        ...
     }

    Pass

    URP只支持一个渲染Pass,对于UnlitShader就是"Unlit"。但还另外存在三个Pass,在渲染过程中的特定阶段会调用,他们是"DepthOnly""DepthNormalsOnly"(这两个顾名思义),和 "Meta"(烘焙lightmap时调用)。 今天我们只关注"Unlit"这个Pass。

    宏编译与参数设置

    [Universal RP/Shaders/Unlit.shader]
    Pass
        {
            Name "Unlit"
    
            HLSLPROGRAM
            #pragma exclude_renderers gles gles3 glcore
            #pragma target 4.5
    
            #pragma shader_feature_local_fragment _SURFACE_TYPE_TRANSPARENT
            #pragma shader_feature_local_fragment _ALPHATEST_ON
            #pragma shader_feature_local_fragment _ALPHAPREMULTIPLY_ON
    
            // -------------------------------------
            // Unity defined keywords
            #pragma multi_compile_fog
            #pragma multi_compile_instancing
            #pragma multi_compile _ DOTS_INSTANCING_ON
            #pragma multi_compile_fragment _ _SCREEN_SPACE_OCCLUSION
            #pragma multi_compile_fragment _ _DBUFFER_MRT1 _DBUFFER_MRT2 _DBUFFER_MRT3
            #pragma multi_compile _ DEBUG_DISPLAY
    
            #pragma vertex UnlitPassVertex
            #pragma fragment UnlitPassFragment
    
            #include "Packages/com.unity.render-pipelines.universal/Shaders/UnlitInput.hlsl"
            #include "Packages/com.unity.render-pipelines.universal/Shaders/UnlitForwardPass.hlsl"
            ENDHLSL
        }

    中间的一串#pragma可以是Shader根据不同关键字编译成不同的版本。 Unity shader_feature在最终版本中不包含未使用的着色器变体。所以shader_feature适用于在我们在编辑器中,选中材质,设置它使用的shader的宏,如果在程序中动态的去设置可能无效。而对于multi_compile,会把所有的变体都编译进程序里,所以适合需要在程序运行中动态改变状态的宏,适合全局设置 。 拓展阅读:unity shader 变种(多重编译 multi_compile)

在定义了顶点着色器和片元着色器之后........... 就是说是不是成熟的软件开发都很会拆文件啊.....一个Shader调用八百个文件......

[Universal RP/Shaders/UnlitInput.hlsl]
#ifndef UNIVERSAL_UNLIT_INPUT_INCLUDED
#define UNIVERSAL_UNLIT_INPUT_INCLUDED

#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/SurfaceInput.hlsl"

CBUFFER_START(UnityPerMaterial)
    float4 _BaseMap_ST;
    half4 _BaseColor;
    half _Cutoff;
    half _Surface;
CBUFFER_END

#ifdef UNITY_DOTS_INSTANCING_ENABLED
UNITY_DOTS_INSTANCING_START(MaterialPropertyMetadata)
    UNITY_DOTS_INSTANCED_PROP(float4, _BaseColor)
    UNITY_DOTS_INSTANCED_PROP(float , _Cutoff)
    UNITY_DOTS_INSTANCED_PROP(float , _Surface)
UNITY_DOTS_INSTANCING_END(MaterialPropertyMetadata)

#define _BaseColor          UNITY_ACCESS_DOTS_INSTANCED_PROP_FROM_MACRO(float4 , Metadata_BaseColor)
#define _Cutoff             UNITY_ACCESS_DOTS_INSTANCED_PROP_FROM_MACRO(float  , Metadata_Cutoff)
#define _Surface            UNITY_ACCESS_DOTS_INSTANCED_PROP_FROM_MACRO(float  , Metadata_Surface)
#endif

#endif

Input这里首先是为了SRP Batch将变量扔进CBUFFER,然后是一些有关DOTS的看不懂设置。本文有关DOTS和Instance的内容先都一律跳过,毕竟我是个看完DOTS定义就下班了的小废物。

[Universal RP/Shaders/UnlitForwardPass.hlsl]
#ifndef URP_UNLIT_FORWARD_PASS_INCLUDED
#define URP_UNLIT_FORWARD_PASS_INCLUDED

#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Unlit.hlsl"
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"

虽然Unlit完全不包含光照计算,include了Lighting库其实是为了间接includeDBuffer.hlslAmbientOcclusion.hlsl,至于Unlit.hlsl后面再说(又拆一个至于吗至于吗至于吗)。

终于到了Shader的主体部分。在URP中,Attributes和Varyings对应Built-In中的a2v和v2f,算是一致的命名规范。

[Universal RP/Shaders/UnlitForwardPass.hlsl]
struct Attributes
{
    float4 positionOS : POSITION;
    float2 uv : TEXCOORD0;

    #if defined(DEBUG_DISPLAY)
    float3 normalOS : NORMAL;
    float4 tangentOS : TANGENT;
    #endif

    UNITY_VERTEX_INPUT_INSTANCE_ID
};

struct Varyings
{
    float2 uv : TEXCOORD0;
    float fogCoord : TEXCOORD1;
    float4 positionCS : SV_POSITION;

    #if defined(DEBUG_DISPLAY)
    float3 positionWS : TEXCOORD2;
    float3 normalWS : TEXCOORD3;
    float3 viewDirWS : TEXCOORD4;
    #endif

    UNITY_VERTEX_INPUT_INSTANCE_ID
    UNITY_VERTEX_OUTPUT_STEREO
};

UnlitShader要做的实际上只是采样一个基础贴图,所以结构体中关键的参数其实只有坐标位置和uv。

顶点着色器

[Universal RP/Shaders/UnlitForwardPass.hlsl]
Varyings UnlitPassVertex(Attributes input)
{
    Varyings output = (Varyings)0;

    UNITY_SETUP_INSTANCE_ID(input);
    UNITY_TRANSFER_INSTANCE_ID(input, output);
    UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(output);

    VertexPositionInputs vertexInput = GetVertexPositionInputs(input.positionOS.xyz);

    output.positionCS = vertexInput.positionCS;
    output.uv = TRANSFORM_TEX(input.uv, _BaseMap);
    #if defined(_FOG_FRAGMENT)
    output.fogCoord = vertexInput.positionVS.z;
    #else
    output.fogCoord = ComputeFogFactor(vertexInput.positionCS.z);
    #endif

    #if defined(DEBUG_DISPLAY)
    // normalWS and tangentWS already normalize.
    // this is required to avoid skewing the direction during interpolation
    // also required for per-vertex lighting and SH evaluation
    VertexNormalInputs normalInput = GetVertexNormalInputs(input.normalOS, input.tangentOS);
    half3 viewDirWS = GetWorldSpaceViewDir(vertexInput.positionWS);

    // already normalized from normal transform to WS.
    output.positionWS = vertexInput.positionWS;
    output.normalWS = normalInput.normalWS;
    output.viewDirWS = viewDirWS;
    #endif

    return output;
}

忽略instance的部分,首先是坐标空间的转换。

[Universal RP/Shaders/UnlitForwardPass.hlsl]
VertexPositionInputs vertexInput = GetVertexPositionInputs(input.positionOS.xyz);
output.positionCS = vertexInput.positionCS;

这个方法其实一次性计算了OS到CSVSWSNDC的所有坐标,后面计算雾效、debug时也用到了CS以外的数据。方法位于ShaderLibrary的ShaderVariablesFunctions.hlsl,后面的很多工具函数都在这个文件之中。

[Universal RP/ShaderLibrary/ShaderVariablesFunctions.hlsl]
VertexPositionInputs GetVertexPositionInputs(float3 positionOS)
{
    VertexPositionInputs input;
    input.positionWS = TransformObjectToWorld(positionOS);
    input.positionVS = TransformWorldToView(input.positionWS);
    input.positionCS = TransformWorldToHClip(input.positionWS);

    float4 ndc = input.positionCS * 0.5f;
    input.positionNDC.xy = float2(ndc.x, ndc.y * _ProjectionParams.x) + ndc.w;
    input.positionNDC.zw = input.positionCS.zw;

    return input;
}

uv的变换跟Built-In是一样的:

[Universal RP/Shaders/UnlitForwardPass.hlsl]
    output.uv = TRANSFORM_TEX(input.uv, _BaseMap);

雾坐标的计算可以分别在顶点/片元着色器中进行,需要传入深度值z坐标。如果逐顶点,用的是CS的z坐标,如果逐片元则传入的是VS的z坐标。

[Universal RP/ShaderLibrary/ShaderVariablesFunctions.hlsl]
real ComputeFogFactor(float zPositionCS)
{
    float clipZ_0Far = UNITY_Z_0_FAR_FROM_CLIPSPACE(zPositionCS);
    return ComputeFogFactorZ0ToFar(clipZ_0Far);
}
real ComputeFogFactorZ0ToFar(float z)
{
    #if defined(FOG_LINEAR)
    // factor = (end-z)/(end-start) = z * (-1/(end-start)) + (end/(end-start))
    float fogFactor = saturate(z * unity_FogParams.z + unity_FogParams.w);
    return real(fogFactor);
    #elif defined(FOG_EXP) || defined(FOG_EXP2)
    // factor = exp(-(density*z)^2)
    // -density * z computed at vertex
    return real(unity_FogParams.x * z);
    #else
        return real(0.0);
    #endif
}

real是一个定义于SRP Core的Common.hlsl的宏,对于支持half的平台,real就是half,否则就是float。 UNITY_Z_0_FAR_FROM_CLIPSPACE这个宏定义于Universal RP/ShaderLibrary/core.hlsl。 有关深度值的拓展阅读:深入URP之Shader篇10: 深度值专题(1)

最后,虽然debugview不是我们的重点,但因为涉及到法线视角的转换,也稍微看一下:

[Universal RP/Shaders/UnlitForwardPass.hlsl]
    #if defined(DEBUG_DISPLAY)
    // normalWS and tangentWS already normalize.
    // this is required to avoid skewing the direction during interpolation
    // also required for per-vertex lighting and SH evaluation
    VertexNormalInputs normalInput = GetVertexNormalInputs(input.normalOS, input.tangentOS);
    half3 viewDirWS = GetWorldSpaceViewDir(vertexInput.positionWS);

    // already normalized from normal transform to WS.
    output.positionWS = vertexInput.positionWS;
    output.normalWS = normalInput.normalWS;
    output.viewDirWS = viewDirWS;
    #endif
[Universal RP/ShaderLibrary/ShaderVariablesFunctions.hlsl]
VertexNormalInputs GetVertexNormalInputs(float3 normalOS, float4 tangentOS)
{
    VertexNormalInputs tbn;

    // mikkts space compliant. only normalize when extracting normal at frag.
    real sign = real(tangentOS.w) * GetOddNegativeScale();
    tbn.normalWS = TransformObjectToWorldNormal(normalOS);
    tbn.tangentWS = real3(TransformObjectToWorldDir(tangentOS.xyz));
    tbn.bitangentWS = real3(cross(tbn.normalWS, float3(tbn.tangentWS))) * sign;
    return tbn;
}
// Computes the world space view direction (pointing towards the viewer).
float3 GetWorldSpaceViewDir(float3 positionWS)
{
    if (IsPerspectiveProjection())
    {
        // Perspective
        return GetCurrentViewPosition() - positionWS;
    }
    else
    {
        // Orthographic
        return -GetViewForwardDir();
    }
}

片元着色器

[Universal RP/Shaders/UnlitForwardPass.hlsl]
half4 UnlitPassFragment(Varyings input) : SV_Target
{
    UNITY_SETUP_INSTANCE_ID(input);
    UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(input);

    half2 uv = input.uv;
    half4 texColor = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, uv);
    half3 color = texColor.rgb * _BaseColor.rgb;
    half alpha = texColor.a * _BaseColor.a;

    AlphaDiscard(alpha, _Cutoff);

    #if defined(_ALPHAPREMULTIPLY_ON)
    color *= alpha;
    #endif

    InputData inputData;
    InitializeInputData(input, inputData);
    SETUP_DEBUG_TEXTURE_DATA(inputData, input.uv, _BaseMap);

#ifdef _DBUFFER
    ApplyDecalToBaseColor(input.positionCS, color);
#endif

    #if defined(_FOG_FRAGMENT)
        #if (defined(FOG_LINEAR) || defined(FOG_EXP) || defined(FOG_EXP2))
        float viewZ = -input.fogCoord;
        float nearToFarZ = max(viewZ - _ProjectionParams.y, 0);
        half fogFactor = ComputeFogFactorZ0ToFar(nearToFarZ);
        #else
        half fogFactor = 0;
        #endif
    #else
    half fogFactor = input.fogCoord;
    #endif
    half4 finalColor = UniversalFragmentUnlit(inputData, color, alpha);

#if defined(_SCREEN_SPACE_OCCLUSION) && !defined(_SURFACE_TYPE_TRANSPARENT)
    float2 normalizedScreenSpaceUV = GetNormalizedScreenSpaceUV(input.positionCS);
    AmbientOcclusionFactor aoFactor = GetScreenSpaceAmbientOcclusion(normalizedScreenSpaceUV);
    finalColor.rgb *= aoFactor.directAmbientOcclusion;
#endif

    finalColor.rgb = MixFog(finalColor.rgb, fogFactor);

    return finalColor;
}

有关纹理采样,SAMPLE_TEXTURE2D是一个定义在SRP Core shader library中的一个跨平台的宏,例如: dx11是

#define SAMPLE_TEXTURE2D(textureName, samplerName, coord2)                               textureName.Sample(samplerName, coord2)

GLES2是

#define SAMPLE_TEXTURE2D(textureName, samplerName, coord2) tex2D(textureName, coord2)

接下来大段是关于alpha的处理: 如果定义了_ALPHATEST_ON,则会使用_Cutoff对alpha进行clip:

[Universal RP/ShaderLibrary/ShaderVariablesFunctions.hlsl]
void AlphaDiscard(real alpha, real cutoff, real offset = real(0.0))
{
    #ifdef _ALPHATEST_ON
    if (IsAlphaDiscardEnabled())
        clip(alpha - cutoff + offset);
    #endif
}

如果定义了_ALPHAPREMULTIPLY_ON,则会对color预乘alpha。

DBUFFER存储Decal贴花的信息。如果物体开启了接受Decal,这里就会应用DBUFFER里的数据。

[Universal RP/Shaders/UnlitForwardPass.hlsl]
#ifdef _DBUFFER
    ApplyDecalToBaseColor(input.positionCS, color);
#endif
[Universal RP/ShaderLibrary/DBuffer.hlsl]
void ApplyDecalToBaseColor(float4 positionCS, inout half3 baseColor)
{
    FETCH_DBUFFER(DBuffer, _DBufferTexture, int2(positionCS.xy));

    DecalSurfaceData decalSurfaceData;
    DECODE_FROM_DBUFFER(DBuffer, decalSurfaceData);

    // using alpha compositing https://developer.nvidia.com/gpugems/GPUGems3/gpugems3_ch23.html, mean weight of 1 is neutral

    // Note: We only test weight (i.e decalSurfaceData.xxx.w is < 1.0) if it can save something
    baseColor.xyz = baseColor.xyz * decalSurfaceData.baseColor.w + decalSurfaceData.baseColor.xyz;

基础色完成后,进行雾坐标的计算。如果在顶点着色器中计算过了,这里就不用再算一遍了。注意在片元着色器中用的是View Space,所以要注意z的正负。

[Universal RP/Shaders/UnlitForwardPass.hlsl]
    #if defined(_FOG_FRAGMENT)
        #if (defined(FOG_LINEAR) || defined(FOG_EXP) || defined(FOG_EXP2))
        float viewZ = -input.fogCoord;
        float nearToFarZ = max(viewZ - _ProjectionParams.y, 0);
        half fogFactor = ComputeFogFactorZ0ToFar(nearToFarZ);
        #else
        half fogFactor = 0;
        #endif
    #else
    half fogFactor = input.fogCoord;
    #endif

前文提到UnlitForwardPass还include了一个Unlit.hlsl,唯一功能就是

[Universal RP/Shaders/UnlitForwardPass.hlsl]
    half4 finalColor = UniversalFragmentUnlit(inputData, color, alpha);

UniversalFragmentUnlit()方法有一个重载,将所有信息装进SurfaceData,再进行最终的计算。我觉得这里这么做是为了跟其他Shader计算PBR光照时保持一致的结构,Unlit其实没有做啥....

[Universal RP/ShaderLibrary/Unlit.hlsl]
#ifndef UNLIT_INCLUDED
#define UNLIT_INCLUDED

#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Debug/Debugging3D.hlsl"
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Input.hlsl"
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/SurfaceData.hlsl"

half4 UniversalFragmentUnlit(InputData inputData, SurfaceData surfaceData)
{
    half3 albedo = surfaceData.albedo;

    #if defined(DEBUG_DISPLAY)
    half4 debugColor;

    if (CanDebugOverrideOutputColor(inputData, surfaceData, debugColor))
    {
        return debugColor;
    }
    #endif

    half4 finalColor = half4(albedo + surfaceData.emission, surfaceData.alpha);

    return finalColor;
}

// Deprecated: Use the version which takes "SurfaceData" instead of passing all of these arguments.
half4 UniversalFragmentUnlit(InputData inputData, half3 color, half alpha)
{
    SurfaceData surfaceData;

    surfaceData.albedo = color;
    surfaceData.alpha = alpha;
    surfaceData.emission = 0;
    surfaceData.metallic = 0;
    surfaceData.occlusion = 1;
    surfaceData.smoothness = 1;
    surfaceData.specular = 0;
    surfaceData.clearCoatMask = 0;
    surfaceData.clearCoatSmoothness = 1;
    surfaceData.normalTS = half3(0, 0, 1);

    return UniversalFragmentUnlit(inputData, surfaceData);
}
#endif

回到UnlitForwardPass,对于Opaque的表面,计算SSAO:

[Universal RP/Shaders/UnlitForwardPass.hlsl]
#if defined(_SCREEN_SPACE_OCCLUSION) && !defined(_SURFACE_TYPE_TRANSPARENT)
    float2 normalizedScreenSpaceUV = GetNormalizedScreenSpaceUV(input.positionCS);
    AmbientOcclusionFactor aoFactor = GetScreenSpaceAmbientOcclusion(normalizedScreenSpaceUV);
    finalColor.rgb *= aoFactor.directAmbientOcclusion;
#endif

首先获取屏幕空间坐标:

[Universal RP/ShaderLibrary/ShaderVariablesFunctions.hlsl]
float2 GetNormalizedScreenSpaceUV(float2 positionCS)
{
    float2 normalizedScreenSpaceUV = positionCS.xy * rcp(GetScaledScreenParams().xy);
    TransformNormalizedScreenUV(normalizedScreenSpaceUV);
    return normalizedScreenSpaceUV;
}

然后采样SSAO,Unlit用的是directAmbientOcclusion,会对采样结果用_AmbientOcclusionParam.w做插值。

[Universal RP/ShaderLibrary/AmbientOcclusion.hlsl]
AmbientOcclusionFactor GetScreenSpaceAmbientOcclusion(float2 normalizedScreenSpaceUV)
{
    AmbientOcclusionFactor aoFactor;

    #if defined(_SCREEN_SPACE_OCCLUSION) && !defined(_SURFACE_TYPE_TRANSPARENT)
    float ssao = SampleAmbientOcclusion(normalizedScreenSpaceUV);

    aoFactor.indirectAmbientOcclusion = ssao;
    aoFactor.directAmbientOcclusion = lerp(half(1.0), ssao, _AmbientOcclusionParam.w);
    #else
    aoFactor.directAmbientOcclusion = 1;
    aoFactor.indirectAmbientOcclusion = 1;
    #endif

    #if defined(DEBUG_DISPLAY)
    switch(_DebugLightingMode)
    {
        case DEBUGLIGHTINGMODE_LIGHTING_WITHOUT_NORMAL_MAPS:
            aoFactor.directAmbientOcclusion = 0.5;
            aoFactor.indirectAmbientOcclusion = 0.5;
            break;

        case DEBUGLIGHTINGMODE_LIGHTING_WITH_NORMAL_MAPS:
            aoFactor.directAmbientOcclusion *= 0.5;
            aoFactor.indirectAmbientOcclusion *= 0.5;
            break;
    }
    #endif

    return aoFactor;
}
half SampleAmbientOcclusion(float2 normalizedScreenSpaceUV)
{
    float2 uv = UnityStereoTransformScreenSpaceTex(normalizedScreenSpaceUV);
    return half(SAMPLE_TEXTURE2D_X(_ScreenSpaceOcclusionTexture, sampler_ScreenSpaceOcclusionTexture, uv).x);
}

最最最最最后,对物体应用雾效。

[Universal RP/Shaders/UnlitForwardPass.hlsl]
    finalColor.rgb = MixFog(finalColor.rgb, fogFactor);

到这里其实就是个简单插值了,没啥可说的。

[Universal RP/ShaderLibrary/ShaderVariablesFunctions.hlsl]
half3 MixFog(half3 fragColor, half fogFactor)
{
    return MixFogColor(fragColor, unity_FogColor.rgb, fogFactor);
}
half3 MixFogColor(half3 fragColor, half3 fogColor, half fogFactor)
{
    #if defined(FOG_LINEAR) || defined(FOG_EXP) || defined(FOG_EXP2)
        half fogIntensity = ComputeFogIntensity(fogFactor);
        fragColor = lerp(fogColor, fragColor, fogIntensity);
    #endif
    return fragColor;
}

UnlitForwardPass部分到此结束!没想到简简单单一个Unlit让我翻了那么多个文件..... 写这篇参考了很多happyfire大佬最近的日更文章,但是URP版本不太一样,新的URP真的加了好多内容。 这篇真的好长,实在是重写到吐血...就当温故而知新了😇😇😇😇😇 晚安!下一篇读DepthOnlyPass!

by ERIN.Z

2024 © typecho & elise