如何在顶部2D小地图上显示用3D透视图渲染的平面世界的可见部分? [英] How to show visible part of planar world rendered with 3D perspective on topside 2D minimap?

查看:105
本文介绍了如何在顶部2D小地图上显示用3D透视图渲染的平面世界的可见部分?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

序言

Q& A 是以下内容的翻版:

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

which was closed (and failed first reopening cycle) due to lack of info and no response of the original author. However I think this is an interesting question though so I decided to Ask&Answer this myself (this time with all the needed specs).

问题

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

Let assume our world is a uniform rectangular square grid (represented by 2D array of tiles) mapped on a plane (let say plane XY (Z=0.0) for simplicity) and is rendered with perspective projection. like this:

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

How to map the perspective frustrum (visible part of the map/plane) to the red colored polygonal shape on the minimap ?

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

To be more universal let assume this as input:

    定义为起点p0
  • 平面(Z=0.0)和两个基本矢量du,dv,两个基本矢量du,dv将图块的2D映射数组映射到3D ...
  • 使用
  • ModelView矩阵和Perspective矩阵
  • plane (Z=0.0) defined as start point p0 and two basis vectors du,dv which maps the 2D map array of tiles to 3D ...
  • ModelView matrix and Perspective matrix used

和想要的输出:

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

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

Limitations (to more or less match the original question):

  • 使用 C ++
  • 旧样式 OpenGL ( GL,GLU )
  • 没有用于向量/矩阵数学的第三方函数库
  • use C++
  • old style OpenGL (GL,GLU)
  • no 3th party lib for vector/matrix math

推荐答案

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

So what we want is to obtain the 4 intersection points between our plane (Z=0.0) and camera frustrum. So the idea is to cast 4 rays (one for each edge of frustrum) from the camera focal point and simply compute the ray/plane intersection. As the plane is Z=0.0 the intersection point has Z=0.0 too so the intersection is quite easy to compute.

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

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

from camera focal point to screen corner (in screen space)

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

and convert it to world global coordinates (by reverting perspective and using inverse modelview matrix it is described later). The ray should be in form:

p(t) = p + dp*t

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

where p is the focal point and dp is direction vector (does not need to be normalized)

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

compute the intersection with XY plane (Z=0.0)

然后作为z=0.0:

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

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

so we can compute the intersection point directly.

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

convert 3D intersection points to u,v inside map

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

for that simple dot product is enough. So if p is our intersection point then:

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

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

where u,v are coordinates in our 2D map array or minimap. In case your u,v are axis aligned then you can use directly (p.x-p0.x,p.y-p0.y) without any dot product

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

How to convert point p from camera coordinates to global world coordinates:

  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轴的缩放比例.现在,还原透视图就是像这样反转透视图分隔线:

This will give you the frustrums near and far planes and scaling for x,y axises. Now reverting perspective is simply inverting the perspective divide like this:

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

znearzfar是投射光线所必需的.有关更多信息,请参见:

The znear and zfar are needed for casting the rays. For more info see:

全球世界坐标

在我们的p上仅使用ModelView矩阵的逆.因此,首先获取矩阵:

simply use inverse of ModelView matrix on our p. So first obtain the matrix:

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

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

As inverse you can use my matrix_inv so now the final step is:

p = Inverse(cam)*p;

但是请不要忘记p必须是同质的,因此(x,y,z,1)表示点,(x,y,z,0)表示向量.

but do not forget that p must be homogenuous so (x,y,z,1) for points and (x,y,z,0) for vectors.

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

Look here if you lack the background knowledge or need vector/matrix math:

以下是小型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);
    }
//---------------------------------------------------------------------------

预览:

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

As you can see we need just matrix*vector multiplication and pseudo inverse matrix functions for this (all others like dot,+,- are really simple and directly encoded as inline code) and both are simple enough to directly implement it in code so no need for GLM or similar lib.

我也懒得将4点多边形裁剪为小地图大小,所以我改用了glViewport.

Also I was too lazy to clip the 4 point polygon to minimap size so instead I used glViewport which did it for me.

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

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

Just select slow download and enter the validation code from image. It does not use any 3th party libs other than GL,GLU. The camera is static so just add keyboard/mouse events to your liking. If you want to port this to your environment just mimic the events behavior and ignore the VCL stuff.

OpenGL初始化基于以下条件完成:

The OpenGL init is done based on this:

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

I just removed the GLEW,GLSL and VAO stuff from it.

这篇关于如何在顶部2D小地图上显示用3D透视图渲染的平面世界的可见部分?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆