Uzyskiwanie efektów trójwymiarowych przy użyciu trójkątów

Flash Player 10 i nowsze wersje, Adobe AIR 1.5 i nowsze wersje

W języku ActionScript transformacje bitmap realizuje się przy użyciu metody Graphics.drawTriangles() , ponieważ modele 3D są reprezentowane przez zbiory trójkątów w przestrzeni. (Jednak program Flash Player ani środowisko AIR nie obsługują bufora głębokości, a zatem obiekty wyświetlane są nadal płaskie, czyli dwuwymiarowe. Opisano to w rozdziale Omówienie obiektów ekranowych 3D w programie Flash Player i środowisku wykonawczym AIR ). Metoda Graphics.drawTriangles() jest zbliżona do metody Graphics.drawPath() , ponieważ rysuje ścieżkę trójkątną na podstawie zbioru współrzędnych.

Z działaniem metody Graphics.drawPath() można zapoznać się w sekcji Rysowanie ścieżek .

Metoda Graphics.drawTriangles() używa obiektu Vector.<Number> do określania położeń punktów na ścieżce trójkątnej:

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

Pierwszy parametr drawTriangles() , tj. parametr vertices , jest jedynym parametrem wymaganym. Ten parametr jest wektorem liczb definiującym współrzędne, według których będą rysowane trójkąty. Każde trzy zestawy współrzędnych (sześć liczb) wyznaczają jedną ścieżkę trójkątną. W wypadku pominięcia parametru indices , długość wektora powinna być zawsze wielokrotnością sześciu, ponieważ każdy trójkąt jest opisany trzema parami współrzędnych (parami wartości x/y). Na przykład:

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

Trójkąty z założenia nie mają punktów wspólnych, gdyby jednak miały punkty wspólne, drugi parametr metody drawTriangles() , o nazwie indices , pozwala na wielokrotne wykorzystanie wartości z wektora vertices w więcej niż jednym trójkącie.

Korzystając z parametru indices , należy pamiętać, że wartości wektora indices są indeksami punktów, a nie indeksami elementów w tablicy vertices . Innymi słowy, indeks w wektorze vertices zdefiniowany w parametrze indices jest rzeczywistym indeksem podzielonym przez 2. A zatem na przykład trzeci punkt w wektorze vertices odpowiada wartości 2 parametru indices , mimo że pierwsza wartość liczbowa opisująca ten punkt znajduje się pod indeksem 4 wektora.

Na przykład, w poniższy sposób, korzystając z parametru indices , można scalić dwa trójkąty, aby ich wspólny bok stał się przekątną kwadratu.

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

Mimo że kwadrat został narysowany poprzez połączenie dwóch trójkątów, w wektorze vertices określono tylko cztery punkty. Dzięki zastosowaniu parametru indices dwa punkty wspólne są używane do opisania obu trójkątów. Zmniejsza to łączną liczbę wierzchołków z 6 (12 liczb) do 4 (8 liczb).

Kwadrat narysowany jako złożenie dwóch trójkątów przy użyciu parametru vertices

Ta technika staje się użyteczna w przypadku dużych siatek trójkątów, w których większość punktów jest wspólna dla dwóch lub większej liczby trójkątów.

Do trójkątów można stosować wszystkie wypełnienia. Wypełnienia są stosowane do wynikowej siatki trójkątów tak, jak do każdego innego kształtu.

Transformowanie bitmap

Transformacje bitmap stwarzają iluzję perspektywy lub „tekstury” na obiekcie trójwymiarowym. W szczególności możliwe jest zniekształcenie bitmapy poprzez przyciągnięcie jej do znikającego punktu, tak aby obraz pozornie kurczył się w miarę zbliżania się do znikającego punktu. Można także użyć bitmapy dwuwymiarowej do utworzenia powierzchni obiektu trójwymiarowego, stwarzając iluzję tekstury lub „owinięcia” tego obiektu.

Powierzchnia dwuwymiarowa ze znikającym punktem i obiekt trójwymiarowy owinięty bitmapą.

Odwzorowywanie UV

Przy pracy z teksturami użyteczny jest parametr uvtData metody drawTriangles() . Parametr ten umożliwia skonfigurowanie odwzorowania UV dla wypełnień bitmapowych.

Odwzorowanie UV jest metodą pokrywania obiektów teksturą. Metoda ta bazuje na dwóch wartościach: przesunięciu poziomym U (x) i pionowym V (y). Wartości te wyrażone są nie w pikselach, lecz w procentach. Wartości 0 U i 0 V odpowiadają lewemu-górnemu narożnikowi obrazu, a wartości 1 U i 1 V odpowiadają prawemu dolnemu narożnikowi:

Położenie punktów UV 0 i 1 na obrazie bitmapowym

Do wektorów trójkąta można przypisać współrzędne UV kojarzące je z odpowiednimi miejscami na obrazie:

Współrzędne UV trójkątnego obszaru obrazu bitmapowego

Wartości UV punktów trójkąta pozostają niezmienne:

Wierzchołki trójkąta przemieszczają się, a bitmapa jest zniekształcana, aby zachować wartości UV poszczególnych punktów.

Gdy w języku ActionScript transformacje 3D są stosowane do trójkąta skojarzonego z bitmapą, obraz bitmapowy jest nakładany na trójkąt na podstawie wartości UV. A zatem, zamiast korzystać z obliczeń macierzowych, można ustawiać lub modyfikować wartości UV w celu uzyskania efektu trójwymiarowości.

Metoda Graphics.drawTriangles() przyjmuje także opcjonalny parametr opisujący transformacje trójwymiarowe: wartość T. Wartość T w parametrze uvtData reprezentuje perspektywę 3D, a ściślej współczynnik skalowania powiązanego z nią wierzchołka. Odwzorowanie UVT uwzględnia zniekształcenie perspektywiczne w odwzorowaniu UV. Na przykład, jeśli obiekt jest ulokowany w przestrzeni trójwymiarowej daleko od punktu obserwacji, tak że z pozoru jest o 50% mniejszy niż „pierwotnie”, wartość T dla tego obiektu wynosiłaby 0,5. Ponieważ obiekty w przestrzeni 3D reprezentowane są przez trójkąty, położenia trójkątów na osi z określają ich wartości T. Wartość T obliczana jest z równania:

T = focalLength/(focalLength + z);
W tym równaniu zmienna focalLength reprezentuje ogniskową lub obliczone położenie „ekranowe”, które decyduje o sile zniekształcenia perspektywicznego w danym widoku.
Ogniskowa i wartość z
A.
punkt obserwacji

B.
ekran

C.
Obiekt 3D

D.
wartość focalLength

E.
wartość z

Wartość T jest używana do skalowania kształtów podstawowych w taki sposób, aby pozornie znajdowały się dalej od obserwatora. Zwykle służy do przekształcania punktów 3D w punkty 2D. W przypadku danych UVT służy także do skalowania bitmapy między punktami w trójkącie rzutowanym perspektywicznie.

W przypadku zdefiniowania wartości UVT wartość T jest na bieżąco dostosowywana do zdefiniowanych wartości UV wierzchołka. Gdy wartość T jest uwzględniana w odwzorowaniu, każde trzy wartości w parametrze uvtData (U, V i T) są powiązane z parą wartości (x i y) w parametrze vertices . Gdy określone są tylko wartości UV, uvtData.length == vertices.length. Po uwzględnieniu wartości T obowiązuje wzór uvtData.length = 1.5*vertices.length.

Poniższy przykład ilustruje obrót płaszczyzny w przestrzeni 3D z wykorzystaniem danych UVT. W przykładzie użyto obrazu o nazwie ocean.jpg oraz klasy pomocniczej, ImageLoader, która ładuje obraz ocean.jpg, umożliwiając przypisanie go do obiektu BitmapData.

Oto kod źródłowy klasy ImageLoader (należy zapisać go w pliku o nazwie 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); 
    } 
    } 
}

A oto kod ActionScript, w którym wykorzystano trójkąty, odwzorowanie UV i wartości T do uzyskania efektu obrotu i jednoczesnego kurczenia się obrazu w miarę, jak zbliża się on do znikającego punktu. Ten kod należy zapisać w pliku o nazwie 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); 
        } 
    } 
}

Aby przetestować przykład, oba pliki klas należy zapisać w tym samym katalogu, co obraz „ocean.jpg”. Podczas wykonywania przykładowego kodu oryginalna bitmapa jest transformowana w taki sposób, że obracając się, znika „w oddali” w przestrzeni trójwymiarowej.

Usuwanie powierzchni

Usuwanie powierzchni to proces polegający na wybieraniu powierzchni obiektu trójwymiarowego, które nie powinny być renderowane, ponieważ są niewidoczne z bieżącego punktu obserwacji. W przestrzeni 3D powierzchnia po „odwrotnej stronie” obiektu trójwymiarowego nie jest widoczna z punktu obserwacji:
Odwrotna strona obiektu 3D jest niewidoczna z punktu obserwacji.
A.
punkt obserwacji

B.
Obiekt 3D

C.
odwrotna strona obiektu trójwymiarowego

Gdyby usuwanie powierzchni nie było stosowane, wszystkie trójkąty byłyby renderowane niezależnie od ich rozmiaru, kształtu lub położenia. Usuwanie powierzchni sprawia, że program Flash Player lub środowisko AIR prawidłowo renderuje obiekt 3D. Ponadto, aby zmniejszyć liczbę niezbędnych cykli renderowania, chcemy niekiedy pominąć niektóre trójkąty w procesie renderowania. Przyjrzyjmy się sześcianowi obracającemu się w przestrzeni. W dowolnym momencie nigdy nie widzimy więcej niż trzech ścian sześcianu, ponieważ pozostałe ściany zwrócone są „od” obserwatora. Ponieważ ściany te nie są widoczne, mechanizm renderujący nie powinien ich rysować. Gdy usuwanie powierzchni nie jest stosowane, program Flash Player lub środowisko AIR renderuje zarówno ściany widoczne, jak i znajdujące się po odwrotnej stronie sześcianu.

Niektóre ściany sześcianu nie są widoczne z bieżącego punktu obserwacji.

A zatem metoda Graphics.drawTriangles() ma czwarty parametr określający sposób usuwania powierzchni:

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

Parametr culling jest wartością z klasy wyliczeniowej TriangleCulling : TriangleCulling.NONE , TriangleCulling.POSITIVE albo TriangleCulling.NEGATIVE . Wartości te zależne są od kierunku opisywania ścieżek trójkątnych opisujących powierzchnię obiektu. Interfejs API języka ActionScript przy usuwaniu powierzchni zakłada, że wszystkie trójkąty kształtu 3D zwrócone „od” obserwatora mają ścieżki opisane w tym samym kierunku. Po obróceniu trójkąta zmienia się kierunek ścieżki. W tym momencie trójkąt może być usunięty.

A zatem wartość TriangleCulling . POSITIVE usuwa trójkąty ze ścieżką opisaną w kierunku dodatnim (zgodnie z ruchem wskazówek zegara). Wartość TriangleCulling . NEGATIVE powoduje usuwanie trójkątów ze ścieżką opisaną w kierunku ujemnym (przeciwnie do ruchu wskazówek zegara). W przypadku sześcianu ściany frontowe mają ścieżki opisane w kierunku dodatnim, ściany po stronie odwrotnej mają ścieżki opisane w kierunku ujemnym:

„Rozwinięty” sześcian z widocznymi kierunkami ścieżek. Po „zwinięciu” kierunek ścieżek po niewidocznej stronie jest odwrócony.

Aby zapoznać się w praktyce z działaniem mechanizmu usuwania powierzchni, możemy wyjść od poprzedniego przykładu Odwzorowywanie UV i ustawić parametr culling metody drawTriangles() na wartość TriangleCulling.NEGATIVE :

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

Jak widać, „odwrotna” strona obracającego się obrazu nie jest renderowana.