WebGL:将画布坐标转换为3D坐标

4
我试图找到WebGL视口的“左”边框,因为我想在那里绘制一些调试信息(像大多数建模程序中的轴向小地图)。我可以获得包含WebGL视口的画布的宽度和高度。
我真的想知道如何计算2D画布坐标到3D坐标?找到3D视口中的左边框的最佳方法是什么?

任何研究此事的人都应该阅读http://webglfactory.blogspot.com/2011/05/how-to-convert-world-to-screen.html或者查看GluProject()和GluUnproject()。

4个回答

7
为了澄清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。
如果有点啰嗦,很抱歉。我从来没有擅长解释那些数学相关的东西。

谢谢您的详细解释。我知道GL视口及其映射到[-1,-1]的情况。我可以看出使用正交投影矩阵有助于在“2d”中呈现。但是这会弄乱我的所有其他对象。也许我应该稍微改一下我的问题:如何将世界坐标转换为屏幕坐标? - Tom
它会如何影响您的其他对象?您将继续使用标准透视矩阵渲染它们。您只需要为2D元素使用正交矩阵。 - Toji
好的,这需要我不断地在透视矩阵之间切换,而我对此并不满意。此外,带有代表轴的“箭头”的模型仍然是3D而不是2D。 - Tom
这真的很有帮助:http://webglfactory.blogspot.com/2011/05/how-to-convert-world-to-screen.html - Tom

1

查看http://games.greggman.com/game/webgl-fundamentals

基本上,如果您想绘制2D图形,请使用2D着色器,不要尝试使用3D着色器。

WebGL在剪辑空间中绘制,所以您只需要将像素转换为剪辑空间即可。

attribute vec2 a_position;

uniform vec2 u_resolution;

void main() {
   // convert positions from pixels to 0.0 to 1.0
   vec2 zeroToOne = a_position / u_resolution;

   // convert from 0->1 to 0->2
   vec2 zeroToTwo = zeroToOne * 2.0;

   // convert from 0->2 to -1->+1 (clipspace)
   vec2 clipSpace = zeroToTwo - 1.0;

   gl_Position = vec4(clipSpace * vec2(1, -1), 0, 1);
}

1
我正在尝试找到我的WebGL视口的“左侧”边框,因为我想在那里绘制许多调试信息(类似于大多数建模程序所拥有的轴迷你地图)。我肯定可以获取包含WebGL视口的画布的宽度和高度。
只需针对这些部分切换视口和投影即可。您可以随时更改它们。

0

为什么不直接使用一些叠加的HTML呢?

<html>
<head>
  <style>
  #container {
    position: relative;
  }
  #debugInfo {
    position: absolute;
    top: 0px;
    left: 0px;
    background-color: rgba(0,0,0,0.7);
    padding: 1em;
    z-index: 2;
    color: white;
  }
  </style>
</head>
<body>
  <div id="container">
    <canvas></canvas>
    <div id="debugInfo">There be info here!</div>
  </div>
</body>
</html>

然后您可以使用以下方式进行更新

 var debugInfo = document.getElementById("debugInfo");
 debugInfo.innerHTML = "some info";

这正是我用来显示FPS、顶点数等的方法。我意识到我的问题应该是:如何将屏幕坐标转换为世界坐标,反之亦然。 - Tom

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接