☰
URP PBR渲染原理与实战:从BRDF到Lit Shader调参指南
2026/9/30 13:02:38 网站建设 项目流程

1. 从“光照看起来不对劲”说起:PBR到底在解决什么问题

很多人第一次在URP里搭场景,把材质往物体上一挂,Directional Light一打,发现效果跟想象中差得远——金属不像金属,塑料不像塑料,暗部死黑,高光要么糊成一团要么刺眼得离谱。然后开始疯狂调参数:Smoothness拉到0.9,Metallic推到1,结果更奇怪了。这个场景我见过太多次,包括我自己早期也是这样过来的。

问题的根源不在于参数没调对,而在于没有理解PBR这套体系到底在做什么。PBR(Physically Based Rendering,基于物理的渲染)不是一个Shader的名字,也不是某个特定效果,它是一整套关于“光如何与表面交互”的数学建模框架。它的核心目标是:让材质在不同光照环境下表现一致、让美术参数具有物理意义、让渲染结果可预测。

URP管线下的PBR实现,跟内置管线有相似之处,但也有很多关键差异。URP用的是Lit Shader作为标准PBR材质,底层基于BRDF(Bidirectional Reflectance Distribution Function,双向反射分布函数)模型,具体来说是Cook-Torrance BRDF的简化版本。这套模型把反射拆成两大部分:漫反射(Diffuse)和镜面反射(Specular),然后通过能量守恒把两者约束在一起。

为什么这件事值得花时间搞清楚?因为一旦你理解了PBR的底层逻辑,调材质就不再是“试参数”,而是“有目的地选择数值”。你知道Metallic=1意味着什么、Smoothness=0.5对应什么粗糙度、为什么非金属的Specular不能乱调。这些不是玄学,是可以推导的。

这篇文章适合谁看?如果你已经在Unity里用过URP,能搭基本的场景和光照,但对PBR的理解还停留在“跟着教程调参数”的阶段,那这篇笔记正好对路。如果你是完全的新手,建议先把URP的基础渲染流程跑通再来看,效果会更好。接下来我会从BRDF的数学结构讲起,然后落到URP Lit Shader的具体参数,再展开到Shader Graph里的自定义PBR实现,最后聊一些实际项目中容易踩的坑。

2. BRDF拆解:Cook-Torrance模型里每一项到底在算什么

2.1 漫反射项:Lambert为什么“不够用”了

最基础的漫反射模型是Lambert,公式简单到不能再简单:Diffuse = Albedo / π * NdotL。这个/π很多人不理解为什么要除,其实是为了能量守恒——半球积分下来,入射能量等于出射能量,不除π的话反射能量会凭空多出来。

但在PBR体系里,Lambert被保留了下来,因为它对大多数非金属表面的漫反射描述已经足够好。URP的Lit Shader里,漫反射部分用的就是Lambert的变体,只不过Albedo会被Metallic参数调制——金属没有漫反射,所以当Metallic=1时,Diffuse项直接归零。

这里有个容易搞混的点:Albedo在PBR里不是“颜色”,而是“基础反射率”。对于非金属,它表示漫反射颜色;对于金属,它表示镜面反射的F0(垂直入射时的反射率)。所以你在URP里调Base Map的时候,实际上是在同时影响两个通道,具体走哪条路取决于Metallic的值。

2.2 镜面反射项:D、G、F三项的物理含义

Cook-Torrance的镜面反射项是PBR的核心,公式长这样:

Specular = (D * G * F) / (4 * NdotL * NdotV)

分子里的D、G、F分别对应三个物理现象:

D项(Normal Distribution Function,法线分布函数):描述微表面法线的统计分布。URP默认用的是GGX分布,公式里涉及粗糙度参数α。α越小,微表面法线越集中,高光越锐利;α越大,法线越分散,高光越模糊。Smoothness和α的关系是α = (1 - Smoothness)^2,这个平方关系是很多人没注意到的——Smoothness从0.9降到0.8,α从0.01变到0.04,高光面积直接翻了四倍。

G项(Geometry Function,几何遮蔽函数):描述微表面之间的自遮挡。当光线以掠射角入射时,微表面的凸起会挡住部分反射光。URP用的是Smith-GGX联合模型,跟D项共享粗糙度参数。这一项在掠射角下影响很大,如果忽略它,物体边缘会出现不自然的亮边。

F项(Fresnel,菲涅尔项):描述反射率随视角变化的现象。垂直看水面,反射很弱;斜着看,反射很强。URP用的是Schlick近似:F = F0 + (1 - F0) * (1 - NdotV)^5。F0对于非金属大约是0.04(对应Specular=0.5),对于金属则等于Albedo。

这三项乘在一起,再除以4 * NdotL * NdotV做归一化,就是完整的镜面反射贡献。分母里的4来自微表面模型的推导,不是随便写的。

2.3 能量守恒:Metallic和Smoothness的联动逻辑

PBR的能量守恒体现在:Diffuse + Specular <= 1。反射出去的能量不能超过入射能量。URP里通过两个方式保证这一点:

  • Metallic增大时,Diffuse按(1 - Metallic)衰减,Specular的F0按lerp(0.04, Albedo, Metallic)增大。
  • Specular项本身在掠射角下会自然增强,但不会超过1。

这意味着你调Metallic的时候,实际上是在“把能量从漫反射通道搬到镜面反射通道”。Metallic=0.5不是“半金属”,而是一个物理上不存在的混合状态,实际项目中应该尽量避免中间值,除非你在做特殊效果。

Smoothness则控制高光的“锐度”,不影响总能量。但它会通过G项间接影响能量分布——粗糙表面在掠射角下能量损失更多。

注意:URP的Lit Shader里有一个Specular Highights选项,关闭后Specular项会被简化,性能提升但物理准确性下降。移动端项目里常见这个取舍。

3. URP Lit Shader参数面板:每个滑条背后的物理量

3.1 Surface Options:不光是“表面类型”那么简单

URP Lit Shader的Surface Options里有几个选项,很多人直接默认不管,但其实它们影响渲染路径和性能:

Workflow Mode有Metallic和Specular两个选项。Metallic模式用Metallic+Smoothness控制,Specular模式用Specular Color+Smoothness控制。两者在数学上等价,但Metallic模式更直观,Specular模式更适合非金属材质的精细调节。实际项目中,金属材质用Metallic模式,非金属用Specular模式,这个分工比较合理。

Surface Type的Opaque和Transparent影响渲染队列和深度写入。Transparent会关闭深度写入,导致排序问题。如果只是需要简单的透明度,优先考虑Alpha Clipping而不是Transparent,前者保留深度写入,性能更好。

Render Face的Front/Back/Both控制剔除面。双面材质在URP里需要额外注意——Both会渲染两个Pass,性能翻倍。如果只是需要单面透明效果,用Front配合Alpha Clipping更划算。

Alpha Clipping开启后,Alpha低于阈值的像素直接丢弃。这个阈值(Threshold)的默认值是0.5,但实际项目中要根据贴图调整。比如树叶贴图,阈值太低会导致边缘残留,太高会吃掉细节。

3.2 Surface Inputs:Base Map和Normal Map的配合

Base Map的Alpha通道在Opaque模式下会被忽略,但在Alpha Clipping或Transparent模式下会参与计算。Normal Map的导入设置很关键——Texture Type必须设为Normal Map,否则Unity不会做正确的解压和空间转换。

Normal Map的强度通过Normal Map Scale参数控制,但这个参数实际上是在Shader里对法线做lerp,不是缩放贴图。强度为0时法线完全展平,强度为1时使用原始法线。超过1的值会导致法线过强,出现不自然的硬边。

Occlusion Map影响间接光的遮蔽,但在URP里它只影响GI部分,不影响直接光照。如果场景没有烘焙GI,这张贴图基本没用。Emission Map则是自发光,配合Emission Color使用,HDR颜色可以超过1,配合Bloom后处理能出辉光效果。

3.3 高级选项:Specular Highlights和Environment Reflections

这两个开关在Lit Shader的Advanced Options里,默认都是开启的。关闭Specular Highlights会跳过D、G、F的计算,直接用Albedo作为高光颜色,性能提升明显但物理准确性下降。关闭Environment Reflections则跳过环境反射采样,适合性能敏感的场景。

Reflections的采样源取决于场景设置:如果开了Reflection Probe,就从Probe采样;否则从Skybox采样。URP里Reflection Probe的混合模式有Simple和Blended两种,Blended支持多个Probe平滑过渡,但性能开销更大。

提示:移动端项目里,如果场景没有Reflection Probe,Environment Reflections会从Skybox采样,这可能导致室内场景出现不自然的天空反射。解决办法是在室内放一个Reflection Probe,或者关闭Environment Reflections改用纯色环境光。

4. 在Shader Graph里手搓一个PBR:从节点到物理

4.1 为什么要在Shader Graph里重写PBR

URP自带的Lit Shader已经够用了,为什么还要在Shader Graph里自己搭?原因有几个:需要自定义光照模型(比如卡通渲染的阶梯高光)、需要加特殊的材质效果(比如清漆层、各向异性)、或者需要优化性能(去掉用不到的功能)。

Shader Graph里的PBR实现,核心是把Cook-Torrance的公式用节点连出来。URP提供了BRDF节点和Lighting节点,但如果你想完全控制,可以自己用数学节点搭。

4.2 关键节点的连接逻辑

一个基础的PBR Shader Graph大概长这样:

  • Normal Vector节点获取世界空间法线,经过Normal Map的扰动后输出。
  • View Direction节点获取视线方向,用于计算NdotV。
  • Light Direction节点获取主光方向,用于计算NdotL。
  • Fresnel Effect节点可以直接算F项,但URP的Fresnel节点用的是简化模型,不如手写Schlick准确。
  • Custom Function节点可以嵌入HLSL代码,适合实现GGX分布和Smith几何项。

D项的实现用Custom Function比较方便,因为涉及幂运算和除法,用节点连会很乱。G项和F项可以用节点搭,但要注意精度问题——Shader Graph默认用half精度,在移动端可能导致高光断裂。

4.3 自定义光照的接入点

Shader Graph里的Lighting节点负责把BRDF结果和光照颜色乘起来。如果你想改光照模型(比如把Lambert换成Half-Lambert),需要替换Diffuse的计算部分。URP的Lighting节点有一个Custom Light节点,可以接入自定义的光照计算。

实际项目中,我一般会把PBR拆成两部分:直接光照用自定义节点算,间接光照用URP自带的SH(Spherical Harmonics)采样。这样既能控制直接光的BRDF,又能保留URP的GI系统。

注意:Shader Graph里手搓PBR的性能不一定比Lit Shader好,因为节点生成的HLSL代码可能有多余计算。如果只是需要微调,优先考虑修改Lit Shader的源码或者用Material Property覆盖。

5. 实际项目里的PBR踩坑记录

5.1 金属材质在暗环境下“消失”

这是最常见的问题:金属材质在暗部完全看不见,因为金属没有漫反射,只有镜面反射,而镜面反射依赖环境光。如果场景没有Reflection Probe,金属在暗部就是一片黑。

解决办法有两个:一是放Reflection Probe,二是给金属加一点Emission作为“伪环境光”。但Emission会破坏能量守恒,所以数值要很小,通常0.01到0.05就够了。

5.2 Smoothness贴图的通道问题

很多美术给的Smoothness贴图是灰度图,但URP的Metallic Map把Metallic放在R通道,Smoothness放在A通道。如果直接导入灰度图,Smoothness会从Alpha通道读取,而灰度图的Alpha通常是1,导致Smoothness全为1,高光锐利得不自然。

解决办法是在导入设置里把Texture Type设为Default,然后取消sRGB勾选,再在Shader里用Remap节点把灰度值映射到Smoothness。或者让美术直接输出正确的通道打包贴图。

5.3 法线贴图的绿色通道翻转

不同软件对法线贴图的绿色通道定义不同(OpenGL是Y+,DirectX是Y-)。Unity用的是OpenGL标准,如果从其他软件导入的法线贴图看起来凹凸相反,需要在导入设置里勾选Flip Green Channel。

这个问题在URP里比内置管线更常见,因为URP的Normal Map解压方式有变化。如果翻转后还是不对,检查一下Normal Map的压缩格式——URP默认用DXT5nm,这个格式只保留了两个通道,第三个通道是重建的。

5.4 透明材质的排序问题

URP的Transparent模式默认按物体中心排序,对于大面积的透明物体(比如水面),排序经常出错。解决办法是用Sorting Priority手动指定渲染顺序,或者改用Alpha Clipping加Dither。

如果必须用Transparent,可以在Shader里开启Depth Write,但这样会导致半透明物体互相遮挡时出现硬边。折中方案是用Depth Priming,在URP Asset里开启后,不透明物体先写深度,透明物体再渲染,能减少大部分排序问题。

提示:URP 12以上的版本支持Forward+渲染路径,透明物体的排序质量比Forward好很多,但需要硬件支持Compute Shader。移动端项目要谨慎评估。

6. 从BRDF到画面:一些调参之外的经验

PBR的数学框架是固定的,但实际画面效果还受很多因素影响。后处理里的Color Grading、Tonemapping、Bloom都会改变PBR的最终表现。比如ACES Tonemapping会压缩高光,让金属的高光不那么刺眼;Bloom则会放大高光区域,让金属看起来更“亮”。

光照强度的单位也很关键。URP默认用Lux作为光照单位,但很多人习惯用Intensity的任意值。如果光照强度不对,PBR的参数再准也没用。一般来说,Directional Light的Intensity在1到3之间比较合理,Point Light要根据距离衰减调整。

环境光的设置容易被忽略。URP的Environment Lighting有Skybox、Gradient、Color三种模式。Skybox模式最真实但性能开销大,Gradient模式适合风格化场景,Color模式最简单但容易让暗部死黑。实际项目中,我一般用Gradient模式,Sky颜色稍亮,Equator颜色接近地面反射色,Ground颜色稍暗。

最后说一个反直觉的经验:PBR材质在风格化场景里不一定好看。卡通渲染、低多边形风格、像素风这些场景,用PBR反而会让画面显得“脏”。这时候要么改用Unlit Shader,要么把PBR的Specular和Smoothness压到很低,让画面更干净。工具是死的,画面是活的,知道什么时候不用PBR,比知道怎么用PBR更重要。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询