【问题标题】:Pixel-perfect projection matrix?像素完美的投影矩阵?
【发布时间】:2015-12-20 15:53:58
【问题描述】:

我正在尝试了解我应该将相机位置放置在观察函数(或模型矩阵中的对象)中多远,才能在顶点着色器中传递像素完美的坐标。

这对于正交投影矩阵来说实际上很简单,但我无法想象数学如何用于透视投影。

这是我正在使用的透视矩阵:

glm::mat4 projection = glm::perspective(45.0f, (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 10000.0f);

着色器中的顶点乘法很简单:

gl_Position = projection * view * model * vec4(position.xy, 0.0f, 1.0);

我基本上是想在屏幕上显示一个需要旋转的四边形并显示透视效果(因此我不能使用正交投影),但我想在像素坐标中指定它的位置和大小应该出现在屏幕上。

【问题讨论】:

  • 你真正想要达到什么目的?以像素坐标指定梯形的四个角,并且您想要对整个事物进行透视校正插值?如果是这样,专注于投影矩阵有点误导(当然可以将任意单应性放入投影矩阵,但经典的perspective 函数在这种情况下没有帮助)。
  • 我试图弄清楚如何在屏幕像素中表达顶点坐标,以在屏幕上以给定的像素位置和像素大小显示四边形,这样一旦旋转它,我仍然可以实现效果我用普通投影矩阵得到的透视所以基本上是的,这不是关于透视,也不是关于距离,我只是想找到一种方法来指定屏幕像素中的坐标并仍然达到那种效果
  • 所以,如果我没猜错的话,您只想在像素坐标中指定一个 不失真 矩形,然后再将其旋转(进入深度)?
  • 是的,我想在 vbo 中使用像素坐标指定顶点属性,但仍然能够旋转/缩放/平移对象,而透视效果仍然适用。再说一次,我想在像素坐标中指定它的开始位置和宽度/高度,一旦我设置了一个视图矩阵并将它与投影矩阵结合使用,我不知道该怎么做

标签: c++ opengl matrix glm-math


【解决方案1】:

如果您想使用梯形视锥,它只能在一个“z 平面”中具有像素坐标。

基础数学

如果您使用标准相机,则 (0,0,0) 处相机的基本数学运算将是

对于 alpha 是垂直 fov(在您的情况下为 45°)

target_y = tan(alpha/2) * z 距离 * ((pixel_y/height)*2-1)
target_x = tan(alpha/2) * z 距离 * ((pixel_x/width)*aspect-ratio*2-1)

反转投影

至于一般情况。您可以“取消投影”以在所有变换之前找到 3D 中的某个点应该在某个特定点上结束。

基本上,您需要取消数学运算。

gl_Position = projection * view * model * vec4(position.xy, 0.0f, 1.0);

所以如果你有你的最终位置并想恢复它,你可以这样做:

unprojection =  model^-1 * view^-1 *projection^-1 * gl_Position //not actual glsl notation, '^-1' being the inverse

这基本上就是 gluUnProjectglm::gtc::matrix_transform::unProject 之类的功能。

但是您应该注意,应用投影矩阵后的最终剪辑空间通常是 [-1,-1,0] 到 [1,1,1],因此如果您想输入像素坐标,您可以应用一个额外的矩阵来转换到那个空间。

类似:

               [2/width,        0,     0    -1]
               [      0, 2/height,     0    -1]
screenToClip = [      0,        0,     1     0]
               [      0,        0,     0     1]

会将 [0,0,0,1] 转换为 [-1,-1,0,1] 并将 [width,height,0,1] 转换为 [1,1,0,1]

此外,您可能最好尝试一些 z 值,例如 0.5,以确保您完全位于视锥内,并且不会在前部或后部附近剪裁。

【讨论】:

    【解决方案2】:

    您可以使用 60 度的视野来实现此效果。基本上,您希望将相机放置在距观察平面一定距离的位置,以便相机形成一个中心点位于屏幕顶部和底部的等边三角形。

    这里有一些代码可以做到这一点:

    float fovy = 60.0f; // field of view - degrees
    float aspect = nScreenWidth / nScreenHeight;
    float zNearClip = 0.1f;
    float zFarClip = nScreenHeight*2.0f;
    float degToRad = MF_PI / 180.0f;
    float fH = tanf(fovY * degToRad / 2.0f) * zNearClip;
    float fW = fH * aspect;
    
    glFrustum(-fW, fW, -fH, fH, zNearClip, zFarClip);
    
    float nCameraDistance = sqrtf( nScreenHeight * nScreenHeight - 0.25f * nScreenHeight * nScreenHeight); 
    
    glTranslatef(0, 0, -nCameraDistance);
    

    您也可以使用 90 度视野。在这种情况下,相机距离是窗口高度的 1/2。但是,这有很多透视缩短。

    在 90 度的情况下,您可以将相机推出整个高度,然后对 x 和 y 分量应用 2 倍缩放(即:glScale (2,2,1)。

    这是我的意思的图像:

    【讨论】:

      【解决方案3】:

      我将扩展 PeterT 的答案,并在此处留下我用来通过非投影找到截锥体平面之一的世界坐标的实用代码

      这假设一个基本的视图矩阵(相机位置在 0,0,0)

      glm::mat4 projectionInv(0);
      glm::mat4 projection = glm::perspective(45.0f, (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 500.0f);
      projectionInv = glm::inverse(projection);
      
      std::vector<glm::vec4> NDCCube;
      NDCCube.push_back(glm::vec4(-1.0f, -1.0f,   -1.0f,     1.0f));
      NDCCube.push_back(glm::vec4(1.0f, -1.0f,    -1.0f,     1.0f));
      NDCCube.push_back(glm::vec4(1.0f, -1.0f,     1.0f,     1.0f));
      NDCCube.push_back(glm::vec4(-1.0f, -1.0f,    1.0f,     1.0f));
      NDCCube.push_back(glm::vec4(-1.0f, 1.0f,    -1.0f,     1.0f));
      NDCCube.push_back(glm::vec4(1.0f, 1.0f,     -1.0f,     1.0f));
      NDCCube.push_back(glm::vec4(1.0f, 1.0f,      1.0f,     1.0f));
      NDCCube.push_back(glm::vec4(-1.0f, 1.0f,     1.0f,     1.0f));
      
      std::vector<glm::vec3> frustumVertices;
      
      for (int i = 0; i < 8; i++)
      {
          glm::vec4 tempvec;
          tempvec = projectionInv * NDCCube.at(i); //multiply by projection matrix inverse to obtain frustum vertex
          frustumVertices.push_back(glm::vec3(tempvec.x /= tempvec.w, tempvec.y /= tempvec.w, tempvec.z /= tempvec.w));
      }
      

      请记住,如果您的透视远距离低于我在投影矩阵中设置的距离,这些坐标将不会出现在屏幕上

      【讨论】:

        【解决方案4】:

        如果您碰巧知道要精确显示像素的“某些项目”的世界坐标宽度,那么这最终会有点琐碎的三角函数(适用于 y FOV 或 x FOV):

        S = Width of item in world coordinates
        T = "Pixel Exact" size of item (say, the width of the texture)
        h = Z distance to the object
        a = 2 * h * tan(Phi / 2)
        b = a / cos(phi / 2)
        r = Total screen resolution (width or height depending on the FOV you want)
        
        a = 2 * h * tan(Phi / 2) = (r / T) * S
        Theta = atan(2*h / a)
        Phi = 180 - 2*Theta
        

        其中 b 是三角形的边,a 是三角形的底,h 是三角形的高度,theta 是 Isosoleces 三角形的两个相等角的夹角,Phi 是所得的 FOV

        所以最终代码可能看起来像

        float frustumWidth = (float(ScreenWidth) / TextureWidth) * InWorldItemWidth; float theta = glm::degrees(atan((2 * zDistance) / frustumWidth)); float PixelPerfectFOV = 180 - 2 * theta;

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2012-03-27
          • 2018-01-23
          • 2015-11-01
          • 2013-08-03
          • 1970-01-01
          • 2015-04-11
          • 1970-01-01
          • 2019-03-05
          相关资源
          最近更新 更多