文章目录
- 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 来测试
上图,左边 原始图形 中的 是切线, 是法线,右边 缩放后的图形 的 是切线,
在统一缩放(缩放分量相同)看起来没有问题
如果在非统一缩放呢?看看:
看起来 还是 保持着 平面上的 切线,但是 就 没有保持 与平面 垂直 了。
所以,在非统一缩放时,用 mMat 矩阵来变换法线是不行的。
那么我们利用一些已知的数学公式来推导:
已知:
- Model Matrix(对象到世界空间变换矩阵) mMat ,我们定义符号为:
- 是
- 是
是目前不知道的,正式是我们要求的 法线变换矩阵,即:IT_mMat
由于法线是垂直于切线的,所以我们可以将一些公理式子列出来:
上面为何第(1), (2) 的 要用转置的,因为我们将 视为 Nx1 的矩阵 所以我们将同样的
(上面关于点积 dot 可以参考我之前写的:LearnGL - 11.1 - 实现简单的Gouraud-Phong光照模型 - dot - 点积的作用)
我们将号等式左边部分在中间添加一个,那么结果为:,其中
就是一个矩阵与它的逆矩阵变换后就抵消了矩阵的所有变换,没有变换的特性就是 单位矩阵 的特性,所以这个是没有问题的
所以:
仔细观察公式中红色部分, 中 的 ,其实这部分就是等式右边的内容:
所以我们的式子再次可以调整为:
再次仔细观察一下与
把等式左边(也就 蓝色 部分)的 去掉,变成下面的等式
现在可以清晰看到和的红色部分的是相同的,那么剩下的部分我们标记上绿色:
这绿色的部分,我们也可以认为他们是相等的
即:
为了便于观察,将等式 左右 两个的内容 分别标记 为 红色、绿色
再将等式两边同时转置一下:
左边 的 本身有转置的,再次转置就抵消了,所以:
右边 的,由于 矩阵转置 的性质:
所以后变成了:
最后边的 转置的转置,所以抵消转置
所以 右边
最终号等式 左右两边,变成了:
那么结果已经出来了, 就是我们的 法线矩阵,就是 的 逆矩阵的转置矩阵
(关于逆矩阵怎么求,你也可以查看我之前的一篇学习笔记:LearnGL - 06.2 - Matrix - 矩阵03 - 逆矩阵、行列式、伴随矩阵、余子式、代数余子式、练习)
总结
这些图形学基础中矩阵变换,建议去吭一下,因为后续会有很多各种各样的变换需求,如果这些基础的变换你能学习、理解这些意义,后续就会越学越顺。
虽然这个 法线矩阵 IT_mMat 本质上暂时没找到很好的理解方式,但是知道他是通过这些数学公式,可重新推导出来的就够了。(当然,如果大神的您,对法线矩阵 有很好的、了解本质的教程,希望您能分享一下,感激不尽!)
而且,后面还有一些 世界空间 到 切线空间 的变换,或是 切线空间 到 世界空间 的变换矩阵(这个知识点,我之前学习过,但是这次为了完善 LearnGL 系列的内容,我会重复再详细的讲解一次,当做温习),这些是为了后续的 法线贴图 需要准备的知识点。
References
- 顶点法向量从物体坐标系变换到世界坐标系