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

2021-06-17 11:17:13 +08:00
 ccraohng

环境是 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 是对的(我看的这篇教程 也是这样),请问这是为什么?

1518 次点击
所在节点    问与答
16 条回复
3dwelcome
2021-06-17 11:29:12 +08:00
gl-matrix 是定死的,但是 shader 里却是灵活的。可以 Row-Major,也可以 Column-Major 。

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

至于为什么 Shader 里要同时提供两种矩阵格式,那是微软年代的历史遗留问题。当年 GPU 弱鸡,为了性能优化,少几个 DP4 都是赚到的,具体可看 http://www.mvps.org/directx/articles/nontranspose.htm
ccraohng
2021-06-17 11:56:02 +08:00
@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
2021-06-17 12:07:11 +08:00
@ccraohng 说错了,不是转置矩阵。gl-matrix 的写法使用行优先,位置调换一下?
3dwelcome
2021-06-17 12:13:12 +08:00
"但是 gl-matrix 是这样的"

不是啊,https://github.com/toji/gl-matrix/blob/master/src/mat4.js 一开始注释里,就写着是 column-major 格式,也就是应该用 Matrix * V 的写法。
3dwelcome
2021-06-17 12:17:09 +08:00
不是啊,github.com/toji/gl-matrix/blob/master/src/mat4.js 一开始注释里,就写着是 column-major 格式,也就是应该用 V * Matrix 的写法。

上面打错了,汗。

Matrix * V 是 row-major 的写法。
ccraohng
2021-06-17 12:20:04 +08:00
@3dwelcome 你看下 https://github.com/toji/gl-matrix/blob/master/src/mat4.js#L829 fromTranslation 函数, 变换值是写在 12 13 14 索引上的
ccraohng
2021-06-17 12:26:13 +08:00
@3dwelcome 抱歉是我举得例子有问题
3dwelcome
2021-06-17 12:34:48 +08:00
@ccraohng 你说的对,那就是注释写错了。

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

那么 gl_Position = M * V;这样写就没错了。(可明明 V * M 更快)
ccraohng
2021-06-17 13:22:31 +08:00
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
2021-06-17 14:13:15 +08:00
好吧,我又来打脸了。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
2021-06-17 14:40:15 +08:00
@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
2021-06-17 14:42:46 +08:00
@ccraohng
所以,不用再担心需要矩阵转置啦,内存布局都是一样的。只需注意,行主序时,向量的变换要写成 v*M,把向量看作行;列主序则是 M*v,把向量看作列。
sillydaddy
2021-06-17 15:07:45 +08:00
@sillydaddy
上面解释的有点啰嗦,用一句话概括下:
m[0],m[4],m[8],m[12],对于行主序矩阵来说,是其第一列。而对于列主序来说,是其第一行。
所以行矩阵的 v*M[第一列],与列矩阵的 M[第一行]*v,是一样的。
ccraohng
2021-06-17 17:09:04 +08:00
@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
2021-06-17 17:51:51 +08:00
@sillydaddy 没有 “转成”这个概念,我错了。是你发的链接中的 不同解释,老哥可以的话加我 v cmhkaWFuamk=
sillydaddy
2021-06-17 18:13:57 +08:00
@ccraohng
哈哈。☕️就不用啦。你发这个帖子也帮我把行列主序的概念理解清楚了。🙏

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://tanronggui.xyz/t/783916

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX