新答案
经过评论的澄清,所提出的问题可以总结为:
如何有效地转换四边形的像素以在 GUI 中使用?
正如原始问题中提到的,最简单的方法是使用正交投影。什么是正交投影?
一种投影方法,其中使用平行线描绘物体或映射表面,将其形状投影到平面上。
在实践中,您可能会将其视为 2D 投影。距离不起作用,OpenGL 坐标映射到像素坐标。看到这个答案 https://stackoverflow.com/a/2602693/735425了解更多信息。
通过使用正交投影而不是透视投影,您可以开始考虑以像素为单位的所有变换。
而不是将四边形定义为(25 x 25)
世界尺寸单位,它是(25 x 25)
维度中的像素。
或者代替翻译50
沿世界 x 轴的世界单位,您可以翻译为50
沿屏幕 x 轴(右侧)的像素。
那么如何创建正交投影呢?
首先,它们通常使用以下参数定义:
-
left
- 左侧垂直裁剪平面的 X 坐标
-
right
- 右侧垂直剪裁平面的 X 坐标
-
bottom
- 底部水平裁剪平面的 Y 坐标
-
top
- 顶部水平裁剪平面的 Y 坐标
-
near
- 近深度剪裁平面
-
far
- 远深度剪裁平面
请记住,所有单位均以像素为单位。典型的正交投影定义为:
glOrtho(0.0, windowWidth, windowHeight, 0.0f, 0.0f, 1.0f);
假设您不(或不能)使用glOrtho https://www.khronos.org/registry/OpenGL-Refpages/gl2.1/xhtml/glOrtho.xml(你有自己的Matrix
类或其他原因),那么您必须自己计算正交投影矩阵。
正交矩阵定义为:
2/(r-l) 0 0 -(r+l)/(r-l)
0 2/(t-b) 0 -(t+b)/(t-b)
0 0 -2/(f-n) -(f+n)/(f-n)
0 0 0 1
Source A https://en.wikipedia.org/wiki/Orthographic_projection, Source B https://www.scratchapixel.com/lessons/3d-basic-rendering/perspective-and-orthographic-projection-matrix/orthographic-projection-matrix
此时,我建议使用预制的数学库,除非您决定使用自己的数学库。我在实践中看到的最常见的错误来源之一是与矩阵相关的,你花在调试矩阵上的时间越少,你就有越多的时间专注于其他更有趣的事情。
GLM http://glm.g-truc.net/0.9.8/index.html是一个广泛使用且受人尊敬的库,旨在对 GLSL 功能进行建模。 GLM 实施glOrtho
看得到here https://github.com/g-truc/glm/blob/master/glm/gtc/matrix_transform.inl在线100
.
如何使用正交投影?
正交投影通常用于在 3D 场景之上渲染 GUI。通过使用以下模式可以很容易地完成此操作:
- 清除缓冲区
- 应用透视投影矩阵
- 渲染您的 3D 对象
- 应用正交投影矩阵
- 渲染 2D/GUI 对象
- 交换缓冲区
旧答案
请注意,这回答了错误的问题。它假设问题归结为“如何从屏幕空间转换为 NDC 空间?”。留下它是为了防止有人搜索这个问题并寻找答案。
目标是将屏幕空间转换为 NDC 空间。因此,我们首先定义这些空间是什么,然后我们可以创建一个转换。
标准化设备坐标
NDC 空间只是对剪辑空间中的顶点执行透视除法的结果。
clip.xyz /= clip.w
Where clip
是剪辑空间中的坐标。
其作用是将所有未裁剪的顶点放入一个单位立方体中(在[-1, 1]
在所有轴上),屏幕中心位于(0, 0, 0)
。任何被剪裁的顶点(位于视锥体之外)都不在该单位立方体内,并且会被 GPU 丢弃。
在 OpenGL 中,此步骤作为以下部分自动完成原始装配 https://www.khronos.org/opengl/wiki/Rendering_Pipeline_Overview#Clipping(D3D11 在光栅化阶段 https://msdn.microsoft.com/en-us/library/windows/desktop/bb205125(v=vs.85).aspx).
屏幕坐标
屏幕坐标只需将标准化坐标扩展到视口的范围即可计算。
screen.x = ((view.w * 0.5) * ndc.x) + ((w * 0.5) + view.x)
screen.y = ((view.h * 0.5) * ndc.y) + ((h * 0.5) + view.y)
screen.z = (((view.f - view.n) * 0.5) * ndc.z) + ((view.f + view.n) * 0.5)
Where,
-
screen
是屏幕空间中的坐标
-
ndc
是标准化空间中的坐标
-
view.x
是视口 x 原点
-
view.y
是视口 y 原点
-
view.w
是视口宽度
-
view.h
是视口高度
-
view.f
视口是否远
-
view.n
视口是否靠近
从屏幕转换为 NDC
由于我们上面有从 NDC 到 Screen 的转换,所以很容易计算逆向 https://www.khronos.org/opengl/wiki/Compute_eye_space_from_window_space#From_window_to_ndc.
ndc.x = ((2.0 * screen.x) - (2.0 * x)) / w) - 1.0
ndc.y = ((2.0 * screen.y) - (2.0 * y)) / h) - 1.0
ndc.z = ((2.0 * screen.z) - f - n) / (f - n)) - 1.0
Example:
viewport (w, h, n, f) = (800, 600, 1, 1000)
screen.xyz = (400, 300, 200)
ndc.xyz = (0.0, 0.0, -0.599)
screen.xyz = (575, 100, 1)
ndc.xyz = (0.4375, -0.666, -0.998)
进一步阅读
有关所有变换空间的更多信息,请阅读OpenGL 转换 http://www.songho.ca/opengl/gl_transform.html.
编辑评论
在对原始问题的评论中,Bo 将屏幕空间原点指定为左上角。
对于 OpenGL,视口原点(以及屏幕空间原点)位于左下角。看GL视口 https://www.khronos.org/registry/OpenGL-Refpages/es2.0/xhtml/glViewport.xml.
如果您的像素坐标确实是左上角原点,那么在转换时需要考虑到这一点screen.y
to ndc.y
.
ndc.y = 1.0 - ((2.0 * screen.y) - (2.0 * y)) / h)
如果您要将屏幕/gui 上的鼠标单击坐标转换为 NDC 空间(作为完全转换为世界空间的一部分),则需要这样做。