第 31 章 光照模型:从 Phong 到 PBR,光是怎么”算”出来的
上一章我们讲了后处理、HDR、颜色空间——那都是”画面算完之后、或算的时候的全局规矩”。这一章,我带你往更底层钻一格:一个像素最后”有多亮、什么颜色”,到底是按什么公式算出来的?
你第 5 章学过”材质是光的脾气说明书”,第 6 章学过”漫反射看朝向、高光看角度”。但你可能一直有个没问出口的疑惑:
第 6 章那些”法线·光方向""半角向量”的公式,到底是谁定的?为什么高光用一个幂次收紧成亮斑?为什么金属和非金属的反光差那么多?这些”配方”是拍脑袋来的,还是有物理依据的?
这一章就回答这个。我把光照的”配方”从最老、最直觉的 Phong 模型,讲到今天主流的 PBR(基于物理的渲染)。你会发现:光照模型不是”某个写死的函数”,而是工程师一代代”逼近真实物理”的尝试,每一代都在修上一代的别扭。而你现在在 Unity 材质面板里拧的”金属度、粗糙度”,背后就是最新一代的配方。
老规矩:现象 → 现实问题 → 工程师方案 → 落进引擎成了哪些参数。
31.1 光照的”配方”:一个像素最终多亮,是 shader 按一套公式算出来的
先点破一个你可能模糊的认知。你第 5 章学过 Shader(着色器)是”材质的配方 + 厨师”,第 6 章学过光打在表面上产生明暗。现在把这两句话焊死:一个像素最后显示什么颜色,就是 Shader 把”材质参数 + 光照 + 视角”喂进一套固定公式,算出来的。
这套”固定公式”,图形学叫 光照模型(Lighting Model / Shading Model)。它回答一个朴素问题:已知表面这块皮的脾气(金属度、粗糙度、颜色)、已知有哪几盏灯、已知你(相机)从哪看——这块皮的某个点,最后该多亮、什么色?
你写过程序,就把光照模型想成一个函数:color = Shade(surface, lights, view)。输入一堆属性,输出一个 RGB。不同”光照模型”就是不同的 Shade 实现:老的实现粗糙、快、不真;新的实现复杂、慢、但像真。
连回第 5 章:你说”材质是光的脾气说明书”——对,那说明书里写的”金属度 0.8、粗糙度 0.2”,最终就是被光照模型这个函数读走、参与计算的。连回第 6 章:你说”漫反射看朝向、高光看角度”——对,那”看朝向""看角度”的具体算法,就写在光照模型里。所以这一章是第 5、6 章的”底层揭盖”:之前你用的是结论,现在看结论是怎么来的。
反问你:你调材质时,有没有把”金属度、粗糙度”当成”美术凭感觉给的数”?这一章结束后,你会知道它们其实是”真实物理属性的近似”——这区别,决定了你能不能调对。
31.2 漫反射的物理来源:朗伯余弦律,以及环境光是”间接光的乞丐版”
我们从最老老实实的一项——漫反射——讲起,因为它物理来源最清楚。
你第 6 章 6.2 学过:表面越正对光越亮,这叫朗伯余弦律(Lambert’s cosine law)——表面接收到的光强正比于”法线”和”光方向”夹角的余弦。当时你算过点积 max(0, N·L),知道了”球面因为法线连续渐变所以有柔和明暗过渡”。
现在我把”朗伯余弦律”在光照模型里的位置钉死:它是漫反射项的核心。一个最朴素的光照模型,漫反射部分就这么写:
漫反射颜色 = 物体本色(Albedo) × 光颜色 × 光强度 × max(0, N·L)
N·L就是”这块表面正对光的程度”(第 6 章的法线点积)。Albedo是物体本来颜色(第 5 章的反照率)。- 光颜色和强度是第 6 章学的光源属性。
- 三者相乘(回想第 6 章 6.5 节说的”光照颜色是乘上去的”),得到这块漫反射该亮的颜色。
所以”朗伯余弦律”不是一句形容,它就是漫反射公式里那个 N·L 项。你第 6 章算的那几道点积,正是所有光照模型漫反射部分都在重复的动作。物理基础硬,所以它从最老的模型到今天的 PBR 都没变——漫反射的物理就是”光被粗糙表面打散、按朝向分配”,这点工程师早就算明白了。
接着说环境光(Ambient Light)。你第 6 章 6.4 节学过:背光面不是纯黑,因为天空、四周墙都在往它反射微弱的光。现实里这叫”间接光”——光打到红墙反弹成红光、再染到旁边的白桌阴影里(第 6 章说的全局光照 GI)。可真算”光弹来弹去”贵到爆炸(第 6 章 6.4 节讲过)。
工程师的早期方案极粗糙:干脆给所有物体垫一层”均匀的环境色”,不管它在哪、朝哪。这就是环境光项:
环境光颜色 = 环境色 × 物体本色(Albedo)
它本质是对”间接光”的粗糙近似——一个连位置、朝向都不看的常量。好处是便宜到忽略,坏处是不真(真实间接光该随位置、随周围物体颜色变,比如红墙旁的物体该泛红,而均匀环境光让它泛同一个底色)。你第 6 章 6.4 节学的 Light Probe、第 6 章的环境光强度旋钮,都是在”把这份粗糙近似做细一点”。但记住它出身:环境光项是”GI 算不起”时的乞丐版补丁,是光照模型里最不物理的一项。
一句话带走:漫反射有硬物理(朗伯余弦律,
N·L),环境光是”间接光的近似补丁”。你调 Ambient Intensity 时,调的是”这份补丁多厚”——调太高,明暗对比被冲淡(第 6 章开头说过,全场均匀亮度就像塑料),因为你在用常量底色盖掉真实的明暗。
31.3 高光(镜面反射):Phong 用反射方向,Blinn-Phong 用半角向量
漫反射搞定了”不挑角度的柔光”,可现实里还有”挑角度的亮斑”——高光(第 6 章 6.3 节)。金属勺子的锐利反光、湿地面的刺眼白点,都是高光。光照模型得有一项专门算它。
回忆第 6 章 6.3 节的物理:高光是光按”入射角=反射角”反弹到特定方向,只有”反弹方向正好射进你眼”才看得见。所以高光强度 = “光反射方向”和”你视线方向”有多接近。
经典模型有两派,你第 6 章 6.3 节提过名字,这里讲清算法:
Phong 模型(1975 年,Bui Tuong Phong 提出):直接算”光反射方向 R”和”视线方向 V”的夹角余弦,再取一个幂次收紧成亮斑:
高光 = 光颜色 × pow( max(0, R·V), shininess )
R是光按反射定律算出的反射方向,V是你(相机)看向表面的方向。R·V接近 1 表示”反弹光正好射进你眼”,最亮。shininess(高光指数,也叫 specular exponent)是那个幂次——指数大,亮斑收得小且锐;指数小,亮斑摊得大且散。这就是你控制”高光多大一块”的旋钮,物理直觉是”表面多像镜子”。
Blinn-Phong 模型(Phong 的改进版):不真算反射方向 R,而是算”半角向量 H”——光方向 L 和视线 V 的”中间方向”,再看 H 多贴合法线 N:
H = normalize(L + V)
高光 = 光颜色 × pow( max(0, N·H), shininess )
- 谁的”半角 H”越贴合法线 N,高光越亮。
- 它和 Phong 数学上等价(在理想镜面下 H 和 R 对 V 的关系一致),但计算更便宜、数值更稳定(不用每次重算反射方向 R,且在掠射角不易出怪异),所以今天绝大多数实时引擎(包括 Unity 传统高光)用的是 Blinn-Phong。
你不用背公式,带走的就一句:高光 = “你视线”和”光该反弹去的方向”的吻合度,再被 shininess(= 光滑度/粗糙度)收紧成亮斑。 光滑度越高,亮斑越小越锐(镜子、金属球);越粗糙,亮斑越大越散(墙、橘子皮)。这回答了第 6 章 6.3 节”为什么金属勺子高光又强又小”:金属度高 → 漫反射弱、能量全跑到镜面项;光滑度高 → 亮斑收得极紧。
落进引擎:在老的非 PBR 材质(Legacy/Diffuse/Specular Shader)里,你能直接看到
Specular Color和Shininess这两个旋钮——它们就是这一节的高光颜色和shininess 指数。你拧 Shininess,就是在拧”亮斑多大”。而到了 PBR(31.5),这俩被”粗糙度 Roughness”一个参数取代,更物理。
31.4 传统模型的两大别扭:参数为啥不对应真实、特殊材质调不对
Phong / Blinn-Phong 用了几十年,可它有两处”别扭”,工程师越用越别扭,最终催生了 PBR。
别扭一:参数是”美术凭感觉调”的,不对应真实物理。
你看 Phong 的参数:高光颜色、Shininess 指数。这些数是”为了画面好看”调的,不是”量出来的物理”。同一个不锈钢勺,在不同光照下(正午、阴天、室内灯),你用 Phong 得为每种光重新调一遍 Shininess 和高光色,才能得到”像那么回事”——因为 Phong 没有”这是真不锈钢”这个物理事实撑着,它只是一堆可调的曲线。美术调参像”凭手感调滤镜”,换光就失真。
别扭二:金属、粗糙、半透、各向异性,很难调对。
- 金属:Phong 里金属的高光是”白亮一片”,可真实金属(金、铜)的高光是带自身颜色的(金勺偏黄)。Phong 的”高光颜色”要你手写金色,还得和漫反射配合,极易调成”塑料镀金”。
- 粗糙:Shininess 一个指数控制高光大小,可真实粗糙表面的高光不是”简单收放”,它和”微表面朝向分布”有关,单个指数表达不了。
- 半透(玉石、蜡烛、皮肤次表面):光会”钻进物体内部再透出来”,Phong 完全没这个概念,做出来像实心塑料。
- 各向异性(拉丝金属、头发、CD 纹):高光是”拉成一条”而不是”一个圆斑”,Phong 的对称亮斑表达不了,做出来像抛光塑料而非拉丝。
所以传统模型的问题一句话概括:它用”美术手调的近似曲线”去模仿物理,结果”不跨光照、不跨材质、特殊材质失真”。 你要花大量人力为每盏灯、每种材质单独调,还调不真。
这正回到你第 5 章学的那句话:PBR 的好处是”美术调一次材质,换到白天、夜晚、室内都还像那么回事,因为参数是物理的,不是为某一张图定制的”。下一节就讲 PBR 怎么做到。
反问你:你做项目时,是不是经常”换个灯光场景,材质就显假、得重调”?那一节你猜,PBR 凭什么能”一次调好、处处不假”?
31.5 PBR(基于物理的渲染)思想:能量守恒 + 微表面,两个参数管住所有材质
PBR 不是某个公式,是一套”按物理规律约束配方”的思想。它的两个核心支柱,你务必带走。
支柱一:能量守恒(Energy Conservation)。
物理铁律:一块表面”反射出去 + 漫反射出去”的光,不能超过”打进来的光”。你不能”越照越亮”——那违反能量守恒,现实中不存在。
传统 Phong 常常违反:美术把高光颜色和高光强度乱拉,结果反射光加起来超过入射光,物体莫名”自发光变亮”。PBR 把这条焊死:漫反射 + 镜面反射的总和 ≤ 入射光。 具体做法是:表面越”金属”、越”光滑”,镜面项占比越高,漫反射项就自动压低(能量被镜面抢走,不会凭空多出来)。所以 PBR 材质”不会越照越亮”,永远物理合理。
支柱二:微表面理论(Microfacet Theory)。
这是 PBR 最妙的一根。现实里没有绝对光滑的表面——你看着光可鉴人的镜子,放大到微米级也是”坑坑洼洼的无数微小斜面”。PBR 假设:任何表面都由无数朝向随机的微小平面的集合组成(微表面)。 所谓”粗糙度”,就是这些微小平面的朝向有多”整齐”:
- 粗糙度低(光滑,如镜子):微平面朝向高度一致(都差不多对着法线),反射光集中 → 高光聚成锐利小点。
- 粗糙度高(毛糙,如墙、橘子皮):微平面朝向乱七八糟,反射光朝四面八方散 → 高光摊成一大片柔光,甚至融入漫反射。
于是”粗糙度”一个参数,就把 Phong 里那个”Shininess 指数 + 各种补丁”全统一了——它物理上对应”微表面朝向的混乱度”,直接决定高光是聚是散。这是 PBR 比 Phong 优雅的地方:一个物理量,管住一片现象。
两个直观参数,取代一堆手调数:
- 金属度(Metalness):这表面是不是金属?0 是非金属(木头、塑料、皮肤,漫反射为主、高光是灰白),1 是金属(几乎不漫反射、高光带自身颜色如金偏黄)。它决定”反光颜色是彩色还是灰白”——这正解了 31.4 的”金属高光带色”难题:PBR 里金属度高,高光自动取 Albedo 的颜色(金子的 Albedo 是金,高光就是金),不用你手调高光色。
- 粗糙度(Roughness):表面多毛糙?0 镜面、1 极糙。它决定”高光多散、多柔”。这取代 Phong 的 Shininess,且物理含义明确。
为什么 PBR 让美术更可控、跨光照一致? 回到 31.4 的别扭:PBR 的参数是”金属度/粗糙度”这种物理属性,不是”为某光调的曲线”。所以同一块不锈钢(Metalness 1、Roughness 0.2),放到正午、阴天、室内,光照模型按真实物理算,都还像不锈钢——因为”它是金属、很光滑”这个事实不变,变的只是打它的光,而光本来就该变。美术调一次,处处不假。这正是第 5 章说的”用现实规律代替手调特效,反而省事又通用”。
各向异性、半透这些 PBR 也有扩展(各向异性粗糙度、次表面散射 SSS),但核心仍是”能量守恒 + 微表面”两支柱。你先用懂这两根,特殊材质是它们的延伸。
我给你一个能直接”看到”微表面的小例子,你就彻底不怕它了。想象一块抛光大理石桌面和一块磨砂墙面,都让一束平行光照上去:
- 大理石(粗糙度低):放大看,微平面几乎都”平躺着”朝着同一方向,入射光被整齐反弹,反射光集中到一个小方向——于是从那个方向看,你看到一个又小又亮的镜面高光,能隐约映出窗的形状。能量守恒在这里表现为:漫反射很弱(光几乎全去镜面项了),所以大理石底子不抢戏,全靠那道反光撑质感。
- 磨砂墙(粗糙度高):微平面朝四面八方乱躺,同一束入射光被反弹到满场都是,反射光分散——你从任何角度看都只看到”一片柔和的亮”,没有集中的亮斑,且这部分能量被摊薄,所以墙显得温润哑光、漫反射主导。
你看,同样一束光,微平面”整齐还是乱”,决定了高光是”聚成针尖”还是”散成一片”——这就是粗糙度的全部物理含义,一个参数管住 Phong 里要手调半天还调不对的现象。你调 Smoothness 滑块时,本质就是在调”这些微平面有多整齐”。
一句话收 PBR 思想:与其让美术手调”高光多亮、多尖”,不如给两个物理旋钮——金属度(反光什么色)、粗糙度(高光多散)——再用能量守恒和微表面把它们焊在真实物理上。调参从”凭感觉”变成”描述这块皮真实长啥样”。
31.6 现代 Unity 与 PBR:你第 5 章拧的滑块,背后就是这套
落到你天天用的 Unity。现代 Unity(URP 通用渲染管线、HDRP 高清渲染管线)里,默认材质 Lit(或 Standard)就是 PBR 材质。你第 5 章调过的”金属度、光滑度”滑块,背后就是 31.5 的 Metalness / Roughness:
Metallic滑块 = 金属度(0~1)。Smoothness滑块 = 1 - 粗糙度(光滑度高 = 粗糙度高反向)。你第 5 章见过”Smoothness(光滑度,=1-粗糙度)“这个说法,现在你知道它物理上就是”微表面多整齐”。Albedo贴图 = 物体本色,金属度高时它还兼职”高光颜色”(金子反光是金)。Normal Map(法线贴图)= 第 4/5 章的微观朝向扰动,在 PBR 里它微调”微表面整体朝向”,影响高光形态。
所以你第 5 章那些滑块,不是美术玄学,是 PBR 的物理参数。你当时”把金属度拉 1、光滑度拉高,不锈钢就出来”,正是 31.5 两支柱在起作用:金属度决定反光带色、光滑度决定高光锐利。
关键连回第 30 章:PBR 必须在线性空间算才对!你第 30 章学了,颜色数学(相乘、相加、微表面反射的能量分配)只有在 Linear 空间才物理正确。如果项目 Color Space 设成 Gamma(第 30 章 30.5 节说的发灰根因),PBR 的反光、能量守恒全算歪——金属度再对,出来的也是”灰扑扑的假金属”。所以”用对 PBR”的前提是”第 30 章的 Linear 颜色空间 + sRGB 贴图”先设对。这两章是咬合的:第 30 章是地基(颜色对),第 31 章是配方(光算对),缺一画面就假。
反问你:你之前用 Standard 材质,有没有”金属度拉满了还是像塑料”?除了 31.7 要讲的 IBL 缺失,会不会是项目颜色空间没设 Linear,导致 PBR 在弯曲空间里算了?
31.7 排查实战:金属像塑料、高光怪、颜色跳变
光懂原理,得能排错。我给你三最常翻车的现场,照着查。
翻车一:金属看起来像塑料
现象:你把 Metalness 拉到 1,结果还是灰突突、没”金属那股锃亮带色”。
根因常有两个:
- 金属度没真拉满 / Albedo 没给对:金属度 1 时,漫反射几乎归零,物体颜色全靠”反射环境”——可如果你场景没有可反射的环境(没有 Skybox、没有环境贴图),金属没东西可反,就黑或灰,像塑料。修法:确认 Metalness 真=1,且给场景一个环境贴图(IBL,基于图像的光照 / 环境反射贴图)——让金属有”天、墙”可反。这正是第 6 章 6.4 节说的”环境光/间接光”的进阶版:PBR 用环境贴图当反射源,金属才活。
- 项目不在 Linear 空间(连第 30 章):PBR 在 Gamma 空间算歪,金属发灰。先查 Color Space。
翻车二:高光怪异 / 光斑巨大或死黑
现象:高光要么摊成一片怪光、要么缩成一个针尖死白点。
根因在粗糙度(Smoothness):
- 光斑巨大、糊成一片 → 粗糙度太高(Smoothness 太低),微表面太乱,反射全散了。想锐利高光就降粗糙度(提 Smoothness)。
- 缩成针尖死白、周围全黑 → 粗糙度太低(Smoothness 太高),微表面太整齐,反射聚成一个极小的点,稍偏视角就看不见。适度加点粗糙度让它”柔一点”。
- 还有个隐藏坑:法线贴图方向错了(第 4/5 章),高光会乱飞——先查法线是不是反了、UV 接缝是不是漏光。
翻车三:不同光下颜色跳变、材质不统一
现象:同一物体,在场景 A 正常、场景 B 就发灰或变色,或者几种材质风格打架。
根因:你混用了旧的非 PBR 材质(Legacy/Diffuse/Specular Shader,第 31.3 那种手调高光)和 PBR 材质。非 PBR 不跨光照、参数不物理,换光就失真、和 PBR 物件放一起风格割裂。修法:统一换成 Lit(PBR)材质,按 31.5 的物理参数重调。这也是第 14.9 说的”组合要服务于统一风格”——材质模型不统一,画面就像拼凑的。
排查口诀:金属像塑料 → 查金属度+IBL+Linear;高光怪 → 查粗糙度+法线;颜色跳 → 查是不是混了非 PBR 旧材质,全换 Lit。
31.8 收个尾,连回主线
我们把这一章扣回第 1 章:游戏用”逼近物理的近似”换”像真又实时”。
- 真实现象:光被表面按朝向漫反射、按镜面高光、被微表面散射、能量不会凭空多。
- 工程师的麻烦:逐像素实时算光贵;传统模型参数不物理、特殊材质失真。
- 方案演进落进引擎:
- Phong/Blinn-Phong:漫反射
N·L(朗伯,连第 6 章)+ 高光N·H幂次(shininess 控光斑)。便宜,但参数手调、不跨光。 - PBR:能量守恒(反射+漫反射≤入射)+ 微表面(粗糙度=微平面混乱度)+ 金属度/粗糙度两物理参数。跨光照一致。
- Unity:Lit 材质 = PBR,你第 5 章的滑块就是它;前提是第 30 章 Linear 颜色空间设对。
- Phong/Blinn-Phong:漫反射
- 你能做的:金属度拉满+给 IBL 救”塑料感”;粗糙度调对救”高光怪”;统一 Lit 救”颜色跳”。
这一章和第 5、6、30 章全咬合:第 5 章的材质滑块是 PBR 参数,第 6 章的明暗是高光/漫反射公式的果,第 30 章的线性空间是 PBR 能算对的地基。你走到这,已经不只是”会拧旋钮的人”,而是”知道每个旋钮为什么长这样、背后是哪条物理”的人。
下一部分如果你继续,会进更深的渲染管线与性能——但到这,图形学最该懂的”光怎么算、色怎么对”你已经握住了。
【思考题】
- 漫反射的物理来源是朗伯余弦律,它在光照模型公式里具体对应哪一项?环境光为什么被说成”对间接光的粗糙近似”?它和第 6 章讲的全局光照(GI)是什么关系?
- Phong 和 Blinn-Phong 算高光,分别用”哪个向量和哪个向量”的吻合度?为什么今天实时引擎多用 Blinn-Phong?specular exponent(高光指数)控制高光的什么?
- 传统 Phong 模型有哪两大别扭?为什么金属的高光在 Phong 里难调对(尤其金子那种带色高光)?
- PBR 的两大支柱(能量守恒、微表面)各解决了传统模型的什么问题?金属度和粗糙度这两个直观参数,分别决定高光的什么?为什么 PBR 材质”换到不同光照下都还像那么回事”?
- 为什么说 PBR 必须在”线性空间”算才对?连回你第 30 章学的颜色空间——如果项目设为 Gamma 空间,PBR 的金属会出什么怪相?
【参考答案】
- 漫反射公式里
max(0, N·L)那一项就是朗伯余弦律——N是表面法线、L是光方向,点积即”表面正对光的程度”,第 6 章你算过的点积正是它。环境光项是一个”均匀常量底色”(环境色 × Albedo),它不看位置、不看朝向,是对”光在场景里弹来弹去、把间接光染到暗面”的粗糙近似——而那份真实的间接光就是第 6 章讲的全局光照(GI)。GI 算不起(光弹 N 次指数贵),所以早期用均匀环境光当乞丐版补丁;Light Probe、环境贴图是把它做细的进阶。 - Phong 用”光反射方向 R 与视线 V 的吻合度”(
pow(R·V, shininess));Blinn-Phong 用”半角向量 H(L 与 V 的中间方向)与法线 N 的吻合度”(pow(N·H, shininess))。今天多用 Blinn-Phong 因为它更便宜(不用每像素重算反射方向 R)、在掠射角数值更稳定、不易出怪异。specular exponent(=shininess 高光指数)是幂次:指数大→亮斑小且锐(像镜子),指数小→亮斑大且散(像毛面)。 - 两大别扭:①参数是美术凭感觉调的、不对应真实物理(换光就得重调);②金属/粗糙/半透/各向异性等特殊材质难调对。金属高光难调,因 Phong 的高光颜色要你手写、且和漫反射脱节——真实金属(金、铜)高光带自身颜色(金偏黄),Phong 里你得手填金色高光色再和漫反射配合,极易调成”塑料镀金”;而 PBR 里金属度高时光自动取 Albedo 颜色,无需手调。
- 能量守恒解决”传统模型能越照越亮、违反物理”——PBR 焊死”漫反射+镜面≤入射光”,金属/光滑时镜面抢能量、漫反射自动压低,不会凭空变亮。微表面解决”单个 shininess 表达不了粗糙本质”——用”粗糙度=微平面朝向混乱度”一个物理量,统一决定高光是聚是散。金属度决定”反光颜色是彩色(金属,取 Albedo 色)还是灰白(非金属)“;粗糙度决定”高光多散多柔”。PBR 跨光照一致,因为参数是物理属性(“它是金属、多光滑”的事实不变),变的只是打它的光——而光本就该随场景变,所以同一材质换光仍像真,不像 Phong 那样为每光重调。
- 因为 PBR 里的反射、能量分配、颜色相乘都是”物理数学”,只有线性空间(输入 0.5 即一半亮)才算得对;sRGB(Gamma)空间是弯曲的,数学算歪。连第 30 章:若项目设 Gamma 空间,PBR 在弯曲空间算,金属反光、能量守恒全错,出来”灰扑扑的假金属”——你金属度拉满也像塑料(翻车一的根源之一)。所以”用对 PBR”前提是第 30 章 Linear 颜色空间 + 颜色类贴图勾 sRGB。你能否把”第 30 章的红苹果在 Gamma 空间发暗不正”和”这里 PBR 金属发灰”归到同一个根因:光照数学不在线性空间做?