跳转至

Glm矩阵与向量乘法

示例

auto mat = glm::translate(glm::mat4(1.0f), glm::vec3(-0.5, 0.5, 1)); 
    //按列式存储
auto v = glm::vec4(1.0, 1.0, 1.0, 1.0); 
    //列式存储。没有重写一个vec3,重用matrix
auto tmp = mat * v; 
    //为了和glsl统一

输出

matrix: mat4x4((1.000000, 0.000000, 0.000000, 0.000000), (0.000000, 1.000000, 0.000000, 0.000000), (0.000000, 0.000000, 1.000000, 0.000000), (-0.500000, 0.500000, 1.000000, 1.000000))
vector: vec4(1.000000, 1.000000, 1.000000, 1.000000)
result: vec4(0.500000, 1.500000, 2.000000, 1.000000)

整理成行列式,方便查看

  • 根据资料显示,glm的向量是列向量
  • 而且,向量 = 矩阵 * 向量
  • 因此,可以将示例整理成如下形式
|  1.0 0.0 0.0 0.0 |   | 1 |    | 0.5 |
|  0.0 1.0 0.0 0.0 | x | 1 | =  | 1.5 |
|  0.0 0.0 1.0 0.0 |   | 1 |    | 2.0 |
| -0.5 0.5 1.0 1.0 |   | 1 |    | 1.0 |

分析

手动计算

我们根据矩阵的计算方式,手动计算一下:

计算公式

| m11 m12 m13 m14 |   | x |
| m21 m22 m23 m24 | x | y | 
| m31 m32 m33 m34 |   | z |
| m41 m42 m43 m44 |   | w |

= 
    (
        x*m11 + y*m12 + z*m13 + w*m14,
        x*m21 + y*m22 + z*m23 + w*m24,
        x*m31 + y*m32 + z*m33 + w*m34,
        x*m41 + y*m42 + z*m43 + w*m44
    )

计算结果

|   1     0 0 0 |   | 1 |
|   0     1 0 0 | x | 1 | = (1, 1, 1, 1)
|   0     0 1 0 |   | 1 |
| -0.5 -0.5 1 1 |   | 1 |

发现结果和程序运行的对不到!那只有一种可能了,glm的 (4x4) * (4x1) 并不是数学中矩阵的计算方法!

查看glm源码

template<typename T, qualifier Q>
    GLM_FUNC_QUALIFIER typename mat<4, 4, T, Q>::col_type operator*
    (
        mat<4, 4, T, Q> const& m,
        typename mat<4, 4, T, Q>::row_type const& v
    )
    {
        typename mat<4, 4, T, Q>::col_type const Mov0(v[0]);            
            //x
        typename mat<4, 4, T, Q>::col_type const Mov1(v[1]);            
            //y
        typename mat<4, 4, T, Q>::col_type const Mul0 = m[0] * Mov0;    
            //第一行 * x
        typename mat<4, 4, T, Q>::col_type const Mul1 = m[1] * Mov1;    
            //第二行 * y
        typename mat<4, 4, T, Q>::col_type const Add0 = Mul0 + Mul1;    
            //第一行 * x + 第二行 * y

        typename mat<4, 4, T, Q>::col_type const Mov2(v[2]);            
            //z
        typename mat<4, 4, T, Q>::col_type const Mov3(v[3]);            
            //w
        typename mat<4, 4, T, Q>::col_type const Mul2 = m[2] * Mov2;    
            //第三行 * z
        typename mat<4, 4, T, Q>::col_type const Mul3 = m[3] * Mov3;    
            //第四行 * w
        typename mat<4, 4, T, Q>::col_type const Add1 = Mul2 + Mul3;    
            //第三行 * z + 第四行 * w

        typename mat<4, 4, T, Q>::col_type const Add2 = Add0 + Add1;    
            //第一行 * x + 第二行 * y + 第三行 * z + 第四行 * w
        return Add2;
    }

梳理源码,惊讶的发现,在glm中:(4x4) * (4x1)的结果居然是(1x4) * (4x4)的结果。

| m11 m12 m13 m14 |   | x |
| m21 m22 m23 m24 | x | y | 
| m31 m32 m33 m34 |   | z |
| m41 m42 m43 m44 |   | w |

=x(m11, m12, m13, m14) + y(m21, m22, m23, m24) + z(m31, m32, m33, m34) + w(m41, m42, m43, m44)

= 
    (
        x*m11 + y*m21 + z*m31 + w*m41,
        x*m12 + y*m22 + z*m32 + w*m42,
        x*m13 + y*m23 + z*m33 + w*m43,
        x*m14 + y*m24 + z*m34 + w*m44
    )
= 

 (x, y, z, w) x | m11 m12 m13 m14 |
                | m21 m22 m23 m24 |
                | m31 m32 m33 m34 |
                | m41 m42 m43 m44 |

按照示例代入,计算结果确实是对的。

结论

因此,不要被glm骗了。

  • 在数学的角度、行列式的角度,glm的向量其实是行向量,应该写成向量 = 向量 * 矩阵(因为矩阵是列式存储的)
  • 但它被人为定义成列向量,并人为重新定义了operator *,最后写成向量 = 矩阵 * 向量的形式
  • 这可能是为了和GLSL做统一,因为在GLSL中,也是向量 = 矩阵 * 向量的形式

总结

glm是OpenGL的数学库,因此OpenGL的情况与glm一致

  1. glm的矩阵是按列优先存储(列主序)
  2. glm的向量被视为列向量
  3. 向量 = 矩阵 * 向量,需注意的是,此处的乘法并不是行列式的乘法,而是被程序员自定义了