c++ - OpenGL-鼠标坐标到空间坐标

标签 c++ opengl graphics glut glfw

我的目标是在鼠标指向的位置放置一个球体(Z坐标为0)。

我看到了这个question,但是我还不了解MVP矩阵的概念,因此我做了一些研究,现在有两个问题:

如何从相机设置(例如查找,眼睛和向上 vector )创建 View 矩阵?

我也阅读了this tutorial about several camera types和这个webgl

我仍然可以将它们放在一起,我也不知道如何获得投影矩阵...

我应该采取什么步骤来实现所有这些?

最佳答案

在渲染中,通常通过模型矩阵, View 矩阵和投影矩阵来变换场景的每个网格。

  • 投影矩阵:
    投影矩阵描述了从场景的3D点到视口(viewport)的2D点的映射。投影矩阵从 View 空间转换为剪辑空间,并且剪辑空间中的坐标转换为范围为(-1,-1,-1)至(1、1、1)的归一化设备坐标(NDC)通过除以剪辑坐标的w分量。
  • View 矩阵:
    View 矩阵描述了从中查看场景的方向和位置。 View 矩阵从世界空间转换为 View (眼睛)空间。在视口(viewport)的坐标系中,X轴指向左侧,Y轴指向上方,Z轴指向 View 之外(请注意,在右侧系统中,Z轴是X-轴的叉积轴和Y轴)。
  • 模型矩阵:
    模型矩阵定义场景中网格的位置,方向和相对大小。模型矩阵将顶点位置从网格转换为世界空间。

  • 模型矩阵如下所示:
    ( X-axis.x, X-axis.y, X-axis.z, 0 )
    ( Y-axis.x, Y-axis.y, Y-axis.z, 0 )
    ( Z-axis.x, Z-axis.y, Z-axis.z, 0 )
    ( trans.x,  trans.y,  trans.z,  1 ) 
    

    View

    在视口(viewport)上,X轴指向左侧,Y轴指向上方,Z轴指向 View 之外(请注意,在右手系统中,Z轴是X轴与Y-轴的叉积轴)。

    view coordinates

    下面的代码定义了一个矩阵,该矩阵准确地封装了计算场景外观所需的步骤:
  • 将模型坐标转换为视口(viewport)坐标。
  • 旋转,以 View 的方向看。
  • 眼睛位置的运动

  • 以下代码与gluLookAtglm::lookAt相同:
    using TVec3  = std::array< float, 3 >;
    using TVec4  = std::array< float, 4 >;
    using TMat44 = std::array< TVec4, 4 >;
    
    TVec3 Cross( TVec3 a, TVec3 b ) { return { a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0] }; }
    float Dot( TVec3 a, TVec3 b ) { return a[0]*b[0] + a[1]*b[1] + a[2]*b[2]; }
    void Normalize( TVec3 & v )
    {
        float len = sqrt( v[0] * v[0] + v[1] * v[1] + v[2] * v[2] );
        v[0] /= len; v[1] /= len; v[2] /= len;
    }
    
    TMat44 Camera::LookAt( const TVec3 &pos, const TVec3 &target, const TVec3 &up )
    { 
        TVec3 mz = { pos[0] - target[0], pos[1] - target[1], pos[2] - target[2] };
        Normalize( mz );
        TVec3 my = { up[0], up[1], up[2] };
        TVec3 mx = Cross( my, mz );
        Normalize( mx );
        my = Cross( mz, mx );
    
        TMat44 v{
            TVec4{ mx[0], my[0], mz[0], 0.0f },
            TVec4{ mx[1], my[1], mz[1], 0.0f },
            TVec4{ mx[2], my[2], mz[2], 0.0f },
            TVec4{ Dot(mx, pos), Dot(my, pos), -Dot(mz, pos), 1.0f }
        };
    
        return v;
    }
    

    投影

    投影矩阵描述了从场景的3D点到视口(viewport)的2D点的映射。它从眼睛空间转换为剪辑空间,并且通过除以剪辑坐标的w分量,将剪辑空间中的坐标转换为归一化设备坐标(NDC)。 NDC的范围是(-1,-1,-1)至(1,1,1)。 NDC之外的每个几何都将被裁剪。

    摄像机视锥的近平面和远平面之间的对象映射到NDC的范围(-1,1)。

    正交投影

    在正交投影中,眼睛空间中的坐标被线性映射到归一化的设备坐标。

    Orthographic Projection

    正射投影矩阵:
    r = right, l = left, b = bottom, t = top, n = near, f = far 
    
    2/(r-l)         0               0               0
    0               2/(t-b)         0               0
    0               0               -2/(f-n)        0
    -(r+l)/(r-l)    -(t+b)/(t-b)    -(f+n)/(f-n)    1
    

    透视投影

    在“透视投影”中,投影矩阵描述了从针孔相机看到的世界3D点到视口(viewport)的2D点的映射。摄像机视锥中的眼睛空间坐标(截断的金字塔)映射到立方体(规范化的设备坐标)。

    Perspective Projection

    透视投影矩阵:
    r = right, l = left, b = bottom, t = top, n = near, f = far
    
    2*n/(r-l)      0              0                0
    0              2*n/(t-b)      0                0
    (r+l)/(r-l)    (t+b)/(t-b)    -(f+n)/(f-n)    -1    
    0              0              -2*f*n/(f-n)     0
    

    在哪里:
    a = w / h
    ta = tan( fov_y / 2 );
    
    2 * n / (r-l) = 1 / (ta * a)
    2 * n / (t-b) = 1 / ta
    

    如果投影是对称的,视线在视口(viewport)的中心,并且视场没有移位,则可以简化矩阵:
    1/(ta*a)  0     0              0
    0         1/ta  0              0
    0         0    -(f+n)/(f-n)   -1    
    0         0    -2*f*n/(f-n)    0
    

    以下函数将计算与gluPerspective相同的投影矩阵:
    #include <array>
    
    const float cPI = 3.14159265f;
    float ToRad( float deg ) { return deg * cPI / 180.0f; }
    
    using TVec4  = std::array< float, 4 >;
    using TMat44 = std::array< TVec4, 4 >;
    
    TMat44 Perspective( float fov_y, float aspect )
    {
        float fn = far + near
        float f_n = far - near;
        float r = aspect;
        float t = 1.0f / tan( ToRad( fov_y ) / 2.0f );
    
        return TMat44{ 
            TVec4{ t / r, 0.0f,  0.0f,                 0.0f },
            TVec4{ 0.0f,  t,     0.0f,                 0.0f },
            TVec4{ 0.0f,  0.0f, -fn / f_n,            -1.0f },
            TVec4{ 0.0f,  0.0f, -2.0f*far*near / f_n,  0.0f }
        };
    }
    

    3在透视投影中恢复 View 空间位置的解决方案
  • 带有视野和纵横比

  • 由于投影矩阵由视场和宽高比定义,因此可以利用视场和宽高比恢复视口(viewport)位置。如果它是对称的透视投影,并且已标准化的设备坐标,则深度和近,远平面都是已知的。

    恢复 View 空间中的Z距离:
    z_ndc = 2.0 * depth - 1.0;
    z_eye = 2.0 * n * f / (f + n - z_ndc * (f - n));
    

    通过XY归一化的设备坐标恢复 View 空间位置:
    ndc_x, ndc_y = xy normalized device coordinates in range from (-1, -1) to (1, 1):
    
    viewPos.x = z_eye * ndc_x * aspect * tanFov;
    viewPos.y = z_eye * ndc_y * tanFov;
    viewPos.z = -z_eye; 
    

    2. 和投影矩阵

    由视场和纵横比定义的投影参数存储在投影矩阵中。因此,可以通过对称矩阵投影中的投影矩阵中的值恢复视口(viewport)位置。

    注意投影矩阵,视场和宽高比之间的关系:
    prjMat[0][0] = 2*n/(r-l) = 1.0 / (tanFov * aspect);
    prjMat[1][1] = 2*n/(t-b) = 1.0 / tanFov;
    
    prjMat[2][2] = -(f+n)/(f-n)
    prjMat[2][2] = -2*f*n/(f-n)
    

    恢复 View 空间中的Z距离:
    A     = prj_mat[2][2];
    B     = prj_mat[3][2];
    z_ndc = 2.0 * depth - 1.0;
    z_eye = B / (A + z_ndc);
    

    通过XY归一化的设备坐标恢复 View 空间位置:
    viewPos.x = z_eye * ndc_x / prjMat[0][0];
    viewPos.y = z_eye * ndc_y / prjMat[1][1];
    viewPos.z = -z_eye; 
    

    3. 与逆投影矩阵

    当然,可以通过反投影矩阵来恢复视口(viewport)位置。
    mat4 inversePrjMat = inverse( prjMat );
    vec4 viewPosH      = inversePrjMat * vec4(ndc_x, ndc_y, 2.0*depth - 1.0, 1.0)
    vec3 viewPos       = viewPos.xyz / viewPos.w;
    

    进一步了解:
  • How to render depth linearly in modern OpenGL with gl_FragCoord.z in fragment shader?
  • Transform the modelMatrix
  • Perspective projection and view matrix: Both depth buffer and triangle face orientation are reversed in OpenGL
  • How to compute the size of the rectangle that is visible to the camera at a given coordinate?
  • How to recover view space position given view space depth value and ndc xy
  • Is it possble get which surface of cube will be click in OpenGL?
  • 关于c++ - OpenGL-鼠标坐标到空间坐标,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46749675/

    相关文章:

    qt - 如何设置QGraphicsView的大小?

    用于 LiFx 控件的 C++ curl POST

    range-v3 partial_sum View 的 C++ 意外值类型

    c++ - 类型定义语句是否相等?

    opengl - 使用实例数组时如何最小化 glVertexAttribPointer 调用?

    c++ - 在 Visual C++ 中调试图像渲染,任何有用的加载项?

    c++ - ftruncate() 是异步的吗?

    c++ - 在着色器中同时在同一纹理单元中使用不同的纹理类型

    c++ - 带有 Gtk+ 的 OpenGL,尽管清除了背景,但仍未绘制形状

    graphics - 为什么在 Mathematica 中使用图形函数时点会消失