在顶点着色器中,我们通常创建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);
此矩阵将Tangent空间中的顶点变换为Eye / Camera空间。
现在进行法线贴图(在前向渲染中完成),我们有两个选择:
light_vector
和view_direction
并将这些向量发送到片段着色器。之后,这些向量处于切线空间。
light_vector
和view_direction
匹配。tbn
矩阵传递给片段着色器,然后通过它转换法线贴图中的每个法线读数。这样我们就可以将这种法线转换为View空间。
选项 1 似乎更快:我们在顶点着色器中进行了大多数变换,只有一次从法线贴图中读取。
选项 2 需要通过TBN矩阵从法线贴图转换每个法线。但似乎有点简单。
问题:
哪个选项更好?
是否有任何性能损失? (也许纹理读取将“覆盖”进行矩阵变换的成本)
更经常使用哪个选项?
答案 0 :(得分:3)
我现在会告诉你很多 - 根据你的申请,选项1甚至可能都不可能。
在延迟着色图形引擎中,您必须在片段着色器中计算光矢量,这将排除选项1.当在延迟着色中进行照明时,您无法保持TBN矩阵,所以你会在构建普通G缓冲区之前,将法线转换为世界空间或视图空间(这不再经常受到欢迎)(TBN矩阵的计算可以在顶点着色器中完成并传递给片段着色器为flat mat3
)。然后使用基础对法线贴图进行采样,并将其写入世界空间。
我可以从经验告诉你,大名称图形引擎(例如虚幻引擎4,CryEngine 3等)现在实际上在世界空间中进行照明。它们也使用延迟着色,因此对于这些引擎,根本没有使用上面建议的选项:)
顺便说一下,如果实际存储的是法线,副法线和切线向量,则会在顶点缓冲区中浪费空间。它们是正交向量空间的基础向量,因此它们都是直角。因此,您可以通过采用叉积来计算给定任意两个的第三个向量。此外,由于它们是直角并且应该已经标准化,因此您不需要对叉积的结果进行标准化(回想一下| a x b | = | 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);
这将让你走向世界空间法线(这些日子对于许多流行的后处理效果往往更有效)。如果您打算更改矩阵以产生视图空间法线,则可能需要对矩阵进行重新规范化。