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






4.90/5 (15投票s)
扩展相机类并创建基本动画。
查看使用本文代码创建的动画
引言
在我之前在 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,则给定点可见。
结论
一旦我所有以前的文章都与光线追踪相关联,我将尝试将相机类引入到我未来文章中的光线追踪模型中,并尝试从中获得一些动画。