【问题标题】:Transformations from pixels to NDC从像素到 NDC 的转换
【发布时间】:2017-08-02 17:58:06
【问题描述】:

假设我的屏幕是 (800 * 600),并且我使用 Triangle_Strip(在 NDC 中)使用以下顶点位置绘制了一个四边形 (2D):

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,以将它们发送到顶点着色器旁边。有人建议我使用正交投影,但我不知道如何正确创建它,就像你一样可以在我的顶点着色器中看到我没有使用任何投影矩阵。谢谢!

这里有一个和我类似但不是很清楚的话题:

-Transform to NDC, calculate and transform back to worldspace

编辑:

我按照公式创建了正交投影矩阵,但是 似乎什么都没有出现,这就是我的处理方式:

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

}

我错过了什么?

【问题讨论】:

  • 只是想澄清一下,您想从屏幕空间转换到 NDC 空间正确吗?
  • 是的,屏幕空间(左上角原点),对不起,我在理解 OpenGL 的概念方面有点困难!

标签: opengl lwjgl


【解决方案1】:

新答案

经过cmets的澄清,提出的问题可以总结为:

如何根据像素有效地转换四边形以在 GUI 中使用?

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

一种投影方法,其中描绘对象或使用平行线映射表面以将其形状投影到平面上。

在实践中,您可能会将其视为 2D 投影。距离不起作用,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 类或其他原因),那么您必须自己计算正交投影矩阵。

正交矩阵定义为:

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 是一个广泛使用和受人尊敬的库,它是为建模 GLSL 功能而构建的。 glOrtho 的 GLM 实现可以在 here100 行看到。

如何使用正交投影?

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

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

旧答案

请注意,这回答了错误的问题。它假设问题归结为“如何从屏幕空间转换为 NDC 空间?”。以防有人搜索此问题以寻找答案。

目标是从屏幕空间转换为 NDC 空间。所以让我们首先定义这些空间是什么,然后我们可以创建一个转换。

标准化设备坐标

NDC 空间只是对剪辑空间中的顶点执行透视分割的结果。

clip.xyz /= clip.w

clip 是剪辑空间中的坐标。

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

在 OpenGL 中,此步骤作为 Primitive Assembly 的一部分自动完成(D3D11 在 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 是屏幕空间坐标
  • ndc 是归一化空间中的坐标
  • view.x 是视口 x 原点
  • view.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)

进一步阅读

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

编辑评论

在对原始问题的评论中,Bo 将屏幕空间原点指定为左上角。

对于 OpenGL,视口原点(以及屏幕空间原点)位于左下角。见glViewport

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

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

如果您将鼠标单击屏幕/gui 的坐标转换为 NDC 空间(作为完全转换为世界空间的一部分),则需要这样做。

【讨论】:

  • @BoHalim 不用担心。只是想确保我回答正确的问题。你能解释一下最终目标是什么吗?据我所知,你有一个四边形,你想通过屏幕像素而不是世界空间单位来翻译/缩放?比如将四边形向右移动 50 像素?
  • @BoHalim 这是用于 GUI 吗?还是四边形会成为您的 3D 场景中的对象?
  • 是的,就是这样(用于 GUI),我想创建自己的!
  • @BoHalim 然后,正如您在问题中提到的,最简单的解决方案是使用正交投影。我将通过概述该做什么来修改我的答案。
  • @BoHalim 请查看我的更新答案,如果它有帮助,请告诉我。
【解决方案2】:

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

【讨论】:

    【解决方案3】:

    如果我理解这个问题,您正在尝试将屏幕空间坐标(定义屏幕大小的坐标)设置为 -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是中间)

    【讨论】:

      猜你喜欢
      • 2019-02-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-02-28
      • 2018-06-06
      • 2014-11-23
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多