V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
V2EX 提问指南
ccraohng
V2EX  ›  问与答

感觉走进死胡同了,请教下大佬们关于 glsl 里的矩阵与向量相乘

  •  
  •   ccraohng · 127 天前 · 404 次点击
    这是一个创建于 127 天前的主题,其中的信息可能已经有所发展或是发生改变。

    环境是 webgl 。

    感觉是哪里错了,但是又很奇怪不知道错在哪里,关键字搜了 why transpose 之类的也是下结论之类的。 向大家请教,请喝杯咖啡。

    glsl 手册 56 页

    vec3 v, u;
    mat3 m;
    
    u = v * m;
    
    is equivalent to
    
    u.x = dot(v, m[0]); // m[0] is the left column of m
    u.y = dot(v, m[1]); // dot(a,b) is the inner (dot) product of a and b
    u.z = dot(v, m[2]);
    
    
    And
    u = m * v;
    
    is equivalent to
    
    u.x = m[0].x * v.x + m[1].x * v.y + m[2].x * v.z;
    u.y = m[0].y * v.x + m[1].y * v.y + m[2].y * v.z;
    u.z = m[0].z * v.x + m[1].z * v.y + m[2].z * v.z;
    
    

    向量使用的是列向量吧,比如这时候有一个平移变换 T(tx, ty, tz)

    按照我的理解应该是

    [
      1, 0, 0, tx, 
      0, 1, 0, ty
      0, 0, 1, tz, 
      0, 0, 0, 1
    ]
    

    但是在 gl-matrix 变换里 是这样:

    [
      1, 0, 0, 0, 
      0, 1, 0, 0
      0, 0, 1, 0, 
      tz, ty, tz, 1
    ]
    

    这时候在一个 shader (这是实际正确的) 里

    
    attribute vec4 aVertexPosition;
    uniform mat4 uModelMatrix;
    
    void main(void) {
      gl_Position =  uModelMatrix * aVertexPosition;
    }
    

    在上面的 shader 里,如果按照 gl-matrix 的写法,变成了

    M = [
      1, 0, 0, 0, 
      0, 1, 0, 0
      0, 0, 1, 0, 
      tz, ty, tz, 1
    ]
    
    V = [x, y, z, 1];
    
    gl_Position = M * V;
    
    

    相乘后 tx, ty, tz 不能变换上去吧?

    但是 gl-matrix 是对的(我看的这篇教程 也是这样),请问这是为什么?

    3dwelcome
        1
    3dwelcome   127 天前
    gl-matrix 是定死的,但是 shader 里却是灵活的。可以 Row-Major,也可以 Column-Major 。

    区别就是 M*v 和 v*M 的写法不一样。

    至于为什么 Shader 里要同时提供两种矩阵格式,那是微软年代的历史遗留问题。当年 GPU 弱鸡,为了性能优化,少几个 DP4 都是赚到的,具体可看 http://www.mvps.org/directx/articles/nontranspose.htm
    ccraohng
        2
    ccraohng   127 天前
    @3dwelcome

    是我的表述问题。
    上面手册里定点向量实际为

    [x,
    y,
    z,
    1]

    它的平移变换矩阵,按照 `uModelMatrix * aVertexPosition` 的写法应该(我的理解)为

    ```
    [
    1, 0, 0, tx,
    0, 1, 0, ty
    0, 0, 1, tz,
    0, 0, 0, 1
    ]
    ```

    但是 gl-matrix 是这样的

    ```
    [
    1, 0, 0, 0,
    0, 1, 0, 0
    0, 0, 1, 0,
    tz, ty, tz, 1
    ]
    ```


    gl-matrix 写法是 T(tx, ty, tz) 的转置矩阵,按照我的理解在 shader 里位置应该调换一下

    ```
    gl_Position = aVertexPosition * uModelMatrix
    ```

    为啥
    ccraohng
        3
    ccraohng   127 天前
    @ccraohng 说错了,不是转置矩阵。gl-matrix 的写法使用行优先,位置调换一下?
    3dwelcome
        4
    3dwelcome   127 天前
    "但是 gl-matrix 是这样的"

    不是啊,https://github.com/toji/gl-matrix/blob/master/src/mat4.js 一开始注释里,就写着是 column-major 格式,也就是应该用 Matrix * V 的写法。
    3dwelcome
        5
    3dwelcome   127 天前
    不是啊,github.com/toji/gl-matrix/blob/master/src/mat4.js 一开始注释里,就写着是 column-major 格式,也就是应该用 V * Matrix 的写法。

    上面打错了,汗。

    Matrix * V 是 row-major 的写法。
    ccraohng
        6
    ccraohng   127 天前
    @3dwelcome 你看下 https://github.com/toji/gl-matrix/blob/master/src/mat4.js#L829 fromTranslation 函数, 变换值是写在 12 13 14 索引上的
    ccraohng
        7
    ccraohng   127 天前
    @3dwelcome 抱歉是我举得例子有问题
    3dwelcome
        8
    3dwelcome   127 天前
    @ccraohng 你说的对,那就是注释写错了。

    我看 mat4.js 里的 translate 函数,也证明内部结构 row-major,并不符合标准的 OpenGL Matrix 格式,也许是为了和一些 3D 软件相互兼容。

    那么 gl_Position = M * V;这样写就没错了。(可明明 V * M 更快)
    ccraohng
        9
    ccraohng   127 天前
    https://developer.mozilla.org/zh-CN/docs/Web/API/WebGLRenderingContext/uniformMatrix

    这个方法假定矩阵是列优先。

    [
    1, 0, 0, tx,
    0, 1, 0, ty
    0, 0, 1, tz,
    0, 0, 0, 1
    ]

    会变成

    [
    1, 0, 0, 0,
    0, 1, 0, 0
    0, 0, 1, 0,
    tz, ty, tz, 1
    ]
    与正确结果相反,所以 gl-matrix 使用的是

    [
    1, 0, 0, 0,
    0, 1, 0, 0
    0, 0, 1, 0,
    tz, ty, tz, 1
    ]
    3dwelcome
        10
    3dwelcome   127 天前
    好吧,我又来打脸了。gl-matrix 注释没错,矩阵是列优先。

    我去查看了 https://eli.thegreenplace.net/2015/memory-layout-of-multi-dimensional-arrays 里面的详细说明。

    这个矩阵
    [
    1, 0, 0, tx,
    0, 1, 0, ty
    0, 0, 1, tz,
    0, 0, 0, 1
    ]

    在连续内存里,真实排列就是:
    [1,0,0,0][0,1,0,0][0,0,1,0][tx,ty,tz,1]

    所以 fromTranslation 函数, 变换值是填在 12 13 14 索引上,是完全没问题的。

    所以不是 gl-matrix 写法问题,而是内存布局本来就是这样写的。
    sillydaddy
        11
    sillydaddy   127 天前   ❤️ 2
    @ccraohng
    @3dwelcome
    这个行主序、列主序,确实挺绕脚的,今天看两位在这讨论,于是又去查了一下,

    下面这个解释应该是靠谱的:
    https://blog.lazybee.me/d3dopengl_matrix/
    意思就是,行主序和列主序,在实际程序里面,真正的内存布局都是一样的,都是 m[12] m[13] m[14]表示平移值,但是,2 者对于行和列的解释不一样,行主序主张第 i 行第 j 列的值存储在 a[i][j],即 m[i*4+j]中,列主序则主张第 i 行第 j 列的值存储在 a[j][i],即 m[j*4+i]中。
    那么这两种不同的主张,为什么会有相同的内存布局呢?这是因为,行主序主张把向量看作是“行”,坐标变换写作 v=v*M,而列主序主张把向量看作是“列”,于是 v=M*v 。这样的要求,导致按行主序(v*M)运算时,要从 M 中按列取,而按列主序(M*v)运算时则要按行取。而行主序对“行”的定义,并不是列主序对“行”的定义,反而恰恰是列主序对“列”的定义(如前所述的 a[i][j]和 a[j][i]),所以,内存布局就一样了。所以关键在于是 v*M 还是 M*v 。

    下面这个说明里面也解释了作者的无奈。
    https://glmatrix.net
    sillydaddy
        12
    sillydaddy   127 天前
    @ccraohng
    所以,不用再担心需要矩阵转置啦,内存布局都是一样的。只需注意,行主序时,向量的变换要写成 v*M,把向量看作行;列主序则是 M*v,把向量看作列。
    sillydaddy
        13
    sillydaddy   127 天前
    @sillydaddy
    上面解释的有点啰嗦,用一句话概括下:
    m[0],m[4],m[8],m[12],对于行主序矩阵来说,是其第一列。而对于列主序来说,是其第一行。
    所以行矩阵的 v*M[第一列],与列矩阵的 M[第一行]*v,是一样的。
    ccraohng
        14
    ccraohng   127 天前
    @sillydaddy

    什么行列优先。

    我是奇怪 gl-matrix 的写法为何“反着”的,
    在 shader 里是
    [
    1, 0, 0, tx,
    0, 1, 0, ty
    0, 0, 1, tz,
    0, 0, 0, 1
    ] *
    [x,
    y,
    z,
    1]

    MDN 上说了 uniformMatrix 是以列优先录入值,即坐标 0 - 3 会被转成 m[0] ( m[0] is the left column of m )

    [
    1, 0, 0, 0,
    0, 1, 0, 0
    0, 0, 1, 0,
    tz, ty, tz, 1
    ] 刚好被 转成

    [
    1, 0, 0, tx,
    0, 1, 0, ty
    0, 0, 1, tz,
    0, 0, 0, 1
    ]
    ccraohng
        15
    ccraohng   127 天前
    @sillydaddy 没有 “转成”这个概念,我错了。是你发的链接中的 不同解释,老哥可以的话加我 v cmhkaWFuamk=
    sillydaddy
        16
    sillydaddy   127 天前
    @ccraohng
    哈哈。☕️就不用啦。你发这个帖子也帮我把行列主序的概念理解清楚了。🙏
    关于   ·   帮助文档   ·   FAQ   ·   API   ·   我们的愿景   ·   广告投放   ·   感谢   ·   实用小工具   ·   2183 人在线   最高记录 5497   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 18ms · UTC 12:15 · PVG 20:15 · LAX 05:15 · JFK 08:15
    ♥ Do have faith in what you're doing.