Strona wykorzystuje ciasteczka by usprawnić komfort z jej korzystania. Korzystając ze strony akceptujesz naszą Politykę Ciasteczek. X

Podstawy Stage3D część I: wyświetlanie wielu obiektów [zaawansowany]

Dwa tygodnie temu pisałem jak przystosować Flash Professional do obsługi Flash Player 11 przy okazji korzystając z przykładu w dokumentacji na wyświetlanie grafiki 3D (http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/display3D/Context3D.html#includeExamplesSummary). Jednak skorzystanie z gotowego rozwiązania to jedno, a zrozumienie co właściwie robi i jak działa to już zupełnie inna sprawa - dlatego tym razem napiszę co nieco o podstawowych zagadnieniach Stage3D (jak to Adobe nazywa swoją akcelerację 3D we Flash'u).
Tak na wszelki wypadek cały kod z owego przykładu (wraz z dodatkowymi bibliotekami potrzebnymi do jego uruchomienia) znajduje się tutaj: Context3DExample.zip

Pierwszą cześć tego artykułu poświęcę na opis tworzenia i wyświetlania kilku obiektów naraz przy jednoczesnym zachowaniu możliwości ich transformacji. Dla ułatwienia sugeruje stworzyć osobną klasę dla sześcianów, kopiując już istniejący kod (z przykładu w dokumentacji) odpowiedzialny za tworzenie geometrii: IndexBuffer i VertexBuffer. Taka klasa może wyglądać tak:

private var vbBuffer:VertexBuffer3D;
private var ibIndex:IndexBuffer3D;
private var m3D:Matrix3D;
private var nCount:int;

public function Box3D(context:Context3D) {
			
            var triangles:Vector.<uint> = Vector.<uint>([...]);
	ibIndex = context.createIndexBuffer(triangles.length);
	ibIndex.uploadFromVector(triangles, 0, triangles.length);
			
	const dataPerVertex:int = 6;
	var vertexData:Vector.<Number> = Vector.<Number>([...]);
	vbBuffer = context.createVertexBuffer(vertexData.length / dataPerVertex, dataPerVertex);
	vbBuffer.uploadFromVector(vertexData, 0, vertexData.length / dataPerVertex);
					
	m3D = new Matrix3D();
	nCount = 12;	
}
				
public function render(context:Context3D):void {
	context.setVertexBufferAt( 0, vbBuffer, 0, Context3DVertexBufferFormat.FLOAT_3 );
	context.setVertexBufferAt( 1, vbBuffer, 3, Context3DVertexBufferFormat.FLOAT_3 );
	context.drawTriangles(ibIndex, 0, nCount);
}
				
public function getMatrix3D():Matrix3D {return m3D;}
Nie ma tutaj nic szczególnie skomplikowanego - warto jedynie pamiętać, że matryca m3D będzie przechowywać transformacje (pozycję, skalę) więc sześcian najlepiej zbudować na pozycji 0,0,0 a później go przestawić w wybrane miejsce za pomocą funkcji appendTranslation.
Może jeszcze tylko wyjaśnię, że funkcją setVertexBufferAt ustawiamy który VertexBuffer będzie wykorzystywany do wyrysowania trójkątów (funkcja drawTriangles), a wywoływana jest dwa razy bo za pierwszym razem przekazujemy wierzchołki, a za drugim kolory.
Tak więc przygotowanie klasy sześcianu jest relatywnie łatwe, problemy zaczynają się gdy chcemy je wyświetlić. Zakładając, że wszystkie te kostki będą przechowywane w wektorze vBox funkcję render w przykładzie Context3DExample należy przebudować na coś takiego:
private function render(event:Event):void {
	renderContext.clear(.3, .3, .3);
			
	for each(var box:Box3D in vBox) {
		finalTransform.identity();
		finalTransform.append(box.getMatrix3D());
		finalTransform.append(world);
		finalTransform.append(view);
		finalTransform.append(projection);
	renderContext.setProgramConstantsFromMatrix(Context3DProgramType.VERTEX, 0, finalTransform, true);
				
		box.render(renderContext);
	}
			
	renderContext.present();
}
Tutaj już się robi trochę ciekawiej, a to za sprawą dwóch najważniejszych rzeczy: funkcji setProgramConstantsFromMatrix oraz konstrukcji matrycy finalTransform. Za pomocą setProgramConstantsFromMatrix ustawiamy matrycę (transformację) dla renderowanego obiektu i nie da się jej pominąć (a przynajmniej nie ma to sensu, bo nic nie zobaczymy na ekranie), podczas gdy finalTransform posłuży nam do przygotowanie owej matrycy.
Dla osób który nigdy nie miały do czynienia z grafiką 3D będzie to zapewne z początku trudne do zrozumienia, ale wszystko sprawdza się do faktu, że każdy obiekt (sześciany w naszym wypadku) tak naprawdę istnieje niezmodyfikowany przez cały czas działania aplikacji. Innymi słowy gdzieś tam tworzymy naszą kostkę, definiujemy jej trójkąty i nadajemy kolor, ale sama z siebie nie ma żadnej pozycji w przestrzeni - do tego wykorzystywane są właśnie matryce. Sęk w tym, że matryce również same z siebie nic konkretnego nie robią i nie mają żadnego bezpośredniego wpływy na geometrię aż do momentu gdy nie wykorzystamy setProgramConstantsFromMatrix by połączyć geometrię z transformacją. Jednak należy pamiętać, że jest to tylko pewnego rodzaju filtr - "Program" (osobny kod odpowiedzialny za renderowanie) bierze geometrię i przepuszcza ją przez matryce, a rezultat wyświetla na ekranie, nie modyfikując przy tym żadnych zmiennych.
Teraz jeśli chodzi o finalTransform - każdą klatkę animacji zaczynamy od wyzerowania tej matrycy (funkcja identity) by mieć pewność, że nie pozostaną tam jakieś inne transformacje. Ma to szczególne znacznie podczas operacji dodawania (append), ponieważ już istniejąca transformacja ma wpływ na późniejsze rezultaty, na przykład dodanie przesunięcia o 10 jednostek w lewo a potem obrotu o 90 stopni ustawi obiekt w innym miejscu niż gdybyśmy najpierw dodali obrót o 90 stopni a potem przesuniecie o 10 jednostek w lewo. Dokładnie jak w prawdziwym świecie, kierunek jaki obejmiemy na komendę "idź w lewo" będzie zależeć od tego w jaką stronę już spoglądamy. W każdym bądź razie, najlepiej samemu z tym poeksperymentować, pamiętając jedynie, że kolejność dodawani do siebie matryc ma ogromne znaczenie i dlatego właśnie w takiej a nie innej kolejności dodawane są do finalTransform matryce odpowiedzialne za: świat (world), pozycję kamery (view) oraz sposób wyświetlania (projection).

Jak zwykle cały kod użyty w tym artykule jest spakowany i gotowy do pobrania stąd: Tutorial3D.zip