gpt4 book ai didi

c++ - 如何在顶部 2D 小 map 上显示以 3D 视角呈现的平面世界的可见部分?

转载 作者:太空狗 更新时间:2023-10-29 21:10:50 26 4
gpt4 key购买 nike

序言

问答是对以下内容的重制:

由于缺乏信息和原作者没有回应而被关闭(并且第一次重新开放周期失败)。不过,我认为这是一个有趣的问题,所以我决定自己提问并回答(这次有所有需要的规范)。

问题

假设我们的世界是一个均匀的矩形正方形网格(由 2D 瓷砖阵列表示)映射在一个平面上(比如说平面 XY (Z=0.0)简单性)并使用透视投影渲染。像这样:

preview

如何将透视截锥体( map /平面的可见部分)映射到小 map 上的红色多边形?

为了更通用,假设这是输入:

  • 平面 (Z=0.0) 定义为起始点 p0 和两个基础 vector du,dv,它映射了 2D map 图 block 数组到 3D ...
  • 使用了
  • ModelView矩阵和Perspective矩阵

想要的输出:

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

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

  • 使用C++
  • 旧式 OpenGL (GL,GLU)
  • 没有 vector/矩阵数学的第 3 方库

最佳答案

所以我们想要的是获得我们的平面 (Z=0.0) 和相机平截头体之间的 4 个交点。所以这个想法是从相机焦点转换 4 条光线(一个用于平截头体的每个边缘)并简单地计算光线/平面交点。因为平面是 Z=0.0,交点也有 Z=0.0,所以交点很容易计算。

  1. 为每个角/边转换光线

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

    frustrum

    并将其转换为世界全局坐标(通过恢复透视和使用逆模型 View 矩阵,稍后描述)。射线应为以下形式:

    p(t) = p + dp*t

    其中p是焦点,dp是方向 vector (不需要归一化)

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

    作为 z=0.0 然后:

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

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

  3. 将 3D 交叉点转换为 u,v inside map

    对于那个简单的点积就足够了。所以如果 p 是我们的交点,那么:

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

    其中 u,v 是二维 map 数组或小 map 中的坐标。如果您的 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. 全局坐标

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

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

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

    p = Inverse(cam)*p;

    但不要忘记 p 必须是齐次的,所以 (x,y,z,1) 点和 (x,y,z,0 ) 用于 vector 。

如果您缺乏背景知识或需要 vector/矩阵数学,请看这里:

这里是这个的小 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

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

我也懒得将 4 点多边形裁剪到小 map 大小,所以我使用了 glViewport 帮我完成了。

这里是Win32 BDS2006 VCL/C++/OpenGL1.0 Demo:

只需选择慢速下载,然后输入图片中的验证码。它不使用除 GL,GLU 之外的任何第 3 方库。相机是静态的,所以只需根据自己的喜好添加键盘/鼠标事件。如果您想将其移植到您的环境中,只需模仿事件行为并忽略 VCL 内容。

OpenGL init是基于这个完成的:

我刚刚从中删除了GLEW、GLSLVAO 内容。

关于c++ - 如何在顶部 2D 小 map 上显示以 3D 视角呈现的平面世界的可见部分?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52475104/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com