一、什么是 PBR?

PBR(Physically Based Rendering,基于物理的渲染)是现代游戏、影视行业的主流渲染方案。

与传统的 Blinn-Phong 光照相比,PBR 的核心区别在于:

对比项 传统光照(Blinn-Phong) PBR
材质参数 高光颜色、高光强度(艺术调整) 金属度、粗糙度(物理可测量)
能量守恒 ❌ 无保证 ✅ 严格遵守
跨光照一致性 ❌ 不同环境需重调 ✅ 任何光照下表现正确
效果上限 中等 照片级真实感

PBR 广泛应用于:《赛博朋克2077》《艾尔登法环》《战神》等 AAA 大作,以及 Unity、Unreal、Blender 等主流引擎的默认材质系统。

📌 一句话理解:PBR 是用物理规律约束美术行为——只要参数符合现实,材质在任何光照环境下都自然正确。



二、PBR 的三大核心条件

一个渲染系统要称得上 PBR,必须同时满足以下三条:

① 微表面理论(Microfacet Theory)

宏观上看起来光滑的表面,微观上由无数朝向各异的**微小镜面(微平面)**组成。

粗糙表面(高 Roughness):     光滑表面(低 Roughness):

  ↗↘↗↙↗↘↗↙                      → → → → → →
  微平面朝向分散                  微平面近乎平行
  → 高光模糊、分散               → 高光锐利、集中

**粗糙度(Roughness)**参数控制微平面的分散程度:

  • Roughness = 0:完美镜面,高光极小极亮
  • Roughness = 1:完全漫反射,无明显高光

② 能量守恒(Energy Conservation)

反射出去的光能 ≤ 接收到的光能,不能无中生有。

入射光  =  漫反射  +  镜面反射  +  吸收
  1.0   =    kD    +    kS     + 损耗

实现方式:

hlsl

复制

float3 F  = FresnelSchlick(HdotV, F0);   // 镜面反射比例
float3 kS = F;
float3 kD = (1.0 - kS) * (1.0 - metallic); // 漫反射 = 1 - 镜面
// 金属材质无漫反射(metallic=1 → kD=0)

③ 基于物理的 BRDF

使用符合物理规律的双向反射分布函数描述光照交互,详见下一章。


三、核心数学:Cook-Torrance BRDF

BRDF(Bidirectional Reflectance Distribution Function,双向反射分布函数)定义了入射光在某观察方向上被反射的比例。

PBR 中最常用的是 Cook-Torrance BRDF

                 D(h) · F(v,h) · G(l,v,h)
fr(l, v) = kD·------ + ─────────────────────
                  π         4·(n·l)·(n·v)

  漫反射项(Lambert)    镜面反射项(Cook-Torrance)

其中 D、F、G 三项各司其职:



3.1 D —— 法线分布函数(GGX)

描述有多少微平面的法线朝向了半角向量 h,决定高光形状

GGX(Trowbridge-Reitz) 是当前工业标准:

hlsl

复制

float DistributionGGX(float NdotH, float roughness)
{
    float a  = roughness * roughness;
    float a2 = a * a;
    float NdotH2 = NdotH * NdotH;
    float denom  = (NdotH2 * (a2 - 1.0) + 1.0);
    return a2 / (PI * denom * denom);
}

为什么用 roughness²? 直接用 roughness 线性值时高光变化不自然,平方后符合人眼感知的线性变化。

GGX 相比老牌的 Beckmann 分布,优势在于**"长尾效应"**:高光边缘有更自然的渐隐,非常接近真实金属质感。


3.2 F —— 菲涅耳方程(Fresnel)

描述不同入射角下镜面反射率的变化——观察角越斜,反射越强

生活中随处可见菲涅耳效应:正视水面看到水底,侧视水面看到倒影。

垂直观察(grazing = 0):   掠射观察(grazing = 90°):
  反射率 ≈ F0(基础值)        反射率 → 1.0(几乎全反射)

使用 Schlick 近似 高效计算:

hlsl

复制

float3 FresnelSchlick(float cosTheta, float3 F0)
{
    return F0 + (1.0 - F0) * pow(saturate(1.0 - cosTheta), 5.0);
}

F0(基础反射率) 由材质决定:

  • 非金属(塑料、皮肤、木头):F0 ≈ float3(0.04, 0.04, 0.04)(约 4%)
  • 金属:F0 = Albedo 颜色(金的 F0 是金黄色,铁的 F0 是灰色)

hlsl

复制

// 金属工作流中 F0 的计算
float3 F0 = lerp(float3(0.04, 0.04, 0.04), albedo, metallic);

3.3 G —— 几何遮蔽函数(Smith)

描述微平面之间的自遮挡和自阴影,粗糙表面的微平面会互相遮挡,导致能量损失。

             ↓ 光线被相邻微平面遮挡
    ↗↘↗↘↗↘↗↘
    ←→←→←→←→   ← 部分出射光被阻挡(几何遮蔽)

使用 Smith + Schlick-GGX 组合:

hlsl

复制

float GeometrySchlickGGX(float NdotX, float roughness)
{
    float r = roughness + 1.0;
    float k = (r * r) / 8.0;          // 直接光照用此 k 值
    return NdotX / (NdotX * (1.0 - k) + k);
}

float GeometrySmith(float NdotV, float NdotL, float roughness)
{
    // 分别计算视线方向和光线方向的遮蔽,相乘
    return GeometrySchlickGGX(NdotV, roughness)
         * GeometrySchlickGGX(NdotL, roughness);
}

四、金属度/粗糙度工作流

现代 PBR 流程统一使用 Metallic-Roughness 工作流,一套贴图打天下:

贴图 通道 含义
Albedo(BaseColor) RGB 固有色(金属存储 F0,非金属存储漫反射色)
Metallic R(灰度) 金属度:0 = 非金属,1 = 金属
Roughness R(灰度) 粗糙度:0 = 完全光滑,1 = 完全粗糙
Normal RGB 法线贴图(增加表面细节)
AO R(灰度) 环境遮蔽(凹陷处变暗)


典型材质参数参考

材质 Metallic Roughness F0
黄金 1.0 0.1 (1.0, 0.77, 0.34)
1.0 0.6 (0.56, 0.57, 0.58)
塑料(光滑) 0.0 0.1 (0.04, 0.04, 0.04)
皮肤 0.0 0.7 (0.03, 0.03, 0.03)
木头 0.0 0.8 (0.04, 0.04, 0.04)

五、完整 Shader 实现(URP)

5.1 Shader 属性声明

hlsl

复制

Shader "Custom/PBR"
{
    Properties
    {
        _AlbedoMap    ("Albedo",    2D)    = "white" {}
        _AlbedoColor  ("Albedo Color", Color) = (1,1,1,1)
        _NormalMap    ("Normal Map", 2D)   = "bump"  {}
        _MetallicMap  ("Metallic",  2D)    = "black" {}
        _Metallic     ("Metallic",  Range(0,1)) = 0
        _RoughnessMap ("Roughness", 2D)    = "white" {}
        _Roughness    ("Roughness", Range(0,1)) = 0.5
        _AOMap        ("AO Map",    2D)    = "white" {}
    }

    SubShader
    {
        Tags { "RenderType"="Opaque" "RenderPipeline"="UniversalPipeline" }

        Pass
        {
            Name "ForwardLit"
            Tags { "LightMode"="UniversalForward" }

            HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile _ _MAIN_LIGHT_SHADOWS _MAIN_LIGHT_SHADOWS_CASCADE

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

            #define PI 3.14159265358979

5.2 D、F、G 三大函数

hlsl

复制

// ── D:GGX 法线分布函数 ──────────────────────────
float D_GGX(float NdotH, float roughness)
{
    float a  = roughness * roughness;
    float a2 = a * a;
    float d  = (NdotH * NdotH * (a2 - 1.0) + 1.0);
    return a2 / (PI * d * d);
}

// ── F:菲涅耳(Schlick 近似)────────────────────
float3 F_Schlick(float cosTheta, float3 F0)
{
    return F0 + (1.0 - F0) * pow(saturate(1.0 - cosTheta), 5.0);
}

// ── G:几何遮蔽(Smith + Schlick-GGX)───────────
float G_SchlickGGX(float NdotX, float roughness)
{
    float r = roughness + 1.0;
    float k = (r * r) / 8.0;
    return NdotX / (NdotX * (1.0 - k) + k);
}

float G_Smith(float NdotV, float NdotL, float roughness)
{
    return G_SchlickGGX(NdotV, roughness) * G_SchlickGGX(NdotL, roughness);
}

5.3 顶点着色器

hlsl

复制

struct Attributes
{
    float4 positionOS : POSITION;
    float3 normalOS   : NORMAL;
    float4 tangentOS  : TANGENT;
    float2 uv         : TEXCOORD0;
};

struct Varyings
{
    float4 positionCS : SV_POSITION;
    float3 positionWS : TEXCOORD0;
    float3 normalWS   : TEXCOORD1;
    float3 tangentWS  : TEXCOORD2;
    float3 bitangentWS: TEXCOORD3;
    float2 uv         : TEXCOORD4;
};

Varyings vert(Attributes IN)
{
    Varyings OUT;
    VertexPositionInputs posInputs = GetVertexPositionInputs(IN.positionOS.xyz);
    VertexNormalInputs   normInputs = GetVertexNormalInputs(IN.normalOS, IN.tangentOS);

    OUT.positionCS   = posInputs.positionCS;
    OUT.positionWS   = posInputs.positionWS;
    OUT.normalWS     = normInputs.normalWS;
    OUT.tangentWS    = normInputs.tangentWS;
    OUT.bitangentWS  = normInputs.bitangentWS;
    OUT.uv           = TRANSFORM_TEX(IN.uv, _AlbedoMap);
    return OUT;
}

5.4 完整片元着色器

hlsl

复制


六、法线贴图详解

法线贴图是 PBR 材质中非常重要的一环,它在不增加面数的情况下为模型表面添加细节。

无法线贴图:                 有法线贴图:
  平滑光照,无细节             凹凸感、划痕、纹理细节
  ┌─────────────┐             ┌~~~~~─────~~~~~┐
  │             │             │  表面有起伏感  │
  └─────────────┘             └~~~~~─────~~~~~┘

法线贴图的 RGB 通道存储的是切线空间中的法线方向:

  • R → X(左右偏移)
  • G → Y(上下偏移,注意 OpenGL/DX 方向差异)
  • B → Z(深度,通常偏蓝)

hlsl

复制

// URP 中解码法线贴图并变换到世界空间
float3 normalTS = UnpackNormal(SAMPLE_TEXTURE2D(_NormalMap, sampler_NormalMap, uv));
float3x3 TBN    = float3x3(tangentWS, bitangentWS, normalWS);
float3 N        = normalize(mul(normalTS, TBN));

七、色调映射与 Gamma 校正

PBR 计算在线性空间中进行,但显示器是 Gamma 空间,必须做两步处理:

步骤一:色调映射(Tone Mapping)

HDR(高动态范围)光照值映射到 LDR(0~1)显示范围:

hlsl

复制

// Reinhard 色调映射(简单实用)
color = color / (color + 1.0);

// ACES 色调映射(更接近电影调色,对比更强)
float3 ACESToneMapping(float3 x) {
    float a = 2.51, b = 0.03, c = 2.43, d = 0.59, e = 0.14;
    return saturate((x*(a*x+b))/(x*(c*x+d)+e));
}

步骤二:Gamma 校正

hlsl

复制

// 线性空间 → Gamma 2.2 空间(用于显示器输出)
color = pow(max(color, 0), 1.0 / 2.2);

注意:如果你使用的是 URP 并开启了 HDR + Post Processing,色调映射和 Gamma 校正通常由后处理管线自动完成,Shader 中无需手动处理。


八、效果对比:不同参数表现

Roughness 变化效果

Roughness: 0.0      0.2      0.5      0.8      1.0
           ●        ●        ●        ●        ●
         (镜面)   (抛光)   (半哑)   (磨砂)   (粉笔)
          小亮斑  略大亮斑  中等高光  模糊高光  几乎无高光

Metallic 变化效果

Metallic:  0.0                    1.0
           ●          →           ●
          (塑料)               (金属)
          高光白色            高光带颜色(随 Albedo)
          有漫反射             无漫反射(全镜面)

九、与 NPR 的组合:半写实风格

PBR 和 NPR 并非对立,现代游戏常常混合使用

游戏 策略
《原神》 PBR 材质工作流 + NPR 卡通光照 + SDF 面部阴影
《崩坏:星穹铁道》 PBR 底层 + 卡通描边 + Ramp 阴影
《蔚蓝档案》 强 NPR 卡通 + 少量 PBR 金属质感

混合方案核心思路:

  1. 用 PBR 工作流制作贴图(保证材质物理正确)
  2. 替换光照计算部分为 NPR 卡通化处理
  3. 保留 F0/菲涅耳让金属感真实

十、推荐学习资源

资源 链接 说明
LearnOpenGL PBR learnopengl.com 最权威的 PBR 入门教程
Unity 中文课堂 learn.u3d.cn 三节课掌握 PBR(中文)
毛星云 PBR 白皮书 知乎专栏 深度 PBR 理论完全解析
Substance 材质规范 AdobeHelp 工业级 PBR 材质制作指南

小结

本文系统讲解了 PBR 的完整知识体系:

  • ✅ 三大核心条件:微表面理论、能量守恒、物理 BRDF
  • ✅ DFG 三项:GGX 法线分布、Schlick 菲涅耳、Smith 几何遮蔽
  • ✅ 金属/粗糙度工作流:F0 插值、贴图规范
  • ✅ 完整 URP Shader:从顶点到片元的完整实现
  • ✅ 法线贴图:TBN 矩阵变换
  • ✅ 色调映射 + Gamma 校正:Reinhard / ACES

PBR 是现代渲染的基础,理解它不仅能写出更好的 Shader,也能与美术同学更高效地沟通材质制作规范。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐