1. 项目概述从经验模型到物理基础的跨越如果你已经跟着《Unity Shader 入门精要》走过了标准光照模型那么恭喜你你已经迈出了理解虚拟世界光影的第一步。但当你把那些漂亮的Blinn-Phong模型放到一个稍微复杂点的场景里比如一个金属球放在粗糙的木桌上旁边还有一盏暖色的台灯你可能会发现怎么调参数都觉得“假”——金属感不够锐利木头的高光太“塑料”不同光源下的颜色过渡生硬。这就是标准经验模型的局限它像是一个经验丰富但理论不足的老画师能画出好看的画但解释不了光与物质相互作用的深层原理。而PBR即基于物理的渲染就是来解决这个问题的。它不是一套全新的魔法而是一套更严谨、更自洽的“绘画理论”它告诉你为什么金属是那样反光为什么粗糙的表面会散射光线以及环境光究竟该如何影响物体。这个系列笔记的第三篇我们就来深入拆解PBR光照的核心特别是那些让画面从“好看”跃升到“真实”的关键细节。无论你是想成为技术美术TA还是希望自己的游戏画面质感更上一层楼的开发者理解这些基础都是构建你光影知识体系的坚实一步。2. PBR核心思想与标准光照模型的本质区别2.1 标准光照模型经验主义的“快捷方式”在深入PBR之前我们必须回头看清我们走过的路。标准光照模型比如经典的Lambert漫反射和Blinn-Phong高光本质上是一系列经验公式的集合。它们通过几个直观的参数如漫反射颜色、高光强度、光泽度来模拟光的效果非常高效在早期的硬件上立下了汗马功劳。它的核心工作方式是“分而治之”将光照效果人为地拆解为几个独立的部分环境光 (Ambient)一个常数用来模拟间接光照的全局基底简单但粗暴。漫反射 (Diffuse)模拟光线在物体表面均匀向各个方向散射的现象通常用Lambert余弦定律计算。高光反射 (Specular)模拟光线在光滑表面上的镜面反射形成亮斑Blinn-Phong模型通过半角向量来近似计算。注意这种拆解在物理上是不准确的。在真实世界中光线与表面的交互是一个复杂的物理过程漫反射和镜面反射并非完全独立它们与材料的微观结构紧密相关。标准模型为了计算效率牺牲了物理正确性。其最大的问题在于参数的非直观性和互斥性。调整“高光强度”和“光泽度”来让一个材质看起来像木头还是像金属很大程度上依赖于美术师的“感觉”和反复调试。而且这些参数在不同的光照环境下表现不一致——你在阳光下调好的材质放到烛光旁可能就完全不对味了。这导致了资产无法在不同场景中复用极大地增加了美术工作量。2.2 PBR的基石能量守恒与微表面理论PBR抛弃了这种“猜谜”式的参数调整转而建立在两条更坚实的物理基石上。第一块基石能量守恒。这是最核心的原则。它规定物体表面反射出的光能总量绝对不能超过它接收到的光能总量。在渲染中这意味着漫反射和镜面反射高光是此消彼长的关系。一个表面反射的镜面光越强越像镜子它的漫反射就一定越弱。在标准模型中你可以同时把漫反射和高光调到很亮这显然违背了物理规律也是画面显得“假”和“油腻”的重要原因之一。在PBR着色器中能量守恒通常这样体现// 一个简化的概念性表达 float3 kS calculateSpecularReflectance(...); // 镜面反射系数 float3 kD (1.0 - kS) * (1.0 - metallic); // 漫反射系数并受金属度影响对于非金属电介质kS通常很小大部分能量用于漫反射。对于纯金属导体kS接近1几乎没有漫反射。第二块基石微表面理论。这是PBR用来描述物体表面视觉外观的核心模型。它认为任何肉眼看起来平整的表面在微观尺度上都是由无数个朝向随机分布的微小镜面组成的“崎岖山地”。法线分布函数 (Normal Distribution Function, NDF)描述这些微表面法线方向的统计分布。它直接决定了高光斑的形状和锐利程度。比如GGX/Trowbridge-Reitz分布是现在最流行的NDF它能产生边缘拖尾的、更真实的高光非常适合模拟金属和光滑表面。几何函数 (Geometry Function / Shadowing-Masking)描述微表面之间的相互遮挡导致光线被遮蔽Shadowing或 masking。这影响了高光在掠射角grazing angle下的表现让边缘看起来更暗、更自然。常见的有关Smith-Schlick模型。菲涅尔方程 (Fresnel Equation)描述反射率如何随着观察角度视线与法线夹角变化。在掠射角下几乎所有材质包括非金属的反射率都会趋近于1完全反射。这是为什么我们看水池边缘、玻璃边缘总是特别亮的原因。Schlick近似是着色器中常用的高效菲涅尔公式。PBR通过这三个函数的组合用一套相对统一的参数如粗糙度、金属度、基础色来驱动从而能够物理准确地模拟从粗糙石膏到抛光铜器的一切材质。2.3 直接光照与间接光照的鸿沟标准光照模型通常只处理直接光照——即直接从光源发出、击中物体、再进入眼睛的光路。对于间接光照——光线在场景中弹射一次以上比如天花板反射的日光照亮了桌子底部——往往用一个全局恒定的环境光颜色来糊弄过去。而真实的视觉感受间接光照贡献了至少一半以上的信息。PBR管线特别是结合了现代实时全局光照如Unity的Enlighten, GPU Lightmapper或更先进的屏幕空间/体素GI后能够以物理一致的方式计算间接漫反射和间接高光环境反射。这意味着你的金属球不仅能反射直接的点光源还能正确地反射它周围的天空盒、墙壁和其他物体这才是沉浸感的关键来源。3. PBR核心工作流与材质参数解析理解了理论我们来看如何在Unity中实践。一套标准的PBR工作流通常围绕一张贴图或一套参数展开。3.1 金属度/粗糙度工作流这是目前UnityURP/HDRP和大多数游戏引擎的主流工作流。它的核心思想是将材质属性分离到不同的贴图通道中最大化利用纹理数据。核心贴图构成基础色 (Albedo)是什么材质的本色去除了光照信息的纯颜色。对于非金属它就是漫反射颜色对于纯金属它的漫反射为黑色Albedo存储的是镜面反射的F0值菲涅尔反射率在0度角的值。注意事项这张图必须是sRGB颜色空间并且绝对不能包含任何光照信息如阴影、高光。一个常见的错误是把从照片里抠出来的纹理直接当Albedo用那样会破坏PBR的能量守恒。你需要使用 Substance Painter、Quixel Mixer 等工具烘焙或手工绘制出干净的Albedo。金属度 (Metallic)是什么一张单通道灰度贴图。白色值1.0表示该像素是纯金属如金、银、铁黑色值0.0表示该像素是非金属/电介质如塑料、木材、石材。灰色区域表示锈蚀、污渍等过渡状态。实操心得在绘制时思维要转变。不要想着“画高光”而是想着“定义材料的导电性”。一个生锈的铁罐只有锈蚀脱落露出金属本体的地方才是高亮的白色Metallic1锈层本身是黑色Metallic0。这张图定义了材质在能量守恒中的根本行为。粗糙度 (Roughness)是什么一张单通道贴图。白色表示表面非常粗糙如磨砂水泥光线散射严重高光模糊、范围大黑色表示表面非常光滑如抛光漆面高光锐利、集中。与“光滑度”的区分Unity的Standard Shader中使用的是“Smoothness”它是Roughness的反相Smoothness 1 - Roughness。在导入外部资源如Quixel Megascans时务必注意通道对应关系通常需要反转绿色通道。环境光遮蔽 (Ambient Occlusion, AO)是什么一张单通道贴图模拟物体自身几何结构带来的阴影如裂缝、凹槽、接触面等区域接收间接光更少。它通常与间接光照计算相乘增加画面的体积感和细节。使用技巧AO贴图最好单独提供而不是合并到Albedo的Alpha通道。这样可以在着色器中更灵活地控制其强度。有时为了性能也可以选择不使用AO贴图而依赖屏幕空间环境光遮蔽SSAO后处理。法线贴图 (Normal Map)是什么虽然不完全是PBR独有的但它是增强表面微观细节、配合粗糙度定义表面起伏的关键。它通过RGB通道存储每个像素的法线方向在不增加模型面数的情况下极大提升细节。注意事项确保你的法线贴图是切线空间Tangent Space的并且导入Unity时正确设置纹理类型为“Normal map”引擎会自动对其进行适合移动端或PC端的解码和压缩。贴图打包实践为了节省纹理采样指令和内存带宽通常会将Metallic、Roughness、AO甚至高度图Height打包到一张纹理的RGBA通道中俗称“MRAO”或“ORM”贴图。例如R通道AOG通道RoughnessB通道MetallicA通道可选高度、自发光或其他信息在Shader中只需采样一次然后用.r、.g、.b、.a来解包各个参数非常高效。3.2 镜面反射/光泽度工作流这是一种相对传统的工作流现在较少使用但在一些旧项目或特定资源中还能见到。它将镜面反射的颜色F0和光泽度Glossiness作为主要参数。它与金属度工作流可以互相转换但金属度工作流在艺术创作上更直观非金属即金属物理上也更一致。4. 在Unity中实现一个基础PBR着色器理论说了这么多我们动手写一个简化版的、基于金属度工作流的PBR片元着色器来串联所有概念。我们将使用Unity URP的ShaderLab框架。4.1 属性与变量定义首先定义着色器所需的属性和贴图。Shader Custom/SimplePBR { Properties { // 基础纹理 _BaseMap (Albedo (RGB), 2D) white {} _BaseColor (Base Color, Color) (1,1,1,1) // 金属度/粗糙度/AO打包贴图 _MRAMap (Metallic(R) Roughness(G) AO(B), 2D) white {} _MetallicScale (Metallic Scale, Range(0,1)) 1.0 _RoughnessScale (Roughness Scale, Range(0,1)) 1.0 _AOScale (AO Scale, Range(0,1)) 1.0 // 法线贴图 _NormalMap (Normal Map, 2D) bump {} _NormalScale (Normal Scale, Float) 1.0 // 环境反射立方体贴图可由引擎提供 // _EnvCubeMap (Environment Cubemap, Cube) _Skybox {} } ... }这里我们使用一张_MRAMap来打包三个单通道参数这是常见的优化手段。_*Scale参数用于在最终计算前对贴图值进行整体调节提供美术控制权。4.2 数据结构与纹理采样在片元着色器Fragment Shader中我们需要准备数据。struct FragmentInput { float2 uv : TEXCOORD0; float3 normalWS : TEXCOORD1; float3 tangentWS : TEXCOORD2; float3 bitangentWS : TEXCOORD3; float3 positionWS : TEXCOORD4; float4 positionCS : SV_POSITION; }; // 在片元着色器中 FragmentInput i ...; // 从顶点着色器传递而来 // 采样纹理 float4 albedoColor SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, i.uv) * _BaseColor; float3 mra SAMPLE_TEXTURE2D(_MRAMap, sampler_MRAMap, i.uv).rgb; float3 normalTS UnpackNormalScale(SAMPLE_TEXTURE2D(_NormalMap, sampler_NormalMap, i.uv), _NormalScale); // 解包MRA参数 float metallic mra.r * _MetallicScale; float roughness mra.g * _RoughnessScale; // 注意如果贴图存储的是光滑度这里需要 1 - mra.g float ao mra.b * _AOScale; // 构建切线空间到世界空间的矩阵转换法线 float3x3 TBN float3x3(i.tangentWS, i.bitangentWS, i.normalWS); float3 normalWS normalize(mul(normalTS, TBN));这一步我们获取了所有表面参数基础色、金属度、粗糙度、AO和世界空间法线。注意粗糙度的处理要明确你的贴图存储的是粗糙度还是光滑度。4.3 核心光照计算BRDF的实现接下来是核心实现一个简化的Cook-Torrance BRDF模型。我们使用UE4/Unity流行的GGX微表面模型和Schlick菲涅尔近似。// 1. 准备向量 float3 N normalWS; // 法线 float3 V normalize(_WorldSpaceCameraPos - i.positionWS); // 视线向量 float3 L normalize(_MainLightPosition.xyz); // 主光源方向平行光 float3 H normalize(V L); // 半角向量 float NdotL max(dot(N, L), 0.0001); // 防止除零 float NdotV max(dot(N, V), 0.0001); float NdotH max(dot(N, H), 0.0); float VdotH max(dot(V, H), 0.0); // 2. 计算漫反射项 (Lambert 非金属部分) float3 F0 lerp(0.04, albedoColor.rgb, metallic); // 基础反射率非金属约0.04 float3 kS fresnelSchlick(VdotH, F0); // 菲涅尔反射系数 float3 kD (1.0 - kS) * (1.0 - metallic); // 能量守恒漫反射部分 float3 diffuseBRDF kD * albedoColor.rgb / PI; // Lambert漫反射 // 3. 计算镜面反射项 (Cook-Torrance) // 法线分布函数 (GGX/Trowbridge-Reitz) float alpha roughness * roughness; // 迪士尼参数化将roughness平方后使用更符合感知 float alpha2 alpha * alpha; float denomNDF (NdotH * NdotH) * (alpha2 - 1.0) 1.0; float D alpha2 / (PI * denomNDF * denomNDF); // 几何函数 (Smith-SchlickGGX) float k (roughness 1.0) * (roughness 1.0) / 8.0; // 直接光照的k float G1_NdotL NdotL / (NdotL * (1.0 - k) k); float G1_NdotV NdotV / (NdotV * (1.0 - k) k); float G G1_NdotL * G1_NdotV; // 菲涅尔方程 (Schlick近似) float3 F kS; // 上面已经用半角向量H计算过了这里直接使用 // Cook-Torrance BRDF float3 specularBRDF (D * G * F) / max(4.0 * NdotV * NdotL, 0.0001); // 4. 合并直接光照 float3 directLightColor _MainLightColor.rgb; float3 directRadiance (diffuseBRDF specularBRDF) * directLightColor * NdotL;这段代码是PBR的心脏。fresnelSchlick是一个自定义函数计算菲涅尔反射。注意F0的计算对于非金属metallic0它是一个常数约0.04对应常见电介质的反射率对于金属metallic1它就是Albedo颜色本身。这完美体现了金属度工作流的优雅。4.4 加入环境光照仅有直接光是不够的。我们加入基于图像的光照Image Based Lighting, IBL来模拟环境贡献。// 5. 环境光照简化版IBL // 采样环境立方体贴图这里使用Unity内置的采样函数需要SH数据和反射探针 float3 ambientSH SampleSH(N); // 球谐函数采样用于漫反射环境光 float3 diffuseIBL ambientSH * albedoColor.rgb * ao; // 漫反射环境光 * AO // 对于镜面反射环境光我们使用粗糙度来采样预过滤的环境贴图mipmap float3 R reflect(-V, N); float mipLevel roughness * UNITY_SPECCUBE_LOD_STEPS; // 根据粗糙度选择mip层级 float4 encodedSpecularIBL SAMPLE_TEXTURECUBE_LOD(unity_SpecCube0, samplerunity_SpecCube0, R, mipLevel); float3 specularIBL DecodeHDREnvironment(encodedSpecularIBL, unity_SpecCube0_HDR); // 解码HDR // 环境光菲涅尔使用NdotV而非VdotH float3 F_env fresnelSchlickRoughness(NdotV, F0, roughness); float3 kS_env F_env; float3 kD_env (1.0 - kS_env) * (1.0 - metallic); float3 ambient kD_env * diffuseIBL specularIBL * kS_env; // 合并环境光贡献fresnelSchlickRoughness是另一个考虑了粗糙度的菲涅尔近似因为对于粗糙表面菲涅尔效应会减弱。SampleSH和SAMPLE_TEXTURECUBE_LOD是Unity提供的工具函数它们背后关联着场景的光照探针和反射探针数据。这是将物体融入环境的关键。4.5 最终输出最后合并所有光照贡献并输出。// 6. 最终颜色合成 float3 finalColor directRadiance ambient; // 7. 色调映射简单的Reinhard finalColor finalColor / (finalColor 1.0); // 8. 伽马校正如果渲染到sRGB目标如显示器 finalColor pow(finalColor, 1.0/2.2); return float4(finalColor, 1.0);我们加入了简单的Reinhard色调映射来防止高光区域过曝并进行了伽马校正确保颜色在sRGB显示器上显示正确。在实际的URP/HDRP管线中这些步骤通常由后处理栈完成。5. 常见问题、性能考量与实战技巧当你把上面的Shader写出来并应用到模型上后可能会遇到各种问题。这里记录一些典型的坑和优化思路。5.1 材质看起来“不对”问题排查清单问题现象可能原因解决方案材质整体发灰、发白Albedo贴图包含了光照信息如烘焙的光照贴图或值域过高。检查Albedo贴图确保它是纯漫反射颜色在sRGB空间下颜色值通常不应超过0.8线性空间约0.6。使用校色工具校正。金属部分看起来像塑料1. 金属度贴图设置错误该白的不白。2. Albedo颜色对于金属部分太暗或饱和度太低。1. 检查Metallic贴图纯金属区域应为白色255。2. 纯金属的Albedo应设置为该金属的反射颜色如铜为橙黄色金为金黄色而非灰色。高光非常模糊或“油腻”粗糙度值整体偏高或粗糙度/光滑度通道对应错误。明确你的工作流。如果是粗糙度工作流黑色光滑白色粗糙。检查贴图并尝试调低_RoughnessScale。边缘没有菲涅尔效应不亮菲涅尔计算错误或环境光特别是镜面反射IBL太弱。检查菲涅尔函数fresnelSchlick的实现。确保环境立方体贴图天空盒或反射探针足够亮且HDR范围正确。法线贴图效果怪异或反向法线贴图YG通道方向错误OpenGL与DirectX格式差异。在Unity导入设置中尝试勾选或取消勾选“Create from Grayscale”下的“Flip Green (Y) Channel”。接缝处有颜色或光照断裂模型UV有接缝或者切线空间计算在接缝处不连续。这是切线空间法线贴图的固有缺陷。尽量优化模型UV减少不必要的接缝。对于硬表面可以考虑使用对象空间法线贴图但会失去模型自由旋转的能力。5.2 性能优化要点PBR计算比Phong复杂得多在移动端或大量物体渲染时性能是必须考虑的。简化BRDF在移动端可以考虑使用更简单的模型如Blinn-Phong的物理修正版或者使用查找纹理LUT来预计算部分BRDF积分。减少纹理采样如前所述使用MRAO等打包贴图。对于远处物体可以使用纹理数组或虚拟纹理减少采样次数。简化IBL对于动态物体完全实时采样立方体贴图代价高。可以使用低分辨率的反射探针。对于漫反射IBL用低阶球谐函数SH代替采样代价极低且效果不错。对于镜面反射IBL使用预积分环境贴图预过滤的GGX mipmap链并配合BRDF积分贴图2D LUT这是业界标准做法Unity内置管线已实现。Shader变体与关键字剔除使用Shader变体来为不同质量的设备编译不同复杂度的版本。例如低配机可以关闭法线贴图、使用更简单的环境光等。5.3 美术制作流程建议从真实测量数据开始在Substance Source或类似库中有很多基于真实世界测量的PBR材质参数如基础色、金属度、粗糙度。从这些数据开始调整比凭空捏造要快得多也准确得多。使用线性空间工作流确保Unity项目颜色空间设置为“Linear”。在Gamma空间下做PBR颜色混合和光照计算都是错误的很难调出正确效果。善用参考图找一张在理想光照环境如均匀的穹顶光下拍摄的真实物体照片作为参考在同样的HDR环境光下对比你的模型这是最有效的调试方法。分层绘制思维在Substance Painter中绘制材质时遵循“基底材质 - 宏观磨损 - 微观细节 - 污渍尘埃”的层次。每一层都对应地影响Albedo、Roughness、Metallic和Normal这样制作出的材质富有变化且物理可信。6. 从基础到进阶PBR管线扩展掌握了基础PBR你的画面已经能打败90%的自研引擎了。但要追求影视级或3A级的质感还需要了解以下扩展方向。6.1 清漆与多层材质很多真实材质并非单一层比如涂了清漆的木头、带了氧化层的金属。这需要多层材质模型。一种常见的简化方法是使用清漆层在基础PBR材质之上再叠加一个薄薄的、独立的微表面层通常是光滑的、非金属的并单独计算其反射。这可以通过额外的粗糙度、法线和菲涅尔参数来控制在Shader中意味着多一次BRDF计算和混合。6.2 各向异性基础PBR假设微表面是各向同性的即各个方向的粗糙度一致。但拉丝金属、CD光盘、头发等材质具有方向性的高光这就是各向异性。实现上通常需要一张方向图Flow Map来扰动法线分布函数NDF的朝向或者使用专门的法线分布函数如Anisotropic GGX。6.3 次表面散射对于蜡、玉石、皮肤、牛奶等半透明材质光线会进入表面下方经过多次散射后再从另一点射出形成柔和的透光效果。这是次表面散射。实时渲染中常用简化模型如屏幕空间次表面散射SSSSS或预积分的皮肤着色模型。它需要额外的厚度图、散射颜色和强度参数。6.4 高度图与视差效果法线贴图只能模拟光照变化不能改变轮廓和遮挡关系。高度图或称视差贴图存储了表面的真实高度信息。通过视差映射技术可以在着色时根据视线方向偏移纹理坐标模拟出简单的几何凹凸让砖缝真的“凹”进去鹅卵石真的“凸”出来极大地增强立体感。这是提升材质细节性价比极高的手段。PBR不是一个终点而是一个新的起点。它提供了一套可靠、可预测的框架让你能基于物理规律去构建光影而不是与虚幻的参数搏斗。当你理解了能量如何在材质表面分配理解了微表面如何与光线舞蹈你便获得了一种“翻译”现实世界到数字世界的能力。从今天起尝试用PBR的思维去观察身边的物体手机外壳的金属光泽、笔记本封皮的粗糙触感、水杯壁上的反光……你会发现那些曾经习以为常的视觉现象背后都有一套简洁优美的数学公式在描述。而这正是技术美术工作的迷人之处——在艺术与科学的交叉点上创造令人信服的虚拟真实。
Unity PBR渲染实战:从微表面理论到金属度/粗糙度工作流
发布时间:2026/7/12 17:10:11
1. 项目概述从经验模型到物理基础的跨越如果你已经跟着《Unity Shader 入门精要》走过了标准光照模型那么恭喜你你已经迈出了理解虚拟世界光影的第一步。但当你把那些漂亮的Blinn-Phong模型放到一个稍微复杂点的场景里比如一个金属球放在粗糙的木桌上旁边还有一盏暖色的台灯你可能会发现怎么调参数都觉得“假”——金属感不够锐利木头的高光太“塑料”不同光源下的颜色过渡生硬。这就是标准经验模型的局限它像是一个经验丰富但理论不足的老画师能画出好看的画但解释不了光与物质相互作用的深层原理。而PBR即基于物理的渲染就是来解决这个问题的。它不是一套全新的魔法而是一套更严谨、更自洽的“绘画理论”它告诉你为什么金属是那样反光为什么粗糙的表面会散射光线以及环境光究竟该如何影响物体。这个系列笔记的第三篇我们就来深入拆解PBR光照的核心特别是那些让画面从“好看”跃升到“真实”的关键细节。无论你是想成为技术美术TA还是希望自己的游戏画面质感更上一层楼的开发者理解这些基础都是构建你光影知识体系的坚实一步。2. PBR核心思想与标准光照模型的本质区别2.1 标准光照模型经验主义的“快捷方式”在深入PBR之前我们必须回头看清我们走过的路。标准光照模型比如经典的Lambert漫反射和Blinn-Phong高光本质上是一系列经验公式的集合。它们通过几个直观的参数如漫反射颜色、高光强度、光泽度来模拟光的效果非常高效在早期的硬件上立下了汗马功劳。它的核心工作方式是“分而治之”将光照效果人为地拆解为几个独立的部分环境光 (Ambient)一个常数用来模拟间接光照的全局基底简单但粗暴。漫反射 (Diffuse)模拟光线在物体表面均匀向各个方向散射的现象通常用Lambert余弦定律计算。高光反射 (Specular)模拟光线在光滑表面上的镜面反射形成亮斑Blinn-Phong模型通过半角向量来近似计算。注意这种拆解在物理上是不准确的。在真实世界中光线与表面的交互是一个复杂的物理过程漫反射和镜面反射并非完全独立它们与材料的微观结构紧密相关。标准模型为了计算效率牺牲了物理正确性。其最大的问题在于参数的非直观性和互斥性。调整“高光强度”和“光泽度”来让一个材质看起来像木头还是像金属很大程度上依赖于美术师的“感觉”和反复调试。而且这些参数在不同的光照环境下表现不一致——你在阳光下调好的材质放到烛光旁可能就完全不对味了。这导致了资产无法在不同场景中复用极大地增加了美术工作量。2.2 PBR的基石能量守恒与微表面理论PBR抛弃了这种“猜谜”式的参数调整转而建立在两条更坚实的物理基石上。第一块基石能量守恒。这是最核心的原则。它规定物体表面反射出的光能总量绝对不能超过它接收到的光能总量。在渲染中这意味着漫反射和镜面反射高光是此消彼长的关系。一个表面反射的镜面光越强越像镜子它的漫反射就一定越弱。在标准模型中你可以同时把漫反射和高光调到很亮这显然违背了物理规律也是画面显得“假”和“油腻”的重要原因之一。在PBR着色器中能量守恒通常这样体现// 一个简化的概念性表达 float3 kS calculateSpecularReflectance(...); // 镜面反射系数 float3 kD (1.0 - kS) * (1.0 - metallic); // 漫反射系数并受金属度影响对于非金属电介质kS通常很小大部分能量用于漫反射。对于纯金属导体kS接近1几乎没有漫反射。第二块基石微表面理论。这是PBR用来描述物体表面视觉外观的核心模型。它认为任何肉眼看起来平整的表面在微观尺度上都是由无数个朝向随机分布的微小镜面组成的“崎岖山地”。法线分布函数 (Normal Distribution Function, NDF)描述这些微表面法线方向的统计分布。它直接决定了高光斑的形状和锐利程度。比如GGX/Trowbridge-Reitz分布是现在最流行的NDF它能产生边缘拖尾的、更真实的高光非常适合模拟金属和光滑表面。几何函数 (Geometry Function / Shadowing-Masking)描述微表面之间的相互遮挡导致光线被遮蔽Shadowing或 masking。这影响了高光在掠射角grazing angle下的表现让边缘看起来更暗、更自然。常见的有关Smith-Schlick模型。菲涅尔方程 (Fresnel Equation)描述反射率如何随着观察角度视线与法线夹角变化。在掠射角下几乎所有材质包括非金属的反射率都会趋近于1完全反射。这是为什么我们看水池边缘、玻璃边缘总是特别亮的原因。Schlick近似是着色器中常用的高效菲涅尔公式。PBR通过这三个函数的组合用一套相对统一的参数如粗糙度、金属度、基础色来驱动从而能够物理准确地模拟从粗糙石膏到抛光铜器的一切材质。2.3 直接光照与间接光照的鸿沟标准光照模型通常只处理直接光照——即直接从光源发出、击中物体、再进入眼睛的光路。对于间接光照——光线在场景中弹射一次以上比如天花板反射的日光照亮了桌子底部——往往用一个全局恒定的环境光颜色来糊弄过去。而真实的视觉感受间接光照贡献了至少一半以上的信息。PBR管线特别是结合了现代实时全局光照如Unity的Enlighten, GPU Lightmapper或更先进的屏幕空间/体素GI后能够以物理一致的方式计算间接漫反射和间接高光环境反射。这意味着你的金属球不仅能反射直接的点光源还能正确地反射它周围的天空盒、墙壁和其他物体这才是沉浸感的关键来源。3. PBR核心工作流与材质参数解析理解了理论我们来看如何在Unity中实践。一套标准的PBR工作流通常围绕一张贴图或一套参数展开。3.1 金属度/粗糙度工作流这是目前UnityURP/HDRP和大多数游戏引擎的主流工作流。它的核心思想是将材质属性分离到不同的贴图通道中最大化利用纹理数据。核心贴图构成基础色 (Albedo)是什么材质的本色去除了光照信息的纯颜色。对于非金属它就是漫反射颜色对于纯金属它的漫反射为黑色Albedo存储的是镜面反射的F0值菲涅尔反射率在0度角的值。注意事项这张图必须是sRGB颜色空间并且绝对不能包含任何光照信息如阴影、高光。一个常见的错误是把从照片里抠出来的纹理直接当Albedo用那样会破坏PBR的能量守恒。你需要使用 Substance Painter、Quixel Mixer 等工具烘焙或手工绘制出干净的Albedo。金属度 (Metallic)是什么一张单通道灰度贴图。白色值1.0表示该像素是纯金属如金、银、铁黑色值0.0表示该像素是非金属/电介质如塑料、木材、石材。灰色区域表示锈蚀、污渍等过渡状态。实操心得在绘制时思维要转变。不要想着“画高光”而是想着“定义材料的导电性”。一个生锈的铁罐只有锈蚀脱落露出金属本体的地方才是高亮的白色Metallic1锈层本身是黑色Metallic0。这张图定义了材质在能量守恒中的根本行为。粗糙度 (Roughness)是什么一张单通道贴图。白色表示表面非常粗糙如磨砂水泥光线散射严重高光模糊、范围大黑色表示表面非常光滑如抛光漆面高光锐利、集中。与“光滑度”的区分Unity的Standard Shader中使用的是“Smoothness”它是Roughness的反相Smoothness 1 - Roughness。在导入外部资源如Quixel Megascans时务必注意通道对应关系通常需要反转绿色通道。环境光遮蔽 (Ambient Occlusion, AO)是什么一张单通道贴图模拟物体自身几何结构带来的阴影如裂缝、凹槽、接触面等区域接收间接光更少。它通常与间接光照计算相乘增加画面的体积感和细节。使用技巧AO贴图最好单独提供而不是合并到Albedo的Alpha通道。这样可以在着色器中更灵活地控制其强度。有时为了性能也可以选择不使用AO贴图而依赖屏幕空间环境光遮蔽SSAO后处理。法线贴图 (Normal Map)是什么虽然不完全是PBR独有的但它是增强表面微观细节、配合粗糙度定义表面起伏的关键。它通过RGB通道存储每个像素的法线方向在不增加模型面数的情况下极大提升细节。注意事项确保你的法线贴图是切线空间Tangent Space的并且导入Unity时正确设置纹理类型为“Normal map”引擎会自动对其进行适合移动端或PC端的解码和压缩。贴图打包实践为了节省纹理采样指令和内存带宽通常会将Metallic、Roughness、AO甚至高度图Height打包到一张纹理的RGBA通道中俗称“MRAO”或“ORM”贴图。例如R通道AOG通道RoughnessB通道MetallicA通道可选高度、自发光或其他信息在Shader中只需采样一次然后用.r、.g、.b、.a来解包各个参数非常高效。3.2 镜面反射/光泽度工作流这是一种相对传统的工作流现在较少使用但在一些旧项目或特定资源中还能见到。它将镜面反射的颜色F0和光泽度Glossiness作为主要参数。它与金属度工作流可以互相转换但金属度工作流在艺术创作上更直观非金属即金属物理上也更一致。4. 在Unity中实现一个基础PBR着色器理论说了这么多我们动手写一个简化版的、基于金属度工作流的PBR片元着色器来串联所有概念。我们将使用Unity URP的ShaderLab框架。4.1 属性与变量定义首先定义着色器所需的属性和贴图。Shader Custom/SimplePBR { Properties { // 基础纹理 _BaseMap (Albedo (RGB), 2D) white {} _BaseColor (Base Color, Color) (1,1,1,1) // 金属度/粗糙度/AO打包贴图 _MRAMap (Metallic(R) Roughness(G) AO(B), 2D) white {} _MetallicScale (Metallic Scale, Range(0,1)) 1.0 _RoughnessScale (Roughness Scale, Range(0,1)) 1.0 _AOScale (AO Scale, Range(0,1)) 1.0 // 法线贴图 _NormalMap (Normal Map, 2D) bump {} _NormalScale (Normal Scale, Float) 1.0 // 环境反射立方体贴图可由引擎提供 // _EnvCubeMap (Environment Cubemap, Cube) _Skybox {} } ... }这里我们使用一张_MRAMap来打包三个单通道参数这是常见的优化手段。_*Scale参数用于在最终计算前对贴图值进行整体调节提供美术控制权。4.2 数据结构与纹理采样在片元着色器Fragment Shader中我们需要准备数据。struct FragmentInput { float2 uv : TEXCOORD0; float3 normalWS : TEXCOORD1; float3 tangentWS : TEXCOORD2; float3 bitangentWS : TEXCOORD3; float3 positionWS : TEXCOORD4; float4 positionCS : SV_POSITION; }; // 在片元着色器中 FragmentInput i ...; // 从顶点着色器传递而来 // 采样纹理 float4 albedoColor SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, i.uv) * _BaseColor; float3 mra SAMPLE_TEXTURE2D(_MRAMap, sampler_MRAMap, i.uv).rgb; float3 normalTS UnpackNormalScale(SAMPLE_TEXTURE2D(_NormalMap, sampler_NormalMap, i.uv), _NormalScale); // 解包MRA参数 float metallic mra.r * _MetallicScale; float roughness mra.g * _RoughnessScale; // 注意如果贴图存储的是光滑度这里需要 1 - mra.g float ao mra.b * _AOScale; // 构建切线空间到世界空间的矩阵转换法线 float3x3 TBN float3x3(i.tangentWS, i.bitangentWS, i.normalWS); float3 normalWS normalize(mul(normalTS, TBN));这一步我们获取了所有表面参数基础色、金属度、粗糙度、AO和世界空间法线。注意粗糙度的处理要明确你的贴图存储的是粗糙度还是光滑度。4.3 核心光照计算BRDF的实现接下来是核心实现一个简化的Cook-Torrance BRDF模型。我们使用UE4/Unity流行的GGX微表面模型和Schlick菲涅尔近似。// 1. 准备向量 float3 N normalWS; // 法线 float3 V normalize(_WorldSpaceCameraPos - i.positionWS); // 视线向量 float3 L normalize(_MainLightPosition.xyz); // 主光源方向平行光 float3 H normalize(V L); // 半角向量 float NdotL max(dot(N, L), 0.0001); // 防止除零 float NdotV max(dot(N, V), 0.0001); float NdotH max(dot(N, H), 0.0); float VdotH max(dot(V, H), 0.0); // 2. 计算漫反射项 (Lambert 非金属部分) float3 F0 lerp(0.04, albedoColor.rgb, metallic); // 基础反射率非金属约0.04 float3 kS fresnelSchlick(VdotH, F0); // 菲涅尔反射系数 float3 kD (1.0 - kS) * (1.0 - metallic); // 能量守恒漫反射部分 float3 diffuseBRDF kD * albedoColor.rgb / PI; // Lambert漫反射 // 3. 计算镜面反射项 (Cook-Torrance) // 法线分布函数 (GGX/Trowbridge-Reitz) float alpha roughness * roughness; // 迪士尼参数化将roughness平方后使用更符合感知 float alpha2 alpha * alpha; float denomNDF (NdotH * NdotH) * (alpha2 - 1.0) 1.0; float D alpha2 / (PI * denomNDF * denomNDF); // 几何函数 (Smith-SchlickGGX) float k (roughness 1.0) * (roughness 1.0) / 8.0; // 直接光照的k float G1_NdotL NdotL / (NdotL * (1.0 - k) k); float G1_NdotV NdotV / (NdotV * (1.0 - k) k); float G G1_NdotL * G1_NdotV; // 菲涅尔方程 (Schlick近似) float3 F kS; // 上面已经用半角向量H计算过了这里直接使用 // Cook-Torrance BRDF float3 specularBRDF (D * G * F) / max(4.0 * NdotV * NdotL, 0.0001); // 4. 合并直接光照 float3 directLightColor _MainLightColor.rgb; float3 directRadiance (diffuseBRDF specularBRDF) * directLightColor * NdotL;这段代码是PBR的心脏。fresnelSchlick是一个自定义函数计算菲涅尔反射。注意F0的计算对于非金属metallic0它是一个常数约0.04对应常见电介质的反射率对于金属metallic1它就是Albedo颜色本身。这完美体现了金属度工作流的优雅。4.4 加入环境光照仅有直接光是不够的。我们加入基于图像的光照Image Based Lighting, IBL来模拟环境贡献。// 5. 环境光照简化版IBL // 采样环境立方体贴图这里使用Unity内置的采样函数需要SH数据和反射探针 float3 ambientSH SampleSH(N); // 球谐函数采样用于漫反射环境光 float3 diffuseIBL ambientSH * albedoColor.rgb * ao; // 漫反射环境光 * AO // 对于镜面反射环境光我们使用粗糙度来采样预过滤的环境贴图mipmap float3 R reflect(-V, N); float mipLevel roughness * UNITY_SPECCUBE_LOD_STEPS; // 根据粗糙度选择mip层级 float4 encodedSpecularIBL SAMPLE_TEXTURECUBE_LOD(unity_SpecCube0, samplerunity_SpecCube0, R, mipLevel); float3 specularIBL DecodeHDREnvironment(encodedSpecularIBL, unity_SpecCube0_HDR); // 解码HDR // 环境光菲涅尔使用NdotV而非VdotH float3 F_env fresnelSchlickRoughness(NdotV, F0, roughness); float3 kS_env F_env; float3 kD_env (1.0 - kS_env) * (1.0 - metallic); float3 ambient kD_env * diffuseIBL specularIBL * kS_env; // 合并环境光贡献fresnelSchlickRoughness是另一个考虑了粗糙度的菲涅尔近似因为对于粗糙表面菲涅尔效应会减弱。SampleSH和SAMPLE_TEXTURECUBE_LOD是Unity提供的工具函数它们背后关联着场景的光照探针和反射探针数据。这是将物体融入环境的关键。4.5 最终输出最后合并所有光照贡献并输出。// 6. 最终颜色合成 float3 finalColor directRadiance ambient; // 7. 色调映射简单的Reinhard finalColor finalColor / (finalColor 1.0); // 8. 伽马校正如果渲染到sRGB目标如显示器 finalColor pow(finalColor, 1.0/2.2); return float4(finalColor, 1.0);我们加入了简单的Reinhard色调映射来防止高光区域过曝并进行了伽马校正确保颜色在sRGB显示器上显示正确。在实际的URP/HDRP管线中这些步骤通常由后处理栈完成。5. 常见问题、性能考量与实战技巧当你把上面的Shader写出来并应用到模型上后可能会遇到各种问题。这里记录一些典型的坑和优化思路。5.1 材质看起来“不对”问题排查清单问题现象可能原因解决方案材质整体发灰、发白Albedo贴图包含了光照信息如烘焙的光照贴图或值域过高。检查Albedo贴图确保它是纯漫反射颜色在sRGB空间下颜色值通常不应超过0.8线性空间约0.6。使用校色工具校正。金属部分看起来像塑料1. 金属度贴图设置错误该白的不白。2. Albedo颜色对于金属部分太暗或饱和度太低。1. 检查Metallic贴图纯金属区域应为白色255。2. 纯金属的Albedo应设置为该金属的反射颜色如铜为橙黄色金为金黄色而非灰色。高光非常模糊或“油腻”粗糙度值整体偏高或粗糙度/光滑度通道对应错误。明确你的工作流。如果是粗糙度工作流黑色光滑白色粗糙。检查贴图并尝试调低_RoughnessScale。边缘没有菲涅尔效应不亮菲涅尔计算错误或环境光特别是镜面反射IBL太弱。检查菲涅尔函数fresnelSchlick的实现。确保环境立方体贴图天空盒或反射探针足够亮且HDR范围正确。法线贴图效果怪异或反向法线贴图YG通道方向错误OpenGL与DirectX格式差异。在Unity导入设置中尝试勾选或取消勾选“Create from Grayscale”下的“Flip Green (Y) Channel”。接缝处有颜色或光照断裂模型UV有接缝或者切线空间计算在接缝处不连续。这是切线空间法线贴图的固有缺陷。尽量优化模型UV减少不必要的接缝。对于硬表面可以考虑使用对象空间法线贴图但会失去模型自由旋转的能力。5.2 性能优化要点PBR计算比Phong复杂得多在移动端或大量物体渲染时性能是必须考虑的。简化BRDF在移动端可以考虑使用更简单的模型如Blinn-Phong的物理修正版或者使用查找纹理LUT来预计算部分BRDF积分。减少纹理采样如前所述使用MRAO等打包贴图。对于远处物体可以使用纹理数组或虚拟纹理减少采样次数。简化IBL对于动态物体完全实时采样立方体贴图代价高。可以使用低分辨率的反射探针。对于漫反射IBL用低阶球谐函数SH代替采样代价极低且效果不错。对于镜面反射IBL使用预积分环境贴图预过滤的GGX mipmap链并配合BRDF积分贴图2D LUT这是业界标准做法Unity内置管线已实现。Shader变体与关键字剔除使用Shader变体来为不同质量的设备编译不同复杂度的版本。例如低配机可以关闭法线贴图、使用更简单的环境光等。5.3 美术制作流程建议从真实测量数据开始在Substance Source或类似库中有很多基于真实世界测量的PBR材质参数如基础色、金属度、粗糙度。从这些数据开始调整比凭空捏造要快得多也准确得多。使用线性空间工作流确保Unity项目颜色空间设置为“Linear”。在Gamma空间下做PBR颜色混合和光照计算都是错误的很难调出正确效果。善用参考图找一张在理想光照环境如均匀的穹顶光下拍摄的真实物体照片作为参考在同样的HDR环境光下对比你的模型这是最有效的调试方法。分层绘制思维在Substance Painter中绘制材质时遵循“基底材质 - 宏观磨损 - 微观细节 - 污渍尘埃”的层次。每一层都对应地影响Albedo、Roughness、Metallic和Normal这样制作出的材质富有变化且物理可信。6. 从基础到进阶PBR管线扩展掌握了基础PBR你的画面已经能打败90%的自研引擎了。但要追求影视级或3A级的质感还需要了解以下扩展方向。6.1 清漆与多层材质很多真实材质并非单一层比如涂了清漆的木头、带了氧化层的金属。这需要多层材质模型。一种常见的简化方法是使用清漆层在基础PBR材质之上再叠加一个薄薄的、独立的微表面层通常是光滑的、非金属的并单独计算其反射。这可以通过额外的粗糙度、法线和菲涅尔参数来控制在Shader中意味着多一次BRDF计算和混合。6.2 各向异性基础PBR假设微表面是各向同性的即各个方向的粗糙度一致。但拉丝金属、CD光盘、头发等材质具有方向性的高光这就是各向异性。实现上通常需要一张方向图Flow Map来扰动法线分布函数NDF的朝向或者使用专门的法线分布函数如Anisotropic GGX。6.3 次表面散射对于蜡、玉石、皮肤、牛奶等半透明材质光线会进入表面下方经过多次散射后再从另一点射出形成柔和的透光效果。这是次表面散射。实时渲染中常用简化模型如屏幕空间次表面散射SSSSS或预积分的皮肤着色模型。它需要额外的厚度图、散射颜色和强度参数。6.4 高度图与视差效果法线贴图只能模拟光照变化不能改变轮廓和遮挡关系。高度图或称视差贴图存储了表面的真实高度信息。通过视差映射技术可以在着色时根据视线方向偏移纹理坐标模拟出简单的几何凹凸让砖缝真的“凹”进去鹅卵石真的“凸”出来极大地增强立体感。这是提升材质细节性价比极高的手段。PBR不是一个终点而是一个新的起点。它提供了一套可靠、可预测的框架让你能基于物理规律去构建光影而不是与虚幻的参数搏斗。当你理解了能量如何在材质表面分配理解了微表面如何与光线舞蹈你便获得了一种“翻译”现实世界到数字世界的能力。从今天起尝试用PBR的思维去观察身边的物体手机外壳的金属光泽、笔记本封皮的粗糙触感、水杯壁上的反光……你会发现那些曾经习以为常的视觉现象背后都有一套简洁优美的数学公式在描述。而这正是技术美术工作的迷人之处——在艺术与科学的交叉点上创造令人信服的虚拟真实。