用Flash
Mx动态画线工具创立实时3D物体
作者Chad
Corbin:卡罗拉多州
Boulder公司独立互动媒体设计师,以技术优秀和富于创新闻名。最新作品modelspace(
http://modelspace.lo9ic.com/)赢得2002旧金山Flash前卫电影节技术功绩奖。
近年来,在flash影片中运用3D物体的需求越来越强烈,很多Flash用户也都尝试在影片中创建3D效果,但结果并不理想。为满足这一需求很多第三方插件应运而生。这些插件通过为3D物体的每一个位置生成关键贞(这就需要很多层,贞以及场景)来建立3D物体的幻象从而把其它3D建模程序中的3D物体转化到flash影片中。。这样即使建立一个很基本的3D互动场景也需要很多的层,桢以及场景和过渡。对于复杂的3D互动和3D环境,这种方式就显得过于笨拙。
本文介绍的解决方案就是用一种方法去建立一个实时的3D物体,使每个位置上效果的转化就可以在调用的瞬间完成。同时也使可编程的3D互动以及创作更真实更引人入胜的Flash影片成为可能。
在下面的文章中将介绍在Flash
Mx中建立实时3D效果和创建基于矩阵计算和Flash
Mx动态画线工具的3D引擎的方法。同时还对矩阵计算做一个简单的介绍以更好的阐明3D引擎背后的理论。如果你对3D
引擎的数学原理感兴趣,建议你仔细阅读这一部分。如果你只是想看一下代码,那你就可以跳过这一节,直接进入actionscript。
本文内容目录:
矩阵和矩阵乘法
Flashmx动态画线基础
3D
actionscript
3D
影片
3D
性能的影响因素
文章开始之前,请先下载源文件以更好的理解本文:
下载源文件
点击下载该文件(只适用windows,8k)
本文的方法和其它3D引擎的不同之处:
今天网络上随处可见的3D引擎所依赖的代码会修改3D物体的坐标信息,在全局坐标系统来讲。代码的每次执行来完成一个我称之为破坏性的转化。这种破坏性转化的执行方式看上去好象没什么大不了的,因为它可以创造出很好的3D效果。然而在某些情况下,我门有必要保持一个3D对象的原始坐标信息。
现有的比较好的方法能够保留3D对象的原始坐标信息,我们称为非破坏性转化。这些转化正是很多专业3D建模程序的基石。它让我们创建的3D物体可从任意角度观察的同时还具有在全局坐标系统上可伸缩,可移动性并可作成动画的形式。要完成这些转化,我们不得不使用矩阵计算,这同现有3D
Flash
代码有明显的不同。
二:矩阵和矩阵乘法
矩阵有多种形式,行矩阵,列矩阵,矩形矩阵,正方形矩阵等等。本文所涉及到的矩阵只有两种:行矩阵和正方形矩阵。一个行矩阵实质上就是一个一维数列。我们构建3D引擎时,将会用行矩阵来储存3D空间中一个点的x,y,和z坐标信息。从数学上来讲,行矩阵的形式如下:
A=|a1,a2,a3|
一个正方形矩阵可以想象为一个二维数列(一列数列),正方形矩阵要用来存储3D引擎中的旋转信息。方形矩阵的形式为:
B
=
|
b11,
b12,
b13
|
|
b21,
b22,
b23
|
|
b31,
b32,
b33
|
行矩阵和正方形矩阵都可以做乘法运算。两个矩阵相乘产生的新矩阵相当于一个矩阵绕另一个矩阵做旋转。利用这一事实,我们可以为3D引擎建立一组方程。这些方程将会在下文MatrixMatrixMultiply()和MatrixVectorMultiply()函数中出现。
建立旋转矩阵
要建立3D旋转,我们可以从一些标准旋转矩阵中选择一个。比如,可以用下面的矩阵来完成一个绕x轴的旋转:
|1
,
0
,
0
|
Tx=|0
,-cos(T),
sin(T)|
|0,
sin(T)
,cos(T)|
或者也可以使用y轴或z轴旋转矩阵,格式是相同的。
然而这些标准矩阵还不能满足3D旋转引擎的要求。由于引擎中将会加入互动性能,所以物体的旋转中心将是一个由用户的鼠标所定义的任意轴。在这种情况下需使用如下矩阵:
|tan(T)*x*x+cos(T)
,tan(T)*x
*y
-sin(T)*z,
tan(T)*X*z+sin(T)*y|
Txyz
=
|tan(T)*x*y+sin(T)*z
,
tan(T)*y*y+cos(T)
,
tan(T)*z*y-sin(T)*x|
|tan(T)*x*z-sin(T)*y
,
tan(T)*y*z+sin(T)*x,
tan(T)*z*z+cos(T)
|
这个矩阵实质上是x,y和z轴旋转矩阵的结合,它可以完成绕坐标轴或其它任意轴的旋转。后面的SetTransformMatrix()函数中使用这个矩阵。
你并不需要费神去了解这些变量的意义,只要看到这个矩阵的形式就足够了。
投影与透视
由于显示器只局限于展示二维的物体,所以必须用投影的方式把3D物体“平面化”然后展示在二维的显示屏上。由于我们已经习惯于一个有透视感的世界,这个平面化的形象会看起来很奇怪。为修正这一
2维和3维间的差别,透视方面的效果必须加以考虑。因此,我将用下面的透视矩阵来乘以3D物体的坐标轴:
|F/z
0
0
|
P=
|0
F/z
0
|
|0
0
1/z|
在这个矩阵中,F
代表透视常量且可以是任何大于0的数字。数值越大,物体越趋于扁平,而数值减小时透视感则显著增加。从这个矩阵中我们可以建立下列方程:
X’=F*x/z
y’=F*y/z
这些方程将会在3D
actionscript部分中的RenderScene()函数中用到。常量F也将会在InitMovie()函数中出现,作用和透视矩阵中的F相同。
下一节我们将讨论Flash中的动态画线过程。
用Flash
Mx动态画线工具创立实时3D物体2
三:Flash
MX
动态画线基础
通过Actionscript画直线,曲线以及区域填充是Flash
MX
新增的功能之一。它们所需的代码也很简单.
直线:
新的画线命令LineTo()将在Mc实例中画一条直线。这个命令必须连在设置线条属性的LineStyle()命令后使用。要在Actionscript中画线的代码如下:
instance.lineStyle(thickness,
rgb,
alpha);
instance.lineTo(x,
y);
下面的表格列出了这两条命令的常用参数及其意义:
linestyle()参数
意义
instance
要应用线条属性的mc实例名称
thickness
数值,直线或曲线的厚度,以像素计
rgb
字符串,直线或曲线的十六进制颜色值
alpha
数值,直线或曲线的可见性百分比
琀敓敬瑣??
θ???([.
lineto()参数
意义
instance
要画线的mc的实例名称
x
数值,终点的x坐标,以像素计,从mc的起始点算起
y
数值,终点的y坐标,以像素计,从mc的起始点算起
注意,由于线段是从Mc实例内的当前画线位置开始,所以LineTo语句不需要起始点的坐标。这意味着每个后续的线段都是从前一个线段的终点开始的。如果你所画的线没有闭合的情形下,这可能会引起理解上的困难。但你可以通过Moveto()命令来改变画线的位置。
Instance.Moveto(x,y);
曲线:
curveto()命令也是新增的,它在mc实例中画一条曲线。和Lineto
命令一样,它也必须和LineStyle命令连用。用actionscript画曲线的代码如下:
instance.lineStyle(thickness,
rgb,
alpha);
instance.curveTo(control
x,
control
y,
x,
y);
curveto()函数的参数几意义如下表:
curveto()参数
意义
instance
要画线的mc的实例名称
control
x
数值,控制点的x坐标,以像素计,从mc的起始点算起
control
y
数值,控制点的y坐标,以像素计,从mc的起始点算起
x
数值,终点的x坐标,以像素计,从mc的起始点算起
y
数值,终点的y坐标,以像素计,从mc的起始点算起
和直线不同,曲线需要一个控制点。控制点允许你定义所画曲线的曲率。这和一些基于向量的演示程序中的控制点作用类似。
和直线一样,曲线也是从mc实例中的当前画线位置开始,所画曲线不闭合时,也需要用到Moveto()函数。
多边形填充
beginFill()和endFill()是两个新增工具。它们和Lineto,vurveto和LineStyle连用时,可以画出一个以所画直线为边界的闭合的填充区域。actionscript中画填充的代码如下:
instance.lineStyle(thickness,
rgb,
alpha);
instance.beginFill(rgb,
alpha);
…
画直线和曲线的代码
…
…
instance.endFill();
beginFill()的参数几意义如下表:
beginfill参数
意义
instance
要画线的mc的实例名称
rgb
字符串,直线或曲线的十六进制颜色值
alpha
数值,直线或曲线的可见性百分比
画实填充时需注意Moveto()命令将会消除填充区域因此,最好在一个Moveto()命令后开始填充,在下一个Moveto()命令前结束填充。同时,如果beginFill()和endFill()之间的代码不能构成一个闭合的多边形,填充将自动闭合。
清除画线
Flash
M琀敓敬瑣??
????([.X也提供了新的命令来清除用以上方法画出的直线,曲线和填充。清除一个mc实例中动态画出的物体的命令为:
Instance.clear();
如果直线或曲线需要修改或重画时,
这个命令就很有用。每次我们转移3D
物体时,我们都会用到这一命令。注意这一命令在清除线段的同时将会把一个mc中所有的颜色,透明度,线条厚度以及画线位置等信息全部清除。
教程的下一部分将分析3D
actionscript的代码。
用Flash
Mx动态画线工具创立实时3D物体3
背景信息介绍之后,我们要开始建立3D引擎了。教程所带的Fla文件中,代码位于第一贞中。
初始化环境
影片开始我们先定义我们的全局变量。全局变量集中在一起定义是一个比较好的习惯,这样便于以后重新定义。下面的init()函数将会设置我们的全局变量:
function
initMovie()
{
TransformMatrix
=
new
Array(new
Array(1,0,0),
new
Array(0,1,0),
new
Array(0,0,1),
new
Array(0,0,0));
f
=
300;
moviewidth
=
600;
movieheight
=
400;
lines
=
0;
curves
=
0;
surfaces
=
0;
}
旋转模型的所有信息都将存储在这个transformMatrix变量中。初始值
((1,0,0),
(0,1,0),
(0,0,1))
对应于物体无旋转时的位置。当模型旋转时,变量将被更新以反映当时的视角。
变量F相当于3D
空间中观察物体的“摄象机”的焦距。它的值将会影响物体的透视感。值越大,物体越趋于扁平。
创建3D物体
接下来,我们来创建3D空间中的3D物体。instance()会为物体创建一个ā平台。平台将处于舞台的中心并可调用创建3D物体的make3Dobj()函数:
Function
initScene()
{
createEmptyMovieClip("Scene",
1);
Scene._x
=
moviewiDth/2;
Scene._y
=
movieheight/2;
make3Dobj("line",
new
Array(new
Array(50,100,0),
new
Array(50,-100,0)),
1,
"0x009900",
50);
}
make3Dobj()函数可以根据输入的数据创建出直线,曲线或填充。参数依次为线条粗细度(line
weight),颜色(color)及透明度值(alpha&#118alue)。
Function
make3Dobj(objtype,
pointarray,
lineweight,
linecolour,
linealpha,
Fillcolour,
Fillalpha)
{
obj
=
Scene.createEmptyMovieClip(objtype
+
"_"
+
this[objtype
+
"s"],
lines+curves+surFaces);
obj.pointarray
=
pointarray;
obj.lineweight
=
lineweight;
obj.linecolour
=
linecolour;
obj.linealpha
=
linealpha;
obj.Fillcolour
=
Fillcolour;
obj.Fillalpha
=
Fillalpha;
this[objtype
+
"s"]
++;
}
参数的意义如下表:
参数
意义
objtype
字符串;直线,曲线或平面中的其中一个
pointarray
多维数列,见下。
lineweight
数值;直线厚度,以像素计
linecolor
字符串;直线十六进制颜色值
linealpha
数值;直线可见性百分比
fillcolor
字符串;填充的十六进制颜色值
fillalpha
数值;填充的可见性百分比
pointarray(点数列)参数是一个多维数列,用来定义画3D直线,曲线和平面所需的点以及控点的坐标信息。可以在函数内部建立(如上例),也可以把它当成一个全局变量来建立,如下例所示。直线的pointarray参数包含两个数列。第一个数列对应于直线的起始点坐标,第二个对应于终点坐标。下面的actionscript将建立一个直线数列,此数列可被传递给make3Dobj()函数:
Point1
=
new
Array(0,0,0);
Point2
=
new
Array(100,0,0);
Pointarray
=
new
Array(Point1,
Point2);
曲线的pointarray参数包含三个数列,第一及第二个对应于曲线的起点和终点坐标,第三个则对应于控制点坐标。下面的范例actionscript可创建一个曲线并把它传递给
make3Dobj()函数:
Point1
=
new
Array(0,0,0);
Point2
=
new
Array(0,100,0);
Point3
=
new
Array(50,
50,
0);
Pointarray
=
new
Array(Point1,
Point2,
Point3);
平面的pointarray参数则包含任意顺序或者数字的直线和曲线数列,范例actionscript将创建一个平面并将它传递给make3Dobj()函数:
Point1
=
new
Array(0,0,0);
Point2
=
new
Array(100,0,0);
Lineā1
=
new
Array(Point1,
Point2);
Point3
=
new
Array(0,0,0);
Point4
=
new
Array(0,100,0);
Point5
=
new
Array(50,
50,
0);
Curve1
=
new
Array(Point3,
Point4,
Point5);
Pointarray
=
new
Array(Line1,
Curve1);
设置变换矩阵
接下来,我们需要一个方法来更新我们的变换矩阵以旋转3D物体。函数settransFormMatrix()可以通过先建立一个临时的变换矩阵然后调用MatrixMatrixmultiply()函数(作用是让矩阵相乘并返回结果)来完成这一任务。如果你读了矩阵计算那一部分,这些方程应该会有点熟悉:
function
SetTransformMatrix(x,
y,
z,
M)
{
vectorLength
=
Math.sqrt(x*x+y*y+z*z);
if
(vectorLength>.0001)
{
x
/=
vectorLength;
y
/=
vectorLength;
z
/=
vectorLength;
Theta
=
vectorLength/500;
cosT
=
Math.cos(Theta);
sinT
=
Math.sin(Theta);
tanT
=
1-cosT;
T
=[[],
[],
[]];
T[0][0]
=
tanT*x*x+cosT;
T[0][1]
=
tanT*x*y-sinT*z;
T[0][2]
=
tanT*x*z+sinT*y;
T[1][0]
=
tanT*x*y+sinT*z;
T[1][1]
=
tanT*y*y+cosT;
T[1][2]
=
tanT*y*z-sinT*x;
T[2][0]
=
tanT*x*z-sinT*y;
T[2][1]
=
tanT*y*z+sinT*x;
T[2][2]
=
tanT*z*z+cosT;
TransformMatrix
=
MatrixMatrixMultiply(T,
M);
}
}
function
MatrixMatrixMultiply(A,
B)
{
C
=
new
&
Array(new
Array(),
new
Array(),
new
Array());
C[0][0]
=
A[0][0]*B[0][0]+A[0][1]*B[1][0]+A[0][2]*B[2][0];
C[0][1]
=
A[0][0]*B[0][1]+A[0][1]*B[1][1]+A[0][2]*B[2][1];
C[0][2]
=
A[0][0]*B[0][2]+A[0][1]*B[1][2]+A[0][2]*B[2][2];
C[1][0]
=
A[1][0]*B[0][0]+A[1][1]*B[1][0]+A[1][2]*B[2][0];
C[1][1]
=
A[1][0]*B[0][1]+A[1][1]*B[1][1]+A[1][2]*B[2][1];
C[1][2]
=
A[1][0]*B[0][2]+A[1][1]*B[1][2]+A[1][2]*B[2][2];
C[2][0]
=
A[2][0]*B[0][0]+A[2][1]*B[1][0]+A[2][2]*B[2][0];
C[2][1]
=
A[2][0]*B[0][1]+A[2][1]*B[1][1]+A[2][2]*B[2][1];
C[2][2]
=
A[2][0]*B[0][2]+A[2][1]*B[1][2]+A[2][2]*B[2][2];
return
C;
}
输入的参数x,y和z定义了我们的全局变换矩阵旋转时所围绕的轴线。如果赋予x一个非零值,而y和z都为0的话,这条轴线就是x轴。对其它的轴也一样。
场景转化
最终,我们要建立一个函数来展现创建的3D物体。renderScene()通过在原物体和坐标系旋转后重新画出的物体之间循环来完成这一任务。旋转由完成矩阵乘法的MatrixMatrixMultiply()函数来理。要建立透视感,每个x和y都乘以变量F然后除以z。
以下是演示3d物体的代码:
function
RenderScene()
{
i
=
0;
while
(i
<=
lines-1)
{
obj
=
Scene["line_"+i];
obj.clear();
obj.lineStyle(obj.lineweight,
obj.linecolour,
obj.linealpha);
point1
=
MatrixVectorMultiply(TransformMatrix,
obj.pointarray[0]);
point2
=
MatrixVectorMultiply(TransformMatrix,
obj.pointarray[1]);
obj.moveTo(point1[0]/(1-(point1[2]/f)),
point1[1]/(1-(point1[2]/f)));
obj.lineTo(point2[0]/(1-(point2[2]/f)),
point2[1]/(1-(point2[2]/f)));
camdist
=
Math.sqrt(Math.pow((point1[0]+point2[0])/2,2)+Math.pow((point1[1]+point2[1])/2,2)+Math.pow(f-((point1[2]+point2[2])/2),2));
obj.swapDepths(Math.pow(f,3)-(Math.floor(camdist*100)));
i
++;
}
i
=
0;
while
(i
<=
curves-1)
{
obj
=
Scene["curve_"+i];
obj.clear();
obj.lineStyle(obj.lineweight,
obj.linecolour,
obj.linealpha);
point1
=
MatrixVectorMultiply(TransformMatrix,
obj.pointarray[0]);
point2
=
MatrixVectorMultiply(TransformMatrix,
obj.pointarray[1]);
point3
=
MatrixVectorMultiply(TransformMatrix,
obj.pointarray[2]);
obj.moveTo(point1[0]/(1-(point1[2]/f)),
point1[1]/(1-(point1[2]/f)));
obj.curveTo(point3[0]/(1-(point3[2]/f)),
point3[1]/(1-(point3[2]/f)),
point2[0]/(1-(point2[2]/f)),
point2[1]/(1-(point2[2]/f)));
camdist
=
Math.sqrt(Math.pow((point1[0]+point2[0]+point3[0])/3,2)+Math.pow((point1[1]+point2[1]+point3[1])/3,2)+Math.pow(f-((point1[2]+point2[2]+point3[2])/3),2));
obj.swapDepths(Math.pow(f,3)-(Math.floor(camdist*100)));
i
++;
}
i
=
0;
while
(i
<=
surfaces-1)
{
obj
=
Scene["surface_"+i];
obj.clear();
obj.lineStyle(obj.lineweight,
obj.linecolour,
obj.linealpha);
obj.beginFill(obj.fillcolour,
obj.fillalpha);
j
=
0;
xsum
=
ysum
=
zsum
=
0;
while
(j
<=
obj.pointarray.length-1)
{
if
(j
==
0)
{
point1
=
MatrixVectorMultiply(TransformMatrix,
obj.pointarray[j][0]);
obj.moveTo(point1[0]/(1-(point1[2]/f)),
point1[1]/(1-(point1[2]/f)));
xsum
+=
point1[0];
ysuām
+=
point1[1];
zsum
+=
point1[2];
}
if
(obj.pointarray[j].length
==
2)
{
point2
=
MatrixVectorMultiply(TransformMatrix,
obj.pointarray[j][1]);
obj.lineTo(point2[0]/(1-(point2[2]/f)),
point2[1]/(1-(point2[2]/f)));
xsum
+=
point2[0];
ysum
+=
point2[1];
zsum
+=
point2[2];
}
else
{
point1
=
MatrixVectorMultiply(TransformMatrix,
obj.pointarray[j][0]);
point2
=
MatrixVectorMultiply(TransformMatrix,
obj.pointarray[j][1]);
point3
=
MatrixVectorMultiply(TransformMatrix,
obj.pointarray[j][2]);
obj.curveTo(point3[0]/(1-(point3[2]/f)),
point3[1]/(1-(point3[2]/f)),
point2[0]/(1-(point2[2]/f)),
point2[1]/(1-(point2[2]/f)));
xsum
+=
point1[0]+point2[0]+point3[0];
ysum
+=
point1[1]+point2[1]+point3[1];
zsum
+=
point1[2]+point2[2]+point3[2];
}
camdist
=
Math.sqrt(Math.pow((xsum)/(j+1),2)+Math.pow((ysum)/(j+1),2)+Math.pow(f-((zsum)/(j+1)),2));
obj.swapDepths(Math.pow(f,3)-(Math.floor(camdist*100)));
j
++;
}
obj.endFill();
i
++;
}
}
物体的z轴分类处理由swapDepths()命令来完成。为确定物体的堆叠顺序,计算每个物体的中心的方法是由这些点算起并减去到摄象机的距离。因此,物体离摄象机越远,层的次序数就越低。
加入互动
下面就为我们的影片加入一点互动。为此,我们为影片加入一个简单的两贞循环,在第三桢加入关键桢并加入以下代码。当然你也可以试着加入更复杂的互动。
SetTransFormMatrix(-Scene._ymouse,
Scene._xmouse,
0,
TransFormMatrix);
RenDerScene();
gotoAnDPlay(2);
现在我们可以看一下完成的影片了。
翻译:alan
下载阅读本文。[download]upload/2004/3/9/200439122352.mht[/download]