使用三角形產生 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 陣列元素的直接相對索引。換句話說,如 indices 所定義, vertices 向量中的索引實際上就是真實的索引除以 2。例如,對 vertices 向量的第三個座標點而言,即使該點的第一個數值是從向量索引 4 開始,您仍然是使用 2 做為 indices 的值。

例如,以使用 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%,那麼,因為繪製三角形的目的是要表示 3D 空間中的物件,所以物件在 z 軸上的位置就會決定物件的 T 值。也就是說,物件的 T 值就是 0.5。決定 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 空間中旋轉的平面。在範例中,會使用「helper」類別 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

culling 參數具有衍生自 TriangleCulling 列舉類別的值: TriangleCulling.NONE TriangleCulling.POSITIVE TriangleCulling.NEGATIVE 。這些值與定義物件表面之三角形路徑的方向有關。用於決定剔除的 ActionScript API 會假設 3D 形狀的所有外顯三角形都應以相同的路徑方向繪製。一旦某個三角形表面翻轉之後,路徑方向也會變更。此時,就能剔除 (不再顯示) 該三角形。

如此一來, POSITIVE TriangleCulling 值便會移除具有正向路徑方向 (順時針) 的三角形;而 NEGATIVE TriangleCulling 值則會移除具有負向路徑方向 (逆時針) 的三角形。如果是立方體,那麼外顯的表面便具有正向路徑方向,而內藏的表面則具有負向路徑方向:

「展開」的立方體,顯示路徑方向。在「折合」時,內藏表面的路徑方向則相反。

若要瞭解剔除的運作方式,請從先前 UV 對應 的範例開始,將 drawTriangles() 方法的 culling 參數設定為 TriangleCulling.NEGATIVE

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

請注意,當物件旋轉時,不會顯示影像的「背面」。