序言
此常见问题解答是对以下内容的重制:
由于缺少信息且原始作者没有回应而被关闭(并且第一次重新打开周期失败)。但是,我认为这是一个有趣的问题,因此我决定亲自询问并回答这个问题(这次提供了所有必要的规格)。
问题
假设我们的世界是映射在一个平面(为简单起见,假设平面 XY (Z=0.0
)上的统一矩形正方形网格(以2D瓷砖阵列表示),并进行了渲染透视投影。像这样:
如何将视锥角(地图/平面的可见部分)映射到小地图上的红色多边形?
为更通用,让我们假设这是输入:
Z=0.0
的p0
)和两个基本向量du,dv
,该向量将图块的2D映射数组映射到3D ... ModelView
矩阵和Perspective
矩阵和想要的输出:
限制(或多或少与原始问题匹配):
答案 0 :(得分:3)
因此,我们想要获得我们的平面(Z=0.0
)和相机视锥之间的4个交点。因此,想法是从相机焦点投射4束光线(对于平截头体的每个边缘投射一条光线),然后简单地计算出光线/平面的交点。由于平面是Z=0.0
,所以交点也有Z=0.0
,因此交点很容易计算。
每个角/边缘的投射光线
从相机焦点到屏幕角落(在屏幕空间中)
并将其转换为世界全局坐标(通过还原透视图并使用反模型视图矩阵进行稍后描述)。射线应采用以下形式:
p(t) = p + dp*t
其中p
是焦点,dp
是方向矢量(无需规范化)
计算与XY平面(Z=0.0
)的交点
然后作为z=0.0
:
0 = p.z + dp.z*t
t = -p.z/dp.z
所以我们可以直接计算交点。
将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
从摄影机坐标转换为全局世界坐标:
还原视角
首先获取透视矩阵参数
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);
投射光线需要znear
和zfar
。有关更多信息,请参见:
全球世界坐标
在我们的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);
}
//---------------------------------------------------------------------------
预览:
正如您所看到的,我们只需要矩阵*向量乘法和伪逆矩阵函数(所有其他类似dot,+,-
都非常简单,并且直接编码为内联代码),并且都足够简单,可以直接在其中实现代码,因此不需要 GLM 或类似的库。
我也懒得将4点多边形裁剪为小地图大小,所以我改用glViewport
来完成。
此处是Win32 BDS2006 VCL / C ++ / OpenGL1.0演示:
只需选择慢速下载,然后输入图片中的验证码即可。除了 GL,GLU 外,它不使用任何第三方库。相机是静态的,因此只需添加键盘/鼠标事件即可。如果要将其移植到您的环境中,只需模仿事件行为并忽略 VCL 。
OpenGL初始化基于以下条件:
我刚刚从其中删除了 GLEW,GLSL 和 VAO 内容。