通过三角形获得 3D 效果

Flash Player 10 和更高版本,Adobe AIR 1.5 和更高版本

在 ActionScript 中,使用 Graphics.drawTriangles() 方法执行位图转换,因为 3D 模型由空间中的一系列三角形表示。(但是,Flash Player 和 AIR 不支持深度缓冲,因此显示对象在本质上仍然是平面的,即 2D。这一点在了解 Flash Player 和 AIR 运行时的 3D 功能中进行了说明。)Graphics.drawTriangles() 方法通过一组坐标来绘制三角形路径,它与 Graphics.drawPath() 方法类似。

若要熟悉 Graphics.drawPath() 方法的使用,请参阅绘制路径

Graphics.drawTriangles() 方法使用 Vector.<Number> 来指定三角形路径的点位置:

drawTriangles(vertices:Vector.<Number>, indices:Vector.<int> = null, uvtData:Vector.<Number> = null, culling:String = "none"):void

drawTriangles() 的第一个参数是唯一的必需参数:vertices 参数。该参数是由定义坐标的数字组成的矢量,通过该矢量即可绘制三角形。每三组坐标(六个数字)表示一个三角形路径。如果没有 indices 参数,矢量的长度应始终为六的倍数,因为每个三角形都需要三个坐标对(三组 x/y 值对)。例如:

graphics.beginFill(0xFF8000); 
graphics.drawTriangles( 
    Vector.<Number>([ 
        10,10,  100,10,  10,100, 
        110,10, 110,100, 20,100]));

这些三角形不共享任何点,但是如果有共享点,就可以使用第二个 drawTriangles() 参数 indices 来对多个三角形重复使用 vertices 矢量中的值。

使用 indices 参数时,请注意,indices 值是点索引,而不是直接与 vertices 数组元素相关的索引。也就是说,vertices 矢量中由 indices 定义的索引实际上是除以 2 后的实际索引。例如,对于 vertices 矢量的第三个点,即使该点的第一个数值从矢量索引 4 开始,也使用 indices 值 2。

例如,使用 indices 参数合并两个三角形使二者共享对边:

graphics.beginFill(0xFF8000); 
graphics.drawTriangles( 
    Vector.<Number>([10,10, 100,10, 10,100, 100,100]), 
    Vector.<int>([0,1,2, 1,3,2]));

请注意,尽管正方形现在是通过两个三角形绘制的,但在 vertices 矢量中只指定了四个点。通过使用 indices,两个三角形共享的两点将由每个三角形重复使用。这样可以将顶点总数从 6(12 个数字)减少为 4(8 个数字):

使用 vertices 参数通过两个三角形绘制的正方形

对于较大的三角形网格,这种方法非常有用,因为这种情况下大多数点由多个三角形共享。

所有填充方式都可应用于三角形。填充应用于三角形网格的方式与应用于其他形状的方式是相同的。

转换位图

位图转换可在三维对象上提供透视视觉效果或“纹理”。具体而言,您可以朝消失点的方向扭曲位图,这样图像在朝消失点方向移动时会出现收缩效果。或者,您可以使用二维位图为三维对象创建表面,从而提供纹理视觉效果或将三维对象“包裹”起来。

使用消失点的二维表面和用位图包裹的三维对象。

UV 映射

一旦开始处理纹理,您就要使用 drawTriangles() 的 uvtData 参数。此参数用于为位图填充设置 UV 映射。

UV 映射是一种纹理化对象的方法。它依赖于两个值:U 水平 (x) 值和 V 垂直 (y) 值。这两个值不是基于像素值,而是基于百分比。0 U 和 0 V 表示图像的坐上角,1 U 和 1 V 表示右下角:

位图图像上的 UV 0 和 1 位置

可以为三角形的矢量指定 UV 坐标,从而将矢量自身关联到图像上的相应位置:

位图图像的三角形区域的 UV 坐标

UV 值与三角形的点保持一致:

三角形的顶点移动且位图发生扭曲,从而使单个点的 UV 值保持不变

由于 ActionScript 3D 转换应用于与位图关联的三角形,位图图像将根据 UV 值应用于三角形。因此,不要使用矩阵运算,而应通过设置或调整 UV 值来实现三维效果。

Graphics.drawTriangles() 方法也接受关于三维转换的一条可选信息:T 值。uvtData 中的 T 值表示 3D 透视,更具体地说,表示相关顶点的缩放系数。UVT 映射向 UV 映射加入了透视修正处理。例如,如果将对象放置在 3D 空间中远离视点的位置,使之显示为原始大小的 50%,则该对象的 T 值为 0.5。因为在 3D 空间中是通过绘制三角形来表示对象的,所以对象在 z 轴上的位置确定对象的 T 值。用于确定 T 值的等式为:

T = focalLength/(focalLength + z);
在该等式中,focalLength 表示焦距或计算得出的“屏幕”位置,它决定了视图中提供的透视大小。
焦距与 z 值
A.
视点

B.
屏幕

C.
3D 对象

D.
focalLength 值

E.
z 值

T 值用于缩放基本形状,使这些形状看起来更远。该值通常用于将 3D 点转换为 2D 点。而对于 UVT 数据,它也用于在透视三角形内的点之间缩放位图。

在定义 UVT 值时,T 值紧跟在为顶点定义的 UV 值后面。纳入 T 之后,uvtData 参数中的每三个值(U、V 和 T)与 vertices 参数中的每两个值(x 和 y)相匹配。在只有 UV 值的情况下,uvtData.length == vertices.length。在加入 T 值的情况下,uvtData.length = 1.5*vertices.length。

下面的示例演示使用 UVT 数据在 3D 空间中旋转平面。本例使用了一幅名为 ocean.jpg 的图像和一个“帮助器”类 ImageLoader,该类用于加载 ocean.jpg 图像以便将其分配给 BitmapData 对象。

下面是 ImageLoader 类的源代码(请将此代码保存到命名为 ImageLoader.as 的文件中):

package { 
    import flash.display.* 
    import flash.events.*; 
    import flash.net.URLRequest; 
    public class ImageLoader extends Sprite { 
        public var url:String; 
        public var bitmap:Bitmap; 
    public function ImageLoader(loc:String = null) { 
        if (loc != null){ 
            url = loc; 
            loadImage(); 
        } 
    } 
    public function loadImage():void{ 
        if (url != null){ 
            var loader:Loader = new Loader(); 
            loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onComplete); 
            loader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, onIoError); 
             
                var req:URLRequest = new URLRequest(url); 
                loader.load(req); 
            } 
        } 
         
    private function onComplete(event:Event):void { 
            var loader:Loader = Loader(event.target.loader); 
            var info:LoaderInfo = LoaderInfo(loader.contentLoaderInfo); 
            this.bitmap = info.content as Bitmap; 
            this.dispatchEvent(new Event(Event.COMPLETE)); 
    } 
         
    private function onIoError(event:IOErrorEvent):void { 
            trace("onIoError: " + event); 
    } 
    } 
}

下面的 ActionScript 用三角形、UV 映射和 T 值使图像获得这样的显示效果:图像朝着消失点逐渐收缩并不断旋转。将此代码保存到命名为 Spinning3dOcean.as 的文件中:

package { 
    import flash.display.* 
    import flash.events.*; 
    import flash.utils.getTimer; 
     
    public class Spinning3dOcean extends Sprite { 
        // plane vertex coordinates (and t values) 
        var x1:Number = -100,    y1:Number = -100,    z1:Number = 0,    t1:Number = 0; 
        var x2:Number = 100,    y2:Number = -100,    z2:Number = 0,    t2:Number = 0; 
        var x3:Number = 100,    y3:Number = 100,    z3:Number = 0,    t3:Number = 0; 
        var x4:Number = -100,    y4:Number = 100,    z4:Number = 0,    t4:Number = 0; 
        var focalLength:Number = 200;      
        // 2 triangles for 1 plane, indices will always be the same 
        var indices:Vector.<int>; 
         
        var container:Sprite; 
         
        var bitmapData:BitmapData; // texture 
        var imageLoader:ImageLoader; 
        public function Spinning3dOcean():void { 
            indices =  new Vector.<int>(); 
            indices.push(0,1,3, 1,2,3); 
             
            container = new Sprite(); // container to draw triangles in 
            container.x = 200; 
            container.y = 200; 
            addChild(container); 
             
            imageLoader = new ImageLoader("ocean.jpg"); 
            imageLoader.addEventListener(Event.COMPLETE, onImageLoaded); 
        } 
        function onImageLoaded(event:Event):void { 
            bitmapData = imageLoader.bitmap.bitmapData; 
            // animate every frame 
            addEventListener(Event.ENTER_FRAME, rotatePlane); 
        } 
        function rotatePlane(event:Event):void { 
            // rotate vertices over time 
            var ticker = getTimer()/400; 
            z2 = z3 = -(z1 = z4 = 100*Math.sin(ticker)); 
            x2 = x3 = -(x1 = x4 = 100*Math.cos(ticker)); 
             
            // calculate t values 
            t1 = focalLength/(focalLength + z1); 
            t2 = focalLength/(focalLength + z2); 
            t3 = focalLength/(focalLength + z3); 
            t4 = focalLength/(focalLength + z4); 
             
            // determine triangle vertices based on t values 
            var vertices:Vector.<Number> = new Vector.<Number>(); 
            vertices.push(x1*t1,y1*t1, x2*t2,y2*t2, x3*t3,y3*t3, x4*t4,y4*t4); 
            // set T values allowing perspective to change 
            // as each vertex moves around in z space 
            var uvtData:Vector.<Number> = new Vector.<Number>(); 
            uvtData.push(0,0,t1, 1,0,t2, 1,1,t3, 0,1,t4); 
             
            // draw 
            container.graphics.clear(); 
            container.graphics.beginBitmapFill(bitmapData); 
            container.graphics.drawTriangles(vertices, indices, uvtData); 
        } 
    } 
}

若要测试此示例,请在名为“ocean.jpg”的图像所在的目录中保存这两个类文件。您可以看到原始位图如何转换为在 3D 空间中消失于远处并不断旋转的效果。

剔除

剔除是一个过程,用于确定三维对象的哪些表面因对当前视点不可见而不应被渲染器所呈示。在 3D 空间中,三维对象“背面”的表面对视点不可见:
3D 对象的背面对视点不可见。
A.
视点

B.
3D 对象

C.
三维对象的背面

从本质上讲,不管三角形的大小、形状或位置如何,所有三角形都会始终呈示。剔除能确保 Flash Player 或 AIR 正确呈示 3D 对象。此外,为了减少呈示周期,有时您会希望渲染器跳过某些三角形。考虑在空间中旋转的立方体。在任何给定时间,您看到的立方体的面数不会超过三个面,这是因为不可见的面朝向立方体背面的其他方向。因为这些面不可见,所以渲染器不应绘制它们。如果不用剔除,Flash Player 或 AIR 就既要绘制正面也要绘制背面。

立方体的某些面对当前视点不可见

因此,Graphics.drawTriangles() 方法采用第四个参数来建立剔除值:

public function drawTriangles(vertices:Vector.<Number>, indices:Vector.<int> = null, uvtData:Vector.<Number> = null, culling:String = "none"):void

该剔除参数是来自 TriangleCulling 枚举类的值:TriangleCulling.NONETriangleCulling.POSITIVETriangleCulling.NEGATIVE。这些值与定义对象表面的三角形路径的方向有关。用于确定剔除的 ActionScript API 假设 3D 形状的所有外向三角形都是以同一路径方向绘制的。一旦三角形面经过旋转后,其路径方向也会改变。此时可以剔除(不呈示)该三角形。

因此,如果 TriangleCulling 值为 POSITIVE,则会移除正向路径方向(顺时针)的三角形。如果 TriangleCulling 值为 NEGATIVE,则会移除负向路径方向(逆时针)的三角形。对于立方体,朝前的表面具有正向路径方向,而朝后的表面具有负向路径方向:

“展开”的立方体,用以显示路径方向。当立方体“恢复原状”后,背面路径方向变为相反方向。

若要了解剔除的工作原理,请从前面UV 映射中的示例开始,将 drawTriangles() 方法的剔除参数设置为 TriangleCulling.NEGATIVE

container.graphics.drawTriangles(vertices, indices, uvtData, TriangleCulling.NEGATIVE);

请注意,在对象旋转时,未呈现图像的“背面”。