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.
|
|
|