本文将学习 WebGPU 中的 3D 空间体系,预计通过 2 篇文章,来全面了解 从原始的顶点坐标 如何一步一步映射为屏幕坐标。
WebGPU中的XYZ坐标系
在之前的文章中,我们简单提到过 WebGPU 的坐标系,再来复习一下。
无论此时你看的是手机屏幕,还是电脑显示器,我们假设它是个正方形,那么:
**X轴:**屏幕的水平方向为 x 轴,最左侧 x 值为 -1,中间位置 x 值为 0,最右侧 x 值为 1
x 的取值范围为 -1 到 1,即 -1<= x <= 1
**Y轴:**屏幕的上下方向为 y 轴:最上面 y 值为 1,中间位置 y 值为 0,最下面 y 值为 -1
y 的取值范围为 -1 到 1,即 -1<= y <= 1
**Z轴:**想象有一个长度为 1 的线,它从屏幕正中间以垂直的方式向屏幕背后延伸,这根线为 z 轴。紧贴屏幕的那一端 z 值为 0,远离屏幕的那一端 z 值为 1
z 的取值范围为 0 到 1,即 0<= z <=1
补充说明:在很多 3D 软件中,例如 Three.js 或 Blender,通常约定
- X 轴使用 红色线 表示
- Y 轴使用 绿色线 表示
- Z 轴使用 蓝色线 表示
刚好就是 RGB 中的 Red、Green、Blue 这 3 个颜色顺序。
这样无论当前 XYZ 轴如何旋转,我们都可以通过颜色快速对它们进行区分。
值的大小与显示屏的位置关系:
对于 x,y,z 而言,其值越接近于 0 则表示它们越接近屏幕的中心点。
屏幕的中心点位置,相当于这套坐标系的原点,即原点坐标为 (0,0,0)。
上面这段关于 WebGPU xyz 空间坐标体系的讲解,实际上理解起来并不难。
但是我知道你心中可能一直有一个疑惑,那就是:
为什么这套坐标体系和 Three.js 中的坐标体系不一样?
学习过 Three.js 的人都知道,Three.js 采用右手坐标系:
- 伸出右手,五指张开,让手掌垂直于屏幕,掌心朝向自己
- 蜷缩住小拇指、无名指
- 伸直中指并指向自己,此时 中指垂直于 食指
那么就构成了一个右手 3D 空间坐标体系:
- 大拇指方向为 x 正轴方向
- 食指方向为 y 轴正轴方向
- 中指方向为 z 轴正轴方向
右手 3D 空间坐标系 与 我们本文学习的 WebGPU 坐标系最明显的不同点在于 z 轴。
这两套坐标系的 z 轴都是垂直于屏幕,但:
-
右手坐标系中的 z 轴既朝着屏幕前延伸,也朝着屏幕后延伸,而 WebGPU 中的 z 轴仅为 朝着 屏幕后 延伸
-
对于 Three.js 中的坐标系中 x,y,z 的值,取值范围都是 负无穷大 到 正无穷大,而 WebGPU 中他们的取值是有范围的。
再说一遍:x,y 取值范围 -1<= x, y <=1,z 取值范围 0<= z <=1
“屏幕”这个词仅仅是为我们为了方便空间想象而使用的,你可以把 “屏幕” 替换成 “坐标系原点” 再重新读一遍。
WebGPU 中的这套坐标系是它独创的吗?
不是的,3D 引擎或软件底层几乎都采用这种坐标系,例如 虚幻引擎、Unity。
这种空间坐标系有一个正经的名称:标准化设备坐标系(Normalized Device Coordinates),简称 NDC。
当然 Normalized 也可以翻译为 “归一化”,因此我们也可以将 NDC 称呼为 “归一化设备坐标系”。
就我个人喜好而言,我更倾向于使用 “归一化设备坐标系” 这个称呼。
如果在我以后的文章中出现 “NDC坐标”,你明白它是指 “归一化设备坐标系中的一个坐标”。
不过我个人写作风格是能用中文就不用英文。
我们暂时先停止对 归一化设备坐标系 的讲解,回到我们之前写过的那个绘制三角形的示例中。
在之前绘制一个简单三角形的示例中,假设我把顶点的 z 值由 0 修改为其他值,那绘制结果会发生什么变化呢?
请记得我们之前说过,z 值的取值范围应该是 0<= z <= 1
如果你把之前绘制三角形的 z 值由 0.0 修改为 0.5 或者 1,那是不是更远离屏幕,绘制的三角形会显得更小一些呢?
实际运行你会发现 绘制出来的三角形 无论是位置还是大小,都没有任何变化!
为什么???
因为对于 WebGPU 默认的 归一化设备坐标系 而言,他执行的是 正交投影。
透视投影:就像是我们人眼看到的那样,物体 远小近大。
正交投影:物体无论远近,其投影的大小是相同的。
这就是为什么刚才我们修改了 顶点的 z 值,但是最终呈现的大小却没有丝毫变化。
那怎么设置才可以让 z 值生效?
答:在顶点着色器中,通过矩阵转换,将原本的 正交投影 转换成 透视投影,此时表示前后深度的 z 坐标就会生效并体现出来。
这里引申出一个问题:顶点着色器通常用来做什么事情?
这是我们想成为 着色器高手的第一个正式问题
顶点着色器通常用来承担以下几个任务:
- 接收传递进来的顶点坐标(模型数据)
- 将接收到的顶点数据进行适当的转换,并最终输出转化后的顶点信息
- 顶点信息除了坐标外,还可以包含其他信息,例如:纹理UV坐标,插值、以及其他要传递给片元着色的数据
其中 “适当的转换” 就包括我们上面提到的 通过矩阵转换 将 正交投影 变为 透视投影。
目前我们还未接触 相机(camera),我们知道通过更改相机角度可以改变最终渲染的视图结果。
而 相机 本质就是一种矩阵转换。
我们反思另外一个问题:WebGPU 中 xyz 的坐标值究竟取值范围是多少?我们之前反复说的取值范围 -1<= x,y <=1,0<= z <=1 是对的吗?
答:错误的,根本不是那么回事。
之前提到的 -1<= x,y <=1,0<= z <=1 这种取值范围,它只是 归一化设备坐标系(NDC) 下,我们希望顶点完全可见情况下的安全取值范围。
如果我们设置的坐标值超出了所谓的安全取值范围,那么多出来的地方就会被裁切掉,看不见,WebGPU 也不会报错,仅此而已。
但是,假设我们不再是直接将 NDC 坐标直接渲染,而是在顶点着色器中增加了适当的转换(例如矩阵转换),那么意味着写入到顶点缓冲区的 xyz 坐标值是多大都行,根本没有取值范围一说。
至此,我们有 2 个急迫的问题想知道答案:
- 怎么设置可以让 z 坐标生效,让最终渲染呈现出 透视效果?
- 顶点着色器中如何添加 矩阵转换?
这两个问题实际上是同一个问题:正交投影如何转化为透视投影?
本文到此结束,精彩就在下一篇文章中。