文章目录

  • Unity 中的处理
  • OpenGL 处理
  • 为何要使用 IT_mMat
  • 总结
  • References

LearnGL - 学习笔记目录

前些篇:

  • LearnGL - 11.1 - 实现简单的Gouraud-Phong光照模型
  • LearnGL - 11.2 - 实现简单的Phong光照模型
  • LearnGL - 11.3 - 实现简单的Blinn-Phong光照模型
  • LearnGL - 11.4 - 实现简单的Flat BlinnPhong光照模型

了解了 Gouraud-Phong、Phong、Blinn-Phong、Flat Blinn-Phong 光照模型的基本认识。

这篇:我们重点讲解一下 法线从对象空间变换到世界空间 的变换矩阵的推导过程,因为之前四篇光照处理有用到,但没有细节的去讲解到。

本人才疏学浅,如有什么错误,望不吝指出。


Unity 中的处理

在 Unity ShaderLab 中,你可能会看到内置的函数:

// Transforms normal from object to world space
inline float3 UnityObjectToWorldNormal( in float3 norm )
{
#ifdef UNITY_ASSUME_UNIFORM_SCALING
    return UnityObjectToWorldDir(norm);
#else
    // mul(IT_M, norm) => mul(norm, I_M) => {dot(norm, I_M.col0), dot(norm, I_M.col1), dot(norm, I_M.col2)}
    return normalize(mul(norm, (float3x3)unity_WorldToObject));
#endif
}

这函数中:

#ifdef UNITY_ASSUME_UNIFORM_SCALING
    return UnityObjectToWorldDir(norm);
#else

是处理统一缩放的法线变换,所以我们聚焦在后面那块:

// mul(IT_M, norm) => mul(norm, I_M) => {dot(norm, I_M.col0), dot(norm, I_M.col1), dot(norm, I_M.col2)}
    return normalize(mul(norm, (float3x3)unity_WorldToObject));

unity_WorldToObject model matrix 的逆矩阵,然后我们用 float3 * float3x3 来免去了处理 transpose(unity_WorldToObject) 的转置处理,像上面那句也可以这么写,但没必要:

return normalize(mul(transpose((float3x3)unity_WorldToObject), norm));

即: float3x3 * float3 的意思


OpenGL 处理

在我之前的一些 LearnGL 系列笔记中,如:LearnGL - 11.1 - 实现简单的Gouraud-Phong光照模型,写了一个 GLSL 函数 vec3 ObjectToWorldNormal(vec3 n):

// 将对象空间的法线转换到世界空间下的法线
vec3 ObjectToWorldNormal(vec3 n) {
	return normalize(mat3(IT_mMat) * n);	// 等价于:transpose(I_mMat) * vec4(n, 0)
}

(这里顺便吐槽一下,GLSL 中竟然不支持 inline 内联开展?因为我编译 GLSL shader 是会提示:“inline” not supported 之类的,不会又需要什么 GL 的 Extensions 吧?)

这代码中的 IT_mMat 是我在 C++ 层计算好的 mMat 的 逆矩阵 的 转置矩阵,再传入到 shader 的 uniform,对应上面 Unity 中的 unity_WorldToObject,只不过 unity_WorldToObject 没有转置而已,只是 巧妙的利用了 CG/HLSL 中 mul 函数的特性来避免 transpose 函数的转置处理。


为何要使用 IT_mMat

那么为何我们要用 Model Matrix 的 逆矩阵的转置矩阵 来变换法线呢?

先来看看,我们变换顶点都是使用 mMat 从 对象空间 变换到 世界空间,那么如果我们使用这个 mMat 矩阵来变换法线可以吗?

那么我用 GGB 来测试

unity Image 倒圆角_IT

上图,左边 原始图形 中的 unity Image 倒圆角_unity Image 倒圆角_02 是切线,unity Image 倒圆角_法线矩阵_03 是法线,右边 缩放后的图形 的 unity Image 倒圆角_IT_04 是切线, unity Image 倒圆角_unity Image 倒圆角_05

在统一缩放(缩放分量相同)看起来没有问题

如果在非统一缩放呢?看看:

unity Image 倒圆角_unity Image 倒圆角_06

unity Image 倒圆角_法线矩阵_07

看起来 unity Image 倒圆角_IT_04 还是 保持着 平面上的 切线,但是 unity Image 倒圆角_unity Image 倒圆角_05 就 没有保持 与平面 垂直 了。

所以,在非统一缩放时,用 mMat 矩阵来变换法线是不行的。

那么我们利用一些已知的数学公式来推导:

已知:

  • Model Matrix(对象到世界空间变换矩阵) mMat ,我们定义符号为:unity Image 倒圆角_法线矩阵_10
  • unity Image 倒圆角_IT_11
  • unity Image 倒圆角_法线变换矩阵_12
  • unity Image 倒圆角_IT_13unity Image 倒圆角_法线矩阵_10
  • unity Image 倒圆角_IT_M_15unity Image 倒圆角_法线变换矩阵_16

unity Image 倒圆角_unity Image 倒圆角_17 是目前不知道的,正式是我们要求的 法线变换矩阵,即:IT_mMat

由于法线是垂直于切线的,所以我们可以将一些公理式子列出来:

unity Image 倒圆角_法线变换矩阵_18

unity Image 倒圆角_法线变换矩阵_19上面为何第(1), (2) 的 unity Image 倒圆角_unity Image 倒圆角_20 要用转置的,因为我们将 unity Image 倒圆角_unity Image 倒圆角_21 视为 Nx1 的矩阵 所以我们将同样的 unity Image 倒圆角_unity Image 倒圆角_22

(上面关于点积 dot 可以参考我之前写的:LearnGL - 11.1 - 实现简单的Gouraud-Phong光照模型 - dot - 点积的作用)


我们将unity Image 倒圆角_IT_23号等式左边部分在中间添加一个unity Image 倒圆角_法线变换矩阵_24,那么结果为:unity Image 倒圆角_法线矩阵_25,其中 unity Image 倒圆角_IT_M_26

unity Image 倒圆角_IT_M_27 就是一个矩阵与它的逆矩阵变换后就抵消了矩阵的所有变换,没有变换的特性就是 单位矩阵 的特性,所以这个是没有问题的

所以:unity Image 倒圆角_法线矩阵_28


仔细观察公式中红色部分,unity Image 倒圆角_法线变换矩阵_29 中 的 unity Image 倒圆角_IT_M_30 ,其实这部分就是unity Image 倒圆角_IT_31等式右边的内容:unity Image 倒圆角_IT_M_32

所以我们的式子再次可以调整为:unity Image 倒圆角_法线矩阵_33


再次仔细观察一下unity Image 倒圆角_IT_34unity Image 倒圆角_法线变换矩阵_35

unity Image 倒圆角_法线矩阵_36

unity Image 倒圆角_IT_37等式左边(也就 蓝色 部分)的unity Image 倒圆角_IT_38 去掉,变成下面的等式

unity Image 倒圆角_IT_39

现在可以清晰看到unity Image 倒圆角_IT_M_40unity Image 倒圆角_IT_37的红色部分的unity Image 倒圆角_IT_42是相同的,那么剩下的部分我们标记上绿色:unity Image 倒圆角_IT_M_43

这绿色的部分,我们也可以认为他们是相等的

即:unity Image 倒圆角_IT_M_44


为了便于观察,将等式 左右 两个的内容 分别标记 为 红色、绿色

unity Image 倒圆角_unity Image 倒圆角_45

再将等式两边同时转置一下:

unity Image 倒圆角_法线变换矩阵_46

左边 的unity Image 倒圆角_unity Image 倒圆角_47 本身有转置的,再次转置就抵消了,所以:unity Image 倒圆角_unity Image 倒圆角_48


右边 的unity Image 倒圆角_法线变换矩阵_49,由于 矩阵转置 的性质:unity Image 倒圆角_法线变换矩阵_50

所以后变成了:unity Image 倒圆角_IT_M_51

最后边的unity Image 倒圆角_IT_M_52 转置的转置,所以抵消转置 unity Image 倒圆角_法线矩阵_53

所以 右边 unity Image 倒圆角_法线矩阵_54


最终unity Image 倒圆角_IT_M_55号等式 左右两边,变成了:unity Image 倒圆角_IT_M_56


那么结果已经出来了,unity Image 倒圆角_法线矩阵_57 就是我们的 unity Image 倒圆角_IT_58 法线矩阵,就是 unity Image 倒圆角_unity Image 倒圆角_59 的 逆矩阵的转置矩阵

(关于逆矩阵怎么求,你也可以查看我之前的一篇学习笔记:LearnGL - 06.2 - Matrix - 矩阵03 - 逆矩阵、行列式、伴随矩阵、余子式、代数余子式、练习)


总结

这些图形学基础中矩阵变换,建议去吭一下,因为后续会有很多各种各样的变换需求,如果这些基础的变换你能学习、理解这些意义,后续就会越学越顺。

虽然这个 法线矩阵 IT_mMat 本质上暂时没找到很好的理解方式,但是知道他是通过这些数学公式,可重新推导出来的就够了。(当然,如果大神的您,对法线矩阵 有很好的、了解本质的教程,希望您能分享一下,感激不尽!)

而且,后面还有一些 世界空间 到 切线空间 的变换,或是 切线空间 到 世界空间 的变换矩阵(这个知识点,我之前学习过,但是这次为了完善 LearnGL 系列的内容,我会重复再详细的讲解一次,当做温习),这些是为了后续的 法线贴图 需要准备的知识点。


References

  • 顶点法向量从物体坐标系变换到世界坐标系