行主序 vs 列主序 Row Major vs Column Major Vector

计算机图形学数学基础译自 ScratchaPixel 网站,数学基础部分共八篇,本篇为第五篇,感兴趣的同学可以参考我的 GitBook 镜像。

行主序和列主序 Row Major and Column Major

之前描述的向量和点都写为1 x 3(一行三列)形式的矩阵,实际上还可以写为3 x 1(三行一列)形式的矩阵。技术上这两种表达形式都可以,采用某种书写方式只是习惯问题。

向量写为[1x3]矩阵形式: ,被称为行主序(Row Major)。

向量写为[3x1]矩阵形式:,被称为列主序(Column Major)。

之前提到过两个矩阵的形式必须为[mxp][pxn]的形式才能相乘。因此对于行主序的[1x3]的矩阵可以与[3x3]转换矩阵相乘。即:

left

这种形式被称为 左乘(left or pre-multiplication)

但是对于[3x1]的矩阵只能是 右乘(right or post-multiplication)的形式:

right

下图是矩阵的左乘和右乘结果的区别:

multiplication

为了得到相同的结果,我们可以右乘矩阵的转置:

multiplication

OpenGL常用列主序的形式。

C++中矩阵的乘法以及性能影响

C++中矩阵常写为:

class Matrix44 {
  ... float m[4][4];
}

可以看到[4x4]矩阵的16个系数保存在二维数组内,这意味着内存中这16个系数按这种次序:c00, c01, c02, c03, c10, c11, c12, c13, c20, c21, c22, c23, c30, c31, c32, c33。换句话说,它们在内存中连续保存。以下是行主序的向量-矩阵乘法:

// row-major order
x' = x * c00 + y * c10 + z * c20;
y' = x * c01 + y * c11 + z * c21;
z' = x * c02 + y * c12 + z * c22;

可以看到在计算x’的值时,对数组内的取值并不是连续的(取第1, 5, 9的元素),对于y'z'的计算同样存在这个问题。实际上这样会潜在的影响CPU的缓存性能(不做展开…)。解决的方案是采用列主序的形式:

 // column-major order
 x' = c00 * x + c01 * y + c02 * z;
 y' = c10 * x + c11 * y + c12 * z;
 z' = c20 * x + c21 * y + c22 * z;

这种方案可以有效利用CPU的缓存机制。C++代码:

 template<typename T>
 class Vec3 {
public:
   Vec3(T xx, T yy, T zz) : x(xx), y(yy), z(zz) {}
     T x, y, z, w;
 };

 template<typename T>
 class Matrix44 {
public:
   T m[4][4];
   Vec3<T> multVecMatrix(const Vec3<T> &v) {
#ifdef ROWMAJOR     
   return Vec3<T> (
            v.x * m[0][0] + v.y * m[1][0] + v.z * m[2][0], 
            v.x * m[0][1] + v.y * m[1][1] + v.z * m[2][1], 
            v.x * m[0][2] + v.y * m[1][2] + v.z * m[2][2]);
#else
    return Vec3<T> (
            v.x * m[0][0] + v.y * m[0][1] + v.z * m[0][2], 
            v.x * m[1][0] + v.y * m[1][1] + v.z * m[1][2], 
            v.x * m[2][0] + v.y * m[2][1] + v.z * m[2][2]); 
#elseif
   }
 };

 #include <cmath> 
 #include <cstdlib> 
 #include <cstdio> 
 #include <ctime> 

#define MAX_ITER 10e8
int main(int argc, char **argv) {
  clock_t start = clock();
  Vec3<float> v(1,2,3);
  Matrix44<float> M;
  float *tmp = &M.m[0][0];
  for(int i = 0; i< 16; i++) *(tmp + i) = drand48();
  for(int i = 0; i< MAX_ITER; i++) {
    Vec3<float> vt = M.multVecMatrix(v);
  }
  fprintf(stderr, ""Clock time %f\n", (clock() - start) / float(CLOCKS_PER_SEC)");
  return 0;
}

行主序和列主序的计算顺序

在行主序的矩阵中,元素在内存中的存储方式是先由左到右,后由上到下,这是C++中常用的方法,例如有矩阵:

在C++中可以写为:

float m[2][3] = {{1,2,3},{4,5,6}}

这个数组在内存中的存储顺序为:
1 2 3 4 5 6

一些使用列主序的语言,如Fortan, MatLab。元素存储的方式是先由上到下,后由左到右,即:
1 4 2 5 3 6
由于需求我们只考虑C/C++中元素在内存中的排列方式,因此只考虑行主序形式。

总结

summary

  • 4
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值