opengl 从像素到NDC的转换

kd3sttzy  于 2022-11-04  发布在  其他
关注(0)|答案(3)|浏览(236)

假设我的屏幕是(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);

}

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

*设置缩放比例:(50像素,50像素)=〉Vector2f(width,height)
*设置位置:(100像素,100像素)=〉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);

}

我错过了什么?

epfja78i

epfja78i1#

新答案

在评论中作出澄清后,所提出的问题可归纳为:
如何有效地将四边形转换为像素,以便在GUI中使用?
正如在原来的问题中提到的,最简单的方法是使用正投影。什么是正投影?
用平行线将物体的形状投影到平面上来描绘物体或绘制表面的一种投影方法。
实际上,您可以将其视为2D投影。距离不起作用,OpenGL坐标Map为像素坐标。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功能建模而构建的。glOrtho的GLM实现可以在100行的here中看到。

如何使用正投影?

正交投影通常用于在3D场景上渲染GUI。使用以下模式可以很容易地实现这一点:
1.清除缓冲区
1.应用透视投影矩阵
1.渲染3D对象
1.应用正交投影矩阵
1.渲染2D/GUI对象
1.交换缓冲区

旧答案

  • 请注意,这回答了错误的问题。它假设问题归结为“我如何从屏幕空间转换到NDC空间?"。它是为了防止有人搜索到这个问题来寻找答案而留下的。*

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

标准化设备坐标

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

clip.xyz /= clip.w

其中clip是剪辑空间中的坐标。
它的作用是将所有未裁剪的顶点放置到一个单位立方体中(所有轴上的[-1, 1]范围内),屏幕中心在(0, 0, 0)。任何被裁剪的顶点(位于视锥之外)都不在这个单位立方体内,并被GPU丢弃。
在OpenGL中,此步骤作为基本装配的一部分自动完成(D3 D11在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是视口y原点
  • view.w是视埠长度
  • view.h是视口高度
  • view.f是远视埠
  • view.n是附近的视口
    从屏幕转换为NDC

由于我们有上面从NDC到Screen的转换,因此很容易计算相反的结果。

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空间(作为世界空间完全转换的一部分),则需要此功能。

wr98u20j

wr98u20j2#

使用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

gkn4icbw

gkn4icbw3#

如果我理解了这个问题,你是想把屏幕空间坐标(定义屏幕大小的坐标)转换成-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为中间值)

相关问题