假设我的屏幕是(800 * 600
),并且我有一个四边形(2D),使用以下顶点位置绘制Triangle_Strip(在NDC中):
float[] vertices = {-0.2f,0.2f,-0.2f,-0.2f,0.2f,0.2f,0.2f,-0.2f};
我以这种方式设置了我的转换矩阵:
Vector2f position = new Vector2f(0,0);
Vector2f size = new Vector2f(1.0f,1.0f);
Matrix4f tranMatrix = new Matrix4f();
tranMatrix.setIdentity();
Matrix4f.translate(position, tranMatrix, tranMatrix);
Matrix4f.scale(new Vector3f(size.x, size.y, 1f), tranMatrix, tranMatrix);
还有我的顶点着色器:
#version 150 core
in vec2 in_Position;
uniform mat4 transMatrix;
void main(void) {
gl_Position = transMatrix * vec4(in_Position,0,1.0);
}
我的问题是,我应该使用哪个公式来修改带有坐标(以像素为单位)的四边形变换?
例如:
设置比例: (50px, 50px) =>
Vector2f(width,height)
设置位置: (100px, 100px) =>
Vector2f(x,y)
为了更好地理解,我将创建一个函数来将我的像素数据转换为 NDC,以将它们发送到顶点着色器旁边。有人建议我使用正交投影,但我不知道如何正确创建它,正如您在顶点着色器中看到的那样,我没有使用任何投影矩阵。
这是一个与我类似但不是很清楚的主题 - 转换为 NDC,计算并转换回世界空间
<小时 />编辑:
我按照公式创建了正交投影矩阵,但是 似乎什么都没有出现,这是我的进行方式:
public static Matrix4f glOrtho(float left, float right, float bottom, float top, float near, float far){
final Matrix4f matrix = new Matrix4f();
matrix.setIdentity();
matrix.m00 = 2.0f / (right - left);
matrix.m01 = 0;
matrix.m02 = 0;
matrix.m03 = 0;
matrix.m10 = 0;
matrix.m11 = 2.0f / (top - bottom);
matrix.m12 = 0;
matrix.m13 = 0;
matrix.m20 = 0;
matrix.m21 = 0;
matrix.m22 = -2.0f / (far - near);
matrix.m23 = 0;
matrix.m30 = -(right+left)/(right-left);
matrix.m31 = -(top+bottom)/(top-bottom);
matrix.m32 = -(far+near)/(far-near);
matrix.m33 = 1;
return matrix;
}
然后,我将矩阵包含在顶点着色器中
#version 140
in vec2 position;
uniform mat4 projMatrix;
void main(void){
gl_Position = projMatrix * vec4(position,0.0,1.0);
}
我错过了什么?
新答案
在评论中澄清后,提出的问题可以总结为:
如何有效地转换像素方面的四边形以在 GUI 中使用?
如原始问题中所述,最简单的方法是使用正交投影。什么是正交投影?
一种投影方法,其中描绘对象或使用平行线映射表面以将其形状投影到平面上。
在实践中,您可以将其视为 2D 投影。距离不起作用,OpenGL 坐标映射到像素坐标。有关更多信息,请参阅此答案。
通过使用正交投影而不是透视投影,您可以开始根据像素来考虑所有变换。
它不是将四边形定义为维度(25 x 25)
世界单位,而是维度上(25 x 25)
像素。
或者,不是沿世界 x 轴平移50
世界单位,而是沿屏幕 x 轴(向右)平移50
像素。
那么如何创建正射投影呢?
首先,它们通常使用以下参数定义:
left
- 左垂直剪裁平面的 X 坐标right
- 右垂直剪切平面的 X 坐标bottom
- 底部水平裁剪平面的 Y 坐标top
- 顶部水平剪裁平面的 Y 坐标near
- 近深度剪切平面far
- 远深度剪切平面
请记住,所有单位均以像素为单位。典型的正交投影定义为:
glOrtho(0.0, windowWidth, windowHeight, 0.0f, 0.0f, 1.0f);
假设您没有(或不能)使用glOrtho
(您有自己的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
源A、源 B
在这一点上,我建议使用预制的数学库,除非你决定使用自己的数学库。我在实践中看到的最常见的错误来源之一是与矩阵相关的,你花在调试矩阵上的时间越少,你就越有时间专注于其他更有趣的工作。
GLM 是一个广泛使用且备受推崇的库,旨在对 GLSL 功能进行建模。glOrtho
的 GLM 实现可以在第100
行中看到。
如何使用正交投影?
正交投影通常用于在 3D 场景之上渲染 GUI。使用以下模式可以轻松完成此操作:
- 清除缓冲区
- 应用透视投影矩阵
- 渲染 3D 对象
- 应用正交投影矩阵
- 渲染 2D/GUI 对象
- 交换缓冲区
旧答案
请注意,这回答了错误的问题。它假设问题归结为"如何从屏幕空间转换为 NDC 空间?如果有人搜索遇到这个问题来寻找答案,就会留下它。
目标是从屏幕空间转换为 NDC 空间。因此,让我们首先定义这些空间是什么,然后我们可以创建一个转换。
规范化设备坐标
NDC 空间只是在剪辑空间中对顶点执行透视划分的结果。
clip.xyz /= clip.w
其中clip
是剪辑空间中的坐标。
这样做是将所有未裁剪的顶点放入一个单位立方体(在所有轴上的[-1, 1]
范围内),屏幕中心位于(0, 0, 0)
.任何被裁剪的顶点(位于视锥体之外)都不在此单元立方体中,并且被 GPU 抛弃。
在OpenGL 中,此步骤作为基元汇编的一部分自动完成(D3D11 在光栅化器阶段执行此操作)。
屏幕坐标
屏幕坐标是通过将规范化坐标扩展到视口范围来计算的。
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)
哪里
screen
是屏幕空间中的坐标ndc
是归一化空间中的坐标view.x
是视口 x 原点view.y
是视口 y 原点view.w
是视口宽度view.h
是视口高度- 视口远
view.f
view.n
是附近的视口
从屏幕转换为 NDC
由于我们在上面进行了从 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
例:
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 转换。
编辑以征求意见
在对原始问题的评论中,Bo 将屏幕空间原点指定为左上角。
对于 OpenGL,视口原点(以及屏幕空间原点)位于左下角。参见 glViewport。
如果您的像素坐标确实是左上角的原点,那么在将screen.y
转换为ndc.y
时需要考虑这一点。
ndc.y = 1.0 - ((2.0 * screen.y) - (2.0 * y)) / h)
如果您要将鼠标单击屏幕/GUI 的坐标转换为 NDC 空间(作为完全转换为世界空间的一部分),则需要这样做。
NDC 坐标使用glViewport
转换为屏幕(即窗口)坐标。此函数(必须在应用中使用 t)通过原点和大小定义窗口的一部分。
使用的公式可以在 https://www.khronos.org/registry/OpenGL-Refpages/gl2.1/xhtml/glViewport.xml
(x,y) 是原点,通常 (0,0) 是窗口的左下角。
虽然您可以自己推导逆公式,但这里有它们: https://www.khronos.org/opengl/wiki/Compute_eye_space_from_window_space#From_window_to_ndc
如果我理解这个问题,您正在尝试将屏幕空间坐标(定义屏幕大小的坐标)转换为 -1 到 1 坐标。如果是,那么它很简单。等式为:
((coords_of_NDC_space / width_or_height_of_screen) * 2) - 1
这将起作用,因为例如 800 × 600 的屏幕:
800/800 = 11 * 2 = 2
2 - 1 = 1
并检查高度上半个屏幕的坐标:
300/600 = 0.5> 0.5 * 2 = 11 - 1 = 0(NDC 从 -1 到 1,所以 0 是中间)