Mobilne aplikacje multimedialne OpenGL Marek Kulawiak Katedra Systemów Geoinformatycznych Wydział Elektroniki, Telekomunikacji i Informatyki Politechniki Gdańskiej
Open Graphics Library API do tworzenia aplikacji wyświetlających grafikę 2D i 3D w czasie rzeczywistym z akceleracją sprzętową Możliwość programowania w: Ada, C, C++, C#, Common Lisp, Delphi, Fortran, Haskell, Perl, Python, Java,... Dostępność na systemach operacyjnych: Windows, Linux, OS X Pochodne standardu OpenGL: OpenGL ES (Android, ios) WebGL (przeglądarki internetowe)
Pierwsza połowa lat 90. wyliczanie grafiki na procesorze dostosowanie do różnego sprzętu na PC Źródło: http://doc.ubuntu-fr.org/_detail/jeux/doom/doom_screenshot001.png
Początki OpenGL Rok 1992: specyfikacja OpenGL 1.0 Rok 1995: pierwsze karty graficzne dostosowane do wyświetlania grafiki 3D Rok 1996: wydanie przez Microsoft pierwszej wersji standardu Direct3D Rok 1997: OpenGL 1.1
Druga połowa lat 90. GLQuake rasteryzacja z użyciem karty graficznej wyższa rozdzielczość Źródło: http://a.fsdn.com/con/app/proj/c00ngl/screenshots/43360.jpg
Druga połowa lat 90. Rok 1997: ostra krytyka standardu Direct3D Chris Hecker: An Open Letter to Microsoft: Do the Right Thing for the 3D Game Industry Rok 1999: GeForce 256 - pierwsza karta graficzna oferująca T&L (Transform & Lighting) znaczne odciążenie procesora dostępność T&L w OpenGL
Druga połowa lat 90. Quake III Arena (OpenGL) wykorzystanie silnika id Tech 3 w innych popularnych grach Źródła: http://cdn.akamai.steamstatic.com/steam/apps/6030/, http://media.desura.com/cache/images/games/1/1/4/crop_800x600/quake3_3.1.jpg
Struktura programu w OpenGL funkcja tworząca kontekst graficzny funkcja rysująca scenę funkcja skalująca obraz do nowych rozmiarów okna
GL_LINES gl.glbegin(gl2.gl_lines); gl.glcolor3f(1f, 1f, 1f); gl.glvertex3f(-100, -100, 0); gl.glvertex3f( 100, -100, 0); gl.glvertex3f( 100, -100, 0); gl.glvertex3f( 0, 100, 0); gl.glvertex3f( 0, 100, 0); gl.glvertex3f(-100, -100, 0); gl.glend();
GL_LINE_STRIP gl.glbegin(gl2.gl_line_strip); gl.glcolor3f(1f, 1f, 1f); gl.glvertex3f(-100, -100, 0); gl.glvertex3f( 100, -100, 0); gl.glvertex3f( 0, 100, 0); gl.glvertex3f(-100, -100, 0); gl.glend();
GL_TRIANGLES gl.glbegin(gl2.gl_triangles); gl.glcolor3f(1f, 0.8f, 0f); gl.glvertex3f(-100, -100, 0); gl.glvertex3f( 100, -100, 0); gl.glvertex3f( 0, 100, 0); gl.glend();
GL_TRIANGLE_STRIP gl.glbegin(gl2.gl_triangle_strip); gl.glcolor3f(1f, 0.8f, 0f); gl.glvertex3f(-100, -100, 0); gl.glvertex3f( 100, 100, 0); gl.glvertex3f(-100, 100, 0); gl.glvertex3f(-100, -100, 0); gl.glvertex3f( 100, -100, 0); gl.glvertex3f( 100, 100, 0); gl.glend();
GL_QUADS gl.glbegin(gl2.gl_quads); gl.glcolor3f(1f, 0.8f, 0f); gl.glvertex3f(-100, -100, 0); gl.glvertex3f( 100, -100, 0); gl.glvertex3f( 100, 100, 0); gl.glvertex3f(-100, 100, 0); gl.glend();
GL_QUADS gl.glbegin(gl2.gl_quads); gl.glcolor3f(1f, 1f, 0f); gl.glvertex3f(-100, -100, 0); gl.glcolor3f(0f, 1f, 0f); gl.glvertex3f( 100, -100, 0); gl.glcolor3f(0f, 0.5f, 1f); gl.glvertex3f( 100, 100, 0); gl.glcolor3f(1f, 0f, 0f); gl.glvertex3f(-100, 100, 0); gl.glend();
Atrybuty wierzchołków obiektu glvertex glcolor gltexcoord glnormal...
Wektory normalne Normalne wyliczone dla każdej ściany. Normalne wyliczone dla każdego wierzchołka.
Wektory normalne (2) Źródło: http://openinggl.blogspot.com/2012/04/adding-lighting-normals.html
Zmiana atrybutów sceny gl.glpushattrib(gl2.gl_lighting); gl.gldisable(gl2.gl_lighting); gl.glbegin(gl.gl_quads); gl.glcolor3f(1f, 1f, 0f); gl.glcolor3f(0f, 1f, 0f); gl.glcolor3f(0f, 0.5f, 1f); gl.glcolor3f(1f, 0f, 0f); gl.glend(); gl.glpopattrib(); gl.glvertex3f(-100, -100, 0); gl.glvertex3f( 100, -100, 0); gl.glvertex3f( 100, 100, 0); gl.glvertex3f(-100, 100, 0);
Zmiana atrybutów i transformacji gl.glpushmatrix(); gl.gltranslatef(-10f, 0f, -10f); gl.glpushattrib(gl2.gl_lighting); gl.gldisable(gl2.gl_lighting); gl.glbegin(gl.gl_quads); gl.glcolor3f(1f, 1f, 0f); gl.glcolor3f(0f, 1f, 0f); gl.glcolor3f(0f, 0.5f, 1f); gl.glcolor3f(1f, 0f, 0f); gl.glend(); gl.glpopattrib(); gl.glpopmatrix(); gl.glvertex3f(-100, -100, 0); gl.glvertex3f( 100, -100, 0); gl.glvertex3f( 100, 100, 0); gl.glvertex3f(-100, 100, 0);
Macierze transformacji (1) gl.glmatrixmode(gl2.gl_modelview); gl.glloadidentity(); [ ] [ ] [] [] 1000 0 1 0 0 modelviewmatrix = 0010 0001 1000 x x 0100 y = y 0010 z z 0001 w w
Macierze transformacji (2) gl.gltranslatef(-10f, 0f, -10f); [ ][ ] [ ] [] [ ] 100 X 1 0 0 10 0 1 0 Y 0 1 0 0 translationmatrix = = 001 Z 0 0 1 10 000 1 000 1 1 0 0 10 0 10 010 0 0 = 0 0 0 1 10 0 10 000 1 1 1
Macierze transformacji (3) gl.glscalef(2f, 2f, 2f); [ ][ ] [ ] [] [ ] X 000 2000 0 Y 0 0 0 2 0 0 scalingmatrix = = 0 0 Z0 00 20 0001 0001 2000 x 2x 0 2 0 0 y = 2y 0020 z 2z 0001 w w
Listy rozkazów Listy rozkazów (ang. display lists) OpenGL, które są przekazywane do karty graficznej tylko raz i pozostają w jej pamięci. Do narysowania obiektu wystarczy wywołanie jednej funkcji: glcalllist(identyfikator_listy). Zaleta: szybsze działanie programu. Wada: listy nadają się tylko do obiektów o stałym kształcie.
Przykład stworzenia listy rozkazów int listid; // Stworzenie listy. listid = gl.glgenlists(1); gl.glnewlist(listid, GL2.GL_COMPILE); gl.glbegin(gl2.gl_triangles); gl.glcolor3f(1f, 0.8f, 0f); gl.glvertex3f(-100, -100, 0); gl.glvertex3f( 100, -100, 0); gl.glvertex3f( 0, 100, 0); gl.glend(); gl.glendlist(); // Narysowanie obiektu. gl.glcalllist(listid);
Vertex Buffer Objects Technika polegająca na przechowywaniu obiektów zawierających dane o wierzchołkach w pamięci GPU. Podobna prędkość jak przy użyciu list. Możliwość późniejszej modyfikacji zawartości VBO. Wada: trudniejsze w użyciu.
VBO: indeksy wierzchołków float[] vertexarray = {-100, -100, 0, 100, -100, 0, 100, 100, 0, -100, 100, 0}; [0] [1] [2] [3] float[] colorarray = {1f, 1f, 0f, 0f, 1f, 0f, 0f, 0.5f, 1f, 1f, 0f, 0f}; [0] [1] [2] [3] short[] indexarray = {0,1,2, 0,2,3};
VBO: przygotowanie danych FloatBuffer vertices, colors; ShortBuffer indices; int VBOVertices, VBOColors, VBOIndices; float[] vertexarray = {-100, -100, 0, 100, -100, 0, 100, 100, 0, -100, 100, 0}; vertices = BufferUtil.newFloatBuffer(vertexArray.length); vertices.put(vertexarray); vertices.flip(); float[] colorarray = {1f, 1f, 0f, 0f, 1f, 0f, 0f, 0.5f, 1f, 1f, 0f, 0f}; colors = BufferUtil.newFloatBuffer(colorArray.length); colors.put(colorarray); colors.flip(); short[] indexarray = {0, 1, 2, 0, 2, 3}; indices = BufferUtil.newShortBuffer(indexArray.length); indices.put(indexarray); indices.flip();
VBO: inicjalizacja buforów int[] temp = new int[3]; gl.glgenbuffers(3, temp, 0); VBOVertices = temp[0]; gl.glbindbuffer(gl2.gl_array_buffer, VBOVertices); gl.glbufferdata(gl2.gl_array_buffer, vertices.capacity() * BufferUtil.SIZEOF_FLOAT, vertices, GL2.GL_STATIC_DRAW); gl.glbindbuffer(gl2.gl_array_buffer, 0); VBOColors = temp[1]; gl.glbindbuffer(gl2.gl_array_buffer, VBOColors); gl.glbufferdata(gl2.gl_array_buffer, colors.capacity() * BufferUtil.SIZEOF_FLOAT, colors, GL2.GL_STATIC_DRAW); gl.glbindbuffer(gl2.gl_array_buffer, 0); VBOIndices = temp[2]; gl.glbindbuffer(gl2.gl_element_array_buffer, VBOIndices); gl.glbufferdata(gl2.gl_element_array_buffer, indices.capacity() * BufferUtil.SIZEOF_SHORT, indices, GL2.GL_STATIC_DRAW); gl.glbindbuffer(gl2.gl_element_array_buffer, 0);
VBO: rysowanie gl.glenableclientstate(gl2.gl_vertex_array); gl.glenableclientstate(gl2.gl_color_array); gl.glbindbuffer(gl2.gl_array_buffer, VBOColors); gl.glcolorpointer(3, GL2.GL_FLOAT, 0, 0); gl.glbindbuffer(gl2.gl_array_buffer, VBOVertices); gl.glvertexpointer(3, GL2.GL_FLOAT, 0, 0); gl.glbindbuffer(gl2.gl_element_array_buffer, VBOIndices); gl.gldrawelements(gl2.gl_triangles, indices.capacity(), GL2.GL_UNSIGNED_SHORT, 0); gl.gldisableclientstate(gl2.gl_color_array); gl.gldisableclientstate(gl2.gl_vertex_array);
VBO: modyfikacja danych // Inicjalizacja buforów. gl.glbindbuffer(gl2.gl_array_buffer, VBOVertices); gl.glbufferdata(gl2.gl_array_buffer, vertices.capacity() * BufferUtil.SIZEOF_FLOAT, vertices, GL2.GL_STREAM_DRAW); gl.glbindbuffer(gl2.gl_array_buffer, 0); // Rysowanie. gl.glbindbuffer(gl2.gl_array_buffer, VBOVertices); gl.glvertexpointer(3, GL2.GL_FLOAT, 0, 0); FloatBuffer verticesbuffer = gl.glmapbuffer(gl2.gl_array_buffer, GL2.GL_WRITE_ONLY).asFloatBuffer(); verticesbuffer.position(9); verticesbuffer.put(-50.f); verticesbuffer.put(50.f); gl.glunmapbuffer(gl2.gl_array_buffer);
VBO: modyfikacja danych (2) glbufferdata() GL_STATIC_DRAW GL_DYNAMIC_DRAW GL_STREAM_DRAW GL_STATIC_READ GL_DYNAMIC_READ GL_STREAM_READ GL_STATIC_COPY GL_DYNAMIC_COPY GL_STREAM_COPY glmapbuffer() GL_WRITE_ONLY GL_READ_ONLY GL_READ_WRITE
Początki shaderów Rok 2000: GeForce 3 - pierwsza karta graficzna z obsługą shaderów możliwość programowania shaderów w DirectX 8, ale nie w OpenGL 1.2 Rok 2004: wprowadzenie GLSL w OpenGL 2.0 spoźniona obsługa shaderów w OpenGL
OpenGL Shading Language GLSL jest językiem programowania wysokiego poziomu służącego do tworzenia tzw. shaderów, czyli programów wykonywanych na specjalizowanych procesorach karty graficznej. Użycie shaderów powoduje nadpisanie domyślnego potoku renderowania bardziej elastycznym rozwiązaniem, zezwalającym m.in. na dokładniejsze wyliczanie oświetlenia.
Wyliczanie oświetlenia Dla wierzchołków (per-vertex lighting) Dla pikseli (per-pixel lighting) Źródło: http://www.nvnews.net/reviews/geforce_256/cube_mapping.shtml
Struktura shaderów Najczęściej wykorzystywane rodzaje shaderów: są dwa vertex shader: wykonywany dla każdego wierzchołka fragment/pixel shader: wykonywany dla każdego fragmentu zrasteryzowanego obrazu fragmenty odpowiadają pikselom obrazu, ale część z nich jest usuwana
Typy proste w GLSL Skalary Wektory bool bvec2, bvec3, bvec4 uint uvec2, uvec3, uvec4 int ivec2, ivec3, ivec4 float vec2, vec3, vec4 double dvec2, dvec3, dvec4
Wektory Możliwe metody dostępu do składowych: Jak do elementów tablicy [] Za pomocą masek: xyzw, rgba, stpq Przykład: różne sposoby pobrania wartości drugiej składowej: float tmp = vcolor[1]; float tmp = vcolor.g; float tmp = vcolor.y; float tmp = vcolor.t;
Wektory (2) Sposoby tworzenia wektorów: vec4 rgbavector = vec4(0.3, 0.6, 0.9, 1.0); vec3 rgbvector = rgbavector.rgb; vec3 bgrvector = rgbvector.bgr; vec4 anothervector = vec4(bgrvector, 0.5); vec2 yetanothervector = anothervector.zz; yetanothervector[0] = 1.0 yetanothervector[0]; Pytanie do publiczności: Jaka jest zawartość wektora yetanothervector?
Najprostszy możliwy przykład Zastosowanie poniższej pary shaderów spowoduje pokolorowanie obiektu jednolitym, czerwonym kolorem: // Vertex Shader void main(void) { // Wyznaczenie położenia wierzchołka. gl_position = ftransform(); } // Fragment Shader void main(void) { // Przypisanie koloru fragmentowi obrazu. gl_fragcolor = vec4(1.0,0.0,0.0, 1.0); }
Komunikacja z shaderami W OpenGL istnieje mechanizm umożliwiający komunikację z shaderami za pomocą specjalnych rodzajów zmiennych: uniform: zmienne globalne przekazywane z programu głównego do shaderów varying: zmienne tworzone w vertex shaderze i następnie odczytywane we fragment shaderze w zinterpolowanej postaci attribute: wartości dotyczące konkretnych wierzchołków (położenie, kolor, normalne, itp.)
Nieco trudniejszy przykład Uwzględnienie koloru wierzchołków i przekazanie wartości kanału alpha (przezroczystości): // Vertex Shader varying vec4 vcolor; void main(void) { // Wyznaczenie położenia wierzchołka. gl_position = ftransform(); // Zapisanie koloru wierzchołka do zmiennej vcolor. vcolor = gl_color; } // Fragment Shader uniform float alpha; varying vec4 vcolor; void main(void) { // Przypisanie aktualnemu fragmentowi obrazu // automatycznie zinterpolowanego koloru wierzchołka. gl_fragcolor = vec4(vcolor.rgb, alpha); }
Macierze transformacji Zmienne predefiniowane w GLSL: uniform mat4 gl_modelviewmatrix; macierz widoku modelu uniform mat4 gl_projectionmatrix; macierz projekcji uniform mat4 gl_modelviewprojectionmatrix; iloczyn macierzy widoku modelu i projekcji
Macierze transformacji (2) Różne sposoby wyznaczenia pozycji wierzchołka w vertex shaderze: void main() { // Przemnożenie pozycji wierzchołka przez macierz projekcji oraz // macierz widoku modelu. gl_position = gl_projectionmatrix * gl_modelviewmatrix * gl_vertex; // Przemnożenie położenia wierzchołka przez wcześniej wyliczoną // macierz będącą wynikiem iloczynu macierzy projekcji // i macierzy widoku modelu. gl_position = gl_modelviewprojectionmatrix * gl_vertex; // Wykorzystanie gotowej funkcji transformującej pozycję wierzchołka // (brak możliwości dokonania dodatkowych przekształceń). gl_position = ftransform(); }
Stan obecny OpenGL Sukces DirectX na systemach Windows John Carmack: Direct3D is a rather better API today OpenGL still works fine and we wouldn t get any huge benefits by making the switch Komercyjne wykorzystanie OpenGL: Blizzard: porty gier na OS X Valve: porty gier na Linuxa
OpenGL for Embedded Systems Uprosczona wersja OpenGL dostosowana do urządzeń mobilnych 2003: specyfikacja OpenGL ES 1.0 ES 1.0-1.1 oparte na OpenGL 1.3-1.5 2007: specyfikacja OpenGL ES 2.0 ES 2.0 oparty na OpenGL 2.0 Dostępność na systemach operacyjnych: Android, ios, BlackBerry, Symbian Możliwość programowania w: Java, Objective-C, C, C++
Różnice pomiędzy OpenGL a OpenGL ES 1.0 brak glbegin i glend (konieczność korzystania z tablic wierzchołków i VBO) brak wsparcia dla Quadów (trzeba operować na trójkątach) brak display lists i wiele innych braków...
Różnice pomiędzy OpenGL ES 1.1 a ES 2.0 Rezygnacja ze stałego potoku renderowania (konieczność stosowania shaderów nawet w najprostszych aplikacjach). Mniej uproszczeń związanych z transformacją widoku (w tym glpush i glpop, trzeba samemu wyliczać macierze transformacji i przekazywać je do shaderów.
Porównanie OpenGL z OpenGL ES 2.0 Zmiana jednego z atrybutów sceny i wyczyszczenie jej tła: // OpenGL (JOGL) // Włączenie trybu testowania głębokości. gl.glenable(gl2.gl_depth_test); // Wyczyszczenie sceny. gl.glclearcolor(0.0f, 0.0f, 0.0f, 0.0f); gl.glclear(gl2.gl_color_buffer_bit GL2.GL_DEPTH_BUFFER_BIT); // OpenGL ES (Android) // Włączenie trybu testowania głębokości. GLES20.glEnable(GLES20.GL_DEPTH_TEST); // Wyczyszczenie sceny. GLES20.glClearColor(0f, 0f, 0f, 0f);
Porównanie OpenGL z OpenGL ES 2.0 Transformacje macierzy: // OpenGL (JOGL) // Wybór macierzy widoku modelu. gl.glmatrixmode(gl2.gl_modelview); // Przypisanie aktualnej macierzy wartości macierzy jednostowej. gl.glloadidentity(); // Dokonanie transformacji. gl.gltranslatef(x, y, z); // OpenGL ES (Android) // Przypisanie macierzy widoku modelu wartości macierzy jednostkowej. Matrix.setIdentityM(mModelMatrix, 0); // Dokonanie transformacji. Matrix.translateM(mModelMatrix, x, y, z);
Zmienne predefiniowane W OpenGL istnieje wiele predefiniowanych zmiennych dostępnych w shaderach: attribute vec4 gl_color; attribute vec3 gl_normal; attribute vec4 gl_vertex; attribute vec4 gl_multitexcoord0; uniform mat4 gl_modelviewprojectionmatrix; W OpenGL ES 2.0 wszystkie zmienne wykorzystywane w shaderach muszą być odpowiednio zadeklarowane...
Przekazywanie zmiennych do shaderów // Vertex Shader uniform mat4 umvpmatrix; // odpowiednik gl_modelviewprojectionmatrix attribute vec4 aposition; // odpowiednik gl_vertex void main() { gl_position = umvpmatrix * aposition; } // Rysowanie obiektu int mpositionhandle = GLES20.glGetAttribLocation(mProgram, "aposition"); GLES20.glEnableVertexAttribArray(mPositionHandle); GLES20.glVertexAttribPointer(mPositionHandle, 3, GLES20.GL_FLOAT, false, 0, vertexbuffer); int mmvphandle = GLES20.glGetUniformLocation(mProgram, "umvpmatrix"); GLES20.glUniformMatrix4fv(mMVPHandle, 1, false, mvpmatrix, 0); GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, vertexcount); GLES20.glDisableVertexAttribArray(mPositionHandle);
Web Graphics Library (WebGL) API oparte na OpenGL ES w wersji 2.0 renderowanie w obiekcie Canvas dostępnym w standardzie HTML5 działa na wielu różnych platformach (również mobilnych) oferuje sprzętową akcelerację nie wymaga żadnych wtyczek do przeglądarek (w przeciwieństwie do VRML i X3D)
Dostępność WebGL Na komputerach stacjonarnych: Chrome, Firefox, Opera, Safari, Internet Explorer Na urządzeniach mobilnych: Chrome, Firefox for Mobile, Opera Mobile (Android), Internet Explorer, Blackberry Playbook,...
Przykład Stworzenie kontekstu WebGL: var canvas = document.getelementbyid("nazwa_elementu_canvas"); var gl; try { gl = canvas.getcontext("webgl"); gl.viewportwidth = canvas.width; gl.viewportheight = canvas.height; } catch (e) { } if (!gl) { alert("błąd inicjalizacji WebGL."); }
Przykład (2) VBO (z użyciem biblioteki glmatrix): var trianglevertexpositionbuffer = gl.createbuffer(); gl.bindbuffer(gl.array_buffer, trianglevertexpositionbuffer); var vertices = [ 0.0, 1.0, 0.0, 1.0,-1.0, 0.0, 1.0,-1.0, 0.0 ]; gl.bufferdata(gl.array_buffer, new Float32Array(vertices), gl.static_draw); trianglevertexpositionbuffer.itemsize = 3; trianglevertexpositionbuffer.numitems = 3; var trianglevertexcolorbuffer = gl.createbuffer(); gl.bindbuffer(gl.array_buffer, trianglevertexcolorbuffer); var colors = [ 1.0, 0.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0 ]; gl.bufferdata(gl.array_buffer, new Float32Array(colors), gl.static_draw); trianglevertexcolorbuffer.itemsize = 4; trianglevertexcolorbuffer.numitems = 3;
Przykład (3) Rysowanie (z użyciem biblioteki glmatrix): var mvmatrix = mat4.create(); var pmatrix = mat4.create(); gl.viewport(0, 0, gl.viewportwidth, gl.viewportheight); gl.clear(gl.color_buffer_bit gl.depth_buffer_bit); mat4.perspective(45, gl.viewportwidth / gl.viewportheight, 0.1, 100.0, pmatrix); mat4.identity(mvmatrix); mat4.translate(mvmatrix, [-1.5, 0.0, -7.0]); gl.bindbuffer(gl.array_buffer, trianglevertexpositionbuffer); gl.vertexattribpointer(shaderprogram.vertexpositionattribute, trianglevertexpositionbuffer.itemsize, gl.float, false, 0, 0); gl.bindbuffer(gl.array_buffer, trianglevertexcolorbuffer); gl.vertexattribpointer(shaderprogram.vertexcolorattribute, trianglevertexcolorbuffer.itemsize, gl.float, false, 0, 0); setmatrixuniforms(); gl.drawarrays(gl.triangles, 0, trianglevertexpositionbuffer.numitems);
Silniki 3D oparte na WebGL J3D Three.js SpiderGL Babylon.js
Dziękuję za uwagę