2D图形如何运动模拟出3D效果

一.先看看实现效果图

        (左边的2d图片如何运动出右边3d的效果)html

                                       

 

  引言:windows

    对于这个题目,真的很尴尬,不知道取啥,就想了这个题目,涵盖范围很广,很抽象,算是通用知识点吧。想要了解下面几个问题的,能够看看。   函数

     ①2D图形如何运动出3D空间的效果。工具

     ②3D物体如何渲染成2D图形到屏幕上。测试

     ③Unity中模型到世界,世界到相机,相机到屏幕的关系。this

     ④如何经过矩阵进行各类风骚(旋转,缩放,平移,投影等)的变换操做。spa

二.应用知识

    ①向量.net

    ②矩阵,矩阵变换规则3d

    ③透视投影code

三.实现

      问题:图形不断变换,经过简化,图形的本质是由顶点组合而成,所以,能够简化为顶点不断变换。不断意思大概就是每隔一段时间变换,咱们这里

再简化,能够简化为变换一次。即问题能够不断简化如图

      

      对于“顶点变换“,顶点,便是向量,根据矩阵的相关知识,咱们能够了解到,变换矩阵可使向量获得指定的变换。所以就是“顶点经过矩阵变换”。

最后,问题的本质即为顶点和矩阵的之间的交互便可。

1)顶点

      顶点,固然是拥有x,y,z三个份量,根据矩阵变换规则,咱们想要使用矩阵对向量进行变换,须要多一个维度,且第四个份量为1。至于具体缘由这里不加详述,

详情可见:https://blog.csdn.net/zl_gsyy/article/details/73278742。

      即,须要设定一个拥有四维向量Vector4.cs        

 class Vector4
    {
        public double a, b, c, d;

        public Vector4()
        {
            
        }

        public Vector4(double a,double b,double c, double d)
        {
            this.a = a;
            this.b = b;
            this.c = c;
            this.d = d;
        }

        public Vector4(Vector4 v)
        {
            this.a = v.a;
            this.b = v.b;
            this.c = v.c;
            this.d = v.d;
        }    
    }

2)矩阵

      由于咱们须要变换的是4维向量(顶点,能够看作1X4的矩阵),因此再根据矩阵变换规则,咱们须要设定矩阵维4x4的。

        定义 Matrix4x4.cs     

 class Matrix4x4
    {
        double[,] ts;

        public Matrix4x4()
        {
            ts = new double[4, 4];
        }

        public Matrix4x4(Matrix4x4 m)
        {
            for(int i=0;i<4;i++)
            {
                for(int j=0;j<4;j++)
                {
                    ts[i, j] = m[i, j];
                }
            }
        }

        public double this[int x,int y]
        {
            get{
                return ts[x, y];
            }

            set
            {
                ts[x, y] = value;
            }
        }


        /// <summary>
        /// 用这个矩阵变换一个四维向量,返回另一个向量
        /// </summary>
        /// <param name="v"></param>
        /// <returns></returns>
        public Vector4 Mul(Vector4 v)
        {
            Vector4 vNew = new Vector4();
            vNew.a = ts[0, 0] * v.a + ts[1, 0] * v.b + ts[2, 0] * v.c + ts[3, 0] * v.d;
            vNew.b = ts[0, 1] * v.a + ts[1, 1] * v.b + ts[2, 1] * v.c + ts[3, 1] * v.d;
            vNew.c = ts[0, 2] * v.a + ts[1, 2] * v.b + ts[2, 2] * v.c + ts[3, 2] * v.d;
            vNew.d = ts[0, 3] * v.a + ts[1, 3] * v.b + ts[2, 3] * v.c + ts[3, 3] * v.d;
            return vNew;
        }
    }

         根据矩阵变换规则,若是须要变换屡次,屡次变换能够经过相乘合并为一个矩阵,好比某顶点须要先平移,再缩放,能够平移*缩放=合并矩阵。

因此须要在Matrix4x4.cs 中 增长一个矩阵*矩阵获得另一个矩阵 方法。        

  public Matrix4x4 Mul(Matrix4x4 m)
    {
        Matrix4x4 newM = new Matrix4x4();
        for (int i = 0; i < 4; i++)
        {
            for (int j = 0; j < 4; j++)
            {
                for (int k = 0; k < 4; k++)
                {
                    newM[i, j] += this[i, k] * m[k, j];
                }
            }
        }
        return newM;
    }

3)模拟变换过程

   由于咱们这边直接使用点来变换显示,不够直观,可使用由3个顶点组成的三角形,来模拟测试效果。因此,这里再定义一个三角形类Triangles.cs.

 直接使用矩阵对该Triangles进行变换,便是对三个顶点进行变换。 

 class Triangles
    {
        public Vector4 A, B, C;
        private Vector4 a, b, c; //临时变量使用

        public Triangles(Vector4 a,Vector4 b,Vector4 c)
        {
            A =this.a= new Vector4(a);
            B =this.b= new Vector4(b);
            C =this.c= new Vector4(c);
        }
        
        /// <summary>
        /// 变换事后原始顶点保留(变换针对原始数据)
        /// </summary>
        /// <param name="m"></param>
        public void Transform(Matrix4x4 m)
        {
            this.a = m.Mul(this.A);
            this.b= m.Mul(this.B);
            this.c = m.Mul(this.C);
        }

        #region 系统内置方法(这边了解一下就行)
        /// <summary>
        /// 应用windows窗体应用程序内部方法,根据三个顶点画出三角形
        /// </summary>
        /// <param name="e"></param>
        public void Draw(System.Drawing.Graphics e)
        {
            e.TranslateTransform(300, 300);//否则会太靠左上角。Windowform的内部功能
            e.DrawLines(new Pen(Color.Red, 2f), Get2DPointFArr());
        }

        PointF[] Get2DPointFArr()
        {
            PointF[] points = new PointF[4];
            points[0] = Get2DPointF(this.a);
            points[1] = Get2DPointF(this.b);
            points[2] = Get2DPointF(this.c);
            points[3] = Get2DPointF(this.a);
            //咱们这边须要画第四个与第一个顶点同样,系统需求这样作
            return points;
        }


        PointF Get2DPointF(Vector4 v)
        {
            PointF point = new PointF();
            point.X = (float)(v.a / v.d);
            point.Y = (float)(v.b / v.d);
            return point;
        }
        #endregion
    }  

   经过以上,咱们能够实现经过变换矩阵(平移,缩放,旋转)来实现简单变换了。固然经过某种变换的时候首先须要明白对应的哪一个矩阵。参考https://www.cnblogs.com/u3ddjw/p/10282186.html

======================================================正式进入正题=====================================================================      

4)如何实现3D空间变换效果

         ①首先,咱们先声明一个三角形,而且给出其三个顶点的坐标。

         Triangles triangles;
            //注意这边屏幕左上角为(0,0)坐标原点
            Vector4 v1 = new Vector4(0, -0.5, 0, 1);
            Vector4 v2 = new Vector4(0.5f, 0.5, 0, 1);
            Vector4 v3 = new Vector4(-0.5f, 0.5f, 0, 1);
            triangles = new Triangles(v1, v2, v3);

    由于咱们这里给出坐标位置是比例坐标(模型坐标),这种大小固然很小,与屏幕(1320,1080)差距很大,因此咱们须要把它放大道合适的大小。假设就放大250倍恰好是咱们须要的大小。就须要缩放矩阵了,经过https://www.cnblogs.com/u3ddjw/p/10282186.html 查找到缩放矩阵 

故,我声明scale =250,     

       //模型到世界
            m_scale = new Matrix4x4();
            m_scale[0, 0] = scale; //scale =250,这个数值能够任意调节,你能够试试改变这个大小,增强获得缩放的效果的记忆。
            m_scale[1, 1] = scale;
            m_scale[2, 2] = scale;
            m_scale[3, 3] = 1;
            triangles.Transform(m_scale);

最终一张正常的图出现:

          ②为了模拟出直观的3D效果,再来实现把这个三角形进行旋转

          参考上面方法找到旋转矩阵表达式,为了观察直观,咱们每隔一小段时间增长2个角度并显示出来。咱们增长个Timer(就是能够实现

间隔时间调用方法的工具类,系统通常都会自带,好比unity中update)      

     float a = 2;

        /// <summary>
        /// 每隔0.02s调用一次
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void timer1_Tick(object sender, EventArgs e)
        {
            a++;
            double angle = a / 360 * Math.PI;
            m_rotation[0, 0] = Math.Cos(angle);
            m_rotation[0, 2] = Math.Sin(angle);
            m_rotation[1, 1] = 1;
            m_rotation[2, 0] = -1 * Math.Sin(angle);
            m_rotation[2, 2] = Math.Cos(angle);
            m_rotation[3, 3] = 1;
            //由于矩阵变换规则得知屡次变换能够先将变换矩阵相乘 缩放矩阵*旋转矩阵 
            var newTriangle = m_scale.Mul(m_rotation);

            triangles.Transform(newTriangle);
            this.Invalidate(); //重绘,必须写上
        }

获得结果:

       

     上图老是变宽变宰,下面底部线条没有感受在3D空间中变换。这不是真正意义3D变换, 仅仅是模型到世界。

      ③透视投影变换

      

       假设右边的圈就是咱们须要拍摄的摄像机空间的点,还须要进行透视投影变换到屏幕。故,咱们还须要世界到摄像机,

要让他从世界到摄像机, 因此咱们还须要架设一个摄像机去拍摄他,假设这个模型在z轴为0的位置,摄像机假设到z为负的位置,对于模型而言

在摄像机空间上实际上作了位置平移。因此咱们须要一个能够转换为摄像机空间的矩阵(平移矩阵)。

    同上找到平移矩阵,并初始化一个平移矩阵,平移多少位置?仍是先假设250(这边能够自行修改数据调节看看效果)。

故在初始化函数中,   

        //View  世界到相机
            m_view = new Matrix4x4();
            m_view[0, 0] = 1;
            m_view[1, 1] = 1;
            m_view[2, 2] = 1;
            m_view[3, 2] = 1 * scale;
            m_view[3, 3] = 1;

  目前效果上仍是没有变换的,虽然平移了摄像机,可是咱们没有真正的作投影变换。关于投影矩阵,仍是参考https://www.cnblogs.com/u3ddjw/p/10282186.html,

找到投影矩阵。继续在初始化函数中并实现       

         //Projection 相机到屏幕
            m_projection = new Matrix4x4();
            m_projection[0, 0] = 1;
            m_projection[1, 1] = 1;
            m_projection[2, 2] = 1;
            m_projection[2, 3] = 1.0f / scale;

  在timer.cs 中的 timer1_Tick 每隔一段时间函数中补充为

/// <summary>
        /// 每隔0.02s调用一次
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void timer1_Tick(object sender, EventArgs e)
        {
            a+=3;
            double angle = a / 360 * Math.PI;
            m_rotation[0, 0] = Math.Cos(angle);
            m_rotation[0, 2] = Math.Sin(angle);
            m_rotation[1, 1] = 1;
            m_rotation[2, 0] = -1 * Math.Sin(angle);
            m_rotation[2, 2] = Math.Cos(angle);
            m_rotation[3, 3] = 1;
            //由于矩阵变换规则得知屡次变换能够先将变换矩阵相乘 缩放矩阵*旋转矩阵 
            var newTriangle = m_scale.Mul(m_rotation);
            newTriangle = newTriangle.Mul(m_view);
            newTriangle = newTriangle.Mul(m_projection);
            triangles.Transform(newTriangle);
            this.Invalidate(); //重绘,必须写上
        }

这样,最终咱们看到了第一张图的效果图,彻底的3D空间的效果。如今是具备透视变换的三角形在屏幕上不停的旋转,绕着y轴。  

四.拓展

    ①能够尝试改变各个变换矩阵中的值,好比平移矩阵(世界到相机)的值,来看看效果。

  private void trackBar1_Scroll(object sender, EventArgs e)
        {
            m_view[3, 2] = (sender as TrackBar).Value;
        }

        private void trackBar2_Scroll(object sender, EventArgs e)
        {
            double value = (sender as TrackBar).Value;
            m_projection[2, 3] = 1.0f / value;
        }

 

            ②不用三角形,本身尝试五角星,四边形之类的变换,效果各类拉风,可是本质仍是都是顶点经过矩阵的变换