从像素到NDC的转换

时间:2017-03-12 18:32:01

标签: opengl lwjgl

让我们说我的屏幕为(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);

}

我错过了什么?

3 个答案:

答案 0 :(得分:5)

新答案

在评论中作出澄清之后,被问到的问题可归纳为:

  

如何在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 ASource B

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

GLM是一个广泛使用且受人尊敬的库,用于建模GLSL功能。可以在第glOrtho100看到clip.xyz /= clip.w 的GLM实施。

如何使用正交投影?

正交投影通常用于在3D场景的顶部渲染GUI。使用以下模式可以很容易地完成此操作:

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

      

    请注意,这回答了错误的问题。它假设问题归结为"如何从屏幕空间转换为NDC空间?"。如果有人在搜索这个问题时,会留下这个问题。

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

    规范化设备坐标

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

    clip

    其中[-1, 1]是剪辑空间中的坐标。

    这样做是将所有未剪裁的顶点放入单位立方体(所有轴上(0, 0, 0)的范围内),屏幕中心位于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) 。任何被剪裁的顶点(位于视锥体之外)都不在此单位立方体内,并被GPU抛弃。

    在OpenGL中,此步骤作为here的一部分自动完成(D3D11在Primitive Assembly中执行此操作)。

    屏幕坐标

    通过将标准化坐标扩展到视口的范围,可以简单地计算屏幕坐标。

    screen

    其中,

    • ndc是屏幕空间中的坐标
    • view.x是规范化空间中的坐标
    • view.y是viewport x origin
    • view.w是视口和原点
    • view.h是视口宽度
    • view.f是视口高度
    • view.n是远远的视口
    • 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
    • 附近的视口

    从屏幕转换为NDC

    由于我们从NDC转换为屏幕,因此很容易Rasterizer Stage

    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)
    

    示例:

    screen.y

    进一步阅读

    有关所有变换空间的更多信息,请阅读calculate the reverse

    修改评论

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

    对于OpenGL,视口原点(以及屏幕空间原点)位于左下角。请参阅OpenGL Transformation

    如果您的像素坐标是真正的左上角原点,那么在将ndc.y转换为ndc.y = 1.0 - ((2.0 * screen.y) - (2.0 * y)) / h) 时需要考虑这一点。

    {{1}}

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

答案 1 :(得分:1)

使用glViewport将NDC坐标转换为屏幕(即窗口)坐标。此功能(您必须在应用程序中使用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

答案 2 :(得分:0)

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