如何在顶部2D小地图上显示用3D透视图渲染的平面世界的可见部分?

时间:2018-09-24 08:13:10

标签: c++ opengl math perspective

序言

常见问题解答是对以下内容的重制:

由于缺少信息且原始作者没有回应而被关闭(并且第一次重新打开周期失败)。但是,我认为这是一个有趣的问题,因此我决定亲自询问并回答这个问题(这次提供了所有必要的规格)。

问题

假设我们的世界是映射在一个平面(为简单起见,假设平面 XY Z=0.0)上的统一矩形正方形网格(以2D瓷砖阵列表示),并进行了渲染透视投影。像这样:

preview

如何将视锥角(地图/平面的可见部分)映射到小地图上的红色多边形?

为更通用,让我们假设这是输入:

    定义为起点Z=0.0
  • 平面(p0)和两个基本向量du,dv,该向量将图块的2D映射数组映射到3D ...
  • 使用的ModelView矩阵和Perspective矩阵

和想要的输出:

  • 4点多边形(在小地图上),代表外平面的可见部分

限制(或多或少与原始问题匹配):

  • 使用 C ++
  • 旧样式 OpenGL GL,GLU
  • 没有用于向量/矩阵数学的第三方函数库

1 个答案:

答案 0 :(得分:3)

因此,我们想要获得我们的平面(Z=0.0)和相机视锥之间的4个交点。因此,想法是从相机焦点投射4束光线(对于平截头体的每个边缘投射一条光线),然后简单地计算出光线/平面的交点。由于平面是Z=0.0,所以交点也有Z=0.0,因此交点很容易计算。

  1. 每个角/边缘的投射光线

    从相机焦点到屏幕角落(在屏幕空间中)

    frustrum

    并将其转换为世界全局坐标(通过还原透视图并使用反模型视图矩阵进行稍后描述)。射线应采用以下形式:

    p(t) = p + dp*t
    

    其中p是焦点,dp是方向矢量(无需规范化)

  2. 计算与XY平面(Z=0.0)的交点

    然后作为z=0.0

    0 = p.z + dp.z*t
    t = -p.z/dp.z
    

    所以我们可以直接计算交点。

  3. 将3D交点转换为地图内的u,v

    对于那个简单的点积就足够了。因此,如果p是我们的交点,则:

    u = dot(p-p0,du)
    v = dot(p-p0,dv)
    

    其中u,v是2D地图数组或小地图中的坐标。如果您的u,v是轴对齐的,那么您可以直接使用(p.x-p0.x,p.y-p0.y)而无需任何点积

如何将点p从摄影机坐标转换为全局世界坐标:

  1. 还原视角

    首先获取透视矩阵参数

    double per[16],zNear,zFar,fx,fy;
    glGetDoublev(GL_PROJECTION_MATRIX,per);
    zFar =0.5*per[14]*(1.0-((per[10]-1.0)/(per[10]+1.0)));
    zNear=zFar*(per[10]+1.0)/(per[10]-1.0);
    fx=per[0];
    fy=per[5];
    

    这将为您提供近平面和远平面的平截头体以及x,y轴的缩放比例。现在,还原透视图就是像这样反转透视图分隔线:

    p[1]*=(-p[2]/fy);                   // apply inverse of perspective
    p[0]*=(-p[2]/fx);
    

    投射光线需要znearzfar。有关更多信息,请参见:

  2. 全球世界坐标

    在我们的ModelView上简单地使用p矩阵的逆。因此,首先获取矩阵:

    double cam[16];
    glGetDoublev(GL_MODELVIEW_MATRIX,cam);
    

    相反,您可以使用我的matrix_inv,所以现在的最后一步是:

    p = Inverse(cam)*p;
    

    但是请不要忘记p必须是同质的,所以(x,y,z,1)代表点,(x,y,z,0)代表向量。

如果您缺乏背景知识或需要矢量/矩阵数学,请查看此处:

以下是小型C ++示例:

//---------------------------------------------------------------------------
void matrix_mul_vector(double *c,double *a,double *b)
    {
    double q[3];
    q[0]=(a[ 0]*b[0])+(a[ 4]*b[1])+(a[ 8]*b[2])+(a[12]);
    q[1]=(a[ 1]*b[0])+(a[ 5]*b[1])+(a[ 9]*b[2])+(a[13]);
    q[2]=(a[ 2]*b[0])+(a[ 6]*b[1])+(a[10]*b[2])+(a[14]);
    for(int i=0;i<3;i++) c[i]=q[i];
    }
//---------------------------------------------------------------------------
void  matrix_inv(double *a,double *b) // a[16] = Inverse(b[16])
    {
    double x,y,z;
    // transpose of rotation matrix
    a[ 0]=b[ 0];
    a[ 5]=b[ 5];
    a[10]=b[10];
    x=b[1]; a[1]=b[4]; a[4]=x;
    x=b[2]; a[2]=b[8]; a[8]=x;
    x=b[6]; a[6]=b[9]; a[9]=x;
    // copy projection part
    a[ 3]=b[ 3];
    a[ 7]=b[ 7];
    a[11]=b[11];
    a[15]=b[15];
    // convert origin: new_pos = - new_rotation_matrix * old_pos
    x=(a[ 0]*b[12])+(a[ 4]*b[13])+(a[ 8]*b[14]);
    y=(a[ 1]*b[12])+(a[ 5]*b[13])+(a[ 9]*b[14]);
    z=(a[ 2]*b[12])+(a[ 6]*b[13])+(a[10]*b[14]);
    a[12]=-x;
    a[13]=-y;
    a[14]=-z;
    }
//---------------------------------------------------------------------------
void draw_map()
    {
    int i,j;
    double u,v,p[3],dp[3];

    // here 3D view must be already set (modelview,projection)
    glDisable(GL_CULL_FACE);

    // [draw 3D map]
    const int n=30;                 // map size
    double p0[3]={0.0,0.0,0.0};     // map start point
    double du[3]={1.0,0.0,0.0};     // map u step (size of grid = 1.0 )
    double dv[3]={0.0,1.0,0.0};     // map v step (size of grid = 1.0 )
    glColor3f(0.5,0.7,1.0);
    glBegin(GL_LINES);
    for (j=0;j<=n;j++)
        {
        for (i=0;i<3;i++) p[i]=p0[i]+(double(j)*du[i])+(double(0)*dv[i]); glVertex3dv(p);
        for (i=0;i<3;i++) p[i]=p0[i]+(double(j)*du[i])+(double(n)*dv[i]); glVertex3dv(p);
        for (i=0;i<3;i++) p[i]=p0[i]+(double(0)*du[i])+(double(j)*dv[i]); glVertex3dv(p);
        for (i=0;i<3;i++) p[i]=p0[i]+(double(n)*du[i])+(double(j)*dv[i]); glVertex3dv(p);
        }
    glEnd();

    // [compute trapeze points]
    double cam[16],per[16],pt[4][3],zNear,zFar,fx,fy;
    glGetDoublev(GL_PROJECTION_MATRIX,per); // obtain matrices
    glGetDoublev(GL_MODELVIEW_MATRIX,cam);
    matrix_inv(cam,cam);
    zFar =0.5*per[14]*(1.0-((per[10]-1.0)/(per[10]+1.0)));
    zNear=zFar*(per[10]+1.0)/(per[10]-1.0);
    fx=per[0];
    fy=per[5];
    for (j=0;j<4;j++)                       // 4 corners
        {
        for (i=0;i<3;i++) dp[i]=0.0;        // cast ray from camera focus dp
        if (j==0) { p[0]=-1.0; p[1]=-1.0; } // to screen corner p
        if (j==1) { p[0]=-1.0; p[1]=+1.0; }
        if (j==2) { p[0]=+1.0; p[1]=+1.0; }
        if (j==3) { p[0]=+1.0; p[1]=-1.0; }
        p[2]=zNear;                         // start position at screen plane
        p[1]*=(-p[2]/fy);                   // apply inverse of perspective
        p[0]*=(-p[2]/fx);
        // transform to worlds global coordinates
        matrix_mul_vector( p,cam, p);
        matrix_mul_vector(dp,cam,dp);
        // compute intersection of ray and XY plane (z=0) as pt[j] (i exploited the fact that the intersection have z=0.0 for arbitrary plane it would be a bit more complicated)
        for (i=0;i<3;i++) dp[i]=p[i]-dp[i];
        u=p[2]/dp[2];
        if (u<0.0) u=(p[2]-zFar)/dp[2];     // no intersection means "infinite" visibility
        for (i=0;i<3;i++) pt[j][i]=p[i]-(u*dp[i]);
        u=0.0;
        }

    // [draw 2D minimap]
    GLint vp0[4];
    GLint vp1[4]={10,10,150,150};           // minimap position and size ppixels[
    double q0[2]={-1.0,-1.0 };              // minimap start point
    double eu[2]={2.0/double(n),0.0};       // minimap u step
    double ev[2]={0.0,2.0/double(n)};       // minimap v step

    // set 2D view for minimap
    glDisable(GL_DEPTH_TEST);
    glMatrixMode(GL_PROJECTION);
    glPushMatrix();
    glLoadIdentity();
    glMatrixMode(GL_MODELVIEW);
    glPushMatrix();
    glLoadIdentity();
    glGetIntegerv(GL_VIEWPORT,vp0);
    glViewport(vp1[0],vp1[1],vp1[2],vp1[3]);

    glColor3f(0.0,0.0,0.0);                 // clear background
    glBegin(GL_QUADS);
    for (i=0;i<2;i++) p[i]=q0[i]+(double(0)*eu[i])+(double(0)*ev[i]); glVertex2dv(p);
    for (i=0;i<2;i++) p[i]=q0[i]+(double(n)*eu[i])+(double(0)*ev[i]); glVertex2dv(p);
    for (i=0;i<2;i++) p[i]=q0[i]+(double(n)*eu[i])+(double(n)*ev[i]); glVertex2dv(p);
    for (i=0;i<2;i++) p[i]=q0[i]+(double(0)*eu[i])+(double(n)*ev[i]); glVertex2dv(p);
    glEnd();

    glColor3f(0.15,0.15,0.15);              // grid
    glBegin(GL_LINES);
    for (j=0;j<=n;j++)
        {
        for (i=0;i<2;i++) p[i]=q0[i]+(double(j)*eu[i])+(double(0)*ev[i]); glVertex2dv(p);
        for (i=0;i<2;i++) p[i]=q0[i]+(double(j)*eu[i])+(double(n)*ev[i]); glVertex2dv(p);
        for (i=0;i<2;i++) p[i]=q0[i]+(double(0)*eu[i])+(double(j)*ev[i]); glVertex2dv(p);
        for (i=0;i<2;i++) p[i]=q0[i]+(double(n)*eu[i])+(double(j)*ev[i]); glVertex2dv(p);
        }
    glEnd();

    glColor3f(0.5,0.5,0.5);                 // border of minimap
    glLineWidth(2.0);
    glBegin(GL_LINE_LOOP);
    for (i=0;i<2;i++) p[i]=q0[i]+(double(0)*eu[i])+(double(0)*ev[i]); glVertex2dv(p);
    for (i=0;i<2;i++) p[i]=q0[i]+(double(n)*eu[i])+(double(0)*ev[i]); glVertex2dv(p);
    for (i=0;i<2;i++) p[i]=q0[i]+(double(n)*eu[i])+(double(n)*ev[i]); glVertex2dv(p);
    for (i=0;i<2;i++) p[i]=q0[i]+(double(0)*eu[i])+(double(n)*ev[i]); glVertex2dv(p);
    glEnd();
    glLineWidth(1.0);

    // 2D minimap render of the pt[]
    glColor3f(0.7,0.1,0.1);                 // trapeze
    glBegin(GL_LINE_LOOP);
    for (j=0;j<4;j++)
        {
        // get u,v from pt[j]
        for (i=0;i<3;i++) p[i]=pt[j][i]-p0[i];
        for (u=0.0,i=0;i<3;i++) u+=p[i]*du[i];
        for (v=0.0,i=0;i<3;i++) v+=p[i]*dv[i];
        // convert to 2D position and render
        for (i=0;i<2;i++) p[i]=q0[i]+(u*eu[i])+(v*ev[i]); glVertex2dv(p);
        }
    glEnd();

    // restore 3D view
    glMatrixMode(GL_MODELVIEW);
    glPopMatrix();
    glMatrixMode(GL_PROJECTION);
    glPopMatrix();
    glViewport(vp0[0],vp0[1],vp0[2],vp0[3]);
    glEnable(GL_DEPTH_TEST);
    }
//---------------------------------------------------------------------------

预览:

preview

正如您所看到的,我们只需要矩阵*向量乘法和伪逆矩阵函数(所有其他类似dot,+,-都非常简单,并且直接编码为内联代码),并且都足够简单,可以直接在其中实现代码,因此不需要 GLM 或类似的库。

我也懒得将4点多边形裁剪为小地图大小,所以我改用glViewport来完成。

此处是Win32 BDS2006 VCL / C ++ / OpenGL1.0演示:

只需选择慢速下载,然后输入图片中的验证码即可。除了 GL,GLU 外,它不使用任何第三方库。相机是静态的,因此只需添加键盘/鼠标事件即可。如果要将其移植到您的环境中,只需模仿事件行为并忽略 VCL

OpenGL初始化基于以下条件:

我刚刚从其中删除了 GLEW,GLSL VAO 内容。