从0开始做一个软渲染器 —— 法线变换
从0开始做一个软渲染器 —— 法线变换
1. 为什么要进行法线变换
如果只对模型的位置进行变换,例如旋转和缩放(位移不会改变法线),而不考虑法线进行相应变换,那么模型的光照就一整个乱掉。
2. 怎么进行法线变换
如果模型只是旋转或者XYZ等比例的缩放,那么法线直接乘以ModelMatrix
不会有什么问题,但是如果不是等比例的缩放就会出现问题,参考文章图形学 | Shader |用一篇文章理解法线变换、切线空间、法线贴图 - 知乎 (zhihu.com)
假设光线是垂直向-Z方向,光照强度 = \(normal * -light.dir\)正常光照效果如图:
如果缩放比例\(XYZ = 1, 1, 5\),那么按理来说脸上法线正确情况会向两侧扩,也就是的光照强度变弱,脸会变暗。但是直接把法线乘以ModelMatrix
的效果如下:
脸明显变白了,显然法线出现错误。
如何进行正确的法线变换参考下面文章:
正确的normal transform matrix为
\[(M^{-1})^T
\]
求逆操作是一个消耗很大的过程,但是由于Scale Matrix, Rotate Matrix 和 Traslate Matrix
的特殊性可以直接避免普通矩阵的求逆操作,参考下面两篇文章:
- Fast 4x4 Matrix Inverse with SSE SIMD, Explained - Eric's Blog (lxjk.github.io)
- Stop Using Normal Matrix - Eric's Blog (lxjk.github.io)
按照公式(不考虑T变换)
\[M=\left( \begin{matrix} \vec{X} & 0 \\ \vec{Y} & 0 \\ \vec{Z} & 0 \\ \vec{T} & 1 \\ \end{matrix} \right), M^{-1}=\left( \begin{matrix} \frac{1}{{\left|\vec{X}\right|}^{2}}\vec{X} & \frac{1}{{\left|\vec{Y}\right|}^{2}}\vec{Y} & \frac{1}{{\left|\vec{Z}\right|}^{2}}\vec{Z} & \vec{0} \\ -\vec{T}\cdot\frac{1}{{\left|\vec{X}\right|}^{2}}\vec{X} & -\vec{T}\cdot\frac{1}{{\left|\vec{Y}\right|}^{2}}\vec{Y} & -\vec{T}\cdot\frac{1}{{\left|\vec{Z}\right|}^{2}}\vec{Z} & 1 \\ \end{matrix} \right)
\]
代码如下:
V2F BasicShader::VertexShader(const Vertex& v)
{
V2F v2f;
v2f.m_WorldPos = v.m_Position * m_ModelMatrix;
v2f.m_ScreenPos = v.m_Position * m_ModelMatrix * m_VPMatrix;
//法线变换, 求变换矩阵
float MagX_Square = m_ModelMatrix.m_Mat[0][0] * m_ModelMatrix.m_Mat[0][0] +
m_ModelMatrix.m_Mat[0][1] * m_ModelMatrix.m_Mat[0][1] +
m_ModelMatrix.m_Mat[0][2] * m_ModelMatrix.m_Mat[0][2];
float MagY_Square = m_ModelMatrix.m_Mat[1][0] * m_ModelMatrix.m_Mat[1][0] +
m_ModelMatrix.m_Mat[1][1] * m_ModelMatrix.m_Mat[1][1] +
m_ModelMatrix.m_Mat[1][2] * m_ModelMatrix.m_Mat[1][2];
float MagZ_Square = m_ModelMatrix.m_Mat[2][0] * m_ModelMatrix.m_Mat[2][0] +
m_ModelMatrix.m_Mat[2][1] * m_ModelMatrix.m_Mat[2][1] +
m_ModelMatrix.m_Mat[2][2] * m_ModelMatrix.m_Mat[2][2];
Matrix4 normalMatrix = m_ModelMatrix;
normalMatrix.m_Mat[3][0] = 0;
normalMatrix.m_Mat[3][1] = 0;
normalMatrix.m_Mat[3][2] = 0;
normalMatrix.m_Mat[0][0] /= MagX_Square;
normalMatrix.m_Mat[0][1] /= MagX_Square;
normalMatrix.m_Mat[0][2] /= MagX_Square;
normalMatrix.m_Mat[1][0] /= MagY_Square;
normalMatrix.m_Mat[1][1] /= MagY_Square;
normalMatrix.m_Mat[1][2] /= MagY_Square;
normalMatrix.m_Mat[2][0] /= MagZ_Square;
normalMatrix.m_Mat[2][1] /= MagZ_Square;
normalMatrix.m_Mat[2][2] /= MagZ_Square;
v2f.m_Normal = v.m_Normal * normalMatrix;
v2f.m_Texcoord = v.m_TexCoord;
v2f.m_Color = v.m_Color;
return v2f;
}
结果如下图所示,符合预期: