与消失点和地平线相关的 3D 对象的变换 [英] Transformation of 3D objects related to vanishing points and horizon line

查看:19
本文介绍了与消失点和地平线相关的 3D 对象的变换的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试从图片的消失点和地平线开始计算 3D 对象的准确预期变换.

我想要的是,固定图片的消失点和水平线,我想根据我从图片开始设置的消失点和水平线旋转和倾斜3D对象

低于我预期的最终结果.

我怎样才能得到这个结果?

我可以使用什么样的转换?

在这个视频中可以看到我想要的结果.

为了稍后简化代码,这些点应该有一个特定的顺序.我以编程方式对它们进行排序,因此它们是 CCW,并且每个 QUAD 的第一个点位于右上角.第一个QUAD在右边(代表建筑物的Z轴或YZ平面),第二个在左边(代表建筑物的X轴或XY平面).

我还计算每个四边形的中点(平均点),并按屏幕 x 轴此点与排序点之间的角度对点进行排序.在此之后需要进行位置校正(如果屏幕 x 轴与四边形横轴碰撞,则将所有点移动 1),因此四边形的第一个点是右上角.

现在我们需要将我们的线条变成四边形.为此,我们需要建筑物的 y 轴方向......起初我从 4 条线中的每条线投射一个 2D 法线并将它们平均在一起.当心它们都应该在同一个方向......所以如果添加的法线有负的点积,在添加之前平均否定它.这个平均法线是在 XY 平面上的 UP 矢量投影.

但后来我改变了这个我计算了相应的左右四边形水平线之间的2个交点(获得四边形之间建筑物边缘的向上向量/方向).这证明更准确,也更容易计算.

现在要将您的线条转换为四边形,只需找到线条与从每个平面其中一条线的端点投射的法线之间的交点.在此之后,交叉点将与 QUAD 角完全对齐,因此我们可以从现在开始使用它...

  • 视角

    由于我们的建筑物很可能是一个在其平面之间具有直角的盒子,因此我们的 2 个四边形也应该在 3D 中相互垂直.我们可以使用这个……好像我们将它们的消失点与它们的中点连接起来,3D 中的线也应该是 90 度直角.所以我们可以直接从这个得到FOVx角度...

    所以 FOVx90.0deg 之间的比率与屏幕 x 分辨率和 2 个消失点距离(以像素为单位)之间的比率相同......所以由此得出:

    FOVx = 90.0*deg * image_x_resolution/intersections_x_distance

    我们也知道屏幕分辨率比 znear 还可以直接计算.例如,我在 OpenGL 中使用坐标 <-1,+1> 用于屏幕,因此:

    znear = 1.0/tan(0.5*FOVx)

    粗略的这会影响结果的整体比例,所以不要指望米单位......

    zfar 应该明智地选择,这样建筑物实际上是在视锥体中.例如:

    zfar = 1000.0*znear

    它只影响相对于 znear 的视图深度......但它不影响透视本身.

  • 构建 3D 坐标

    QUAD 的垂直线尺寸为我们提供了取决于深度的比例.这可用于计算我们拥有的每个点的 Z 坐标……但为此我们需要知道 QUAD 的原始高度.幸运的是,QUAD 未投影到 3D 的 2D 屏幕坐标应该形成直角.因此,如果我们使用 3 个点(QUAD 中点和它们之间边缘的中点)并对未投影线方向进行点积,结果应该为零.所以我们得到了 4 个方程和 4 个可以代数解的未知数...

    深度关系如下:

    scale(z) = znear/z

    因此,如果我们在问题所在的位置计算 QUAD 的高度,我们可以获得相对于原始 QUAD 高度的比例 l...因为我们有 3 个点:

    z0 = znear*l0/lz1 = znear*l1/lz2 = znear*l2/l点(pnt1-pnt0,pnt2-pnt0)=0

    where unprojected points: pnt0(x0,y0,z0) 是 QUADs 和 pnt1(x1,y1,z1) 之间边的中点>pnt2(x2,y2,z2) 是四边形的中点.l0,l1,l2 是对应的高度尺寸.所以这里唯一的未知数是 z0,z1,z2,l ...

    顺便说一句,这些未投影的点直接为我们提供了 2 个基向量和建筑物坐标系的位置.所以我们也可以组合它的矩阵......第三个也可以不投影或使用叉积代替......

    这是一个带有反转透视叠加的调试渲染立方体:

  • 如您所见,拟合并不完美,这是由于我的 3D 视图中存在与查看窗口纵横比相关的一些错误.如果窗口是方形的(不是图像只是 GL 窗口),则适合是完美的.如果我将纵横比添加到 3D 视图(比例)中,则拟合是完美的,但坐标系的基向量在视觉上的大小并不相同......需要多考虑修复......它很可能有些愚蠢与倒车视角完全无关的简单事情......这里是方形视图屏幕截图:

    这里是我的实际 C++/GL 代码...但要注意我使用了我的渲染引擎中的一些东西(如矢量数学等...)

    //---------------------------------------------------------------------#ifndef _ReversePespective_h#define _ReversePespective_h//---------------------------------------------------------------------------类反向透视{民众:双 FOVx;//[rad] 透视参数双znear,zfar;双倍 [16];//使用的透视投影矩阵代表代表;//选择的坐标系双asp,_asp;//屏幕 ys/xs双倍放大,_zoom;//视图缩放双 panx,pany;//查看位置双 ms[3],mw[3];//鼠标位置 [screen] , [world]枚举_p2D_enum{_p2D_quad0= 0,//2x guad 点(相同高度和垂直平面)_p2D_quad1= 8,//10 8 |一个 |2 0_p2D_qmid0=16,//V1 18 ||16 V0_p2D_qmid1=18,//12 14 |乙 |4 6_p2D_A = 20,_p2D_B =22,_p2D_V0 =24,//quad0 消失点(右)_p2D_V1 =26,//quad1 消失点(左)_p2Ds = 36,};双 p2D[_p2Ds];枚举_p3D_enum{_p3D_O = 0,//是_p3D_X = 3,//X O Z_p3D_Y = 6,//_p3D_Z = 9,_p3Ds = 12,};双 p3D[_p3Ds];内部选择;//鼠标选中的 p2D 点布尔_重绘;//应用需要重绘?反向透视(){ asp=1.0;_asp=1.0;重启();}ReversePerspective(ReversePerspective& a) { *this=a;}~ReversePerspective() {}ReversePerspective* operator = (const ReversePerspective *a) { *this=*a;返回这个;}//ReversePerspective* operator = (const ReversePerspective &a) { ...copy... return this;}void reset()//初始化点{sel=-1;_重绘=真;缩放=1.0;_缩放=1.0;panx=0.0;公司=0.0;矩阵一(每);FOVx=60.0*度;znear=0.1;zfar=1.0;vector_ld(ms,0.0,0.0,0.0);vector_ld(mw,0.0,0.0,0.0);p2D[0]=-0.5;p2D[1]=-0.5;p2D[2]=-0.5;p2D[3]=+0.5;p2D[4]=-0.9;p2D[5]=+0.5;p2D[6]=-0.9;p2D[7]=-0.5;p2D[8]=+0.5;p2D[9]=-0.5;p2D[10]=+0.5;p2D[11]=+0.5;p2D[12]=+0.9;p2D[13]=+0.5;p2D[14]=+0.9;p2D[15]=-0.5;计算();}void view2D()//设置二维模式视图{glDisable(GL_CULL_FACE);glDisable(GL_DEPTH_TEST);glMatrixMode(GL_PROJECTION);glLoadIdentity();glMatrixMode(GL_MODELVIEW);glLoadIdentity();glScaled(zoom*asp,zoom,1.0);glTranslated(panx,pany,0.0);}void view3D()//设置 3D 模式视图{glClear(GL_DEPTH_BUFFER_BIT);glDisable(GL_CULL_FACE);glEnable(GL_DEPTH_TEST);glMatrixMode(GL_PROJECTION);glLoadMatrixd(per);glMatrixMode(GL_MODELVIEW);glLoadIdentity();glScaled(zoom,zoom,1.0);glTranslated(panx,pany,0.0);}void draw2D()//渲染 2D 模式{国际我;双 c[3];_重绘=假;//上轴//四边形消失点/线glColor3f(0.3,0.7,0.3);glBegin(GL_LINES);glVertex2dv(p2D+_p2D_V0);glVertex2dv(p2D+ 0);glVertex2dv(p2D+_p2D_V0);glVertex2dv(p2D+ 6);glVertex2dv(p2D+_p2D_V1);glVertex2dv(p2D+10);glVertex2dv(p2D+_p2D_V1);glVertex2dv(p2D+12);glColor3f(1.0,1.0,0.0);glVertex2dv(p2D+_p2D_V0);glVertex2dv(p2D+_p2D_V1);glColor3f(0.0,1.0,0.0);glVertex2dv(p2D+_p2D_A);glVertex2dv(p2D+_p2D_B);glEnd();//四边形圆周glColor3f(1.0,1.0,1.0);glBegin(GL_LINE_LOOP);对于 (i=0;i<8;i+=2) glVertex2dv(p2D+i);glEnd();glBegin(GL_LINE_LOOP);对于 ( ;i<16;i+=2) glVertex2dv(p2D+i);glEnd();//四边形填充glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);glEnable(GL_BLEND);glBegin(GL_QUADS);glColor4f(0.0,0.0,1.0,0.2);对于 (i=0;i<8;i+=2) glVertex2dv(p2D+i);glColor4f(1.0,0.0,0.0,0.2);对于 ( ;i<16;i+=2) glVertex2dv(p2D+i);glEnd();glDisable(GL_BLEND);//端点glPointSize(5.0);glBegin(GL_POINTS);对于 (i=0;i<=_p2D_qmid1;i+=2){if ((i==0)||(i==8)){ c[0] =0.7;c[1] =0.7;c[2] =0.2;}否则 { c[0] =0.7;c[1] =0.2;c[2] =0.7;}if (i==sel) { c[0]+=0.2;c[1]+=0.2;c[2]+=0.2;}glColor3dv(c);glVertex2dv(p2D+i);}glEnd();glPointSize(1.0);}void draw3D()//渲染 3D 模式{国际我;_重绘=假;//reperglLineWidth(1.0);glBegin(GL_LINES);glColor3f(0.9,0.0,0.0);glVertex3dv(p3D+_p3D_O);glVertex3dv(p3D+_p3D_X);glColor3f(0.0,0.9,0.0);glVertex3dv(p3D+_p3D_O);glVertex3dv(p3D+_p3D_Y);glColor3f(0.0,0.0,0.9);glVertex3dv(p3D+_p3D_O);glVertex3dv(p3D+_p3D_Z);glEnd();glLineWidth(1.0);//端点glPointSize(5.0);glBegin(GL_POINTS);glColor3f(0.0,0.3,0.9);对于 (i=0;i<_p3Ds;i+=3) glVertex3dv(p3D+i);glEnd();glPointSize(1.0);}void compute()//从四点计算所有{int i,j,k,ix[10];双 l,l0,lp,lq;双*p,*q,*p0,ang[10],a,b;//每个四边形的 [平均点数]对于 (i=16;i<20;i++) p2D[i]=0.0;for (i= 0;i<8;i++){ p2D[16]+=p2D[i];我++;p2D[17]+=p2D[i];}对于 (i= 8;i<16;i++){ p2D[18]+=p2D[i];我++;p2D[19]+=p2D[i];}对于 (i=16;i<20;i++) p2D[i]*=0.25;//[reorder points] 维持特定顺序//计算从中点到四角的角度对于 (k=0;k<2;k++){p0=p2D+(k<<1)+16;p=p2D+(k<<3);对于 (j=(k<<2),i=0;i<8;i+=2,j++){ ix[j]=j+j;ang[j]=atanxy(p[i+0]-p0[0],p[i+1]-p0[1])*rad;}}ix[8]=16;ang[8]=0.0;ix[9]=18;ang[9]=0.0;//按角度排序#define swap(i0,i1) { int j0=i0<<1,j1=i1<<1,b;双一;b=ix[i0];ix[i0]=ix[i1];ix[i1]=b;a=ang[i0];ang[i0]=ang[i1];ang[i1]=a;a=p2D[j0+0];p2D[j0+0]=p2D[j1+0];p2D[j1+0]=a;a=p2D[j0+1];p2D[j0+1]=p2D[j1+1];p2D[j1+1]=a;}如果(ang[0]>ang[1])交换(0,1);如果(ang[1]>ang[2])交换(1,2);如果(ang[2]>ang[3])交换(2,3);如果(ang[0]>ang[1])交换(0,1);如果(ang[1]>ang[2])交换(1,2);如果(ang[0]>ang[1])交换(0,1);如果(ang[4]>ang[5])交换(4,5);如果(ang[5]>ang[6])交换(5,6);如果(ang[6]>ang[7])交换(6,7);如果(ang[4]>ang[5])交换(4,5);如果(ang[5]>ang[6])交换(5,6);如果(ang[4]>ang[5])交换(4,5);//右侧的第一个四边形(YZ 平面)左侧的第二个(XY)如果 (p2D[16]fabs(p2D[1]-p2D[7]))||(fabs(p2D[0]-p2D[2])fabs(p2D[9]-p2D[15]))||(fabs(p2D[8]-p2D[10])=0) for (i=0;i<10;i++) if (sel==ix[i]){ sel=i+i;休息;}//[交叉点] .18 一 16//10 8 |一个 |2 0 .|||//V1 18 ||16 V0.lp l0 lq//12 14 |乙 |4 6 .|||//.18 乙 16Intersect2DAxisAxis(p2D+_p2D_A ,p2D+ 0,p2D+ 2,p2D+ 8,p2D+10);Intersect2DAxisAxis(p2D+_p2D_B ,p2D+ 4,p2D+ 6,p2D+12,p2D+14);Intersect2DAxisAxis(p2D+_p2D_V0,p2D+ 0,p2D+ 2,p2D+ 4,p2D+ 6);Intersect2DAxisAxis(p2D+_p2D_V1,p2D+ 8,p2D+10,p2D+12,p2D+14);//2D 基向量(平面)对于 (j=0;j<2;j++) p3D[_p3D_O+j]=0.5*(p2D[_p2D_A+j]+p2D[_p2D_B+j]);对于 (j=0;j<2;j++) p3D[_p3D_X+j]=p2D[18+j];对于 (j=0;j<2;j++) p3D[_p3D_Y+j]=p2D[_p2D_A+j];对于 (j=0;j<2;j++) p3D[_p3D_Z+j]=p2D[16+j];//[看法]//znear=1.0/tan(0.5*FOVx);//p2D[18] = (x0,y0)//p2D[_p2D_O] = (x1,y1)//p2D[16] = (x2,y1)//z0 = znear*l0/l//z1 = znear*l1/l//z2 = znear*l2/l//点(p2D[18]-O,p2D[16]-O)=0#define size(i0,i1) sqrt(((p2D[i0]-p2D[i1])*(p2D[i0]-p2D[i1]))+((p2D[i0+1]-p2D[i1+1]])*(p2D[i0+1]-p2D[i1+1])))FOVx=90.0*deg*divide(2.0,size(_p2D_V0,_p2D_V1));znear=fabs(1.0/tan(0.5*FOVx));zfar=znear*100.0;透视(FOVx*asp*rad,asp,znear,zfar);p0=p3D+_p3D_O;l0=大小(_p2D_A,_p2D_B);p = p2D+18;lp=0.5*(大小(8,14)+大小(10,12));q = p2D+16;lq=0.5*(大小(0, 6)+大小(2, 4));l=fabs(divide(znear*(l0-lp)*(l0-lq),((p[0]-p0[0])*(q[0]-p0[0])+(p[1]-p0[1])*(q[1]-p0[1]))));//2D ->3Dp3D[_p3D_O+2]=-divide(znear*l0,l);p3D[_p3D_X+2]=-divide(znear*lp,l);p3D[_p3D_Y+2]=-divide(znear*l0,l);p3D[_p3D_Z+2]=-divide(znear*lq,l);对于 (i=_p3D_O;i<=_p3D_Z;i+=3) scr2world(p3D+i,p3D+i);#undef 大小//p3D ->重复p0=p3D+_p3D_O;p=p3D+_p3D_X;vector_sub(p,p,p0);vector_one(p,p);p=p3D+_p3D_Y;vector_sub(p,p,p0);vector_one(p,p);p=p3D+_p3D_Z;vector_sub(p,p,p0);vector_one(p,p);//3D 基向量与 Y,X 对齐//vector_mul(p3D+_p3D_Z,p3D+_p3D_Y,p3D+_p3D_X);//vector_mul(p3D+_p3D_X,p3D+_p3D_Y,p3D+_p3D_Z);rep.gpos_set (p3D+_p3D_O);rep.axisx_set(p3D+_p3D_X);rep.axisy_set(p3D+_p3D_Y);rep.axisz_set(p3D+_p3D_Z);//转换回点数a=0.5;p=p3D+_p3D_X;vector_mul(p,p,a);vector_add(p,p,p0);p=p3D+_p3D_Y;vector_mul(p,p,a);vector_add(p,p,p0);p=p3D+_p3D_Z;vector_mul(p,p,a);vector_add(p,p,p0);}无效负载(AnsiString 名称){国际,我;_重绘=真;hnd=FileOpen(name,fmOpenRead);如果(hnd<0){重置();返回;}FileRead(hnd,p2D,16*sizeof(p2D[0]));文件关闭(hnd);计算();}无效保存(AnsiString名称){国际,我;_重绘=真;hnd=文件创建(名称);如果 (hnd<0) 返回;FileWrite(hnd,p2D,16*sizeof(p2D[0]));文件关闭(hnd);}无效鼠标(双x,双y,TShiftState sh){int i,sel0=sel;双 ll、dx、dy、sz;mouse2scr(x,y);ms[0]=x;ms[1]=y;ms[2]=znear;scr2world(mw,ms);sz=0.05*_zoom;sz*=sz;如果(sh.Contains(ssLeft)){如果 (sel>=0){dx=x-p2D[sel+0];p2D[sel+0]=x;dy=y-p2D[sel+1];p2D[sel+1]=y;if (sel==16) for (i=0;i<8;i+=2){ p2D[i+0]+=dx;p2D[i+1]+=dy;}if (sel==18) for (i=8;i<16;i+=2){ p2D[i+0]+=dx;p2D[i+1]+=dy;}计算();_重绘=真;}}别的{//选择最近的点对于 (sel=-1,i=0;i<20;i+=2){dx=p2D[i+0]-x;dx*=dx;dy=p2D[i+1]-y;dy*=dy;dx+=dy;if (dx=fabs(b)) { b=a;a=((p1[0]-p0[0])*(p0[1]-p2[1]))+((p1[1]-p0[1])*(p2[0]-p0[0]]));}else { a=((p1[1]-p0[1])*(p0[0]-p2[0]))+((p1[0]-p0[0])*(p2[1]-p0[1]));}if (fabs(b)<=_zero)//平行 alebo nulove ciary{pi[0]=p0[0];pi[1]=p0[1];双 x0,x1,x2,x3,y0,y1,y2,y3;如果 (p0[0]_zero){ if (x3x1) 返回假;如果 (fabs(y3-y0)<=_zero) 返回真;返回假;}if (y1-y0>_zero){ if (y3y1) 返回假;如果 (fabs(x3-x0)<=_zero) 返回真;返回假;}如果 (fabs(y3-y0)+fabs(x3-x0)<=_zero) 返回真;返回假;} 否则 t=a/b;a=p1[0]-p0[0];b=p1[1]-p0[1];if (fabs(a)>=fabs(b)) { b=a;a=(p2[0]-p0[0])+((p3[0]-p2[0])*t);}else { a=(p2[1]-p0[1])+((p3[1]-p2[1])*t);}if (fabs(b)<=_zero){ b=1/0;} else s=divide(a,b);pi[0]=p0[0]+(p1[0]-p0[0])*s;pi[1]=p0[1]+(p1[1]-p0[1])*s;如果 ((s<0.0)||(s>1.0)) 返回假;如果 ((t<0.0)||(t>1.0)) 返回假;返回真;}void mouse2scr(double &x,double &y)//<-1,1>原始屏幕 ->缩放+平移屏幕<-1,1>{x=(x*_zoom*_asp)-panx;y=(y*_zoom )-pany;}void scr2mouse(double &x,double &y)//<-1,1>原始屏幕<-缩放+平移屏幕<-1,1>{x=(x+panx)*缩放*asp;y=(y+pany)*缩放;}void world2scr(double *s,double *w){//相机 [LCS]//眼睛->g2l(s,w);//[摄像机单元] -><-1,+1>国家数据中心s[0]=-divide(s[0]*per[0],w[2]);s[1]=-divide(s[1]*per[5],w[2]);}void scr2world(double *w,double *s){//<-1,+1>NDC ->[相机单元]w[0]=-divide(s[0]*s[2],per[0]);w[1]=-divide(s[1]*s[2],per[5]);w[2]=s[2];//世界 [GCS]//眼睛->l2g(w,w);}空透视(双fovy,双方面,双zNear,双zFar)//[度]{双 f;for (int i=0;i<16;i++) per[i]=0.0;//原始gluProjection//f=divide(1.0,tan(0.5*fovy*deg))//per[0]=f/aspect;//per[5]=f;//修正gluProjectionf=divide(1.0,tan(0.5*fovy*deg*aspect));每[0]=f;每[5]=f*方面;//z 范围per[10]=divide(zFar+zNear,zNear-zFar);per[11]=-1.0;per[14]=divide(2.0*zFar*zNear,zNear-zFar);glLoadMatrixd(per);//_pertan=per[0];}无效调整大小(双xs,双ys){_重绘=真;asp=divide(ys,xs);_asp=divide(xs,ys);计算();}};//---------------------------------------------------------------------------#万一//---------------------------------------------------------------------------

    这里的重要内容是将 QUAD 点反转为透视参数/矩阵和坐标系的计算函数......所有其余部分仅用于渲染/调整大小和鼠标处理......

    I'm trying to computing the exact prospective transformation of a 3D object starting from a vanishing points and horizon line of a picture.

    What I want is, fixed the vanishing points and horizontal line of a picture, I want rotate and skew an 3D object according with vanishing points and horizontal lines that I set starting from the picture

    Below the final result that I expected.

    How can I obtain this result?

    What kind of transformation can I use?

    In this video is possibile to see the result that I want.

    https://www.youtube.com/watch?v=EsSerR-AjEk

    解决方案

    This is nowhere near my cup of tea so handle with extreme prejudice and also far form solution just some start point hints...

    First of all we need to define some constraints/assumptions in order to make this to work.

    • user selects 4 lines representing 2 perpendicular planes and these 2 QUADs have the same height and altitude. Also the object height axis is almost the same as camera y axis (not rotated images).
    • perspective is centered around image center so central pixel represents view direction
    • pixels are squares

    So what you want to obtain is a 4x4 homogenous matrix that converts from some global 3D coordinates into 2D image coordinates + the perspective division.

    |x'|    | Xx Yx Zx Ox |  |x|
    |y'| =  | Xy Yy Zy Oy | .|y|
    |z'|    | Xz Yz Zz Oz |  |z|
    |w'|    | a  b  c  1  |  |1| 
    

    where (x,y,z) represents some 3D position and (x'/z',y'/z') represents 2D position on screen (image). To make this simple let assume that image center is (0,0).

    To construct your perspective matrix you need to know the FOV angles of camera and its focal length znear. Without it you can estimate it from known stuff on the image...

    Another option is to fit this matrix until the points match. But as it is 15 unknowns it would be very slow (even if many of the parameters are dependent and can be computed from the others).

    [complete reedit] Simple C++ approach example

    1. Two QUADs

      I would start with computing quad per each of the planes:

      To ease up the code later on the points should have a specific order. I programaticaly sort them so they are CCW and first point of each QUAD is in top right corner. First QUAD is on the right (representing Z axis of building or YZ plane) and second is on the left (representing X axis of building or XY plane).

      I also compute the mid point (avg point) for each quad and sort the points by angle between screen x axis this point and sorted point. After this its needed to make a correction of position (shift all points by 1 in case screen x axis is colliding with quad horizontal axis) so the first point of quad is the to right corner.

      Now we need to turn our lines into QUAD. For that we need the building y axis direction ... At first I was casting a 2D normal from each of the 4 lines and average them together. Beware they should all be in the same direction ... so if added normal has negative dot product with the average negate it before adding. This averaged normal is the UP vector projection onto XY plane.

      But later on I changed this I computed 2 intersection points between corresponding left and right QUAD horizontal lines (obtaining the UP vector/direction of the building edge between QUADs). This prove more accurate and also easier to compute.

      Now to convert your lines into QUADS simply find intersections between the lines and normal casted from endpoints of one of the lines per plane. After this the intersection will be aligned exactly as the QUAD corners so we can use that from now...

    2. Perspective

      As our building is most likely a box with right angles between its plane so our 2 QUADs should be also perpendicular to each other in 3D. We can use this ... as if we connect their vanishing points with their mid points the lines in 3D should be also with 90deg right angle. So we can directly obtain the FOVx angle from this...

      So the ratio between FOVx and 90.0deg is the same as ratio between screen x resolution and the 2 vanishing points distance in pixels... So from that:

      FOVx = 90.0*deg * image_x_resolution / intersections_x_distance
      

      As we also know the screen resolution than the znear is also directly computable. for example I use coordinates <-1,+1> in OpenGL for screen so:

      znear = 1.0/tan(0.5*FOVx)
      

      Of coarse this will affect the overall scale of the result so do not expect meters units...

      The zfar should be chosen wisely so the building is actually in the viewing frustrum. For example:

      zfar = 1000.0*znear
      

      It only affects the view depth relative to znear ... but it does not affect perspective itself.

    3. building 3D coordinates

      The QUADs vertical line sizes gives us the scale depended on depth. This can be used to compute Z coordinate for each point we have ... But for that we need to know original height of our QUADs. Luckily for us the unprojected 2D screen coordinates of the QUADs into 3D should form right angles. So if we use 3 points (the QUAD midpoints and midpoint of the edge between them) and do a dot product of the unprojected lines direction the result should be zero. So we got 4 equations and 4 unknowns which is algebraically solvable...

      The depth relation is as follows:

      scale(z) = znear/z
      

      so if we compute the height of QUAD at place where our point in question is we can get the scale relative to original QUAD height l... As we have 3 points then:

      z0 = znear*l0/l
      z1 = znear*l1/l
      z2 = znear*l2/l
      dot(pnt1-pnt0,pnt2-pnt0)=0
      

      where unprojected points: pnt0(x0,y0,z0) is the mid point of the edge between QUADs and pnt1(x1,y1,z1) and pnt2(x2,y2,z2) are the midpoints of the QUADs. The l0,l1,l2 are the corresponding height sizes. So the only unknonws here are z0,z1,z2,l ...

      btw these unprojected points give us 2 basis vectors and position of the buildings coordinate system directly. So we can compose its matrix too... The third can be also unprojected or use cross product instead ...

      Here a debug rendered cube with the reversed perspective overlay:

    As you can see the fit is not perfect that is due some bug in my 3D view related to viewing window aspect ratio. If the window is square (not the image just the GL window) fit is perfect. If I add aspect ratio to the 3D view (scale) the fit is perfect but the basis vectors of the coordinate system are not visually the same size... Need to think about it some more to repair... its most likely some silly simple thing not related to the reversing perspective at all... Here square view screen shots:

    Here my actual C++/GL code for this... but beware I am using some stuff from my rendering engine (like vector math etc...)

    //---------------------------------------------------------------------------
    #ifndef _ReversePespective_h
    #define _ReversePespective_h
    //---------------------------------------------------------------------------
    class ReversePerspective
        {
    public:
        double FOVx;        // [rad] perspective parameters
        double znear,zfar;
        double per[16];     // perspective projection matrix used
        reper  rep;         // selected coordinate system
        double asp,_asp;    // screen ys/xs
        double zoom,_zoom;  // view zoom
        double panx,pany;   // view position
        double ms[3],mw[3]; // mouse position [screen] , [world]
    
        enum _p2D_enum
            {
            _p2D_quad0= 0,  // 2x guad points (same altitude and perpendicular planes)
            _p2D_quad1= 8,  //           10   8 | A | 2  0
            _p2D_qmid0=16,  // V1          18   |   |  16              V0
            _p2D_qmid1=18,  //           12  14 | B | 4  6
            _p2D_A    =20,
            _p2D_B    =22,
            _p2D_V0   =24,  // quad0 vanishing point (right)
            _p2D_V1   =26,  // quad1 vanishing point (left)
            _p2Ds     =36,
            };
        double p2D[_p2Ds];
    
        enum _p3D_enum
            {
            _p3D_O    = 0,  //           Y
            _p3D_X    = 3,  //     X     O     Z
            _p3D_Y    = 6,  //
            _p3D_Z    = 9,
            _p3Ds     =12,
            };
        double p3D[_p3Ds];
    
        int sel;            // mouse selected p2D point
        bool _redraw;       // App need redraw?
    
        ReversePerspective() { asp=1.0; _asp=1.0; reset(); }
        ReversePerspective(ReversePerspective& a) { *this=a; }
        ~ReversePerspective() {}
        ReversePerspective* operator = (const ReversePerspective *a) { *this=*a; return this; }
        //ReversePerspective* operator = (const ReversePerspective &a) { ...copy... return this; }
    
        void reset()        // init points
            {
            sel=-1; _redraw=true;
            zoom=1.0; _zoom=1.0;
            panx=0.0; pany=0.0;
            matrix_one(per);
            FOVx=60.0*deg;
            znear=0.1; zfar=1.0;
            vector_ld(ms,0.0,0.0,0.0);
            vector_ld(mw,0.0,0.0,0.0);
            p2D[ 0]=-0.5; p2D[ 1]=-0.5;
            p2D[ 2]=-0.5; p2D[ 3]=+0.5;
            p2D[ 4]=-0.9; p2D[ 5]=+0.5;
            p2D[ 6]=-0.9; p2D[ 7]=-0.5;
            p2D[ 8]=+0.5; p2D[ 9]=-0.5;
            p2D[10]=+0.5; p2D[11]=+0.5;
            p2D[12]=+0.9; p2D[13]=+0.5;
            p2D[14]=+0.9; p2D[15]=-0.5;
            compute();
            }
        void view2D()       // set 2D mode view
            {
            glDisable(GL_CULL_FACE);
            glDisable(GL_DEPTH_TEST);
            glMatrixMode(GL_PROJECTION);
            glLoadIdentity();
            glMatrixMode(GL_MODELVIEW);
            glLoadIdentity();
            glScaled(zoom*asp,zoom,1.0);
            glTranslated(panx,pany,0.0);
            }
        void view3D()       // set 3D mode view
            {
            glClear(GL_DEPTH_BUFFER_BIT);
            glDisable(GL_CULL_FACE);
            glEnable(GL_DEPTH_TEST);
            glMatrixMode(GL_PROJECTION);
            glLoadMatrixd(per);
            glMatrixMode(GL_MODELVIEW);
            glLoadIdentity();
            glScaled(zoom,zoom,1.0);
            glTranslated(panx,pany,0.0);
            }
        void draw2D()       // render 2D mode
            {
            int i; double c[3]; _redraw=false;
            // up axis
            // quads vanishing points/lines
            glColor3f(0.3,0.7,0.3); glBegin(GL_LINES);
            glVertex2dv(p2D+_p2D_V0); glVertex2dv(p2D+ 0);
            glVertex2dv(p2D+_p2D_V0); glVertex2dv(p2D+ 6);
            glVertex2dv(p2D+_p2D_V1); glVertex2dv(p2D+10);
            glVertex2dv(p2D+_p2D_V1); glVertex2dv(p2D+12);
            glColor3f(1.0,1.0,0.0);
            glVertex2dv(p2D+_p2D_V0); glVertex2dv(p2D+_p2D_V1);
            glColor3f(0.0,1.0,0.0);
            glVertex2dv(p2D+_p2D_A); glVertex2dv(p2D+_p2D_B);
            glEnd();
            // quads circumference
            glColor3f(1.0,1.0,1.0);
            glBegin(GL_LINE_LOOP); for (i=0;i< 8;i+=2) glVertex2dv(p2D+i); glEnd();
            glBegin(GL_LINE_LOOP); for (   ;i<16;i+=2) glVertex2dv(p2D+i); glEnd();
            // quads fill
            glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
            glEnable(GL_BLEND); glBegin(GL_QUADS);
            glColor4f(0.0,0.0,1.0,0.2); for (i=0;i< 8;i+=2) glVertex2dv(p2D+i);
            glColor4f(1.0,0.0,0.0,0.2); for (   ;i<16;i+=2) glVertex2dv(p2D+i);
            glEnd(); glDisable(GL_BLEND);
            // endpoints
            glPointSize(5.0); glBegin(GL_POINTS);
            for (i=0;i<=_p2D_qmid1;i+=2)
                {
                if ((i==0)||(i==8)){ c[0] =0.7; c[1] =0.7; c[2] =0.2; }
                else               { c[0] =0.7; c[1] =0.2; c[2] =0.7; }
                if (i==sel)        { c[0]+=0.2; c[1]+=0.2; c[2]+=0.2; }
                glColor3dv(c);
                glVertex2dv(p2D+i);
                }
            glEnd(); glPointSize(1.0);
            }
        void draw3D()       // render 3D mode
            {
            int i; _redraw=false;
            // reper
            glLineWidth(1.0); glBegin(GL_LINES);
            glColor3f(0.9,0.0,0.0); glVertex3dv(p3D+_p3D_O); glVertex3dv(p3D+_p3D_X);
            glColor3f(0.0,0.9,0.0); glVertex3dv(p3D+_p3D_O); glVertex3dv(p3D+_p3D_Y);
            glColor3f(0.0,0.0,0.9); glVertex3dv(p3D+_p3D_O); glVertex3dv(p3D+_p3D_Z);
            glEnd(); glLineWidth(1.0);
            // endpoints
            glPointSize(5.0); glBegin(GL_POINTS);
            glColor3f(0.0,0.3,0.9); for (i=0;i<_p3Ds;i+=3) glVertex3dv(p3D+i);
            glEnd(); glPointSize(1.0);
            }
        void compute()  // compute all from quad points
            {
            int i,j,k,ix[10];
            double l,l0,lp,lq;
            double *p,*q,*p0,ang[10],a,b;
            // [avg points] for each quad
            for (i=16;i<20;i++) p2D[i]=0.0;
            for (i= 0;i< 8;i++){ p2D[16]+=p2D[i]; i++; p2D[17]+=p2D[i]; }
            for (i= 8;i<16;i++){ p2D[18]+=p2D[i]; i++; p2D[19]+=p2D[i]; }
            for (i=16;i<20;i++) p2D[i]*=0.25;
            // [reorder points] to maintain specific order
            // compute angle from mid point to quad corner
            for (k=0;k<2;k++)
                {
                p0=p2D+(k<<1)+16;
                p =p2D+(k<<3);
                for (j=(k<<2),i=0;i<8;i+=2,j++){ ix[j]=j+j; ang[j]=atanxy(p[i+0]-p0[0],p[i+1]-p0[1])*rad; }
                }
            ix[8]=16; ang[8]=0.0;
            ix[9]=18; ang[9]=0.0;
            // sort by angle
            #define swap(i0,i1) { int j0=i0<<1,j1=i1<<1,b; double a; b=ix[i0]; ix[i0]=ix[i1]; ix[i1]=b; a=ang[i0]; ang[i0]=ang[i1]; ang[i1]=a; a=p2D[j0+0]; p2D[j0+0]=p2D[j1+0]; p2D[j1+0]=a; a=p2D[j0+1]; p2D[j0+1]=p2D[j1+1]; p2D[j1+1]=a; }
            if (ang[0]>ang[1]) swap(0,1);
            if (ang[1]>ang[2]) swap(1,2);
            if (ang[2]>ang[3]) swap(2,3);
            if (ang[0]>ang[1]) swap(0,1);
            if (ang[1]>ang[2]) swap(1,2);
            if (ang[0]>ang[1]) swap(0,1);
            if (ang[4]>ang[5]) swap(4,5);
            if (ang[5]>ang[6]) swap(5,6);
            if (ang[6]>ang[7]) swap(6,7);
            if (ang[4]>ang[5]) swap(4,5);
            if (ang[5]>ang[6]) swap(5,6);
            if (ang[4]>ang[5]) swap(4,5);
            // first quad on right (YZ plane) second on the left (XY)
            if (p2D[16]<p2D[18]){ swap(0,4); swap(1,5); swap(2,6); swap(3,7); swap(8,9); }
            // correct order if wrong by 1 point
            if ((fabs(p2D[0]-p2D[ 6])>fabs(p2D[1]-p2D[ 7]))||(fabs(p2D[0]-p2D[ 2])<fabs(p2D[1]-p2D[ 3]))){ swap(0,3); swap(1,3); swap(2,3); }
            if ((fabs(p2D[8]-p2D[14])>fabs(p2D[9]-p2D[15]))||(fabs(p2D[8]-p2D[10])<fabs(p2D[9]-p2D[11]))){ swap(4,7); swap(5,7); swap(6,7); }
            #undef swap
            // correct sel
            if (sel>=0) for (i=0;i<10;i++) if (sel==ix[i]){ sel=i+i; break; }
            // [intersections]                             . 18   A  16
            //           10   8 | A | 2  0                 .  |   |   |
            // V1          18   |   |  16              V0  . lp  l0  lq
            //           12  14 | B | 4  6                 .  |   |   |
            //                                             . 18   B  16
            Intersect2DAxisAxis(p2D+_p2D_A ,p2D+ 0,p2D+ 2,p2D+ 8,p2D+10);
            Intersect2DAxisAxis(p2D+_p2D_B ,p2D+ 4,p2D+ 6,p2D+12,p2D+14);
            Intersect2DAxisAxis(p2D+_p2D_V0,p2D+ 0,p2D+ 2,p2D+ 4,p2D+ 6);
            Intersect2DAxisAxis(p2D+_p2D_V1,p2D+ 8,p2D+10,p2D+12,p2D+14);
            // 2D basis vectors (flat)
            for (j=0;j<2;j++) p3D[_p3D_O+j]=0.5*(p2D[_p2D_A+j]+p2D[_p2D_B+j]);
            for (j=0;j<2;j++) p3D[_p3D_X+j]=p2D[18+j];
            for (j=0;j<2;j++) p3D[_p3D_Y+j]=p2D[_p2D_A+j];
            for (j=0;j<2;j++) p3D[_p3D_Z+j]=p2D[16+j];
            // [perspective]
            //  znear=1.0/tan(0.5*FOVx);
            //  p2D[18]     = (x0,y0)
            //  p2D[_p2D_O] = (x1,y1)
            //  p2D[16]     = (x2,y1)
            //  z0 = znear*l0/l
            //  z1 = znear*l1/l
            //  z2 = znear*l2/l
            //  dot(p2D[18]-O,p2D[16]-O)=0
            #define size(i0,i1) sqrt(((p2D[i0]-p2D[i1])*(p2D[i0]-p2D[i1]))+((p2D[i0+1]-p2D[i1+1])*(p2D[i0+1]-p2D[i1+1])))
            FOVx=90.0*deg*divide(2.0,size(_p2D_V0,_p2D_V1));
            znear=fabs(1.0/tan(0.5*FOVx));
            zfar=znear*100.0;
            perspective(FOVx*asp*rad,asp,znear,zfar);
            p0=p3D+_p3D_O;  l0=size(_p2D_A,_p2D_B);
            p =p2D+18;      lp=0.5*(size(8,14)+size(10,12));
            q =p2D+16;      lq=0.5*(size(0, 6)+size( 2, 4));
            l=fabs(divide(znear*(l0-lp)*(l0-lq),((p[0]-p0[0])*(q[0]-p0[0])+(p[1]-p0[1])*(q[1]-p0[1]))));
            // 2D -> 3D
            p3D[_p3D_O+2]=-divide(znear*l0,l);
            p3D[_p3D_X+2]=-divide(znear*lp,l);
            p3D[_p3D_Y+2]=-divide(znear*l0,l);
            p3D[_p3D_Z+2]=-divide(znear*lq,l);
            for (i=_p3D_O;i<=_p3D_Z;i+=3) scr2world(p3D+i,p3D+i);
            #undef size
            // p3D -> reper
            p0=p3D+_p3D_O;                                     
            p=p3D+_p3D_X; vector_sub(p,p,p0); vector_one(p,p); 
            p=p3D+_p3D_Y; vector_sub(p,p,p0); vector_one(p,p); 
            p=p3D+_p3D_Z; vector_sub(p,p,p0); vector_one(p,p); 
            // 3D basis vectors aligned to Y,X
    //      vector_mul(p3D+_p3D_Z,p3D+_p3D_Y,p3D+_p3D_X);
    //      vector_mul(p3D+_p3D_X,p3D+_p3D_Y,p3D+_p3D_Z);
            rep.gpos_set (p3D+_p3D_O);
            rep.axisx_set(p3D+_p3D_X);
            rep.axisy_set(p3D+_p3D_Y);
            rep.axisz_set(p3D+_p3D_Z);
            // convert back to points
            a=0.5;
            p=p3D+_p3D_X; vector_mul(p,p,a); vector_add(p,p,p0);
            p=p3D+_p3D_Y; vector_mul(p,p,a); vector_add(p,p,p0);
            p=p3D+_p3D_Z; vector_mul(p,p,a); vector_add(p,p,p0);
            }
        void load(AnsiString name)
            {
            int hnd,i; _redraw=true;
            hnd=FileOpen(name,fmOpenRead); if (hnd<0) { reset(); return; }
            FileRead(hnd,p2D,16*sizeof(p2D[0]));
            FileClose(hnd);
            compute();
            }
        void save(AnsiString name)
            {
            int hnd,i; _redraw=true;
            hnd=FileCreate(name); if (hnd<0) return;
            FileWrite(hnd,p2D,16*sizeof(p2D[0]));
            FileClose(hnd);
            }
        void mouse(double x,double y,TShiftState sh)
            {
            int i,sel0=sel;
            double ll,dx,dy,sz;
            mouse2scr(x,y); ms[0]=x; ms[1]=y; ms[2]=znear; scr2world(mw,ms);
            sz=0.05*_zoom; sz*=sz;
            if (sh.Contains(ssLeft))
                {
                if (sel>=0)
                    {
                    dx=x-p2D[sel+0]; p2D[sel+0]=x;
                    dy=y-p2D[sel+1]; p2D[sel+1]=y;
                    if (sel==16) for (i=0;i< 8;i+=2){ p2D[i+0]+=dx; p2D[i+1]+=dy; }
                    if (sel==18) for (i=8;i<16;i+=2){ p2D[i+0]+=dx; p2D[i+1]+=dy; }
                    compute(); _redraw=true;
                    }
                }
            else{
                // select closest point
                for (sel=-1,i=0;i<20;i+=2)
                    {
                    dx=p2D[i+0]-x; dx*=dx;
                    dy=p2D[i+1]-y; dy*=dy; dx+=dy;
                    if (dx<sz) if ((sel<0)||(dx<ll)){ sel=i; ll=dx; }
                    }
                _redraw|=(sel0!=sel);
                }
            }
        void dzoom(double d)
            {
            double x,y; _redraw=true;
            x=ms[0]; y=ms[1];
            scr2mouse(x,y);
            zoom*=d; _zoom=divide(1.0,zoom);
            mouse2scr(x,y);
            panx-=ms[0]-x;
            pany-=ms[1]-y;
            }
        bool Intersect2DAxisAxis(double *pi,double *p0,double *p1,double *p2,double *p3)    // pi[2] = intersection point if return true
            {
            double s,t,a,b;
            const double _zero=1e-30;
            a=((p1[0]-p0[0])*(p3[1]-p2[1]))-((p1[1]-p0[1])*(p3[0]-p2[0]));
            b=((p1[1]-p0[1])*(p3[0]-p2[0]))-((p1[0]-p0[0])*(p3[1]-p2[1]));
            if (fabs(a)>=fabs(b)) { b=a; a=((p1[0]-p0[0])*(p0[1]-p2[1]))+((p1[1]-p0[1])*(p2[0]-p0[0])); }
            else                  {      a=((p1[1]-p0[1])*(p0[0]-p2[0]))+((p1[0]-p0[0])*(p2[1]-p0[1])); }
            if (fabs(b)<=_zero) // paralelne alebo nulove ciary
                {
                pi[0]=p0[0];
                pi[1]=p0[1];
                double x0,x1,x2,x3,y0,y1,y2,y3;
                if (p0[0]<p1[0]) { x0=p0[0]; x1=p1[0]; } else { x0=p1[0]; x1=p0[0]; }
                if (p0[1]<p1[1]) { y0=p0[1]; y1=p1[1]; } else { y0=p1[1]; y1=p0[1]; }
                if (p2[0]<p3[0]) { x2=p2[0]; x3=p3[0]; } else { x2=p3[0]; x3=p2[0]; }
                if (p2[1]<p3[1]) { y2=p2[1]; y3=p3[1]; } else { y2=p3[1]; y3=p2[1]; }
                if (x1-x0>_zero){ if (x3<x0) return false; if (x2>x1) return false; if (fabs(y3-y0)<=_zero) return true; return false; }
                if (y1-y0>_zero){ if (y3<y0) return false; if (y2>y1) return false; if (fabs(x3-x0)<=_zero) return true; return false; }
                if (fabs(y3-y0)+fabs(x3-x0)<=_zero) return true;
                return false;
                } else t=a/b;
            a=p1[0]-p0[0];
            b=p1[1]-p0[1];
            if (fabs(a)>=fabs(b)) { b=a; a=(p2[0]-p0[0])+((p3[0]-p2[0])*t); }
            else                  {      a=(p2[1]-p0[1])+((p3[1]-p2[1])*t); }
            if (fabs(b)<=_zero){ b=1/0; } else s=divide(a,b);
            pi[0]=p0[0]+(p1[0]-p0[0])*s;
            pi[1]=p0[1]+(p1[1]-p0[1])*s;
            if ((s<0.0)||(s>1.0)) return false;
            if ((t<0.0)||(t>1.0)) return false;
            return true;
            }
        void mouse2scr(double &x,double &y) // <-1,1> raw screen -> zoom+pan screen <-1,1>
            {
            x=(x*_zoom*_asp)-panx;
            y=(y*_zoom     )-pany;
            }
        void scr2mouse(double &x,double &y) // <-1,1> raw screen <- zoom+pan screen <-1,1>
            {
            x=(x+panx)*zoom*asp;
            y=(y+pany)*zoom;
            }
        void world2scr(double *s,double *w)
            {
            // camera [LCS]
    //      eye->g2l(s,w);
            // [camera units] -> <-1,+1> NDC
            s[0]=-divide(s[0]*per[0],w[2]);
            s[1]=-divide(s[1]*per[5],w[2]);
            }
        void scr2world(double *w,double *s)
            {
            // <-1,+1> NDC -> [camera units]
            w[0]=-divide(s[0]*s[2],per[0]);
            w[1]=-divide(s[1]*s[2],per[5]);
            w[2]=s[2];
            // world [GCS]
    //      eye->l2g(w,w);
            }
        void perspective(double fovy,double aspect,double zNear,double zFar) // [deg]
            {
            double f;
            for (int i=0;i<16;i++) per[i]=0.0;
            // original gluProjection
    //      f=divide(1.0,tan(0.5*fovy*deg))
    //      per[ 0]=f/aspect;
    //      per[ 5]=f;
            // corrected gluProjection
            f=divide(1.0,tan(0.5*fovy*deg*aspect));
            per[ 0]=f;
            per[ 5]=f*aspect;
            // z range
            per[10]=divide(zFar+zNear,zNear-zFar);
            per[11]=-1.0;
            per[14]=divide(2.0*zFar*zNear,zNear-zFar);
            glLoadMatrixd(per);
    //      _pertan=per[0];
            }
        void resize(double xs,double ys)
            {
            _redraw=true;
             asp=divide(ys,xs);
            _asp=divide(xs,ys);
            compute();
            }
        };
    //---------------------------------------------------------------------------
    #endif
    //---------------------------------------------------------------------------
    

    The important stuff in here is the compute function that reverses the QUAD points into perspective parameters/matrix and coordinate system ... all the rest is just for rendering/resizing and mouse handling...

    这篇关于与消失点和地平线相关的 3D 对象的变换的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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