从像素到 NDC 的转换

Transformations from pixels to NDC

假设我的屏幕是 (800 * 600) 并且我使用 Triangle_Strip(在国家数据中心):

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);

}

我的问题是,我应该使用哪个公式来修改带有坐标(以像素为单位)的四边形的变换?

例如:

为了更好地理解,我将创建一个函数来将我的像素数据转换为 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);

}

我错过了什么?

新答案

在评论中澄清后,提出的问题可以总结为:

How do I effectively transform a quad in terms of pixels for use in a GUI?

如原始问题中所述,最简单的方法是使用正交投影。什么是正交投影?

a method of projection in which an object is depicted or a surface mapped using parallel lines to project its shape onto a plane.

在实践中,您可能会将其视为二维投影。距离不起作用,OpenGL 坐标映射到像素坐标。 See this answer 了解更多信息。

通过使用正交投影而不是透视投影,您可以开始根据像素来考虑所有变换。

不是将四边形定义为 (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 class或其他原因),那么你必须计算正交投影矩阵你自己。

正交矩阵定义为:

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, Source B

此时我建议使用预制的数学库,除非您决心使用自己的数学库。我在实践中看到的最常见的错误来源之一是与矩阵相关的,你花在调试矩阵上的时间越少,你就越有时间专注于其他更有趣的工作。

GLM is a widely-used and respected library that is built to model GLSL functionality. The GLM implementation of glOrtho can be seen here 在行 100.

如何使用正投影?

正交投影通常用于在 3D 场景之上渲染 GUI。这可以通过使用以下模式轻松完成:

  1. 清除缓冲区
  2. 应用您的透视投影矩阵
  3. 渲染您的 3D 对象
  4. 应用您的正交投影矩阵
  5. 渲染 2D/GUI 对象
  6. 交换缓冲区

旧答案

Note that this answered the wrong question. It assumed the question boiled down to "How do I convert from Screen Space to NDC Space?". It is left in case someone searching comes upon this question looking for that answer.

目标是从 Screen Space 转换为 NDC Space。因此,让我们首先定义那些 space 是什么,然后我们可以创建一个转换。

归一化设备坐标

NDC space 只是对剪辑 space.

中的顶点执行透视除法的结果
clip.xyz /= clip.w

其中clip是剪辑space中的坐标。

这样做是将我们所有未剪裁的顶点放入一个单位立方体中(在所有轴上 [-1, 1] 的范围内),屏幕中心位于 (0, 0, 0)。任何被剪裁的顶点(位于视锥之外)不在这个单位立方体中,并被 GPU 丢弃。

在 OpenGL 中,此步骤作为 Primitive Assembly (D3D11 does this in the Rasterizer Stage) 的一部分自动完成。

屏幕坐标

屏幕坐标只是通过将规范化坐标扩展到视口范围内来计算的。

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是屏幕坐标-space
  • ndc是归一化的坐标-space
  • view.x 是视口 x 原点
  • view.y 是视口 y 原点
  • view.w 是视口宽度
  • view.h是视口高度
  • view.f是远视口
  • view.n
  • 附近的视口

从屏幕转换为 NDC

上面我们有了从NDC到Screen的转换,很容易calculate the reverse

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)

进一步阅读

有关所有转换 space 的更多信息,请阅读 OpenGL Transformation

编辑评论

在原问题的评论中,Bo 将屏幕-space 原点指定为左上角。

对于 OpenGL,视口原点(因此屏幕-space 原点)位于左下角。参见 glViewport

如果您的像素坐标确实是左上原点,那么在将 screen.y 转换为 ndc.y 时需要考虑到这一点。

ndc.y = 1.0 - ((2.0 * screen.y) - (2.0 * y)) / h)

如果您要将 screen/gui 上鼠标单击的坐标转换为 NDC space(作为对世界 space 的完整转换的一部分),则需要这样做.

NDC 坐标使用 glViewport 转换为屏幕(即 window)坐标。此函数(您必须在您的应用程序中使用 t)通过原点和大小定义 window 的一部分。

所用公式见https://www.khronos.org/registry/OpenGL-Refpages/gl2.1/xhtml/glViewport.xml
(x,y)是原点,一般是(0,0)左下角window。

虽然您可以自己推导逆向公式,但这里有它们:https://www.khronos.org/opengl/wiki/Compute_eye_space_from_window_space#From_window_to_ndc

如果我理解这个问题,您正在尝试将屏幕 space 坐标(定义屏幕尺寸的坐标)转换为 -1 到 1 坐标。如果是,那就很简单了。等式是:

((coords_of_NDC_space / width_or_height_of_screen) * 2) - 1  

这可行,因为例如 800 × 600 的屏幕:

800 / 800 = 1
1 * 2 = 2
2 - 1 = 1

并检查高度上半个屏幕的坐标:

300 / 600 = 0.5
0.5 * 2 = 1
1 - 1 = 0(NDC 从 -1 到 1,所以 0 是中间的)