news 2026/10/16 5:47:54

镜面反射揭秘:从高光到真实倒影

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
镜面反射揭秘:从高光到真实倒影

一句话:光打到光滑表面上,像台球撞墙一样按固定角度弹开。只有当你的眼睛正好在弹开的方向上,才能看到它。

金属的亮点、湿地面的倒影、湖面远处的反光、镜子里的人,都属于镜面反射。

在游戏和图形学里,"镜面反射"通常指两件相关的事:

叫法反射的是什么例子
镜面高光(Specular Highlight)光源本身苹果表面的白色亮点、头盔上的光斑
镜面反射 / 环境反射(Reflection)周围的整个环境镜子、湖面倒影、车漆上映出的天空

两者原理相同。高光可以看成"只反射了一盏灯",环境反射则是"把四面八方都反射了"。


一、先分清:漫反射 vs 镜面反射

拿手电筒分别照一张白纸和一面镜子:

白纸(漫反射) 镜子(镜面反射) 光 光 ↘ ↘ ↖ ↑ ↗ ↘ → ← 向四面八方散开 ────●──── ═══════════ ↗ ← 只往一个方向弹 👁 只有站在这里才看得到
漫反射镜面反射
光往哪走各个方向均匀散开集中在反射方向附近
换个角度看亮度不变亮点会移动,甚至消失
颜色来自物体本身(红苹果是红的)主要是光的颜色(红苹果的高光是白的)
和什么有关光源方向光源方向+ 眼睛方向

可以自己试一下:盯着桌上的杯子左右晃头,杯子的颜色不变(漫反射),杯子上的亮点却跟着你移动(镜面反射)。镜面反射依赖视角,这是它和漫反射最本质的区别。


二、物理原理

1. 反射定律:入射角 = 反射角

N(法线) L ↑ R ↖ │ ↗ ↖ │ ↗ θ │ θ ═════════●═════════

L是指向光源的方向,N是法线,反射方向是:

R = 2(N·L)N − L

GLSL 自带reflect(I, N)。注意它的I是入射方向(从光源射向表面),也就是−L,方向反了结果就错。

2. 微表面:为什么高光有大有小

真正的镜子极其光滑,所有光都朝同一个方向弹走,所以你能看到清晰的倒影。

大多数表面在显微镜下是凹凸不平的,可以看成无数块朝向各异的"小镜子"(微表面):

光滑表面(粗糙度低) 粗糙表面(粗糙度高) ───────────── ╱╲╱╲_╱╲╱‾╲╱ 反射光集中 → 高光小而亮 反射光散开 → 高光大而暗淡 例:镜子、抛光金属、湿地面 例:磨砂塑料、橡胶、干燥的石头

粗糙度(Roughness)就是在描述这些小镜子有多乱。现代引擎里,美术调材质最常动的参数之一就是它。

3. 菲涅尔效应:越斜着看越像镜子

站在湖边:

  • 低头看脚下的水,几乎是透明的,能看见水底的石头;
  • 抬头看远处的湖面,像一面镜子,映着天空和对岸的山。

这就是菲涅尔效应(Fresnel):视线越贴着表面(掠射角),反射越强。常用 Schlick 近似:

F = F0 + (1 − F0) × (1 − cosθ)^5 θ 是视线与法线的夹角

以水为例(F0 ≈ 0.02):

视线与法线的夹角反射比例看到的
0°(正对着看)约 2%几乎看透水面
80°约 40%倒影明显
89°(贴着水面)约 92%几乎是镜子

这个效应不只出现在水面上:木桌、塑料、皮肤,甚至一张纸,斜着看都会变亮。游戏里物体边缘泛起的一圈亮光,很大一部分来自它。


三、经典模型:Phong 与 Blinn-Phong

早期 GPU 算力有限,用简单的经验公式模拟高光,现在的风格化游戏和移动端仍在大量使用。

1. Phong:看反射方向离眼睛有多近

高光 = (R · V)^n V 是指向眼睛的方向

R 和 V 越接近,点积越接近 1,高光越亮。

2. Blinn-Phong:用半程向量,更快也更好

H = normalize(L + V) 光线方向和视线方向的"中间方向" 高光 = (N · H)^n

意思是:如果某块小镜子的法线正好是 H,它就能把光反射进眼睛。所以 N 越接近 H,高光越强。

vec3 H = normalize(L + V); float spec = pow(max(dot(N, H), 0.0), shininess); vec3 color = diffuse * max(dot(N, L), 0.0) + specColor * spec;

选它而不是 Phong 有两个原因:计算更便宜,而且 Phong 在掠射角时R·V会小于 0,高光被突然切断;Blinn-Phong 没有这个问题,效果更接近真实。

同样大小的高光,Blinn-Phong 的指数要比 Phong 大约 2~4 倍,从 Phong 迁移过来时要重新调参数。

3. 指数 n:高光的"锐利度"

用数字看 n 的作用:

N 与 H 的夹角n = 8(塑料)n = 128(抛光金属)
0°1.001.00
10°0.890.15
20°0.610.0004

n 小,偏离 20° 还有 61% 的亮度,高光是一大片模糊的光晕;n 大,偏离 10° 就只剩 15%,高光是一个锐利的小亮点。n 越大,表面越光滑。


四、现代做法:PBR(基于物理的渲染)

Blinn-Phong 是"看着像就行"的经验公式,有两个明显问题:调参全靠感觉,同一组参数换个光照环境就不对了;能量也不守恒,高光调亮时物体整体可能比照到它的光还亮。

现在主流引擎(Unity HDRP/URP、Unreal)使用Cook-Torrance 微表面模型,高光由三项相乘:

D × F × G 高光 = ───────────────────── 4 (N·L) (N·V)
项名称回答的问题形象理解
D法线分布(常用 GGX)有多少小镜子正好朝向 H?决定高光的大小和形状
F菲涅尔这个角度反射多少光?斜着看更亮
G几何遮蔽有多少光被旁边的凸起挡住了?粗糙表面在掠射角时会变暗
float D_GGX(float NoH, float roughness) { float a = roughness * roughness; float a2 = a * a; float d = NoH * NoH * (a2 - 1.0) + 1.0; return a2 / (3.14159 * d * d); } vec3 F_Schlick(vec3 F0, float VoH) { return F0 + (1.0 - F0) * pow(1.0 - VoH, 5.0); }

GGX 比 Blinn-Phong 多了一条长长的"拖尾":高光中心之外还有一圈柔和的光晕,更接近真实材质。

金属和非金属的区别

PBR 里最重要的一个参数是金属度(Metallic),它决定了 F0(正对着看时的反射率):

材质F0高光颜色漫反射
塑料、木头、石头、皮肤(非金属)约 0.04(4%)白色(光源的颜色)有,物体颜色来自这里
金(1.00, 0.71, 0.29)金色几乎没有
银(0.95, 0.93, 0.88)近白色几乎没有
铁(0.56, 0.57, 0.58)灰色几乎没有

结论:非金属的颜色在漫反射里,高光是白的;金属没有漫反射,颜色全在高光(反射)里。金子之所以是金色,是因为它反射出来的光是金色的。

同时,PBR 保证能量守恒:被镜面反射走的光,不会再参与漫反射。所以表面越光滑,高光越集中、越亮,但总能量不变。


五、反射整个环境:从高光到倒影

上面只计算了几盏灯的高光。但真实的镜面反射应该映出周围所有东西:天空、墙壁、旁边的角色。

这个问题难在:要知道反射方向 R 上有什么,相当于要从表面再"看"一次场景。游戏里有几种不同成本的做法:

1. 环境贴图(Cubemap)

提前在某个位置拍一张六个面的全景图,着色时沿 R 方向直接查:

vec3 R = reflect(-V, N); vec3 env = textureLod(envCubemap, R, roughness * maxMip).rgb;

和上一篇的纹理过滤直接相关:粗糙的表面反射是模糊的,所以预先把 Cubemap 卷积模糊,存在各级 Mip 里,粗糙度越高取越模糊的级别。这就是 PBR 中"预过滤环境贴图 + BRDF 查找表"(Split-Sum 近似)的核心思路。

2. 反射探针(Reflection Probe)

在场景里放很多个 Cubemap 拍摄点,物体使用离自己最近的那个,还可以用盒体投影(Box Projection)修正室内的反射位置。

3. 平面反射(Planar Reflection)

专门用于镜子、平静水面。把摄像机沿反射平面翻转到"镜子背后",把场景再渲染一遍:

真实摄像机 📷 ↘ ══════════════●══════════ ← 镜子 / 水面 ↗ 镜像摄像机 📷(在平面下方,倒着看)

画面完全正确,但每多一面镜子,场景就要多渲染一次。所以游戏里能照出人的镜子往往很少,而且常常和场景的其他部分隔开。

4. 屏幕空间反射(SSR)

利用当前帧已经渲染好的颜色和深度,沿 R 方向在屏幕上步进,找到和深度缓冲相交的位置,取那里的颜色。

  • 优点:便宜,任意形状的表面都能用,动态物体也能反射;
  • 致命缺点:屏幕上没有的东西,反射里也没有。

5. 光线追踪反射

真正从表面沿 R 发射光线,在整个场景中求交。最准确,但最昂贵,需要硬件支持,通常还要降低分辨率并配合降噪。

对比

方法准确度动态物体成本典型用途
Cubemap / 反射探针低(位置不准)通常不能很低几乎所有物体的基础反射
平面反射高能高(多渲染一遍)镜子、平静的水面
SSR中能中湿地面、光滑地板
光线追踪最高能很高高端 PC / 主机

实际游戏通常混合使用:先用 SSR,SSR 找不到的地方退回反射探针,最远处退回天空盒。


六、案例

案例 1:高光为什么总是白色的

现象:红色塑料球的高光是白色,金戒指的高光却是金色。
原因:塑料是非金属,F0 约 0.04 且无色,反射的是光源本身的颜色;金是金属,F0 本身带颜色。
常见错误:把非金属的高光颜色设成物体颜色,结果看起来像金属;或者金属材质设置了漫反射颜色,看起来像涂了漆的塑料。

案例 2:背光面也有高光

现象:灯在物体背后,物体朝向摄像机的一面却出现了高光。
原因:只计算了N·H,没有检查光是否照到了这一面。
修复:高光乘以N·L > 0的判断,或者直接乘以max(N·L, 0)。

案例 3:远处的金属表面闪烁

现象:远处的金属栏杆、细密的法线贴图上,高光像雪花一样闪。
原因:高光走样。高光非常锐利,一个像素覆盖的区域里法线变化很大,有时采样点正好落在亮点上,有时落不到。上一篇讲过,法线贴图 Mipmap 后向量会变短,也会加剧这个问题。
修复:根据法线的变化程度提高粗糙度(Toksvig、Specular AA),再配合 TAA。

案例 4:地板倒影在屏幕边缘消失

现象:光滑地板上的倒影,往下看时上方物体的倒影突然被截断,或者边缘一片模糊。
原因:SSR 只能反射屏幕上可见的东西,物体移出画面,它的倒影也就没了。
修复:在屏幕边缘逐渐淡出 SSR,交给反射探针接管;高端方案改用光线追踪。

案例 5:室内物体反射出天空

现象:地下室里的金属桶,表面映着蓝天白云。
原因:附近没有放反射探针,物体使用了默认的天空盒作为环境反射。
修复:在室内放置反射探针,并设置合适的影响范围和盒体投影。

案例 6:湖面看起来像一块塑料

现象:水面无论从什么角度看,反射强度都一样,显得很假。
原因:没有加菲涅尔项。
修复:按视角计算 Fresnel,近处透明、远处反光,水面立刻显得真实。

案例 7:金属在没有环境光的场景里发黑

现象:在空旷场景里只放一盏方向光,金属球除了一个亮点,其余全是黑的。
原因:金属几乎没有漫反射,它的外观全靠反射周围环境。周围什么都没有,它就是黑的。
修复:提供天空盒或反射探针。这也是为什么调 PBR 材质时,场景里一定要有环境光照。

案例 8:镜子里的角色和本人不同步

现象:平面反射里,角色的动作慢一帧,或者特效没出现在镜子里。
原因:反射相机在主相机之前渲染,用的还是上一帧的数据;或者某些特效没有加入反射相机的渲染层。
修复:调整更新顺序,检查反射相机的剔除层设置。


七、总结

光打到表面 │ ├─ 散向各个方向 ──▶ 漫反射:颜色来自物体,视角无关 │ └─ 按反射定律弹开 ──▶ 镜面反射:视角相关 ├─ 粗糙度:小镜子有多乱 → 高光的大小 ├─ 菲涅尔:越斜着看越亮 ├─ 金属度:反射光有没有颜色 └─ 反射什么 ├─ 只有光源 → 高光(Blinn-Phong / GGX) └─ 整个环境 → Cubemap / 平面反射 / SSR / 光追
问题答案
镜面反射是什么光按反射定律集中往一个方向弹开
和漫反射的区别依赖视角,亮点会随眼睛移动
高光大小由什么决定粗糙度(微表面的混乱程度),经典模型中是指数 n
为什么斜着看更亮菲涅尔效应
金属和非金属非金属高光是白的、颜色在漫反射;金属高光带颜色、没有漫反射
经典模型Blinn-Phong:(N·H)^n
现代模型Cook-Torrance:D(分布)× F(菲涅尔)× G(遮蔽)
环境反射方案Cubemap、反射探针、平面反射、SSR、光线追踪
常见坑背光高光、高光闪烁、SSR 边缘消失、室内反射天空、缺少菲涅尔、金属发黑

一句话:漫反射告诉你物体是什么颜色,镜面反射告诉你物体有多光滑,以及它周围有什么。高光是它映出的灯,倒影是它映出的整个世界。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/16 5:47:14

从零实现BiLSTM上下文词嵌入:训练细节与多义词可视化分析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/16 5:45:54

八数码问题求解:A*算法与启发式搜索实战解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/16 5:45:45

两串电池主动均衡设计:基于MP2642GR-Z与TM4C129LNCZAD的BMS实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华