在帆布上的透视视觉

在帆布上的透视视觉

本文介绍了在帆布上的透视视觉的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧! 问题描述 今天,我带来了一个关于伪3D和透视的主题。 我正在查看视频 这只是一个图像,但我的第一个问题是:我真的可以在这个环境中进行相机的移动,比如旋转和移动x和y轴?。我试图在画布上制作2个消失点,为每个15度创建线条,并且我获得了透视幻觉,但是我找不到旋转或运动的方式。在那个视频中,我看到像素使用绿色和蓝色创建2维,但我想用线条来理解它是如何工作的。 =https://i.stack.imgur.com/W0Cwe.png =nofollow noreferrer> 没有一个地方正在逐步教导如何使运动变得透彻。我没有找到。我使用视频的方法检查了Java游戏制作商的3D视频和Markus Person创建的有腔的前奏游戏,但是我没有找到这个渲染王的解释。 让我们创造一个计划使用网格。我必须运用哪些逻辑来创造运动?我真的想理解制作这种伪3D的逻辑,而不使用框架或类似的东西。谢谢你的帮助!我会等待你的回答。 我检查了一些关于SNES的模式7的内容。这是我认为的一个好方法。我只是想了解它是如何工作的,以及如何进行轮换。 **注意:我不会为它使用光线投射。 Raycasting我将用来创建墙。 解决方案有趣的问题。我没有抵制和编写它的乐趣,所以这里有一些见解......那么有2个基本的方法。一个是光栅假,另一个是基于Vector的。我会描述后者,因为您可以使用它做更多。 矢量方法 这种方法并没有伪造真正的 3D 。其余的依赖于你想要使用的渲染...现在我假设你可以渲染 2D 线。所有代码块都在 C ++ 中。 转换 p> 您需要向量数学来转换世界和相机空间之间的点并返回。在 3D 图形中通常使用 4x4均匀转换矩阵,许多编程 API 本身支持它们。我将根据确定所用乘法顺序的 OpenGL 矩阵布局进行数学运算。欲了解更多信息,我强烈建议阅读: 了解4x4同质转换矩阵 正如我使用它很多。这些链接的答案也非常有用,特别是 3D 图形管道和全伪逆矩阵。答案本身就是 3D 渲染所需的基本知识(低级别,而不需要渲染材料的任何lib)。 也有像 GLM 这样的库,所以如果你愿意,你可以使用任何支持 4x4 矩阵和 4D 向量的线性代数而不是我的代码。 所以我们有两个 4x4 矩阵( camera )代表我们的相机坐标系,第二个( icamera ),这是它的倒数。现在,如果我们想要在世界和屏幕空间之间进行转换,我们只需要这样做: P = camera * Q Q = icamera * P 其中 P(x,y,z,1) 是相机坐标系中的点,并且 Q(x,y,z,1)是全球世界坐标系中的同一点。 透视 只需将 P 由它的 z 坐标。这将缩放(0,0)周围的对象,所以越远的对象越小。如果我们添加一些屏幕分辨率和轴校正,我们可以使用这个: void perspective(double * P)// apply perspective transform在P { // perspectve除法 P [0] * = znear / P [2]; P [1] * = znear / P [2]; //屏幕坐标系 P [0] = xs2 + P [0]; //移动(0,0)到屏幕中心 P [1] = ys2-P [1]; // axises:x = right,y = up } 所以点 0,0 是屏幕的中心。 xs2,ys2 是屏幕分辨率的一半, znear 是投影的焦距。因此, XY 平面矩形的屏幕分辨率和居中(0,0,znear)将精确覆盖屏幕 渲染3D线 我们可以使用任何图元进行渲染。我选择线路非常简单,可以实现很多。所以我们想要使用 2D 线渲染 API (任何类型)渲染 3D 线。我是 VCL ,因此我选择了 VCL / GDI Canvas ,它应该与您的 Canvas 非常相似。 作为输入,我们在全球世界坐标系中得到了两个 3D 点。为了使用 2D 线渲染,我们需要将 3D 位置转换为 2D 屏幕空间。这是通过矩阵*向量乘法完成的。 由此我们获得两个 3D 点但在相机坐标系中。现在我们需要通过我们的视图区域( Frustrum )剪切线条。我们可以忽略 x,y 轴作为 2D 行api通常为我们做的。所以剩下的唯一东西是剪辑 z 轴。由 znear 和 zfar 。其中 zfar 是我们距相机焦点的最大可见距离。因此,如果我们的行完全位于我们的 z范围之前或之后,我们忽略它并且不呈现。如果它在里面,我们渲染它。如果它穿过 znear 或 zfar ,我们将外部零件切断(通过 x,y 坐标)。 现在我们对这两个点应用透视图并使用 2D code> x,y 坐标。 我的代码如下所示: void draw_line(TCanvas * can,double * pA,double * pB)//绘制3D线 { int i; double D [3],A [3],B [3],t; //转换为相机坐标系 matrix_mul_vector(A,icamera,pA); matrix_mul_vector(B,icamera,pB); (i = 0; i B [2]) [一世]; A [i] = B [i]于; B [I] = d [I];对于(i = 0; i // D = B-A ; //忽略Z视图行 if(A [2]> zfar)return; if(B [2] { t =(znear-A [2])/ D [2]; //剪切线以查看是否需要; A [0] + = D [0] * t; A [1] + = D [1] * t; A [2] = znear; (b [2]> zfar) { t =(zfar-B [2])/ D [2]; B [0] + = D [0] * t; B [1] + = D [1] * t; B [2] = zfar; } //应用透视图透视图(A); 透视图(B); // render can-> MoveTo(A [0],A [1]); can-> LineTo(B [0],B [1]); 渲染 XZ plane 我们可以使用我们的 3D 线将地面和天空平面可视化为正方形网格。所以我们为循环创建循环,使得 x 轴对齐的行和 y 轴对齐的线条覆盖某些原始位置 O 周围某些大小的某个方块。这些行应该是一些 step 彼此之间的距离等于网格单元大小。 原始位置 O 应该在我们的 frustrun 中心附近。如果它是恒定的,那么我们可以走出平面边缘,因此它不覆盖整个(一半)屏幕。我们可以使用我们的相机位置并在其上添加 0.5 *(zfar + znear)* camera_z_axis 。为了保持移动的幻觉,我们需要将 O 与 step 大小对齐。我们可以利用 floor , round 或整数转换。 void draw_plane_xz(TCanvas * can,double y,double step)//绘制3D飞机 { int i; double A [3],B [3],t,size; double U [3] = {1.0,0.0,0.0}; // U = X double V [3] = {0.0,0.0,1.0}; // V = Z double O [3] = {0.0,0.0,0.0}; // Origin //计算视图中心附近的原点但对齐步骤 i = 0; -O [I] = FLOOR(相机[12 + I] /步骤)*步骤; i = 2; -O [I] = FLOOR(相机[12 + I] /步骤)*步骤; O [1] = y; //设置大小,以便飞机安全地覆盖整个视图 t = xs2 * zfar / znear;大小= T; // x将在zfar t = 0.5 *(zfar + znear)中转换为xs2; if(size< t)size = t; //深度范围的一半 t + = step; // +一个网格单元beacuse O关闭到1个网格单元 t * = sqrt(2); //对角线,所以无论我们如何旋转Yaw // U lines for(i = 0; i { A [i] = -O [I] +(尺寸* U [I]) - ((步骤+大小)* V [I]); B [i] = O [i] - (size * U [i]) - ((step + size)* V [i]); (t = -size; t { for(i = 0; i { A [i] + = step * V [i]; B [i] + = step * V [i]; } draw_line(can,A,B); (i = 0; i { A [i] = O [i] - ((步骤+大小)* U [I])+(大小* V [I]); B [i] = O [i] - ((step + size)* U [i]) - (size * V [i]); (t = -size; t { for(i = 0; i { A [i] + = step * U [i]; B [i] + = step * U [i]; } draw_line(can,A,B); } matrix_mul_vector(A,icamera,A); } 所有这一切在小的 VCL / GDI / Canvas 应用程序中我得到了这个: // --------------------------------------------- ------------------------------ #include< vcl.h> //你可以忽略这些行 #include< math.h> #pragma hdrstop #includewin_main.h // -------------------------- ------------------------------------------------- #pragma包(smart_init) #pragma资源* .dfm//直到这里。 TMain * Main; //这是指向我的VCL窗口的指针(你不需要它) // ---这里开始重要的东西:------------------- ------------------- //透视图 double znear = 100.0; //透视焦距 double zfar = 2100.0; //可见性 //查看 double xs2 = 0.0; //屏幕半分辨率 double ys2 = 0.0; //相机 double yaw = 0.0; // euler偏航角[rad] 双相机[16]; //相机直接转换矩阵 double icamera [16]; //相机逆变换矩阵 //键盘布尔 bool _forw = false,_back = false,_right = false,_left = false; // -------------------------------------------- ------------------------------- void matrix_inv(double * a,double * b)// a [ 16] =逆(b [16]) { double x,y,z; //旋转矩阵的转置 a [0] = b [0]; a [5] = b [5]; a [10] = b [10]; x = b [1];一个[1] = B [4];一个[4] = X; x = b [2];一个[2] = B [8];一个[8] = X; x = b [6];一个[6] = B [9];一个[9] = X; //复制投影部分 a [3] = b [3]; a [7] = b [7]; a [11] = b [11]; a [15] = b [15]; //转换原点: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 matrix_mul_vector(double * c,double * a ,double * b)// c [3] = a [16] * b [3] { 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 } // --------------------------------------- ------------------------------------ void compute_matrices()//重新计算相机,icamera在相机位置或偏航变化之后 { //束缚角(yaw> 2.0 * M_PI)yaw- = 2.0 * M_PI; while(yaw // X = right camera [0] = cos(yaw); 相机[1] = 0.0; camera [2] = sin(yaw); // Y = up camera [4] = 0.0; 相机[5] = 1.0; 相机[6] = 0.0; // Z =前进相机[8] = - sin(偏航); 相机[9] = 0.0; 相机[10] = cos(偏转); //无投影相机[3] = 0.0; 相机[7] = 0.0; 相机[11] = 0.0; 相机[15] = 1.0; //计算逆矩阵 matrix_inv(icamera,camera); } // --------------------------------------- ------------------------------------ void perspective(double * P)// apply透视变换 { // perspectve除法 P [0] * = znear / P [2]; P [1] * = znear / P [2]; //屏幕坐标系 P [0] = xs2 + P [0]; //移动(0,0)到屏幕中心 P [1] = ys2-P [1]; // axises:x = right,y = up } // ---------------------------- ----------------------------------------------- void draw_line(TCanvas * can,double * pA,double * pB)//绘制3D线 { int i; double D [3],A [3],B [3],t; //转换为相机坐标系 matrix_mul_vector(A,icamera,pA); matrix_mul_vector(B,icamera,pB); (i = 0; i B [2]) [一世]; A [i] = B [i]于; B [I] = d [I];对于(i = 0; i // D = B-A ; //忽略Z视图行 if(A [2]> zfar)return; if(B [2] { t =(znear-A [2])/ D [2]; //剪切线以查看是否需要; A [0] + = D [0] * t; A [1] + = D [1] * t; A [2] = znear; (b [2]> zfar) { t =(zfar-B [2])/ D [2]; B [0] + = D [0] * t; B [1] + = D [1] * t; B [2] = zfar; } //应用透视图透视图(A); 透视图(B); // render can-> MoveTo(A [0],A [1]); can-> LineTo(B [0],B [1]); } // --------------------------------------- ------------------------------------ void draw_plane_xz(TCanvas * can,double y,双步)//绘制3D飞机 { int i; double A [3],B [3],t,size; double U [3] = {1.0,0.0,0.0}; // U = X double V [3] = {0.0,0.0,1.0}; // V = Z double O [3] = {0.0,0.0,0.0}; // Origin //计算视图中心附近的原点但对齐步骤 i = 0; -O [I] = FLOOR(相机[12 + I] /步骤)*步骤; i = 2; -O [I] = FLOOR(相机[12 + I] /步骤)*步骤; O [1] = y; //设置大小,以便飞机安全地覆盖整个视图 t = xs2 * zfar / znear;大小= T; // x将在zfar t = 0.5 *(zfar + znear)中转换为xs2; if(size< t)size = t; //深度范围的一半 t + = step; // +一个网格单元beacuse O关闭到1个网格单元 t * = sqrt(2); //对角线,所以无论我们如何旋转Yaw // U lines for(i = 0; i { A [i] = -O [I] +(尺寸* U [I]) - ((步骤+大小)* V [I]); B [i] = O [i] - (size * U [i]) - ((step + size)* V [i]); (t = -size; t { for(i = 0; i { A [i] + = step * V [i]; B [i] + = step * V [i]; } draw_line(can,A,B); (i = 0; i { A [i] = O [i] - ((步骤+大小)* U [I])+(大小* V [I]); B [i] = O [i] - ((step + size)* U [i]) - (size * V [i]); (t = -size; t { for(i = 0; i { A [i] + = step * U [i]; B [i] + = step * U [i]; } draw_line(can,A,B); } matrix_mul_vector(A,icamera,A); } // --------------------------------------- ------------------------------------ void TMain :: draw()// this是我的主要渲染例程 { //清除缓冲区 bmp-> Canvas-> Brush-> Color = clWhite; bmp-> Canvas-> FillRect(TRect(0,0,xs,ys)); //初始化/更新变量 double step = 50.0; //平面网格大小 :: xs2 = Main-> xs2; //更新实际屏幕半分辨率 :: ys2 = Main-> ys2; // sky bmp-> Canvas-> Pen-> Color = clBlue; draw_plane_xz(bmp-> Canvas,+ 200.0,step); // terrain bmp-> Canvas-> Pen-> Color = clGreen; draw_plane_xz(bmp-> Canvas,-200.0,step); // render backbuffer Main-> Canvas-> Draw(0,0,bmp); _redraw = false; } // --------------------------------------- ------------------------------------ __fastcall TMain :: TMain(TComponent * Owner) :TForm(Owner)//这是初始化 { bmp = new Graphics :: TBitmap; bmp-> HandleType = bmDIB; bmp-> PixelFormat = pf32bit; pyx = NULL; _redraw = true; //相机开始位置相机[12] = 0.0; 相机[13] = 0.0; 相机[14] = 0.0; compute_matrices(); } // --------------------------------------- ------------------------------------ void __fastcall TMain :: FormDestroy(TObject * Sender )//这是退出 { if(pyx)delete [] pyx; 删除bmp; } // --------------------------------------- ------------------------------------ void __fastcall TMain :: FormResize(TObject * Sender )//在调整大小 { xs = ClientWidth; XS2 = XS>大于1; ys = ClientHeight; YS2 = YS>大于1; bmp->宽度= xs; bmp-> Height = ys; if(pyx)delete [] pyx; pyx = new int * [ys]; (int y = 0; y< ys; y ++)pyx [y] =(int *)bmp-> ScanLine [y]; _redraw = true; } // --------------------------------------- ------------------------------------ void __fastcall TMain :: FormPaint(TObject * Sender )//这被称为强制重绘 { _redraw = true; } // --------------------------------------- ------------------------------------ void __fastcall TMain :: tim_redrawTimer(TObject * Sender )//这是通过我的定时器周期性地调用 { double da = 5.0 * M_PI / 180.0; //转速 double dl = 15.0; //移动速度 bool _recompute = false; if(_left){_redraw = true; _recompute = TRUE;偏航+ = DA; } if(_right){_redraw = true; _recompute = TRUE;偏航= DA; } if(_forw){_redraw = true; _recompute = TRUE; for(int i = 0; i if(_back){_redraw = true; _recompute = TRUE; for(int i = 0; i if(_recompute)compute_matrices(); if(_redraw)draw(); } // --------------------------------------- ------------------------------------ void __fastcall TMain :: FormKeyDown(TObject * Sender ,WORD& Key,TShiftState Shift)//当按键被按下时,这被调用 { // Caption = Key; if(Key == 104)_left = true; if(Key == 105)_right = true; if(Key == 100)_forw = true; if(Key == 97)_back = true; } // --------------------------------------- ------------------------------------ void __fastcall TMain :: FormKeyUp(TObject * Sender ,WORD& Key,TShiftState Shift)//当key被释放时,这被调用 { if(Key == 104)_left = false; if(Key == 105)_right = false; if(Key == 100)_forw = false; if(Key == 97)_back = false; } // --------------------------------------- ------------------------------------ 这里是表头文件(除非你在我的VCL应用程序中进行重构,否则你不需要它) // -------------------------------- ------------------------------------------- #ifndef win_mainH #define win_mainH // --------------------------------- ------------------------------------------ #include< Classes.hpp> #include< Controls.hpp> #include< StdCtrls.hpp> #include< Forms.hpp> #include< ComCtrls.hpp> #include< ExtCtrls.hpp> // -------------------------------------------- ------------------------------- class TMain:public TForm { __published :// IDE管理的组件 TTimer * tim_redraw; void __fastcall FormResize(TObject * Sender); void __fastcall FormPaint(TObject * Sender); void __fastcall FormDestroy(TObject * Sender); void __fastcall tim_redrawTimer(TObject * Sender); void __fastcall FormKeyDown(TObject * Sender,WORD& Key,TShiftState Shift); void __fastcall FormKeyUp(TObject * Sender,WORD& Key,TShiftState Shift); private://用户声明 public://用户声明 __fastcall TMain(TComponent * Owner); void draw(); int xs,ys,xs2,ys2,** pyx; Graphics :: TBitmap * bmp; bool _redraw; }; // -------------------------------------------- ------------------------------- extern PACKAGE TMain * Main; // -------------------------------------------- ------------------------------- #endif VCL 应用程序仅包含带单个计时器( 100ms )的单一表单它并没有其他 VCL 组件。 bmp 就是我的 backbuffer 位图,以避免闪烁。键盘事件只是为了启用转向和移动(使用数字键盘 8,9,4,1 )。 这里预览上面的代码: 现在如果你想添加由雾或体积雾完成的白晰度可见性限制器。您只需根据参数 t 插入渲染颜色和白色: t =(z-znear)/(zfar-znear); // t =< 0,1> 其中 z 是相机空间中的像素坐标所以: color = color *(1.0-t)+ White * t; 但是为了在这里应用它,我们需要对 2D 行进行编码光栅化程序或具有每个顶点颜色的 2D 行API(如 OpenGL )。另一种选择是通过混合雾图像来伪造它,该图像在中心线附近完全实心并且在顶部和底部边缘完全透明。 today I'm bringing a subject about Pseudo 3D and perspective.I was checking the video #1 Java Classical 3D Rendering Tutorial : Creating 3D World where he used a method for render pseudo-3D ceil and floor. I tried to find some tutorial or the name of the method that he used but I didn't find. I saw the algorithm but it is not clear to understand. I started to search about perspective graphics (vanishing points, horizon...) but the unique thing that I got was static drawing. I wanna apply an illusion moving putting the camera inside the plan and moving it. Under follow an example about the perspective floor and ceiling that I wanna make.This is just an image, but my first question is: "I realy can make a movement of the camera in this ambient, like rotation and move x, and y axis?". I tried to make 2 vanishing point in a canvas, creating lines for each degree of 15º, and I got a perspective illusion, but i couldn't find a way to make the rotation, or the movement. In that video I saw the pixels creating 2 dimensions using just the colors green and blue, but I wanna make this using lines, to understand how it works.There isn't a place that is teaching step by step how to make the perspective with movements. I didn't find. I checked the videos of 3D game maker in Java and the Markus Person creating the game called by "Prelude of the chambered" using the method of the video, but I didn't find an explanation for this king of rendering. Lets supose I have to create a plan using a grid. how is the logic that I have to apply in the lines to create the movement? I realy wanna understand the logic to make this kind of pseudo-3D, without using frameworks or thing like that. Thanks for help me! I will wait for your answer.I checked something about MODE 7 of SNES. This is a good way for make it I think. I have just to understand how it works, and how to make the rotation. ** Note: I don't what to use raycasting for it. Raycasting I'll use to create the walls. 解决方案 Interesting problem. I did not resist and code it for fun so here some insights... Well there are 2 basic approaches for this. One is raster fake and second is Vector based. I will describe the latter as you can do much more with it.Vector approachThis approach is not faking anything it really is 3D. The rest depends on the rendering you want to use this for... For now I assume you can render 2D lines. All the code chunks are in C++.TransformationsYou need vector math to transform points between world and camera space and back again. In 3D graphics are usually 4x4 homogenuous transform matrices used for this and many programing APIs support them natively. I will base my math on OpenGL matrix layout which determine the order of multiplication used. For more info I strongly recommend to read this:Understanding 4x4 homogenous transform matricesAs I use a lot from it. The linked answers there are also useful especially the 3D graphics pipeline and Full pseudo inverse matrix. The Answer itself is basic knowledge needed for 3D rendering in a nutshell (low level without the need for any lib apart of the rendering stuff).There are also libs for this like GLM so if you want you can use any linear algebra supporting 4x4 matrices and 4D vectors instead of my code.So lets have two 4x4 matrices one (camera) representing our camera coordinate system and second (icamera) which is its inverse. Now if we want to transform between world and screen space we simply do this:P = camera*QQ = icamera*Pwhere P(x,y,z,1) is point in camera coordinate system and Q(x,y,z,1) is the same point in global world coordinate system.PerspectiveThis is done simply by dividing P by its z coordinate. That will scale objects around (0,0) so the more far object is the smaller will be. If we add some screen resolution and axis correction we can use this:void perspective(double *P) // apply perspective transform on P { // perspectve division P[0]*=znear/P[2]; P[1]*=znear/P[2]; // screen coordinate system P[0]=xs2+P[0]; // move (0,0) to screen center P[1]=ys2-P[1]; // axises: x=right, y=up }so point 0,0 is center of screen. The xs2,ys2 is half of resolution of the screen and znear is focal length of the projection. So XY plane rectangle with screen resolution and center at (0,0,znear) will cover the screen exactly.Rendering 3D lineWe can use any primitives for rendering. I chose line as it is very simple and can achieve much. So what we want is to render 3D line using 2D line rendering API (of any kind). I am VCL based so I chose VCL/GDI Canvas which should be very similar to your Canvas.So as input we got two 3D points in global world coordinate system. In order to render it with 2D line we need to convert the 3D position to 2D screen space. That is done by matrix*vector multiplication.From that we obtain two 3D points but in camera coordinate system. Now we need to clip the line by our view area (Frustrum). We can ignore x,y axises as 2D line api usually does that for us anyway. So the only thing left is clip z axis. Frustrum in z axis is defined by znear and zfar. Where zfar is our max visibility distance from camera focal point. So if our line is fully before or after our z-range we ignore it and do not render. If it is inside we render it. If it crosses znear or zfar we cut the outside part off (by linear interpolation of the x,y coordinates).Now we just apply perspective on both points and render 2D line using their x,y coordinates.My code for this looks like this:void draw_line(TCanvas *can,double *pA,double *pB) // draw 3D line { int i; double D[3],A[3],B[3],t; // transform to camera coordinate system matrix_mul_vector(A,icamera,pA); matrix_mul_vector(B,icamera,pB); // sort points so A.z<B.z if (A[2]>B[2]) for (i=0;i<3;i++) { D[i]=A[i]; A[i]=B[i]; B[i]=D[i]; } // D = B-A for (i=0;i<3;i++) D[i]=B[i]-A[i]; // ignore out of Z view lines if (A[2]>zfar) return; if (B[2]<znear) return; // cut line to view if needed if (A[2]<znear) { t=(znear-A[2])/D[2]; A[0]+=D[0]*t; A[1]+=D[1]*t; A[2]=znear; } if (B[2]>zfar) { t=(zfar-B[2])/D[2]; B[0]+=D[0]*t; B[1]+=D[1]*t; B[2]=zfar; } // apply perspective perspective(A); perspective(B); // render can->MoveTo(A[0],A[1]); can->LineTo(B[0],B[1]); }Rendering XZ planeWe can visualize the ground and sky planes using our 3D line as grid of squares. So we just create for loops rendering the x-axis aligned lines and y-axis aligned lines covering some square of some size around some origin position O. The lines should be some step far between each other equal to grid cell size.The origin position O should be near our frustrun center. If it would be constant then we could walk out of the plane edges so it woul dnot cover the whole (half)screen. We can use our camera position and add 0.5*(zfar+znear)*camera_z_axis to it. To maintain the illusion of movement we need to align the O to step size. We can exploit floor,round or integer cast for this.The resulting plane code looks like this:void draw_plane_xz(TCanvas *can,double y,double step) // draw 3D plane { int i; double A[3],B[3],t,size; double U[3]={1.0,0.0,0.0}; // U = X double V[3]={0.0,0.0,1.0}; // V = Z double O[3]={0.0,0.0,0.0}; // Origin // compute origin near view center but align to step i=0; O[i]=floor(camera[12+i]/step)*step; i=2; O[i]=floor(camera[12+i]/step)*step; O[1]=y; // set size so plane safely covers whole view t=xs2*zfar/znear; size=t; // x that will convert to xs2 at zfar t=0.5*(zfar+znear); if (size<t) size=t; // half of depth range t+=step; // + one grid cell beacuse O is off up to 1 grid cell t*=sqrt(2); // diagonal so no matter how are we rotate in Yaw // U lines for (i=0;i<3;i++) { A[i]=O[i]+(size*U[i])-((step+size)*V[i]); B[i]=O[i]-(size*U[i])-((step+size)*V[i]); } for (t=-size;t<=size;t+=step) { for (i=0;i<3;i++) { A[i]+=step*V[i]; B[i]+=step*V[i]; } draw_line(can,A,B); } // V lines for (i=0;i<3;i++) { A[i]=O[i]-((step+size)*U[i])+(size*V[i]); B[i]=O[i]-((step+size)*U[i])-(size*V[i]); } for (t=-size;t<=size;t+=step) { for (i=0;i<3;i++) { A[i]+=step*U[i]; B[i]+=step*U[i]; } draw_line(can,A,B); } matrix_mul_vector(A,icamera,A); }Now if I put all this together in small VCL/GDI/Canvas application I got this://---------------------------------------------------------------------------#include <vcl.h> // you can ignore these lines#include <math.h>#pragma hdrstop#include "win_main.h"//---------------------------------------------------------------------------#pragma package(smart_init)#pragma resource "*.dfm" // up to here.TMain *Main; // this is pointer to my VCL window (you do not need it)//--- Here starts the important stuff: --------------------------------------// perspectivedouble znear= 100.0; // focal length for perspectivedouble zfar = 2100.0; // visibility// viewdouble xs2=0.0; // screen half resolutiondouble ys2=0.0;// cameradouble yaw=0.0; // euler yaw angle [rad]double camera[16]; // camera direct transform matrixdouble icamera[16]; // camera inverse transform matrix// keyboard boolsbool _forw=false,_back=false,_right=false,_left=false;//---------------------------------------------------------------------------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 matrix_mul_vector(double *c,double *a,double *b) // c[3] = a[16]*b[3] { 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 compute_matrices() // recompute camera,icamera after camera position or yaw change { // bound angle while (yaw>2.0*M_PI) yaw-=2.0*M_PI; while (yaw<0.0 ) yaw+=2.0*M_PI; // X = right camera[ 0]= cos(yaw); camera[ 1]= 0.0 ; camera[ 2]= sin(yaw); // Y = up camera[ 4]= 0.0 ; camera[ 5]= 1.0 ; camera[ 6]= 0.0 ; // Z = forward camera[ 8]=-sin(yaw); camera[ 9]= 0.0 ; camera[10]= cos(yaw); // no projection camera[ 3]= 0.0 ; camera[ 7]= 0.0 ; camera[11]= 0.0 ; camera[15]= 1.0 ; // compute the inverse matrix matrix_inv(icamera,camera); }//---------------------------------------------------------------------------void perspective(double *P) // apply perspective transform { // perspectve division P[0]*=znear/P[2]; P[1]*=znear/P[2]; // screen coordinate system P[0]=xs2+P[0]; // move (0,0) to screen center P[1]=ys2-P[1]; // axises: x=right, y=up }//---------------------------------------------------------------------------void draw_line(TCanvas *can,double *pA,double *pB) // draw 3D line { int i; double D[3],A[3],B[3],t; // transform to camera coordinate system matrix_mul_vector(A,icamera,pA); matrix_mul_vector(B,icamera,pB); // sort points so A.z<B.z if (A[2]>B[2]) for (i=0;i<3;i++) { D[i]=A[i]; A[i]=B[i]; B[i]=D[i]; } // D = B-A for (i=0;i<3;i++) D[i]=B[i]-A[i]; // ignore out of Z view lines if (A[2]>zfar) return; if (B[2]<znear) return; // cut line to view if needed if (A[2]<znear) { t=(znear-A[2])/D[2]; A[0]+=D[0]*t; A[1]+=D[1]*t; A[2]=znear; } if (B[2]>zfar) { t=(zfar-B[2])/D[2]; B[0]+=D[0]*t; B[1]+=D[1]*t; B[2]=zfar; } // apply perspective perspective(A); perspective(B); // render can->MoveTo(A[0],A[1]); can->LineTo(B[0],B[1]); }//---------------------------------------------------------------------------void draw_plane_xz(TCanvas *can,double y,double step) // draw 3D plane { int i; double A[3],B[3],t,size; double U[3]={1.0,0.0,0.0}; // U = X double V[3]={0.0,0.0,1.0}; // V = Z double O[3]={0.0,0.0,0.0}; // Origin // compute origin near view center but align to step i=0; O[i]=floor(camera[12+i]/step)*step; i=2; O[i]=floor(camera[12+i]/step)*step; O[1]=y; // set size so plane safely covers whole view t=xs2*zfar/znear; size=t; // x that will convert to xs2 at zfar t=0.5*(zfar+znear); if (size<t) size=t; // half of depth range t+=step; // + one grid cell beacuse O is off up to 1 grid cell t*=sqrt(2); // diagonal so no matter how are we rotate in Yaw // U lines for (i=0;i<3;i++) { A[i]=O[i]+(size*U[i])-((step+size)*V[i]); B[i]=O[i]-(size*U[i])-((step+size)*V[i]); } for (t=-size;t<=size;t+=step) { for (i=0;i<3;i++) { A[i]+=step*V[i]; B[i]+=step*V[i]; } draw_line(can,A,B); } // V lines for (i=0;i<3;i++) { A[i]=O[i]-((step+size)*U[i])+(size*V[i]); B[i]=O[i]-((step+size)*U[i])-(size*V[i]); } for (t=-size;t<=size;t+=step) { for (i=0;i<3;i++) { A[i]+=step*U[i]; B[i]+=step*U[i]; } draw_line(can,A,B); } matrix_mul_vector(A,icamera,A); }//---------------------------------------------------------------------------void TMain::draw() // this is my main rendering routine { // clear buffer bmp->Canvas->Brush->Color=clWhite; bmp->Canvas->FillRect(TRect(0,0,xs,ys)); // init/update variables double step= 50.0; // plane grid size ::xs2=Main->xs2; // update actual screen half resolution ::ys2=Main->ys2; // sky bmp->Canvas->Pen->Color=clBlue; draw_plane_xz(bmp->Canvas,+200.0,step); // terrain bmp->Canvas->Pen->Color=clGreen; draw_plane_xz(bmp->Canvas,-200.0,step); // render backbuffer Main->Canvas->Draw(0,0,bmp); _redraw=false; }//---------------------------------------------------------------------------__fastcall TMain::TMain(TComponent* Owner) : TForm(Owner) // this is initialization { bmp=new Graphics::TBitmap; bmp->HandleType=bmDIB; bmp->PixelFormat=pf32bit; pyx=NULL; _redraw=true; // camera start position camera[12]=0.0; camera[13]=0.0; camera[14]=0.0; compute_matrices(); }//---------------------------------------------------------------------------void __fastcall TMain::FormDestroy(TObject *Sender) // this is exit { if (pyx) delete[] pyx; delete bmp; }//---------------------------------------------------------------------------void __fastcall TMain::FormResize(TObject *Sender) // this is called on resize { xs=ClientWidth; xs2=xs>>1; ys=ClientHeight; ys2=ys>>1; bmp->Width=xs; bmp->Height=ys; if (pyx) delete[] pyx; pyx=new int*[ys]; for (int y=0;y<ys;y++) pyx[y]=(int*) bmp->ScanLine[y]; _redraw=true; }//---------------------------------------------------------------------------void __fastcall TMain::FormPaint(TObject *Sender) // this is called on forced repaint { _redraw=true; }//---------------------------------------------------------------------------void __fastcall TMain::tim_redrawTimer(TObject *Sender) // this is called periodically by my timer { double da=5.0*M_PI/180.0; // turn speed double dl=15.0; // movement speed bool _recompute=false; if (_left ) { _redraw=true; _recompute=true; yaw+=da; } if (_right) { _redraw=true; _recompute=true; yaw-=da; } if (_forw ) { _redraw=true; _recompute=true; for (int i=0;i<3;i++) camera[12+i]+=dl*camera[8+i]; } if (_back ) { _redraw=true; _recompute=true; for (int i=0;i<3;i++) camera[12+i]-=dl*camera[8+i]; } if (_recompute) compute_matrices(); if (_redraw) draw(); }//---------------------------------------------------------------------------void __fastcall TMain::FormKeyDown(TObject *Sender, WORD &Key,TShiftState Shift) // this is called when key is pushed { //Caption=Key; if (Key==104) _left=true; if (Key==105) _right=true; if (Key==100) _forw=true; if (Key== 97) _back=true; }//---------------------------------------------------------------------------void __fastcall TMain::FormKeyUp(TObject *Sender, WORD &Key, TShiftState Shift) // this is called when key is released { if (Key==104) _left=false; if (Key==105) _right=false; if (Key==100) _forw=false; if (Key== 97) _back=false; }//---------------------------------------------------------------------------Here the Form header file (you do not really need it unless you are reconstructin my VCL app)//---------------------------------------------------------------------------#ifndef win_mainH#define win_mainH//---------------------------------------------------------------------------#include <Classes.hpp>#include <Controls.hpp>#include <StdCtrls.hpp>#include <Forms.hpp>#include <ComCtrls.hpp>#include <ExtCtrls.hpp>//---------------------------------------------------------------------------class TMain : public TForm{__published: // IDE-managed Components TTimer *tim_redraw; void __fastcall FormResize(TObject *Sender); void __fastcall FormPaint(TObject *Sender); void __fastcall FormDestroy(TObject *Sender); void __fastcall tim_redrawTimer(TObject *Sender); void __fastcall FormKeyDown(TObject *Sender, WORD &Key, TShiftState Shift); void __fastcall FormKeyUp(TObject *Sender, WORD &Key, TShiftState Shift);private: // User declarationspublic: // User declarations __fastcall TMain(TComponent* Owner); void draw(); int xs,ys,xs2,ys2,**pyx; Graphics::TBitmap *bmp; bool _redraw;};//---------------------------------------------------------------------------extern PACKAGE TMain *Main;//---------------------------------------------------------------------------#endifThe VCL app is just single Form with single timer (100ms) on it and no other VCL components. The bmp is just my backbuffer bitmap to avoid flickering. The keyboard events are just to enable turning and movement (with numpad 8,9,4,1).Here preview of above code:Now if you want to add the whiteout visibility limiter that is done by Fog or Volumetric fog. You simply interpolate between rendered color and White based on parameter t:t = (z-znear)/(zfar-znear); // t = <0,1>where z is pixel coordinate in camera space so: color = color*(1.0-t) + White*t;But to apply this in here we would need to encode the 2D line rasterizer or have 2D line api with per vertex color (like OpenGL). Another option is to fake it by blending in a Fog image which is fully solid near center line and fully transparent on the top and bottom edges. 这篇关于在帆布上的透视视觉的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!
10-23 17:40