【图形学入门】Phong 光照模型中反射方向公式是怎么来的?

前言

最近在学习 Phong 光照模型时,遇到了一个看起来很“凭空出现”的公式:

\[R = 2(N \cdot L)N - L \]

它用来计算镜面反射方向。

一开始看到这个公式,感觉有点突兀:

  • 为什么是 \(2(N \cdot L)N - L\)
  • 这个 2 是哪里来的?
  • 点积 \(N \cdot L\) 在里面起什么作用?

后来把它从向量分解的角度拆开之后,瞬间就清楚了。

这篇博客记录一下我的理解。


1. Phong 中的几个方向

在 Phong 光照模型中,计算高光时会涉及几个方向:

  • (N):表面法线方向
  • (L):从当前点指向光源的方向
  • (R):光线关于法线反射后的方向
  • (V):从当前点指向观察者,也就是摄像机的方向

高光是否明显,主要看:

\[R \cdot V \]

也就是说:

反射方向 (R) 和观察方向 (V) 越接近,看到的高光越强。

所以问题就变成了:

已知法线方向 (N) 和光照方向 (L),怎么求反射方向 (R)?


2. 先不要背公式,先看反射到底发生了什么

镜面反射的本质是:

入射方向和反射方向关于法线对称。

也就是说,法线 (N) 像一面“镜子的中轴线”。

可以粗略理解成:

        L
       /
      /
     /
    / 
---P---------
    |
    | N
    |

其中 (P) 是当前表面上的点。

我们要求的是 (L) 关于 (N) 对称后的方向,也就是 (R)。


3. 关键思想:把 (L) 拆成两部分

一个向量 (L) 可以拆成两部分:

\[L = L_{\parallel} + L_{\perp} \]

其中:

  • (L_{\parallel}):沿着法线 (N) 的分量
  • (L_{\perp}):垂直于法线 (N) 的分量

换句话说,就是把 (L) 拆成:

L = 朝着法线方向的部分 + 横向偏出去的部分

4. 沿着法线的部分怎么求?

如果 (N) 是单位向量,也就是:

\[|N| = 1 \]

那么 (L) 在 (N) 上的投影就是:

\[L_{\parallel} = (N \cdot L)N \]

这里的 (N \cdot L) 是点积,它表示:

(L) 在 (N) 方向上的长度。

然后再乘上方向 (N),就得到了完整的投影向量。

所以:

\[L_{\parallel} = (N \cdot L)N \]

剩下的垂直分量就是:

\[L_{\perp} = L - L_{\parallel} \]

也就是:

\[L_{\perp} = L - (N \cdot L)N \]


5. 反射时,哪部分变,哪部分不变?

这是整个公式最核心的地方。

当 (L) 关于法线 (N) 反射时:

  • 沿着法线方向的分量不变
  • 垂直于法线方向的分量反向

也就是说:

\[R = L_{\parallel} - L_{\perp} \]

因为原来:

\[L = L_{\parallel} + L_{\perp} \]

反射后,把横向偏出去的部分翻到另一边:

\[R = L_{\parallel} - L_{\perp} \]


6. 开始代入推导

因为:

\[L_{\perp} = L - L_{\parallel} \]

所以:

\[R = L_{\parallel} - L_{\perp} \]

代入:

\[R = L_{\parallel} - (L - L_{\parallel}) \]

展开:

\[R = L_{\parallel} - L + L_{\parallel} \]

合并:

\[R = 2L_{\parallel} - L \]

又因为:

\[L_{\parallel} = (N \cdot L)N \]

所以:

\[R = 2(N \cdot L)N - L \]

最终得到:

\[\boxed{R = 2(N \cdot L)N - L} \]


7. 为什么公式里面有一个 2?

这个 2 不是凭空来的。

它来自这里:

\[R = L_{\parallel} - (L - L_{\parallel}) \]

展开后:

\[R = 2L_{\parallel} - L \]

也就是说:

反射方向可以理解为:先找到 (L) 在法线方向上的投影,然后从这个投影位置“翻过去”。

原来的 (L) 在法线的一侧,反射后的 (R) 在另一侧,所以需要跨过法线方向的投影点。

这个“跨过去”的过程,就导致了 (2L_{\parallel})。


8. 用一个二维例子验证一下

假设法线方向是竖直向上:

\[N = (0, 1) \]

假设光照方向 (L) 是右上方 45 度:

\[L = \left(\frac{\sqrt{2}}{2}, \frac{\sqrt{2}}{2}\right) \]

先计算点积:

\[N \cdot L = 0 \times \frac{\sqrt{2}}{2} + 1 \times \frac{\sqrt{2}}{2} = \frac{\sqrt{2}}{2} \]

然后计算:

\[2(N \cdot L)N = 2 \times \frac{\sqrt{2}}{2} \times (0, 1) \]

最后:

\[R = 2(N \cdot L)N - L \]

代入:

\[R =(0, \sqrt{2})-\left(\frac{\sqrt{2}}{2}, \frac{\sqrt{2}}{2}\right) \]

得到:

\[R = \left( -\frac{\sqrt{2}}{2}, \frac{\sqrt{2}}{2} \right) \]

结果是左上方 45 度。

这正好符合直觉:

原来的 (L) 指向右上方,关于竖直法线反射后,应该指向左上方。


9. 一个容易踩坑的地方:方向定义不同,公式可能长得不一样

有些图形学 API 或教材里,反射公式可能写成:

\[R = I - 2(N \cdot I)N \]

这和本文的公式看起来不一样。

原因不是数学矛盾,而是方向定义不同。

本文使用的是:

  • (L):从表面点指向光源

所以反射方向是:

\[R = 2(N \cdot L)N - L \]

而很多 API 中使用的是:

  • (I):入射方向,也就是光线真正打向表面的方向

此时 (I) 和 (L) 通常相反:

\[I = -L \]

所以公式会变成:

\[R = I - 2(N \cdot I)N \]

本质上是同一个东西,只是向量方向的约定不同。


10. 总结

Phong 光照模型里的反射方向公式:

\[R = 2(N \cdot L)N - L \]

不是硬背出来的,而是从向量分解自然推出来的。

核心逻辑是:

\[L = L_{\parallel} + L_{\perp} \]

其中:

\[L_{\parallel} = (N \cdot L)N \]

反射时:

  • 沿法线方向的分量不变
  • 垂直法线方向的分量反向

所以:

\[R = L_{\parallel} - L_{\perp} \]

最后推出:

\[R = 2(N \cdot L)N - L \]

一句话理解:

先把光照方向 (L) 拆成“沿法线方向”和“偏离法线方向”两部分,然后把偏离法线的那部分翻到另一边。

这个公式本质上就是一次关于法线的向量镜面对称。

posted @ 2026-05-01 15:40  BATTAB  阅读(48)  评论(0)    收藏  举报