您的位置:首页 > 产品设计 > UI/UE

使用Silverlight构建一个简单的3D引擎 【译改 Building a Simple 3D Engine with Silverlight】

2010-08-14 18:05 537 查看
原文 http://www.simple-talk.com/dotnet/.net-framework/building-a-simple-3d-engine-with-silverlight/

这篇文章将会展示如何利用silverlight和C#构建一个简单的3d引擎,这个引擎允许您:

- 画一组由线组成的3维空间

- 环绕中心点旋转这些线组成的3维空间

- 添加或者移除向量线

http://www.bodurov.com/NearestStars/

http://www.comiscience.com/Silverlight/Index

关于引擎完整应用的链接

为了更好的展示是如何实现这种技术,作者开发了一个示例程序,叫向量观察器Vector Visualizer(点击此处获得源码、示例和更多信息)。作者的意图是向大家展示如何利用矩阵数学来制作一个3D投影和旋转地效果,并且向大家证明这些实现并不难。

然后,大家可能会问自己为什么需要这么一个工具,这是不是就是发明一个轮子一样? 毕竟已经存在了很多功能丰富的3d引擎。不说什么代码重用之类的东西,至少所有的东西都需要有第一次的发明创造。(这里省略了没有的废话,大概就是这意思)

更多的考虑,一些非常丰富的3d引擎可能对于个人的需要略显冗余。当你只是需要简单3d效果时,选择一个轻便的但适合自己的3d引擎会更好,并且相关的3d用户体验可能会更加出色。即使您不想使用自己的引擎,而是使用别人的库文件,这篇文章对您理解基础的3d只是也是很有帮助的,这样也会方便您更好的使用别人的库。

向量和矩阵

3d引擎的基本目的就是使一个3维空间物体可以在一个平面上被观察。所以,为了实现这个目的,我们必须先了解一些线性代数的概念。

Note:推荐听一听Gilbert Strang教授的课来深入了解线性代数的概念。available on YouTube here

在这篇文章中,作者只聚焦于简单基础的3d功能,更加复杂的如z顺序和画皮肤(这两个词可能翻译有误)的技术将被忽略。正因如此,我们只需要了解一下核心概念:

- 什么是矩阵?

- 什么是向量?

- 如何进行矩阵和向量乘法

- 如何进行矩阵与矩阵相乘

矩阵是一个包含多组数据或公式的长方形表格。在我们描述的情况下,一个向量被表达为一个在3维空间中的点。我们的基本目标就是描述这个3维的点,然后使用一个矩阵,在一个我们可以旋转3d空间的平面中跟踪他的投影。

我确定很多人在于读这篇文章的时候会想起一部电影 立方体(The Matrix)。这部片子里面有很多让人印象深刻的3d模型和宇宙空间的各种设想和匪夷所思的概念(作者原意不是这个,但类似,总结发言领会精神)。对不起我再次打断您的思绪,但是也许这部片子会给您带来一些提示。

The movie plot hinges on the idea that humans live in a virtual reality, with their entire world being controlled by a program called Matrix. People go about their daily life, completely unaware of the controlling influence that the matrix has over their lives.(以上英文是继续关于电影的联想,我觉得会影响读者思考,并且自己翻译水平有限,就不翻译了)。同样的,在一个普通的平面上观察;一个点在3d控件中旋转地投影的改变,应该可以是自由随机的、不能被预测判断的。然而,事实上这些模式却可以被矩阵操作精确和准确的预判,也就是说我们可以利用矩阵的变化去控制点的旋转和投影。就像我们的英雄Neo看世界一样,他可以看出那些抽象的,别人看不到的、想象不到的情景。

现在到了您该选择的时候了,是选择放弃继续阅读,去使用其他的3d引擎,还是停留在这里,好好理解这些神秘的抽象的东西是如何实现的,继续阅读下去。(作者示意下面才是正文,TM的C了,早知道我不翻译上面了)

矩阵操作

现在,我们已经明确了矩阵决定3维点投影的变化。下一步我们可以更深的去了解这些点是如何旋转地了。

假设我们将一个点绕着Z轴转动30度。首先,我们需要建立一个可以使这样子操作的矩阵。也就是说,我们需要找到一个矩阵,它可以使一个3维点进行变换,使这个店可以绕着z轴转30度。

Figure: 1


然后我们使用这个矩阵去乘以要旋转的3d的点(向量),来获取那个旋转后的我们想要得到的点。(丫这里说的很不清楚,没有基础的人根本不能动,我解释一下,就是有个矩阵,你暂时不用管是什么,(以后会说是角度和平移量的矩阵),反正用这个矩阵乘以你想变化移动的那个点(Figure 1左边的点)之后,就有一个新点(向量)(Figure 1右边的点)出现)

我们同样可以矩阵与矩阵相乘。假如我们知道一个点绕着z轴旋转30度的矩阵,以及该点绕着y轴旋转10度的矩阵,如果我们简单的同时将这两个矩阵相乘,得到一个新的矩阵。这个新矩阵可以同时表达出这个点绕z轴和y轴旋转的角度。看Figure2

Figure: 2


通过这些例子的说明,使我们知道为了构建一个3d引擎,我们需要了解两个操作:

1. 如何矩阵乘向量

2. 如何矩阵乘矩阵

矩阵向量操作

我们使用3*3的矩阵(3行3列),反映一个点在3维空间中的真实画面,以及我们的向量将会是一个3维的点(X,Y,Z)(方便小学文化者简化理解就是在x,y,z轴上的坐标,但实际不完全是,因为向量还有方向)。之后,让我们看看如何实现矩阵向量相乘。很幸运,我们有很容易的方式去进行计算。下面的例子可以帮助大家很好了解这种乘法:

Figure: 3


这里我们有一个矩阵,以及一个点坐标 X=5,Y=1,Z=2. 我们按照图3的方法去做乘法,我们就会得到一个新点

(以上这些只是乘法运算的简单操作)

矩阵矩阵操作

Figure: 4


与矩阵和向量的操作相似,看Figure4。

单位矩阵

最后一个概念,在我们准备建立一个为了使点在xyz轴旋转的矩阵时,我们不得不提及“单位矩阵”。我们将要使用的矩阵的建立都基于在单位矩阵之上。下面的图指出了一个3*3单位矩阵:


该矩阵中,除了在对角线上的数据为1外,所有的在矩阵中的数据都是0。所有矩阵乘以单位矩阵都将不会改变结果,而获得与原矩阵相同的矩阵

使用C#写一个矩阵操作

是时候将这些矩阵概念转化成C#代码了。在源代码中(我在文章前部有给链接),你会这倒如下对象:

- Point3D对象 存储了(X,Y,Z)3d点的值。我们将使用它存储起始点和当前点的X,Y,Z值。之后作者将解释为什么要用它存储。

- Matrix3D对象 这个对象有一个私有属性,_matrix,它是一个double类型的2维数组。一个数组用来存储行,一个数组用来存储列。(这tm直译的,sb都知道)

这里是关于C#的详细定义。它允许我们使用从在操作符来计算矩阵与向量乘法。在我们遇到的情况中,我们将用Matrix3D乘Point3D。下面是这个操作的实现:

public static Point3D operator *(Matrix3D matrix1, Point3D point3D)
{
var x = point3D.InitialX * matrix1._matrix[0, 0] +
point3D.InitialY * matrix1._matrix[0, 1] +
point3D.InitialZ * matrix1._matrix[0, 2];
var y = point3D.InitialX * matrix1._matrix[1, 0] +
point3D.InitialY * matrix1._matrix[1, 1] +
point3D.InitialZ * matrix1._matrix[1, 2];
var z = point3D.InitialX * matrix1._matrix[2, 0] +
point3D.InitialY * matrix1._matrix[2, 1] +
point3D.InitialZ * matrix1._matrix[2, 2];
point3D.X = x;
point3D.Y = y;
point3D.Z = z;
return point3D;
}


之所以作者同时存储起始和当前点的坐标值,是因为作者总是使用起始坐标变化当前的点,而不是使用当前坐标变化。这是因为每次变化都会有失真情况。也就是说,如果我旋转了一个角度到一个点,然后再以同样的角度倒退回来,并不能回到原始点,因为实际的转动会损失一些准确性。所以,为了避免这宗情况,我总是根据起始点坐标进行变化。

下面让我们看看如何使用C#进行两个矩阵的相乘:

public static Matrix3D operator *(Matrix3D matrix1, Matrix3D matrix2)
{
var matrix = new Matrix3D();
for(var i = 0; i < 3; i++)
{
for(var j = 0; j < 3; j++)
{
matrix._matrix[i, j] =
(matrix2._matrix[i, 0] * matrix1._matrix[0, j]) +
(matrix2._matrix[i, 1] * matrix1._matrix[1, j]) +
(matrix2._matrix[i, 2] * matrix1._matrix[2, j]);
}
}
return matrix;
}


一下C#代码被用来设置单位矩阵:

public Matrix3D MakeIdentity()
{
this._matrix[0, 0] = this._matrix[1, 1] = this._matrix[2, 2] = 1;
this._matrix[0, 1] = this._matrix[0, 2] =
this._matrix[1, 0] = this._matrix[1, 2] =
this._matrix[2, 0] = this._matrix[2, 1] = 0;
return this;
}


作者管这个方法叫做Matrix3D对象的构造方法,因为所有的矩阵的建立都基于这个单位矩阵。

现在,我们需要为矩阵写一段代码,这个矩阵可以使我们的3D点绕着X,Y,Z轴旋转。实际上,这里有3个矩阵,看Figure6,每个矩阵代表绕着3个轴旋转的过程。(原话是 one for each plane, and each one is built on top of the identity matrix

,其实很好理解,不过我这里还是要具体说明,请大家尽量考虑真实的3d情况)。以rotate around X为例,如果一个3d坐标,我们让整个坐标系绕着x轴旋转,则在x轴不会变,会与原x轴重合,但是y和z都会想对变化,都会产生一定的变化角度,即图中的angle。为什么有的是cos有的是sin,有的是正的,有的是负的?大家可以自己去画下图,做一个坐标的变换,算一些高中数学,你会发现,如果你把x,y,z轴指向的方向变了之后,这些cos和sin以及其正负也会发生变化。很容易,很简单。

Figure: 6



将上述的内容转化成C#代码,如下:

public static Matrix3D NewRotateAroundX(double radians)
{
var matrix = new Matrix3D();
matrix._matrix[1, 1] = Math.Cos(radians);
matrix._matrix[1, 2] = Math.Sin(radians);
matrix._matrix[2, 1] = -(Math.Sin(radians));
matrix._matrix[2, 2] = Math.Cos(radians);
return matrix;
}
public static Matrix3D NewRotateAroundY(double radians)
{
var matrix = new Matrix3D();
matrix._matrix[0, 0] = Math.Cos(radians);
matrix._matrix[0, 2] = -(Math.Sin(radians));
matrix._matrix[2, 0] = Math.Sin(radians);
matrix._matrix[2, 2] = Math.Cos(radians);
return matrix;
}
public static Matrix3D NewRotateAroundZ(double radians)
{
var matrix = new Matrix3D();
matrix._matrix[0, 0] = Math.Cos(radians);
matrix._matrix[0, 1] = Math.Sin(radians);
matrix._matrix[1, 0] = -(Math.Sin(radians));
matrix._matrix[1, 1] = Math.Cos(radians);
return matrix;
}


为了建立一个矩阵来绕着3个轴旋转一个3D点,我们只需要做矩阵相乘,代码如下:

public static Matrix3D NewRotate(double radiansX, double radiansY, double radiansZ)
{
var matrix = NewRotateAroundX(radiansX);
matrix = matrix * NewRotateAroundY(radiansY);
matrix = matrix * NewRotateAroundZ(radiansZ);
return matrix;
}


这里注意(作者没写),这其实解释那个我上面说的暂时不用考虑是干什么的矩阵。不过这里的矩阵只能负责旋转,还不能负责位置的平移、

弧度和角度

大家肯呢个已经注意到,作者使用弧度计算旋转角。弧度描述了弧长和半径的关系(具体含义请wiki)。然后,我们生活的这个星球围绕他自己的轴做一个近似360度的圆球旋转,我们的先知们将这个圆分成了360的各部分,并且把它称之为角度。所以,为了尊重我们的祖先的文化遗产,我们使用角度带进我们的矩阵。为了实现它,我做了一个小小的函数将角度转为弧度,如下:

public static double DegreesToRadians(double degrees)
{
return (Math.PI / 180) * degrees;
}


并且在这个过程,旋转矩阵也可以使用角度去建立:

public static Matrix3D NewRotateByDegrees
(double degreesX, double degreesY, double degreesZ)
{
return NewRotate(
Angle.DegreesToRadians(degreesX),
Angle.DegreesToRadians(degreesY),
Angle.DegreesToRadians(degreesZ)
);
}


透视效果

我们使用期待的矩阵乘法完成了3D点坐标的变换操作后。我们可以简单的分配新的X Y值画到屏幕上,如下:

PointOnTheScreen.X = TransformedPoint3D.X
PointOnTheScreen.Y = TransformedPoint3D.Y


然而,因为所有的点围绕旋转的中心点不一定是(0,0)点,所以我们必须再加上这些中心点离(0,0)点的偏移距离,如下:

PointOnTheScreen.X = TransformedPoint3D.X + RotationCenterPoint.X
PointOnTheScreen.Y = TransformedPoint3D.Y + RotationCenterPoint.Y


这就是我们所有需要做的了,为了构建一个基本的3D变换,如果我们想实现更多,我们应该考虑一下透视效果(Perspective Effect)。就是将整个物体拉近了看,拉远了看,所有3D游戏的场景效果都是这样子。当然了,这里还可以做透视,这就是虚拟牛逼于现实的地方。

下面的公式和代码就表现了,我们离物体越近(高Z值),物体就变得越大越清晰。以及我们离物体越远(低Z值),物体就越小与模糊

PointOnTheScreen.X =
TransformedPoint3D.X * Coefficient / (TransformedPoint3D.Z + Coefficient) + RotationCenterPoint.X
PointOnTheScreen.Y =
TransformedPoint3D.Y * Coefficient / (TransformedPoint3D.Z + Coefficient) + RotationCenterPoint.Y


以这种方式,我可以提高和降低coefficient因子来控制变化效果的程度。自己调节Coefficient试吧,或者看原文,这里不阐述了。不要太懒了哦~

在源代码里,这个函数功能在Line2D类中的方法MapToPoints中实现。这个方法实现了从3维的点匹配到2维计算机屏幕点的过程

结论

如果想实现基本的矩阵操作,构建3d变换很容易,3个处理过程:

1.建立一个希望的自己想要的变换矩阵,利用多个矩阵相乘。

2.将变换矩阵乘以3d点

3.将变换后的3d点对称到2维平面。

总之就是希望大家成功!! 一切祝好!

如果大家有什么想了解译者的,来这里吧 http://www.comiscience.com 吼吼!

原文 http://www.simple-talk.com/dotnet/.net-framework/building-a-simple-3d-engine-with-silverlight/
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐