1

在顶点着色器中,我们通常创建 TBN 矩阵:

vec3 n = normalize(gl_NormalMatrix * gl_Normal);
vec3 t = normalize(gl_NormalMatrix * Tangent.xyz);
vec3 b = normalize(gl_NormalMatrix * Bitangent.xyz);
mat3 tbn = mat3(t, b, n);

该矩阵将顶点从切线空间转换到眼睛/相机空间。

现在对于法线贴图(在前向渲染中完成),我们有两个选项:

  1. 逆 tbn 矩阵和变换light_vector并将view_direction这些向量发送到片段着色器。之后,这些向量位于切线空间中。
    • 这样在片段着色器中我们只需要从法线贴图中读取法线。由于这些法线在切线空间中(通过“定义”),因此它们与变换后的light_vector和相匹配view_direction
    • 这样我们就可以在切线空间中进行光照计算。
  2. tbn矩阵传递给片段着色器,然后通过它转换从法线贴图中读取的每个法线。这样我们就可以将这样的法线转换为视图空间。
    • 这样我们在 Eye/Camera 空间中进行光照计算

选项1似乎更快:我们在顶点着色器中进行了大部分转换,只有一个从法线贴图读取。

选项2需要通过 TBN 矩阵从法线贴图转换每个法线。但它似乎有点简单。

问题:

哪个选项更好?

有没有性能损失?(也许纹理读取将“覆盖”进行矩阵转换的成本)

哪个选项更常用?

4

1 回答 1

3

我现在就告诉你这么多 - 根据你的应用程序,选项 1 甚至可能是不可能的。

在延迟着色图形引擎中,您必须在片段着色器中计算光矢量,这排除了选项 1。当需要在延迟着色中进行照明时,您也不能保留 TBN 矩阵,因此您将转换您的法线在构建普通 G-Buffer 时提前进入世界空间或视图空间(这不再经常受到青睐)(TBN 矩阵的计算可以在顶点着色器中完成并作为片段着色器传递给片段着色器)flat mat3)。然后使用基础对法线贴图进行采样并将其写入世界空间。

我可以根据经验告诉你,大牌图形引擎(例如 Unreal Engine 4、CryEngine 3 等)现在实际上在世界空间中进行光照。他们还使用延迟着色,因此对于这些引擎,您上面提出的任何选项都没有使用:)


顺便说一句,如果您实际上存储的是法线、副法线和切线向量,那么您就是在浪费顶点缓冲区中的空间。它们是正交向量空间的基向量,所以它们都是直角。因此,您可以通过取叉积来计算给定任意两个向量的第三个向量。此外,由于它们是直角并且应该已经归一化,因此您不需要对叉积的结果进行归一化(回想一下 |axb| = |a| * |b| * sin(a,b))。因此,这在您的顶点着色器中就足够了:


   // Normal and tangent should be orthogonal, so the cross-product
   //   is also normalized - no need to re-normalize.
   vec3 binormal = cross (normal,  tangent);
        TBN      = mat3  (tangent, binormal, normal);

这将使您走上通往世界空间法线的道路(对于当今许多流行的后处理效果来说,这往往更有效)。如果您打算改变它以产生视图空间法线,您可能必须重新规范化矩阵。

于 2013-08-09T22:04:10.837 回答