矩阵的平移(translate)没有想象的那么简单
矩阵在计算机图形学中的地位是举足轻重的,几乎所有的变换都需要通过矩阵来完成。比如我接触的DirectX9就用IDirect3DDevice9::SetTransform()的第二个参数就要求传入矩阵的指针,而OpenGL也有直接载入矩阵的方法:glLoadMatrixf(),足以见矩阵在相关领域的作用是多么大。
对三维物体的变换(transform)包括基本的平移(translation)、旋转(rotation)、缩放(scaling)\错切(shearing)以及它们的组合变换。它们都作用在一个4×4的矩阵中。因此计算机图形学重点的研究对象就是4×4的矩阵。这里谈一谈我在学习矩阵的平移时候遇到的问题,其实矩阵的平移(translate)没有想象的那么简单。
到谷歌翻译搜索translate找到的翻译竟然没有“平移”这一层意思!哎,是我错了呢还是谷歌翻译没有收录呢?反过来翻译倒是有“平移”=“translation”。可是教材上写的“translate”可是等于“平移”的,而且其他的三维模型库都有名为“translate”这个函数,所以我强烈建议谷歌翻译添加“平移”这一层释义。
不过呢,我理解的“平移”可不是字面上的意思,而是在对“translate”这个函数的实现上出现了理解上的偏差。什么偏差呢,那要从《OpenGL超级宝典》(第四版)的源代码说起。
一开始我是《超级宝典》(第四版)的路线进行学习的,结合电子书和书中附带的源代码进行学习。源代码里有一个公共文件夹,里面有Richard S. Wright Jr.大神写的3D数学库,也就两个文件math3d.h和math3d.cpp。里面有一簇函数:m3dTranslationMatrix44(),根据字面意思,应该对矩阵进行平移的吧,它的实现也非常简单,这里为了方便起见,我将源代码贴出来:
// Create a Translation matrix. Only 4x4 matrices have translation components
inline void m3dTranslationMatrix44(M3DMatrix44f m, float x, float y, float z)
{ m3dLoadIdentity44(m); m[12] = x; m[13] = y; m[14] = z; }
inline void m3dTranslationMatrix44(M3DMatrix44d m, double x, double y, double z)
{ m3dLoadIdentity44(m); m[12] = x; m[13] = y; m[14] = z; }
// Translate matrix. Only 4x4 matrices supported
inline void m3dTranslateMatrix44(M3DMatrix44f m, float x, float y, float z)
{ m[12] += x; m[13] += y; m[14] += z; }
inline void m3dTranslateMatrix44(M3DMatrix44d m, double x, double y, double z)
{ m[12] += x; m[13] += y; m[14] += z; }
后来我到了自己写数学库的阶段了,我的目标是用C++优雅的代码改写并且封装一下大神这些算法,看到上面的代码,我就这么写:
/*---------------------------------------------------------------------------*/
void Matrix44F::Translate( float x, float y, float z )
{
m[12] += x;
m[13] += y;
m[14] += z;
}
/*---------------------------------------------------------------------------*/
后面我在进行蒙皮动画的运算时,总是得不到正确的效果,这让我伤透了脑筋。我逐渐地怀疑起我写的数学库了。于是我拿Qt自带的数学库来作同样的运算,结果是正确的。这加深了我的怀疑,经过层层排除,我将错误锁定在了上面的Translate()函数。后面我拿Qt源码中的QMatrix4x4::translate()函数作对比,得出了结论:我写的Translate()函数是错误的,正确的写法远没有那么简单。
其实正确的平移方法是:先取一个单位矩阵,做原来Translate()函数那样的平移,然后用原矩阵左乘平移后的矩阵。于是我将代码改写了一下:
/*---------------------------------------------------------------------------*/
voidMatrix44F::Translate( float x, float y, float z )
{
Matrix44F matrix;
matrix.m[12] += x;
matrix.m[13] += y;
matrix.m[14] += z;
*this *= matrix;// 左乘应用矩阵
}
/*---------------------------------------------------------------------------*/
运行结果出来,正确。
哎,原来是这样一个问题症结啊。写这篇文章提醒一下自己,同时也提醒一下同行们,可能有些计算的问题是《超级宝典》(第四版)带来的呢。