Unity PBR基础知识
PBR原理
基于物理的渲染(Physically Based Rendering,PBR)是指使用基于物理原理和微平面理论建模的着色/光照模型,以及使用从现实中测量的表面参数来准确表示真实世界材质的渲染理念。
PBR基础理念
微平面理论(Microfacet Theory):微平面理论是将物体表面建模成做无数微观尺度上有随机朝向的理想镜面反射的小平面(microfacet)的理论。在实际的PBR 工作流中,这种物体表面的不规则性用粗糙度贴图或者高光度贴图来表示。
能量守恒(Energy Conservation):出射光线的能量永远不能超过入射光线的能量。随着粗糙度的上升镜面反射区域的面积会增加,作为平衡,镜面反射区域的平均亮度则会下降。
菲涅尔反射(Fresnel Reflectance):光线以不同角度入射会有不同的反射率。相同的入射角度,不同的物质也会有不同的反射率。万物皆有菲涅尔反射。F0是即 0 度角入射的菲涅尔反射值。大多数非金属的F0范围是0.02-0.04,大多数金属的F0范围是0.7-1.0。
线性空间(Linear Space):光照计算必须在线性空间完成,shader 中输入的gamma空间的贴图比如漫反射贴图需要被转成线性空间,在具体操作时需要根据不同引擎和渲染器的不同做不同的操作。而描述物体表面属性的贴图如粗糙度,高光贴图,金属贴图等必须保证是线性空间。
色调映射(Tone Mapping):也称色调复制(tone reproduction),是将宽范围的照明级别拟合到屏幕有限色域内的过程。因为基于HDR渲染出来的亮度值会超过显示器能够显示最大亮度,所以需要使用色调映射,将光照结果从HDR转换为显示器能够正常显示的LDR。
物质的光学特性(Substance Optical Properties):现实世界中有不同类型的物质可分为三大类:绝缘体(Insulators),半导体(semi-conductors)和导体(conductors)。在渲染和游戏领域,我们一般只对其中的两个感兴趣:导体(金属)和绝缘体(电解质,非金属)。其中非金属具有单色/灰色镜面反射颜色。而金属具有彩色的镜面反射颜色。即非金属的F0是一个float。而金属的F0是一个float3。
PBR的范畴
基于物理的材质(Material)
基于物理的光照(Lighting)
基于物理适配的摄像机(Camera)
渲染方程
目前主流的是迪士尼原则的BRDF,着色模型是艺术导向的,而不一定要是完全物理正确的,并且对微平面BRDF的各项都进行了严谨的调查,提出了清晰明确而简单的解决方案。
属性名 | 含义 | 说明 |
---|---|---|
baseColor (基础色) | 表面颜色 | 通常由纹理贴图提供。 |
subsurface (次表面) | 控制漫反射形状 | 使用次表面近似模拟次表面散射效果。 |
metallic (金属度) | 电介质与金属之间的混合 | 0 表示电介质,1 表示金属。金属模型没有漫反射成分,其镜面反射等于基础色。 |
specular (镜面反射强度) | 入射镜面反射量 | 用于替代折射率(IOR)进行调节。 |
specularTint (镜面反射颜色) | 镜面反射的颜色调节 | 用于控制基础色部分的镜面反射颜色。掠射角下仍为非彩色反射。 |
roughness (粗糙度) | 控制表面反射性质 | 粗糙度越高,反射越模糊;同时影响漫反射和镜面反射。 |
anisotropic (各向异性强度) | 镜面反射各向异性程度 | 0 表示各向同性,1 表示最大各向异性,用于拉伸高光。 |
sheen (光泽度) | 额外的掠射高光分量 | 主要用于模拟布料材质的掠射光。 |
sheenTint (光泽颜色) | 光泽度的颜色调节 | 控制 sheen 项的颜色倾向。 |
clearcoat (清漆强度) | 第二层镜面波瓣(specular lobe) | 用于模拟特殊材质表面(如车漆)的一层透明涂层。 |
clearcoatGloss (清漆光泽度) | 清漆表面光滑度 | 0 表示“缎面(satin)”效果,1 表示“光泽(gloss)”效果。 |
💡 注释:
• lobe(波瓣) 指的是镜面反射的形状或区域,用于描述光照与表面之间的反射分布。在 PBR 渲染中,通常一个材质可以包含一个或多个镜面波瓣,用以模拟不同类型的反射。
BRDF是一个可以插入渲染方程的函数,是渲染方程的核心内容。所有 BRDF 函数输出的结果是:入射光和出射光能量之间的比率的值。
目前的BRDF 公式
BRDF = k d π + k s ⋅ D ⋅ G ⋅ F 4 ( N ⋅ L ) ( N ⋅ V ) \text{BRDF} = \frac{k_d}{\pi} + k_s \cdot \frac{D \cdot G \cdot F}{4 (\mathbf{N} \cdot \mathbf{L}) (\mathbf{N} \cdot \mathbf{V})} BRDF=πkd+ks⋅4(N⋅L)(N⋅V)D⋅G⋅F
将 G 4 ( N ⋅ L ) ( N ⋅ V ) \frac{G}{4 (\mathbf{N} \cdot \mathbf{L}) (\mathbf{N} \cdot \mathbf{V})} 4(N⋅L)(N⋅V)G 合并为 V V V项再乘以 π \pi π得到
Unity BRDF = k d + k s ⋅ ( D ⋅ V ⋅ F ) ⋅ π \text{Unity BRDF} = k_d + k_s \cdot (D \cdot V \cdot F) \cdot \pi Unity BRDF=kd+ks⋅(D⋅V⋅F)⋅π
其中 k d = D i f f u s e C o l o r k_d= DiffuseColor kd=DiffuseColor k s = S p e c u l a r C o l o r k_s = SpecularColor ks=SpecularColor
光照公式:
Lighting = ( DiffuseColor π ) + ( SpecularColor ⋅ D ( x ) ⋅ V ( x ) ⋅ F ( x ) ⋅ ( N ⋅ L ) ⋅ LightColor ) \text{Lighting} = \left( \frac{\text{DiffuseColor}}{\pi} \right) + \left( \text{SpecularColor} \cdot D(x) \cdot V(x) \cdot F(x) \cdot (\mathbf{N} \cdot \mathbf{L}) \cdot \text{LightColor} \right) Lighting=(πDiffuseColor)+(SpecularColor⋅D(x)⋅V(x)⋅F(x)⋅(N⋅L)⋅LightColor)
其中
D项为法线分布函数
/*** Summary:* 此函数实现了 GGX(Trowbridge-Reitz)微表面分布函数,用于物理基础渲染中的镜面反射部分。* * 参数说明:* - a2: 表示粗糙度参数 α 的平方(alpha^2),决定了表面微表面的分布宽窄。* - NoH: 表示法线向量和半角向量之间的点积,即 N·H,反映光照与视角间的关系。** 算法描述:* 1. 首先根据输入参数计算中间变量 d,其表达式可以化简为:* d = (a2 - 1) * NoH² + 1* 该值用于控制分布函数的锐度。** 2. 最后返回的结果为:* D = a2 / (PI * d²)* 这个公式即为 GGX 分布函数的标准形式,用于描述表面微细结构对镜面高光的影响,* 在物理基础渲染中常用于计算BRDF的镜面部分。*/float D_GGX_Func( float a2, float NoH ){float d = ( NoH * a2 - NoH ) * NoH + 1; return a2 / ( PI*d*d ); }
V项为可见性项
/*** Summary:* 此函数实现了 Smith 模型下的几何可见性项近似(针对 GGX 粗糙度),* 用于物理基础渲染中镜面部分的可见性计算。** 参数说明:* - a2: 粗糙度参数 \(\alpha^2\),影响微表面的分布范围。* - NoV: 表示法线与视线向量之间的点积(N·V)。* - NoL: 表示法线与光线向量之间的点积(N·L)。** 算法描述:* 1. 首先对粗糙度参数 a2 求平方根,得到参数 a,用于控制几何项的过渡。* 2. 分别计算对视线和光线的几何可见性项* 3. 取这两者和的一半的倒数作为最终可见性结果*/float Vis_SmithJointApprox( float a2, float NoV, float NoL ){float a = sqrt(a2);float Vis_SmithV = NoL * ( NoV * ( 1 - a ) + a );float Vis_SmithL = NoV * ( NoL * ( 1 - a ) + a );return 0.5 * rcp( Vis_SmithV + Vis_SmithL );}
F项为菲涅尔方程
/*** Summary:* 此函数实现了 Schlick 的菲涅耳近似,用于物理基础渲染中镜面反射的 Fresnel 部分计算。** 参数说明:* - SpecularColor: 物体表面的镜面反射颜色(或称基础反射率)。* - VoH: 表示视线向量和半角向量的点积(V·H)。** 算法描述:* 1. 首先计算 ( 1 - VoH ) 的五次方以得到 Fresnel 的衰减系数 Fc。* 2. 结合一个经验性因子(示例中为 50.0 * SpecularColor.g)来增强金属高光特性:* 3. 最终返回由基础镜面色和衰减系数混合后的结果* 其中 saturate( 50.0 * SpecularColor.g )表示对值范围的钳制以避免溢出。*/float3 F_Schlick_Func( float3 SpecularColor, float VoH ){float Fc = Pow5( 1 - VoH ); return saturate( 50.0 * SpecularColor.g ) * Fc + (1 - Fc) * SpecularColor;}
代码实现
Shader "Test/PBRShader"
{Properties{[MainTexture] _BaseMap("Albedo", 2D) = "white" {}[MainColor] _BaseColor("Color", Color) = (1,1,1,1)_MetallicMap("Metallic Map",2D) = "white"{}_Metallic("Metallic",Range(0.0,1.0)) = 1.0_RoughnessMap("Roughness Map",2D) = "white"{}_Roughness("Roughness",Range(0.0,1.0)) = 1.0_NormalMap("Normal Map",2D) = "bump"{}_Normal("Normal",float) = 1.0_OcclusionMap("OcclusionMap",2D) = "white"{}_OcclusionStrength("Occlusion Strength",Range(0.0,1.0)) = 1.0_Cutoff("Alpha Cutoff", Range(0.0, 1.0)) = 0.5}SubShader{Tags{"RenderType" = "Opaque" "RenderPipeline" = "UniversalPipeline" "UniversalMaterialType" = "Lit" "IgnoreProjector" = "True" "ShaderModel"="4.5"}LOD 0Pass{HLSLPROGRAM#pragma exclude_renderers gles gles3 glcore#pragma target 4.5// -------------------------------------// Material Keywords#pragma shader_feature_local_fragment _ALPHATEST_ON// -------------------------------------// 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//--------------------------------------// 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/ShaderLibrary/Core.hlsl"#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"inline half Pow2 (half x){return x*x;}inline half Pow4 (half x){return x*x * x*x;}inline half Pow5 (half x){return x*x * x*x * x;}/*** Summary:* 此函数实现了 Lambert 漫反射的基本公式,用于物理基础渲染中的漫反射部分。** 参数说明:* - DiffuseColor: 表示物体表面的漫反射颜色。** 算法描述:* 1. 直接使用 \( \pi \) 作为归一化因子,对漫反射颜色进行能量守恒校正。* 2. 最终返回的漫反射分量为:\[ \frac{\text{DiffuseColor}}{\pi} \],* 这是传统的 Lambert 漫反射模型公式。*/float3 Diffuse_Lambert( float3 DiffuseColor ){return DiffuseColor / PI;}/*** Summary:* 此函数实现了 GGX(Trowbridge-Reitz)微表面分布函数,用于物理基础渲染中的镜面反射部分。* * 参数说明:* - a2: 表示粗糙度参数 α 的平方(alpha^2),决定了表面微表面的分布宽窄。* - NoH: 表示法线向量和半角向量之间的点积,即 N·H,反映光照与视角间的关系。** 算法描述:* 1. 首先根据输入参数计算中间变量 d,其表达式可以化简为:* d = (a2 - 1) * NoH² + 1* 该值用于控制分布函数的锐度。** 2. 最后返回的结果为:* D = a2 / (PI * d²)* 这个公式即为 GGX 分布函数的标准形式,用于描述表面微细结构对镜面高光的影响,* 在物理基础渲染中常用于计算BRDF的镜面部分。*/float D_GGX_Func( float a2, float NoH ){float d = ( NoH * a2 - NoH ) * NoH + 1; return a2 / ( PI*d*d ); }/*** Summary:* 此函数实现了 Smith 模型下的几何可见性项近似(针对 GGX 粗糙度),* 用于物理基础渲染中镜面部分的可见性计算。** 参数说明:* - a2: 粗糙度参数 \(\alpha^2\),影响微表面的分布范围。* - NoV: 表示法线与视线向量之间的点积(N·V)。* - NoL: 表示法线与光线向量之间的点积(N·L)。** 算法描述:* 1. 首先对粗糙度参数 a2 求平方根,得到参数 a,用于控制几何项的过渡。* 2. 分别计算对视线和光线的几何可见性项* 3. 取这两者和的一半的倒数作为最终可见性结果*/float Vis_SmithJointApprox( float a2, float NoV, float NoL ){float a = sqrt(a2);float Vis_SmithV = NoL * ( NoV * ( 1 - a ) + a );float Vis_SmithL = NoV * ( NoL * ( 1 - a ) + a );return 0.5 * rcp( Vis_SmithV + Vis_SmithL );}/*** Summary:* 此函数实现了 Schlick 的菲涅耳近似,用于物理基础渲染中镜面反射的 Fresnel 部分计算。** 参数说明:* - SpecularColor: 物体表面的镜面反射颜色(或称基础反射率)。* - VoH: 表示视线向量和半角向量的点积(V·H)。** 算法描述:* 1. 首先计算 ( 1 - VoH ) 的五次方以得到 Fresnel 的衰减系数 Fc。* 2. 结合一个经验性因子(示例中为 50.0 * SpecularColor.g)来增强金属高光特性:* 3. 最终返回由基础镜面色和衰减系数混合后的结果* 其中 saturate( 50.0 * SpecularColor.g )表示对值范围的钳制以避免溢出。*/float3 F_Schlick_Func( float3 SpecularColor, float VoH ){float Fc = Pow5( 1 - VoH ); return saturate( 50.0 * SpecularColor.g ) * Fc + (1 - Fc) * SpecularColor;}half3 EnvBRDFApprox( half3 SpecularColor, half Roughness, half NoV ){const half4 c0 = { -1, -0.0275, -0.572, 0.022 };const half4 c1 = { 1, 0.0425, 1.04, -0.04 };half4 r = Roughness * c0 + c1;half a004 = min( r.x * r.x, exp2( -9.28 * NoV ) ) * r.x + r.y;half2 AB = half2( -1.04, 1.04 ) * a004 + r.zw;AB.y *= saturate( 50.0 * SpecularColor.g );return SpecularColor * AB.x + AB.y;}float GetSpecularOcclusion(float NoV, float RoughnessSq, float AO){return saturate( pow( NoV + AO, RoughnessSq ) - 1 + AO );}float3 AOMultiBounce( float3 BaseColor, float AO ){float3 a = 2.0404 * BaseColor - 0.3324;float3 b = -4.7951 * BaseColor + 0.6417;float3 c = 2.7552 * BaseColor + 0.6903;return max( AO, ( ( AO * a + b ) * AO + c ) * AO );}float3 StandardBRDF( float3 DiffuseColor, float3 SpecularColor, float Roughness, float3 N, float3 V, float3 L,float3 LightColor,float Shadow){float a2 = Pow4( Roughness );float3 H = normalize(L + V);float NoH = saturate(dot(N,H));float NoV = saturate(abs(dot(N,V)) + 1e-5);float NoL = saturate(dot(N,L));float VoH = saturate(dot(V,H));float3 Radiance = NoL * LightColor * Shadow * PI;float3 DiffuseLighting = Diffuse_Lambert(DiffuseColor) * Radiance;float D = D_GGX_Func( a2, NoH );float Vis = Vis_SmithJointApprox( a2, NoV, NoL );float3 F = F_Schlick_Func( SpecularColor, VoH );float3 SpecularLighting = (D * Vis * F) * Radiance;float3 DirectLighting = DiffuseLighting + SpecularLighting;return DirectLighting;}float3 DirectLighting_float(float3 DiffuseColor, float3 SpecularColor, float Roughness,float3 WorldPos, float3 N, float3 V){float3 DirectLighting = half3(0,0,0);#if defined(_MAIN_LIGHT_SHADOWS_SCREEN) && !defined(_SURFACE_TYPE_TRANSPARENT)float4 positionCS = TransformWorldToHClip(WorldPos);float4 ShadowCoord = ComputeScreenPos(positionCS);#elsefloat4 ShadowCoord = TransformWorldToShadowCoord(WorldPos);#endiffloat4 ShadowMask = float4(1.0,1.0,1.0,1.0);half3 DirectLighting_MainLight = half3(0,0,0);{Light light = GetMainLight(ShadowCoord,WorldPos,ShadowMask);half3 L = light.direction;half3 LightColor = light.color;half Shadow = light.shadowAttenuation;DirectLighting_MainLight = StandardBRDF(DiffuseColor,SpecularColor,Roughness,N,V,L,LightColor,Shadow);}half3 DirectLighting_AddLight = half3(0,0,0);#ifdef _ADDITIONAL_LIGHTSuint pixelLightCount = GetAdditionalLightsCount();for(uint lightIndex = 0; lightIndex < pixelLightCount ; ++lightIndex){Light light = GetAdditionalLight(lightIndex,WorldPos,ShadowMask);half3 L = light.direction;half3 LightColor = light.color;half Shadow = light.shadowAttenuation * light.distanceAttenuation;DirectLighting_AddLight += StandardBRDF(DiffuseColor,SpecularColor,Roughness,N,V,L,LightColor,Shadow);}#endifDirectLighting = DirectLighting_MainLight + DirectLighting_AddLight;return DirectLighting;}float3 IndirectLighting_float(float3 DiffuseColor, float3 SpecularColor, float Roughness, float3 WorldPos, float3 N, float3 V,float Occlusion){float3 IndirectLighting = half3(0,0,0);float NoV = saturate(abs(dot(N,V)) + 1e-5);//SHfloat3 DiffuseAO = AOMultiBounce(DiffuseColor,Occlusion);float3 RadianceSH = SampleSH(N);float3 IndirectDiffuse = RadianceSH * DiffuseColor * DiffuseAO;//IBLhalf3 R = reflect(-V,N);half3 SpeucularLD = GlossyEnvironmentReflection(R,WorldPos,Roughness,Occlusion);half3 SpecularDFG = EnvBRDFApprox(SpecularColor,Roughness,NoV);float SpecularOcclusion = GetSpecularOcclusion(NoV,Pow2(Roughness),Occlusion);float3 SpecularAO = AOMultiBounce(SpecularColor,SpecularOcclusion);float3 IndirectSpecular = SpeucularLD * SpecularDFG * SpecularAO;IndirectLighting = IndirectDiffuse + IndirectSpecular;return IndirectLighting;}struct Attributes{float4 positionOS : POSITION;float3 normalOS : NORMAL;float4 tangentOS : TANGENT;float2 texcoord : TEXCOORD0;UNITY_VERTEX_INPUT_INSTANCE_ID};struct Varyings{float2 uv : TEXCOORD0;float3 positionWS : TEXCOORD1;half3 normalWS : TEXCOORD2;half4 tangentWS : TEXCOORD3; float4 shadowCoord : TEXCOORD4;float4 positionCS : SV_POSITION;UNITY_VERTEX_INPUT_INSTANCE_ID};TEXTURE2D(_BaseMap); SAMPLER(sampler_BaseMap);TEXTURE2D(_MetallicMap); SAMPLER(sampler_MetallicMap);TEXTURE2D(_RoughnessMap); SAMPLER(sampler_RoughnessMap);TEXTURE2D(_NormalMap); SAMPLER(sampler_NormalMap);TEXTURE2D(_OcclusionMap); SAMPLER(sampler_OcclusionMap);CBUFFER_START(UnityPerMaterial)half4 _BaseColor;half _Metallic;half _Roughness;half _Normal;half _OcclusionStrength;half _Cutoff;CBUFFER_END/*** Summary:* 该函数 LitPassVertex 是 Unity 中顶点着色器的一部分,用于处理传入的顶点属性,生成用于后续光照计算的中间数据(Varyings)。* * 主要步骤:* 1. 实例化支持: * - 调用 UNITY_SETUP_INSTANCE_ID(input) 初始化输入顶点的实例 ID。 * - 通过 UNITY_TRANSFER_INSTANCE_ID(input, output) 将实例 ID 从输入传递到输出,确保 GPU 实例化时每个实例的信息可以正确传播。** 2. 位置与法线计算: * - 使用 GetVertexPositionInputs 从局部空间位置(input.positionOS.xyz)中获取世界空间位置(positionWS)和裁剪空间位置(positionCS)。* - 使用 GetVertexNormalInputs 从法线(input.normalOS)和切线(input.tangentOS)中计算出经过转换的世界空间法线(normalWS)以及切线信息。** 3. UV 与切线数据输出: * - 将输入纹理坐标(input.texcoord)赋值给输出 UV。* - 将计算得到的世界空间法线赋值给输出 normalWS。** 4. 切线处理: * - 计算 sign 值:通过 input.tangentOS.w 与 GetOddNegativeScale() 相乘,考虑到模型可能存在的奇异缩放情况。 * - 构建世界空间切线向量 tangentWS,并将 sign 值作为 w 分量存储到输出中。** 5. 其他输出数据: * - 传递世界空间位置(positionWS)给输出,供后续光照计算使用。 * - 计算并传递阴影坐标(shadowCoord),用于阴影贴图的采样。 * - 将裁剪空间位置(positionCS)赋给输出,后续会用于顶点投影到屏幕上。** 6. 最终返回: * - 返回包含所有计算结果的输出结构体(Varyings),供后续像素着色器阶段使用。*/Varyings LitPassVertex(Attributes input){Varyings output = (Varyings)0;UNITY_SETUP_INSTANCE_ID(input);UNITY_TRANSFER_INSTANCE_ID(input, output);VertexPositionInputs vertexInput = GetVertexPositionInputs(input.positionOS.xyz);VertexNormalInputs normalInput = GetVertexNormalInputs(input.normalOS, input.tangentOS);output.uv = input.texcoord;output.normalWS = normalInput.normalWS;real sign = input.tangentOS.w * GetOddNegativeScale();half4 tangentWS = half4(normalInput.tangentWS.xyz, sign);output.tangentWS = tangentWS;//half3 viewDirWS = GetWorldSpaceNormalizeViewDir(vertexInput.positionWS);output.positionWS = vertexInput.positionWS;output.shadowCoord = GetShadowCoord(vertexInput);output.positionCS = vertexInput.positionCS;return output;}/*** Summary:* 该函数 LitPassFragment 是 Unity 中像素着色器的核心部分,负责最终颜色计算。通过采样纹理、处理材质属性、计算光照模型等步骤,生成物体表面每个像素的最终颜色输出。* * 主要步骤:* 1. 实例化与基础数据准备:* - 调用 UNITY_SETUP_INSTANCE_ID(input) 初始化实例 ID。* - 从输入结构提取 UV、世界坐标、视角方向等基础参数。** 2. 法线与切线空间处理:* - 通过 TBN 矩阵(切线-副切线-法线矩阵)将法线从切线空间转换到世界空间:* - 规范化输入的世界空间法线(WorldNormal)和切线(WorldTangent)* - 计算副切线(WorldBinormal)并通过 tangentWS.w 处理镜像缩放* - 构建 TBN 矩阵用于后续法线贴图转换** 3. 阴影与屏幕空间数据:* - 获取阴影坐标(ShadowCoord)用于阴影贴图采样* - 通过 GetNormalizedScreenSpaceUV 生成屏幕空间 UV(ScreenUV)* - 初始化阴影掩码(ShadowMask)为默认值** 4. 基础材质属性处理:* - 采样基础颜色贴图并与 _BaseColor 属性混合,得到 BaseColorAlpha* - 执行 Alpha 测试(仅在 _ALPHATEST_ON 启用时):* - 通过 clip 指令丢弃低于 _Cutoff 阈值的像素** 5. 高级材质属性计算:* - 金属度(Metallic):通过贴图采样与 _Metallic 属性控制* - 粗糙度(Roughness):通过贴图采样与属性控制,限制最小值避免除零错误* - 法线贴图处理:解压法线贴图数据并应用 _Normal 强度参数* - 环境光遮蔽(Occlusion):采样贴图并通过 _OcclusionStrength 控制影响强度** 6. 光照模型计算:* - 分离漫反射颜色(DiffuseColor)和镜面反射颜色(SpecularColor):* - 金属度越高,漫反射越暗,镜面反射越接近基础色* - 计算直接光照(DirectLighting):* - 调用 DirectLighting_float 函数处理平行光/点光源等直接光源贡献* - 计算间接光照(IndirectLighting):* - 调用 IndirectLighting_float 处理环境光、反射探针等间接光源* - 应用环境光遮蔽系数** 7. 最终返回:* - 将直接与间接光照结果相加,输出不透明度为 1 的最终颜色* - 返回 half4 类型的颜色值到渲染目标(SV_Target)*/half4 LitPassFragment(Varyings input) : SV_Target{UNITY_SETUP_INSTANCE_ID(input);float2 UV = input.uv;float3 WorldPos = input.positionWS;half3 ViewDir = GetWorldSpaceNormalizeViewDir(WorldPos);half3 WorldNormal = normalize(input.normalWS);half3 WorldTangent = normalize(input.tangentWS.xyz);half3 WorldBinormal = normalize(cross(WorldNormal,WorldTangent) * input.tangentWS.w);half3x3 TBN = half3x3(WorldTangent,WorldBinormal,WorldNormal);float4 ShadowCoord = input.shadowCoord;float2 ScreenUV = GetNormalizedScreenSpaceUV(input.positionCS);half4 ShadowMask = float4(1.0,1.0,1.0,1.0);half4 BaseColorAlpha = SAMPLE_TEXTURE2D(_BaseMap,sampler_BaseMap,UV) * _BaseColor;half3 BaseColor = BaseColorAlpha.rgb;half BaseAlpha = BaseColorAlpha.a;#if defined(_ALPHATEST_ON)clip(BaseAlpha - _Cutoff);#endiffloat Metallic = saturate(SAMPLE_TEXTURE2D(_MetallicMap,sampler_MetallicMap,UV).r * _Metallic);float Roughness = saturate(SAMPLE_TEXTURE2D(_RoughnessMap,sampler_RoughnessMap,UV).r * _Roughness);half3 NormalTS = UnpackNormalScale(SAMPLE_TEXTURE2D(_NormalMap,sampler_NormalMap,UV),_Normal);WorldNormal = normalize(mul(NormalTS,TBN));half Occlusion = SAMPLE_TEXTURE2D(_OcclusionMap,sampler_OcclusionMap,UV).r;Occlusion = lerp(1.0,Occlusion,_OcclusionStrength);float3 DiffuseColor = lerp(BaseColor,float3(0.0,0.0,0.0),Metallic);float3 SpecularColor = lerp(float3(0.04,0.04,0.04),BaseColor,Metallic);Roughness = max(Roughness,0.001f);half3 DirectLighting = DirectLighting_float(DiffuseColor,SpecularColor,Roughness,WorldPos,WorldNormal,ViewDir);half3 IndirectLighting = IndirectLighting_float(DiffuseColor,SpecularColor,Roughness,WorldPos,WorldNormal,ViewDir,Occlusion);half4 color = half4(DirectLighting + IndirectLighting,1.0f);return color;}ENDHLSL}Pass{Name "ShadowCaster"Tags{"LightMode" = "ShadowCaster"}ZWrite OnZTest LEqualColorMask 0Cull[_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}}FallBack "Hidden/Universal Render Pipeline/FallbackError"
}
效果实测