为了澄清dantenwolf的回答,你的3D空间与2D画布之间的坐标映射正是你想要的。你可以通过
gl.viewport
和传递给着色器的矩阵来控制它。
gl.viewport
仅仅在你绘制的画布上阻止了一个像素矩形。大多数情况下,这与你的画布尺寸完全匹配,但有些情况下你只想在部分区域内进行绘制。(比如分屏游戏) 从现在开始,你所绘制到的画布区域将被称为视口。如果您愿意,可以把它当作"画布"。
最简单地说,视口在X轴和Y轴上始终具有从-1到1的隐式坐标系统。这是由你的顶点着色器输出的
gl_Position
所操作的空间。如果你在(-1,-1)处输出一个顶点,它将位于视口的左下角。位于(1,1)处的顶点将位于右上角。(是的,我暂时忽略深度)使用这个,你可以构建几何体来映射到该空间,并且不需要任何矩阵变换就可以绘制它,但是这可能会有点棘手。
为了让生活更轻松,我们使用投影矩阵。投影矩阵仅仅是将你的几何体从任意的3D空间转换成视口所需的-1到1的空间的矩阵。最常见的一个是透视矩阵。如何创建它看起来会有点不同,这取决于你使用的库,但通常是这样的:
var fov = 45;
var aspectRatio = canvas.width/canvas.height;
var near = 1.0;
var far = 1024.0;
var projectionMat = mat4.perspective(fov, aspect, near, far);
我不打算深入解释这些所有的值都代表什么,但你可以清楚地看到我们使用了画布的宽度和高度来帮助设置这个投影。这使得它不会因为画布大小而显得被拉伸或挤压。然而,归根结底,就是将空间中的任何三维点乘以这个矩阵将产生一个映射到-1到1空间的点,考虑到距离“相机”和其他一切。(实际上可能落在该范围之外,但这仅表示它已经超出了相机的范围。)这就是使我们的3D场景看起来3D的原因。
同时,还有可能专门为绘制2D图形创建投影矩阵。这称为正交矩阵,其设置通常如下所示:
var left = 0;
var top = 0;
var right = canvas.width;
var bottom = canvas.height;
var near = 1.0;
var far = 1024.0;
var projectionMat = mat4.ortho(left, right, bottom, top, near, far);
这个矩阵与透视矩阵不同,因为它完全忽略了你位置的z分量。相反,这个矩阵将平面坐标(如像素)转换为-1到1的范围。因此,你的场景看起来不是3D的,但更容易在屏幕上精确控制物体出现的位置。所以,使用上述矩阵,如果我们给它一个顶点在(16, 16, 0),它将显示在我们的画布上的(16, 16)处(假设视口与画布具有相同的尺寸)。因此,当你想要绘制像平面UI元素这样的东西时,这就是你想要的类型矩阵!
好处在于,因为这些只是传递给着色器的值,所以你可以从一个绘制调用到另一个绘制调用使用完全不同的矩阵。通常,你会使用透视矩阵绘制所有的3D几何图形,然后使用正交矩阵绘制所有的UI。
如果有点啰嗦,很抱歉。我从来没有擅长解释那些数学相关的东西。