要说明datenwolf的答案,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);
我不打算进入什么所有这些值的含义,但你可以清楚地看到,我们使用画布宽度和高度来帮助设置此投影。这可以使其不会看到拉伸或压扁取决于画布的大小。然而,要理解的是,在空间中取任意3D点并乘以该矩阵将产生一个映射到-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。
道歉,如果这有点散漫。我从来都不擅长解释所有这些数学问题。
感谢您的长篇解释更新。我知道GL视口及其映射到[-1,-1]。我可以看到使用正交投影矩阵可以有助于在“2d”中进行渲染。然而,这会弄乱我所有的其他对象。也许我应该改写一下我的问题:如何将世界转换为屏幕坐标? – Tom
它会如何混淆你的其他对象?您将继续使用您的标准透视矩阵呈现它们。您只需要为2D元素使用正交矩阵。 – Toji
那么,这将需要我不断改变视角矩阵,我对此并不满意。 此外,表示轴的“箭头”的模型仍然是3d而不是2d。 – Tom