65.9K
CodeProject 正在变化。 阅读更多。
Home

实现基本相机模型(针孔)II

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.90/5 (15投票s)

2010 年 2 月 9 日

GPL3

3分钟阅读

viewsIcon

40695

downloadIcon

636

扩展相机类并创建基本动画。

查看使用本文代码创建的动画

引言

在我之前在 CodeProject 上的文章“实现一个基础相机模型(针孔)”中,我们创建了一个可以显示 R3 对象的相机类。

为了测试相机,我们创建了一个静态立方体。 现在,我们的想法是创建新的对象,例如球体和网格,对它们应用旋转和平移,并且还在帧之间对相机位置应用移动,从而为我们提供一个基本的动画。

背景

必须您具有高等数学知识。 另外,请阅读我在 CodeProject 上的关于光线追踪的先前文章。 特别是因为,这里我们只是在扩展先前看到的相机类

步骤 1:创建对象

为了测试相机,我们需要创建一些图形对象。 这不是本文的一部分,但是我在这里粘贴代码只是为了参考。 我们的重点是相机类及其定位。 诸如旋转对象(球体、椭圆体等)的创建可以是未来文章的主题。

如前所述,我们每个对象的主要元素是由 P(x,y,z) 表示的 R3 点 P。 由此,我们可以在本文中创建这些对象

  • 立方体
  • 球体
  • Grid

立方体

立方体定义来自我们之前的文章

public class Cube
{
    public Geometry.Point p1;
    public Geometry.Point p2;
    public Geometry.Point p3;
    public Geometry.Point p4;
    public Geometry.Point p5;
    public Geometry.Point p6;
    public Geometry.Point p7;
    public Geometry.Point p8;
    public Cube()
    {
        p1 = new Geometry.Point(-1, -1, 1);
        p2 = new Geometry.Point(-1, 1, 1);
        p3 = new Geometry.Point(1, 1, 1);
        p4 = new Geometry.Point(1, -1, 1);
        p5 = new Geometry.Point(-1, -1, -1);
        p6 = new Geometry.Point(-1, 1, -1);
        p7 = new Geometry.Point(1, 1, -1);
        p8 = new Geometry.Point(1, -1, -1);
    }
    public void Scale(double scale)
    {
        p1 = new Geometry.Point(p1.x * scale, p1.y * scale, p1.z * scale);
        p2 = new Geometry.Point(p2.x * scale, p2.y * scale, p2.z * scale);
        p3 = new Geometry.Point(p3.x * scale, p3.y * scale, p3.z * scale);
        p4 = new Geometry.Point(p4.x * scale, p4.y * scale, p4.z * scale);
        p5 = new Geometry.Point(p5.x * scale, p5.y * scale, p5.z * scale);
        p6 = new Geometry.Point(p6.x * scale, p6.y * scale, p6.z * scale);
        p7 = new Geometry.Point(p7.x * scale, p7.y * scale, p7.z * scale);
        p8 = new Geometry.Point(p8.x * scale, p8.y * scale, p8.z * scale);
    }
    public void Translate(Vector translate)
    {
        p1 = new Geometry.Point(p1.x + translate.x, p1.y + 
                                translate.y, p1.z + translate.z);
        p2 = new Geometry.Point(p2.x + translate.x, p2.y + 
                                translate.y, p2.z + translate.z);
        p3 = new Geometry.Point(p3.x + translate.x, p3.y + 
                                translate.y, p3.z + translate.z);
        p4 = new Geometry.Point(p4.x + translate.x, p4.y + 
                                translate.y, p4.z + translate.z);
        p5 = new Geometry.Point(p5.x + translate.x, p5.y + 
                                translate.y, p5.z + translate.z);
        p6 = new Geometry.Point(p6.x + translate.x, p6.y + 
                                translate.y, p6.z + translate.z);
        p7 = new Geometry.Point(p7.x + translate.x, p7.y + 
                                translate.y, p7.z + translate.z);
        p8 = new Geometry.Point(p8.x + translate.x, p8.y + 
                                translate.y, p8.z + translate.z);
    }
    internal void Draw(Camera oCamera1, System.Drawing.Graphics g, 
                       Rectangle rect, double fMax, Color color)
    {
        Geometry.Point point = new Geometry.Point(p1.x, p1.y, p1.z);
        Geometry.Point pointAux1 = oCamera1.GetProjectedMappedPoint(point);
        point = new Geometry.Point(p2.x, p2.y, p2.z);
        Geometry.Point pointAux2 = oCamera1.GetProjectedMappedPoint(point);
        point = new Geometry.Point(p3.x, p3.y, p3.z);
        Geometry.Point pointAux3 = oCamera1.GetProjectedMappedPoint(point);
        point = new Geometry.Point(p4.x, p4.y, p4.z);
        Geometry.Point pointAux4 = oCamera1.GetProjectedMappedPoint(point);
        point = new Geometry.Point(p5.x, p5.y, p5.z);
        Geometry.Point pointAux5 = oCamera1.GetProjectedMappedPoint(point);
        point = new Geometry.Point(p6.x, p6.y, p6.z);
        Geometry.Point pointAux6 = oCamera1.GetProjectedMappedPoint(point);
        point = new Geometry.Point(p7.x, p7.y, p7.z);
        Geometry.Point pointAux7 = oCamera1.GetProjectedMappedPoint(point);
        point = new Geometry.Point(p8.x, p8.y, p8.z);
        Geometry.Point pointAux8 = oCamera1.GetProjectedMappedPoint(point);
        if (pointAux1 != null && pointAux2 != null && 
            pointAux3 != null && pointAux4 != null &&
            pointAux5 != null && pointAux6 != null && 
            pointAux7 != null && pointAux8 != null)
        {
            double x1 = rtPoint.GetCoord(-fMax, fMax, rect.Left, rect.Right, pointAux1.x);
            double y1 = rtPoint.GetCoord(-fMax, fMax, rect.Left, rect.Right, pointAux1.y);
            double x2 = rtPoint.GetCoord(-fMax, fMax, rect.Left, rect.Right, pointAux2.x);
            double y2 = rtPoint.GetCoord(-fMax, fMax, rect.Left, rect.Right, pointAux2.y);
            double x3 = rtPoint.GetCoord(-fMax, fMax, rect.Left, rect.Right, pointAux3.x);
            double y3 = rtPoint.GetCoord(-fMax, fMax, rect.Left, rect.Right, pointAux3.y);
            double x4 = rtPoint.GetCoord(-fMax, fMax, rect.Left, rect.Right, pointAux4.x);
            double y4 = rtPoint.GetCoord(-fMax, fMax, rect.Left, rect.Right, pointAux4.y);
            double x5 = rtPoint.GetCoord(-fMax, fMax, rect.Left, rect.Right, pointAux5.x);
            double y5 = rtPoint.GetCoord(-fMax, fMax, rect.Left, rect.Right, pointAux5.y);
            double x6 = rtPoint.GetCoord(-fMax, fMax, rect.Left, rect.Right, pointAux6.x);
            double y6 = rtPoint.GetCoord(-fMax, fMax, rect.Left, rect.Right, pointAux6.y);
            double x7 = rtPoint.GetCoord(-fMax, fMax, rect.Left, rect.Right, pointAux7.x);
            double y7 = rtPoint.GetCoord(-fMax, fMax, rect.Left, rect.Right, pointAux7.y);
            double x8 = rtPoint.GetCoord(-fMax, fMax, rect.Left, rect.Right, pointAux8.x);
            double y8 = rtPoint.GetCoord(-fMax, fMax, rect.Left, rect.Right, pointAux8.y);
            Pen pen = new Pen(color);
            g.DrawLine(pen, (int)x1, (int)y1, (int)x2, (int)y2);
            g.DrawLine(pen, (int)x2, (int)y2, (int)x3, (int)y3);
            g.DrawLine(pen, (int)x3, (int)y3, (int)x4, (int)y4);
            g.DrawLine(pen, (int)x4, (int)y4, (int)x1, (int)y1);
            g.DrawLine(pen, (int)x5, (int)y5, (int)x6, (int)y6);
            g.DrawLine(pen, (int)x6, (int)y6, (int)x7, (int)y7);
            g.DrawLine(pen, (int)x7, (int)y7, (int)x8, (int)y8);
            g.DrawLine(pen, (int)x8, (int)y8, (int)x5, (int)y5);
            g.DrawLine(pen, (int)x1, (int)y1, (int)x5, (int)y5);
            g.DrawLine(pen, (int)x2, (int)y2, (int)x6, (int)y6);
            g.DrawLine(pen, (int)x3, (int)y3, (int)x7, (int)y7);
            g.DrawLine(pen, (int)x4, (int)y4, (int)x8, (int)y8);
            pen.Dispose();
        }
    }
    internal void Rotate(double ax, double ay, double az)
    {
        p1 = rtPoint.RotX(ax, p1);
        p2 = rtPoint.RotX(ax, p2);
        p3 = rtPoint.RotX(ax, p3);
        p4 = rtPoint.RotX(ax, p4);
        p5 = rtPoint.RotX(ax, p5);
        p6 = rtPoint.RotX(ax, p6);
        p7 = rtPoint.RotX(ax, p7);
        p8 = rtPoint.RotX(ax, p8);
        p1 = rtPoint.RotY(ay, p1);
        p2 = rtPoint.RotY(ay, p2);
        p3 = rtPoint.RotY(ay, p3);
        p4 = rtPoint.RotY(ay, p4);
        p5 = rtPoint.RotY(ay, p5);
        p6 = rtPoint.RotY(ay, p6);
        p7 = rtPoint.RotY(ay, p7);
        p8 = rtPoint.RotY(ay, p8);
        p1 = rtPoint.RotZ(az, p1);
        p2 = rtPoint.RotZ(az, p2);
        p3 = rtPoint.RotZ(az, p3);
        p4 = rtPoint.RotZ(az, p4);
        p5 = rtPoint.RotZ(az, p5);
        p6 = rtPoint.RotZ(az, p6);
        p7 = rtPoint.RotZ(az, p7);
        p8 = rtPoint.RotZ(az, p8);
    }
}

球体

此处创建的球体基于球坐标,分别从 0 到 PI 和 0 到 2PI 运行纬度和经度。 (注意* 墨卡托投影为 -PI/2 到 PI/2 和 -PI 到 PI)。

我们如何做到这一点? 我们定义一个步长,以从纬度和经度的切片数量中从 0 到 PI 和 0 到 2PI 进行积分,我们希望在我们的球体模型中拥有这些切片。 例如,如果我们想要 10 个切片,则步长为 2*PI / 10。 之后,只需将球坐标应用于每个积分步的 theta 和 phi 即可...

public class Sphere
{
    public Geometry.Point[,] points;
    int m_Lat;
    int m_Lon;

    public Sphere(int m, int n)
    {
        m_Lat = m;
        m_Lon = n;
        double di = (Math.PI * 2.0) / (double)m;
        double dt = (Math.PI * 2.0) / (double)n;
        points = new Geometry.Point[m, n];
        double ai = 0;

        for (int i = 0; i < m; i++)
        {
            double at = 0;
            for (int j = 0; j < n; j++)
            {
                double x = Math.Sin(ai) * Math.Cos(at);
                double y = Math.Sin(ai) * Math.Sin(at);
                double z = Math.Cos(ai);
                points[i, j] = new Point(x, y, z);
                at += dt;
            }
            ai += di;
        }
    }

    public void Scale(double scale)
    {
        for (int i = 0; i < m_Lat; i++)
        {
            for (int j = 0; j < m_Lon; j++)
            {
                points[i, j] = new Point(points[i, j].x * scale, 
                  points[i, j].y * scale, points[i, j].z * scale);
            }
        }
    }

    public void Translate(Vector translate)
    {
        for (int i = 0; i < m_Lat; i++)
        {
            for (int j = 0; j < m_Lon; j++)
            {
                points[i, j] = new Point(points[i, j].x + translate.x, 
                  points[i, j].y + translate.y, points[i, j].z + translate.z);
            }
        }
    }

    internal void Draw(Camera oCamera1, System.Drawing.Graphics g, 
                       Rectangle rect, double fMax, Color color)
    {
        Pen pen = new Pen(color);
        for (int i = 0; i < m_Lat; i++)
        {
            for (int j = 0; j < m_Lon; j++)
            {
                int iplus1 = i + 1;
                if (iplus1 == m_Lat)
                    iplus1 = 0;
                int jplus1 = j + 1;
                if (jplus1 == m_Lon)
                    jplus1 = 0;

                Point a = new Point(points[i, j]);
                Point b = new Point(points[iplus1, j]);
                Point c = new Point(points[iplus1, jplus1]);
                Point d = new Point(points[i, jplus1]);
                Geometry.Point pointAux1 = oCamera1.GetProjectedMappedPoint(a);
                Geometry.Point pointAux2 = oCamera1.GetProjectedMappedPoint(b);
                Geometry.Point pointAux3 = oCamera1.GetProjectedMappedPoint(c);
                Geometry.Point pointAux4 = oCamera1.GetProjectedMappedPoint(d);

                if (pointAux1 != null && pointAux2 != null && 
                    pointAux3 != null && pointAux4 != null)

                {
                    double x1 = rtPoint.GetCoord(-fMax, fMax, rect.Left, 
                                                 rect.Right, pointAux1.x);
                    double y1 = rtPoint.GetCoord(-fMax, fMax, rect.Left, 
                                                 rect.Right, pointAux1.y);
                    double x2 = rtPoint.GetCoord(-fMax, fMax, rect.Left, 
                                                 rect.Right, pointAux2.x);
                    double y2 = rtPoint.GetCoord(-fMax, fMax, rect.Left, 
                                                 rect.Right, pointAux2.y);
                    double x3 = rtPoint.GetCoord(-fMax, fMax, rect.Left, 
                                                 rect.Right, pointAux3.x);
                    double y3 = rtPoint.GetCoord(-fMax, fMax, rect.Left, 
                                                 rect.Right, pointAux3.y);
                    double x4 = rtPoint.GetCoord(-fMax, fMax, rect.Left, 
                                                 rect.Right, pointAux4.x);
                    double y4 = rtPoint.GetCoord(-fMax, fMax, rect.Left, 
                                                 rect.Right, pointAux4.y);

                    g.DrawLine(pen, (int)x1, (int)y1, (int)x2, (int)y2);
                    g.DrawLine(pen, (int)x2, (int)y2, (int)x3, (int)y3);
                    g.DrawLine(pen, (int)x3, (int)y3, (int)x4, (int)y4);
                    g.DrawLine(pen, (int)x4, (int)y4, (int)x1, (int)y1);
                }
            }
        }
        pen.Dispose();
    }
}

Grid

网格对象是通过在 z=o 平面上从 -1 到 1 运行矩形中的点来创建的,x 和 y 都是如此。

我们如何做到这一点? 首先,我们定义一个类似于网格的 R3 中的域,该域由像矩阵网格一样排列的点 (x,y,z) 组成。 为了检索点,我们设置初始 xo = -1,并通过添加到初始点的因子运行 n 次,或者更好地 xn = xn-1 + k。 我们对 yo 执行相同的操作。

因此,我们获得了网格模型中的所有 x、y、z 坐标...

public class Grid
{
    public Geometry.Point[,] points;
    int m_Lat;
    int m_Lon;

    public Grid(int m, int n)
    {
        m_Lat = m;
        m_Lon = n;

        double di = 2.0 / (double)m;
        double dt = 2.0 / (double)n;
        points = new Geometry.Point[m, n];

        double ai = -1;
        for (int i = 0; i < m; i++)
        {
            double at = -1;
            for (int j = 0; j < n; j++)
            {
                double x = ai;
                double y = at;
                double z = 0;
                points[i, j] = new Point(x, y, z);
                at += dt;
            }
            ai += di;
        }
    }

    public void Scale(double scale)
    {
        for (int i = 0; i < m_Lat; i++)
        {
            for (int j = 0; j < m_Lon; j++)
            {
                points[i, j] = new Point(points[i, j].x * scale, 
                  points[i, j].y * scale, points[i, j].z * scale);
            }
        }
    }

    public void Translate(Vector translate)
    {
        for (int i = 0; i < m_Lat; i++)
        {
            for (int j = 0; j < m_Lon; j++)
            {
                points[i, j] = new Point(points[i, j].x + translate.x, 
                  points[i, j].y + translate.y, points[i, j].z + translate.z);
            }
        }
    }

    internal void Draw(Camera oCamera1, System.Drawing.Graphics g, 
                       Rectangle rect, double fMax, Color color)
    {
        Pen pen = new Pen(color);
        for (int i = 0; i < m_Lat; i++)
        {
            for (int j = 0; j < m_Lon; j++)
            {
                int iplus1 = i + 1;
                if (iplus1 == m_Lat)
                    iplus1 = 0;
                int jplus1 = j + 1;
                if (jplus1 == m_Lon)
                    jplus1 = 0;

                Point a = new Point(points[i, j]);
                Point b = new Point(points[iplus1, j]);
                Point c = new Point(points[iplus1, jplus1]);
                Point d = new Point(points[i, jplus1]);
                Geometry.Point pointAux1 = oCamera1.GetProjectedMappedPoint(a);
                Geometry.Point pointAux2 = oCamera1.GetProjectedMappedPoint(b);
                Geometry.Point pointAux3 = oCamera1.GetProjectedMappedPoint(c);
                Geometry.Point pointAux4 = oCamera1.GetProjectedMappedPoint(d);

                if (pointAux1 != null && pointAux2 != null && 
                    pointAux3 != null && pointAux4 != null)
                {
                    double x1 = rtPoint.GetCoord(-fMax, fMax, rect.Left, 
                                                 rect.Right, pointAux1.x);
                    double y1 = rtPoint.GetCoord(-fMax, fMax, rect.Left, 
                                                 rect.Right, pointAux1.y);
                    double x2 = rtPoint.GetCoord(-fMax, fMax, rect.Left, 
                                                 rect.Right, pointAux2.x);
                    double y2 = rtPoint.GetCoord(-fMax, fMax, rect.Left, 
                                                 rect.Right, pointAux2.y);
                    double x3 = rtPoint.GetCoord(-fMax, fMax, rect.Left, 
                                                 rect.Right, pointAux3.x);
                    double y3 = rtPoint.GetCoord(-fMax, fMax, rect.Left, 
                                                 rect.Right, pointAux3.y);
                    double x4 = rtPoint.GetCoord(-fMax, fMax, rect.Left, 
                                                 rect.Right, pointAux4.x);
                    double y4 = rtPoint.GetCoord(-fMax, fMax, rect.Left, 
                                                 rect.Right, pointAux4.y);

                    g.DrawLine(pen, (int)x1, (int)y1, (int)x2, (int)y2);
                    g.DrawLine(pen, (int)x2, (int)y2, (int)x3, (int)y3);
                    g.DrawLine(pen, (int)x3, (int)y3, (int)x4, (int)y4);
                    g.DrawLine(pen, (int)x4, (int)y4, (int)x1, (int)y1);
                }
            }
        }

        pen.Dispose();
    }

    internal void Rotate(double ax, double ay, double az)
    {
        for (int i = 0; i < m_Lat; i++)
        {
            for (int j = 0; j < m_Lon; j++)
            {
                points[i, j] = rtPoint.RotX(ax, points[i, j]);
                points[i, j] = rtPoint.RotY(ay, points[i, j]);
                points[i, j] = rtPoint.RotZ(az, points[i, j]);
            }
        }
    }
}

整合所有内容

现在我们可以创建一些不同的对象,并从中应用旋转并移动相机。 通过扩展非常基本的测试项目,我添加了一个计时器,该计时器会更新所有对象并渲染场景。

以下是最终代码

private void timer1_Tick(object sender, EventArgs e)
{
    int m_iScreenPixels = 400;     // the size of our screen
    double m_fFocalLenght = 1.0;   // the camera focal distance
    // camera target position...
    Geometry.Point m_oTarget = new Geometry.Point(5, 5, 5);
    double m_fVirtualSize = 1;     // R3 Domain reference
    
    // creates a bitmap
    Bitmap newBitmap = new Bitmap(m_iScreenPixels, 
                       m_iScreenPixels,
                       PixelFormat.Format32bppArgb);
    // creates a graphics
    Graphics g = Graphics.FromImage(newBitmap);

    // creates a camera
    Camera oCamera1 = new Camera();
    
    // creates an image rectangle reference
    Rectangle rect = new Rectangle(0, 0, m_iScreenPixels, 
                                   m_iScreenPixels);

    // Creates auxiliar parameter to the R3 reference size
    double fMax = m_fVirtualSize;

    
    // cleans the graphics background to black 
    g.Clear(Color.Black);

    // Defines the camera position
    Geometry.Point eye = new Geometry.Point(m_oCameraPos);

    // translate camera position (note this updates each time) 
    m_oCameraPos = rtPoint.Translate(eye, new Vector(0.001, 0, -0.35));

    // setup the camera, initializing all parameters
    oCamera1.Setup(m_fFocalLenght, new Geometry.Point(eye.x, 
                   eye.y, eye.z), new Vector(eye, m_oTarget));

    ...

    // creates a sphere with 10 slices latitudes and 10 slices longitudes
    Sphere s1 = new Sphere(10, 10);
    s1.Scale(2); // scales the sphere by 2
    s1.Translate(new Vector(5, 5, 5));  // translates the sphere by(5,5,5)
    s1.Draw(oCamera1, g, rect, fMax, Color.Yellow); // draws the sphere

    // creates a grid with 50 points in each direction
    Grid g1 = new Grid(50, 50);
    g1.Scale(50);  // scales the grid by 50
    g1.Rotate(3.14, 0, 0);  // rotates the grid around z axis by ~PI
    g1.Draw(oCamera1, g, rect, fMax, Color.White);  // draws the grid

    // creates a cube centered at 10,3,0
    Cube c1 = new Cube();
    c1.Translate(new Vector(10, 3, 0));
    c1.Rotate(angle, 0, 0); // rotates the cube around x axis
    c1.Draw(oCamera1, g, rect, fMax, Color.Red); // draws the cube 

    // creates a cube centered at -3,10,0
    Cube c2 = new Cube();
    c2.Translate(new Vector(-3, 10, 0));
    c2.Rotate(0, angle, 0);  // rotates the cube around y axis
    c2.Draw(oCamera1, g, rect, fMax, Color.Green); // draws the cube 

    // creates a cube centered at 0,-3,10
    Cube c3 = new Cube();
    c3.Translate(new Vector(0, -3, 10));
    c3.Rotate(0, 0, angle); // rotates the cube around z axis 
    c3.Draw(oCamera1, g, rect, fMax, Color.Blue); // draws the cube  

    // saves the bitmap
    newBitmap.Save("c:\\temp\\bitmap1.png");
    // reload the image and displays
    pictureBox1.Load("c:\\temp\\bitmap1.png");

    angle += 0.2; // update rotation angle
    angle2 += 0.1; // update rotation angle2
}

Using the Code

所有需要的代码都在文章顶部的 zip 文件中的一个项目中; 只需下载并编译即可。

关注点

有必要检测投影点是在相机前面还是在相机后面。 获取计算的一种方法是使用视角方向与公式中的相机(位置-给定点)向量之间的点积:dot = |v1|*|v2|*Cos(t)。

由于 Cos 返回给我们 -PI/2 到 PI/2,我们可以找出该点是否进入视锥。 使用的视锥角为 PI/3,因此计算是,如果 abs(t) <= PI/3,则给定点可见。

结论

一旦我所有以前的文章都与光线追踪相关联,我将尝试将相机类引入到我未来文章中的光线追踪模型中,并尝试从中获得一些动画。

© . All rights reserved.