Strona 1 Grafika 3D w systemie Android Wprowadzenie do OpenGL ES Podstawy rysowania Rzutowanie i kamera Klasa GLSurfaceView Algorytm rysowania Tekstury
Strona 2 Grafika 3D w systemie Android W komputerach, programowanie grafiki trójwymiarowej możliwe jest m.in. przy użyciu wieloplatformowej biblioteki OpenGL (Open Graphics Library). Obok DirectX, stanowi ona jeden ze standardów programowania grafiki. Dla systemów wbudowanych, urządzeń o małej mocy obliczeniowej i ograniczonych zasobach (takich jak urządzenia mobilne) opracowano zoptymalizowaną i uproszczoną wersję tej biblioteki OpenGL ES (Embeded System). W systemie Android dostępna jest ona w pakiecie android.opengl. Dzięki takiemu rozwiązaniu i znajomości wykorzystania biblioteki OpenGL w aplikacjach komputerowych, możliwe jest zastosowanie podobnej metodologii programowania grafiki w urządzeniach mobilnych. Szczegółowa dokumentacja bibliotek graficznych dostępna jest w Internecie: OpenGL: http://www.opengl.org/documentation/ OpenGL ES: http://developer.android.com/reference/android/opengl/package-summary.html
Strona 3 Wprowadzenie do OpenGL ES W systemie Android wspierane są wersje OpenGL ES 1.0/1.1 i 2.0. Należy podkreślić, że obie wersje stosują nieco inne podejście do tworzenia kodu źródłowego aplikacji graficznej. Charakterystyka biblioteki OpenGL ES 1.0 / 1.1: dostępna od wersji systemu Android 1.0 (API 1), użycie potoku renderowania i kodowanie operacji nieco wygodniejsze niż w wersji 2.0 (dla początkujących programistów grafiki). Charakterystyka biblioteki OpenGL ES 2.0: dostępna od wersji systemu Android 2.2 (API 8), stosunkowo większa wydajność i szybkość grafiki, możliwość programowania tzw. shader ów (większe możliwości efektów), nie jest obsługiwana przez programowe emulatory systemu Android. Z tego powodu, dalsze rozważania dotyczą wersji OpenGL ES 1.0 / 1.1.
Strona 4 Podstawy rysowania reprezentacje obiektów Podstawowa koncepcja tworzenia i rysowania grafiki 3D oparta jest na trzech rodzajach reprezentacji obiektów: Reprezentacja punktowa (wierzchołkowa) Reprezentacja krawędziowa Reprezentacja wypełniana
Strona 5 Podstawy rysowania bufory Do rysowania wykorzystywane są dwie tablice (bufory), definiujące obiekt: bufor punktów/wierzchołków (vertex buffer) i bufor indeksów (index buffer). Obiekt trójwymiarowy 2 x3,y3,z3 0,0,0 x1,y1,z1 0 x4,y4,z4 3 0,0,0 Bufor punktów (wierzchołków) x1,y1,z1 0 1 x2,y2,z2 float punkty[] = { -0.5, -0.5f, 0, // 0 0.5f, -0.5f, 0, // 1 0.0f, 0.5f, 0 // 2 }; Kolejność: przeciwna do ruchu wskazówek zegara. x2,y2,z2 1 2 x3,y3,z3 float punkty[] = { -0.5f, 0.5f, 0, // 0-0.5f, -0.5f, 0, // 1 0.5f, -0.5f, 0, // 2 0.5f, 0.5f, 0 // 3 }; Bufor indeksów short indeksy[] = { 0, 1, 2 }; short indeksy[] = { 0, 1, 2, 0, 2, 3 };
Strona 6 Podstawy rysowania najważniejsze metody API Z punktu widzenia rysowania, zasadnicze metody biblioteki OpenGL ES to: glvertexpointer definiuje zbiór punktów (wierzchołków), gldrawarrays, gldrawelements rysują zbiór punktów, glclear czyści powierzchnię rysowania, glclearcolor, glcolor4f ustalają kolor tła i rysowania, glulookat ustala parametry kamery, glfrustumf ustala parametry bryły widzenia, glviewport ustala parametry ekranu (rzutu). OpenGL ES jest maszyną stanów: wywołanie dowolnej metody powoduje ustalenie pewnych parametrów dla kolejnych wywoływanych po niej metod. Przykładowo, wywołanie metody glvertexpointer definiuje bufor punktów, który będzie wykorzystywany przez wywołaną w następnej kolejności metodę gldrawelements. Wymagana znajomość pojęcia bryły widzenia w grafice 3D.
Strona 7 Podstawy rysowania metoda glvertexpointer Metoda ta służy do definiowania zbioru punktów (wierzchołków) w tzw. buforze wierzchołków. Punkty definiowane są w lokalnym układzie współrzędnych (x, y, z), który jest zaczepiony np. w środku obiektu. Y x3,y3,z3 Z X Układ współrzędnych prawoskrętny (oś Z skierowana od ekranu). x1,y1,z1 x2,y2,z2 Przykładowy obiekt 0,0,0 (-1,1) (1,1) (0,0) (-1,-1) (1,-1) Domyślny układ współrzędnych ekranu w OpenGL float punkty[] = { -0.5f, -0.5f, 0, // x1, y1, z1 0.5f, -0.5f, 0, // x2, y2, z2 0.0f, 0.5f, 0 // x3, y3, z3 }; // współrzędne w tablicy Java
Strona 8 Podstawy rysowania tworzenie bufora wierzchołków Tablicę języka Java zawierającą współrzędne punktów należy skonwertować na natywny bufor wierzchołków, przy pomocy pakietu java.nio (zawiera on klasy buforów ByteBuffer, ShortBuffer, FloatBuffer). // utworzenie bufora bajtowego // 3 punkty * 3 współrzędne * 4 bajty (float) = 36 bajtów ByteBuffer buf = ByteBuffer.allocateDirect(3*3*4); buf.order(byteorder.nativeorder()); // porządek bajtów natywny // dla urządzenia // bufor wierzchołków (vertex buffer) FloatBuffer vb = buf.asfloatbuffer(); // bufor float z bajtowego // umieszczenie współrzędnych punktów w buforze wierzchołków vb.put(punkty); // tablica punkty zdefiniowana na str.30 vb.position(0); // ustawienie pozycji bufora na 1-szą współrzędną // zdefiniowanie bufora wierzchołków w OpenGL glvertexpointer(3, GL10.GL_FLOAT, 0, vb); bufor punktów (vertex buffer) liczba współrzędnych typ współrzędnych liczba bajtów oddzielających punkty w pamięci
Strona 9 Podstawy rysowania tworzenie bufora indeksów Tablicę języka Java zawierającą indeksy należy skonwertować na natywny bufor indeksów, przy pomocy pakietu java.nio. Rysowanie odbywa się w oparciu o współrzędne z bufora wierzchołków (vertex buffer). Mogą one być wielokrotnie wykorzystywane (tzw. indeksowanie bufora punktów). Służy do tego bufor indeksów index buffer. // indeksy punktów w tablicy Java short indeksy[] = { 0, 1, 2 }; // punkty o indeksach 0, 1, 2 // utworzenie bufora bajtowego // 3 indeksy * 2 bajty (short) = 6 bajtów ByteBuffer buf = ByteBuffer.allocateDirect(3*2); buf.order(byteorder.nativeorder()); // porządek bajtów natywny // dla urządzenia // bufor indeksów (index buffer) ShortBuffer ib = buf.asshortbuffer(); // bufor short z bajtowego ib.put(indeksy); ib.position(0); // umieszczenie indeksów w buforze indeksów // ustawienie pozycji bufora na 1-szą współrzędną
Strona 10 Podstawy rysowania metoda gldrawelements Do odrysowania zawartości bufora wierzchołków służy metoda gldrawarrays. W przypadku wykorzystania bufora indeksów, do rysowania należy użyć metody gldrawelements. Metody te umożliwiają odrysowanie punktów przy pomocy kształtów geometrycznych dostępnych w bibliotece OpenGL ES. gldrawarrays(gl10.gl_triangles, 0, 3); gldrawelements(gl10.gl_triangles, 3, GL10.GL_UNSIGNED_SHORT,ib); rodzaj kształtu GL_POINTS GL_LINES GL_LINE_STRIP GL_LINE_LOOP GL_TRIANGLES GL_TRIANGLE_STRIP GL_TRIANGLE_FAN liczba indeksów = liczba rysowanych punktów tylko punkty tylko linie pasek linii (2 punkty + każdy kolejny punkt tworzy kolejną linię) pętla linii tylko trójkąty typ wartości indeksów GL_UNSIGNED_SHORT lub GL_UNSIGNED_BYTE pasek trójkątów (3 punkty + każdy kolejny punkt tworzy kolejny trójkąt) wachlarz trójkątów (pierwszy punkt to wierzchołek każdego trójkąta = środek wachlarza, każdy kolejny punkt tworzy kolejny trójkąt) bufor indeksów (index buffer)
Strona 11 Podstawy rysowania metody glclear i glcolor4f Metoda glclear służy do czyszczenia powierzchni rysowania. argument metody glclear GL_COLOR_BUFFER_BIT GL_DEPTH_BUFFER_BIT GL_STENCIL_BUFFER_BIT efekt wyzerowanie bufora koloru (usunięcie grafiki) wyzerowanie bufora głębi (Z-bufor) wyzerowanie bufora szablonu (np. efekt cienia) Metoda glclearcolor ustala kolor tła. Metoda glcolor4f służy do ustalania koloru dla następnej operacji rysowania. Parametry metody to kolejno: nasycenie składowej czerwonej, zielonej, niebieskiej i współczynnik alfa (przezroczystość) wszystkie jako liczby typu float z zakresu <0, 1>. glclear(gl_color_buffer_bit); // wyczyszczenie ekranu glclearcolor(1.0f, 0.0f, 0.0f, 0.5f); // RGBalfa, kolor tła glcolor4f(1.0f, 0.0f, 0.0f, 0.5f); // RGBalfa, kolor rysow.
Rzutowanie i kamera Końcowym etapem tworzenia grafiki 3D jest rzutowanie obiektu 3D na powierzchnię rysowania 2D. Odpowiadają za to metody: glulookat ustala położenie, kierunek i orientację kamery, glfrustumf definiuje tzw. bryłę widzenia (ostrosłup widzenia), glviewport ustala rozmiar ekranu (płaszczyzny rzutowania). współrzędne kamery (wewnątrz bryły widzenia) punkt widoku, na który skierowano kamerę Strona 12 Wymagana znajomość pojęcia bryły widzenia w grafice 3D. wektor góry, od środka układu wsp. (orientacja kamery tutaj pionowa) 0, -1, 0 => do góry nogami glulookaat(gl, 0, 0, 5, 0, 0, 0, 0, 1, 0); float x = (float) szer/wys; // aspekt (proporcje ekranu) glmatrixmode(gl10.gl_projection); // rzutowanie perspektywiczne glfrustumf(-x, // lewa strona ostrosłupa widzenia x, // prawa strona ostrosłupa widzenia -1, // spód ostrosłupa widzenia 1, // szczyt ostrosł.widzenia (wys.ostrosłupa = 2) 3, // odległość kamera-przednia ściana ostrosłupa 7); // odl.kamera-tylna ściana ostr.(głęb.ostr. = 4) glviewport(0, 0, szer, wys); // lewy-dolny róg (0, 0)
Strona 13 Klasa GLSurfaceView Każda aplikacja OpenGL ES 1.0 zawiera aktywność wykorzystującą obiekt klasy GLSurfaceView (widok w postaci powierzchni rysowania) i interfejs GLSurfaceView.Renderer (kontroluje proces rysowania). Tworzą one łącze między operacjami rysowania a systemem operacyjnym. Budowa i ogólny algorytm aplikacji OpenGL ES 1.0 jest następujący: 1. Utworzenie klasy implementującej interfejs GLSurfaceView.Renderer, 2. Konfiguracja parametrów rzutowania i kamery, 3. Zdefiniowanie obiektów 3D (bufor wierzchołków, bufor indeksów), 4. Zaprogramowanie operacji rysowania w metodzie ondrawframe, 5. Utworzenie obiektu klasy GLSurfaceView, 6. Konfiguracja silnika graficznego w obiekcie klasy GLSurfaceView przy użyciu metody setrenderer, 7. Utworzenie klasy dla aktywności i ustalenie kontrolki GLSurfaceView jako widoku treści w tej aktywności. Fragmenty kodu na str. 37-38 oznaczone na żółto wyświetlają puste okno.
Strona 14 Algorytm rysowania implementacja interfejsu Renderer // silnik graficzny 1 public class NaszRenderer implements GLSurfaceView.Renderer { metoda wywoływana automatycznie dla utworzenia powierzchni rysowania (inicjalizacja grafiki, tworzenie obiektów) public void onsurfacecreated(gl10 gl, EGLConfig eglconfig) { gl.glclearcolor(0.5f, 0.5f, 0.5f, 1.0f); // kolor tła 3 definiuj_obiekt(); włącz użycie bufora indeksów gl.glenableclientstate(gl10.gl_vertex_array); 2 } metoda wywoływana automatycznie po zmianie wymiarów powierzchni rysowania (np. po zmianie orientacji ekranu) public void onsurfacechanged(gl10 gl, int szer, int wys) { gl.glviewport(0, 0, szer, wys); float aspekt = (float) szer/wys; // proporcje ekranu gl.glmatrixmode(gl10.gl_projection); // tryb macierzy rzutowania gl.glloadidentity(); // macierz jednostkowa (stan domyślny) gl.glfrustumf(-aspekt, aspekt, -1, 1, 3, 7); // macierz rzutowania } //...
Strona 15 Algorytm rysowania implementacja interfejsu Renderer (c.d.) 2 4 //... public void ondrawframe(gl10 gl) { // usunięcie grafiki: } //... metoda wywoływana automatycznie przy każdym odrysowywaniu powierzchni (operacje rysowania) gl.glclear(gl10.gl_color_buffer_bit GL10.GL_DEPTH_BUFFER_BIT); gl.glmatrixmode(gl10.gl_modelview); // tryb macierzy MODELVIEW gl.glloadidentity(); // macierz jednostkowa (stan domyślny) // parametry kamery: GLU.gluLookAt(gl, 0, 0, 5, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f); gl.glcolor4f(0.0f, 1.0f, 0.0f, 0.5f); // kolor rysowania (str.34) // bufor wierzchołków (str.31): gl.glvertexpointer(3, GL10.GL_FLOAT, 0, vb); // rysowanie bez bufora indeksów: // gl.gldrawarrays(gl10.gl_triangles, 0, VERTICES); // rysowanie z bufora indeksów (str.33): gl.gldrawelements(gl10.gl_triangles, INDEXES, GL10.GL_UNSIGNED_SHORT, ib);
Strona 16 Algorytm rysowania tworzenie buforów punktów i indeksów private final static int VERTICES = 3; // liczba wierzchołków private final static int INDEXES = 3; // liczba indeksów private FloatBuffer vb; // bufor wierzchołków (vertex buffer) private ShortBuffer ib; // bufor indeksów (index buffer) public void definiuj_obiekt() definicja współrzędnych dla 3 { GLSurfaceView (patrz str. 30): float punkty[] = (0,0,0) środek powierzchni { -0.5f, -0.5f, 0, // (x1, y1, z1) (1,1,0) prawy górny róg 0.5f, -0.5f, 0, // (x2, y2, z2) (-1,-1,0) lewy dolny róg 0.0f, 0.5f, 0 // (x3, y3, z3) }; // współrzędne punktów w tablicy Java definicja indeksów (patrz str. 32) short indeksy[] = { 0, 1, 2 }; // indeksy punktów w tablicy Java ByteBuffer vbuf = ByteBuffer.allocateDirect(VERTICES*3*4); vbuf.order(byteorder.nativeorder()); tworzenie bufora punktów (patrz str. 31) vb = vbuf.asfloatbuffer(); vb.put(punkty); vb.position(0); współrzędne do bufora punktów (patrz str. 31) } ByteBuffer ibuf = ByteBuffer.allocateDirect(VERTICES*2); ibuf.order(byteorder.nativeorder()); tworzenie bufora indeksów (patrz str. 32) ib = ibuf.asshortbuffer(); ib.put(indeksy); ib.position(0); indeksy do bufora indeksów (patrz str. 32)
Strona 17 Algorytm rysowania aktywność dla rysowania public class OpenGLActivity extends Activity { private GLSurfaceView sv; @Override public void oncreate(bundle savedinstancestate) { 5 6 7 } super.oncreate(savedinstancestate); sv = new GLSurfaceView(this); sv.setrenderer(new NaszRenderer()); //sv.setrendermode(glsurfaceview.rendermode_when_dirty); setcontentview(sv); powierzchnia rysowania jako widok treści w aktywności @Override public void onpause() { super.onpause(); sv.onpause(); } 7 utworzenie widoku (powierzchnia rysowania) konfiguracja silnika graficznego i ustalenie obiektu renderującego dla powierzchni rysowania odrysowywanie tylko gdy jest to konieczne np. inna aktywność na pierwszym planie } @Override public void onresume() { super.onresume(); sv.onresume(); } powrót ze stanu pauzy - aktywność przechodzi na pierwszy plan
Strona 18 Algorytm rysowania rejestracja i uruchomienie aktywności Rejestracja aktywności w pliku deskryptora AndroidManifest.xml: <application <activity android:name=".openglactivity" android:label="rysowanie w OpenGL"> </activity> </application> Rejestrowanie nie jest konieczne, jeżeli aktywność jest aktywnością główną aplikacji. Jeżeli wykorzystywane są specyficzne możliwości OpenGL ES, w pliku AndroidManifest.xml należy określić wymagania aplikacji: <! -- Informacja dla systemu, że aplikacja wymaga OpenGL ES 2.0 ---> <uses-feature android:glesversion="0x00020000" android:required="true"/> <! -- Wymagania dotyczące formatu kompresji tekstur ---> <supports-gl-texture android:name="gl_img_texture_compression_pvrtc"> Aktywność może być uruchamiana w standardowy sposób (np. w menu): Intent intencja = new Intent(this, OpenGLActivity.class); startactivity(intencja);
Strona 19 Tekstury Tekstura to obraz (mapa bitowa) odwzorowujący właściwości powierzchni obiektu. Teksturowanie, to proces umieszczanie map bitowych lub innych obrazów na powierzchni rysowanego obiektu. Współrzędne wierzchołków obiektu są odwzorowywane (mapowane) na współrzędne tekstury. Dopasowanie tekstury do kształtu obiektu uzyskuje się stosując algorytmy nazywane owinięciami. Wyznaczają one tzw. współrzędne tekstury. W pewnych przypadkach konieczne jest zaprogramowanie własnych funkcji do obliczania współrzędnych tekstury. Dopasowanie rozmiarów obiektu do rozmiarów tekstury uzyskuje się poprzez normalizację. Powierzchnia obiektu jest zmniejszana tak, aby mieściła się w wymiarach 1 x 1. W bibliotece OpenGL tekstura jest kwadratem o wymiarach 1 x 1, gdzie lewy górny róg posiada współrzędne (0, 0).
Strona 20 Metody OpenGL ES do obsługi tekstur Wybrane funkcje do obsługi tekstur: GLUtils.texImage2D wczytuje mapę bitową (teksturę) z pliku glgentextures tworzy niepowtarzalny identyfikator tekstury (odniesienie do tekstury tzw. nazwę tekstury) glbindtexture wiąże bieżącą teksturę z identyfikatorem j.w. gltexparameter definiuje parametry tekstury gltexenv definiuje dodatkowe parametry tekstury gltexcoordpointer określa współrzędne tekstury (mapowanie współrzędnych tekstury na obszar zdefiniowany metodą glvertexpointer) glactivetexture ustala aktywną jednostkę teksturującą Szczegóły powyższych metod dostępne są w dokumentacji OpenGL ES.