靠北,这篇写了超级长结果被草稿覆盖了.....我是真的很emo😇😇😇😇😇
就极精简地复述一遍....好难过😭😭
文件结构
URP管线的Shader都位于Packages/Universal RP/Shaders
目录,是Shader的主要框架和相关的Pass。通用的渲染方法函数一般位于Packages/Universal RP/Shaders Library
目录,这是一个为SRP提供的函数包,可以为我们省去许多重复性的工作。另外涉及到Material面板的文件位于Packages/Universal RP/Editor/Shader GUI
.
本文代码块的第一行表示代码所在的目录位置,就不多重复了~
有关拓展阅读:[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/Shaders
,UnityEditor.Rendering.Universal.ShaderGUI
是它所在的命名空间。这些脚本文件都继承于BaseShaderGUI类。在BaseShaderGUI中,定义了有关属性的面板和更新关键字的方法。
ShaderGUI
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.hlsl
和AmbientOcclusion.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!