从0开始做一个软渲染器 —— 法线变换

从0开始做一个软渲染器 —— 法线变换

1. 为什么要进行法线变换

如果只对模型的位置进行变换,例如旋转和缩放(位移不会改变法线),而不考虑法线进行相应变换,那么模型的光照就一整个乱掉。

2. 怎么进行法线变换

如果模型只是旋转或者XYZ等比例的缩放,那么法线直接乘以ModelMatrix不会有什么问题,但是如果不是等比例的缩放就会出现问题,参考文章图形学 | Shader |用一篇文章理解法线变换、切线空间、法线贴图 - 知乎 (zhihu.com)

假设光线是垂直向-Z方向,光照强度 = \(normal * -light.dir\)正常光照效果如图:

normal_corr.png

如果缩放比例\(XYZ = 1, 1, 5\),那么按理来说脸上法线正确情况会向两侧扩,也就是的光照强度变弱,脸会变暗。但是直接把法线乘以ModelMatrix的效果如下:

normal_err.png

脸明显变白了,显然法线出现错误。

如何进行正确的法线变换参考下面文章:

正确的normal transform matrix

\[(M^{-1})^T \]

求逆操作是一个消耗很大的过程,但是由于Scale Matrix, Rotate Matrix 和 Traslate Matrix的特殊性可以直接避免普通矩阵的求逆操作,参考下面两篇文章:

按照公式(不考虑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;
}

结果如下图所示,符合预期:

normal_corr2.png

posted @ 2024-03-05 16:09  DogWealth~  阅读(17)  评论(0编辑  收藏  举报