OpenGL ES 2.0 na Androidu
Projekt

Aplikacija na kraju prikazuje kocku kojoj se rotira i kojoj je svaki vrh druga boja, te pomoću menu-a se može pogledati drugi primjer vjetromjera. Proći ću samo kroz najvažnije stvari koje sam radio u projektu, te kroz one koje su mi stvarali probleme prilikom izrade i za koje i nema previše primjera na internetu.

Kreiranje projekta

Kreiranje aplikacije u IDE-u, u ovome slučaju se koristi Eclipse:

- New Project -> Android -> Android Application Project te se popune prazna polja. Pod minimal sdk se stavi 2.3.3. jer je to prva verzija Androida koja potpuno podržava OpenGL ES 2.0 -> npr. kao na slici
- Na sve ostalo se može ostati kako je predložene i kliktati Next do kraja

Kreiranje projekta
Osnovne stvari o programiranju OpenGL ES za Android

Dvije su fundamentalne klase u Android framework-u koja nam dopuštaju kreiranje i manipuliranje grafikom pomoću OpenGL ES API-a:

- GlSurfaceView: je View klasa u kojoj možemo crtati i manipulirati objektima koristeći OpenGL API pozive. Pomoću ove klase konfiguriramo i renderiramo display i slično. Koristi se tako da se napravi instanca ove klase i poveže sa Renderer instancom.
- GlSurfaceView.Renderer: sučelje koje definira metode potrebne za crtanje grafike u GlSurfaceView. Potrebno je implementirati ovo sučelje u odvojenoj klasi i onda ju povezati sa GlSurfaceView pomoću metode GlSurfaceView.setRenderer()
. Tri su metode koje je potrebno implementirati:

-> onSurfaceCreated(): poziva se jednom kada se kreira GLSurfaceView.
-> onDrawFrame(): poziva se prilikom svakog iscrtavanja GLSurfaceView.
-> onSurfaceChanged(): poziva se kada se promijeni geometrija GLSurfaceView-a, kao što su promjene u veličini GLSurfaceViewa ili promjena orijentacije ekrana uređaja.

Dodavanje u AndroidManifest.xml koju verziju OpenGL ES API-a će podržavati aplikacija. Pa će tako za ES 2.0 biti potrebno dodati sljedeće:

<!-- Tell the system this app requires OpenGL ES 2.0. --> <uses-feature android:glEsVersion="0x00020000" android:required="true" /> Sa ovime smo rekli da samo uređaji koji podržavaju OpenGL ES 2.0 mogu instalirati ovu aplikaciju.

Sada se još na početak svake klase u kojoj se žele koristiti mogućnosti ES 2.0 treba ubaciti sljedeće: import android.opengl.GLES20;

Shader-i

Crtanje određenog objekta zahtijeva iznenađujuće puno koda, jer se mora pružiti jako puno detalja za grafičko renderiranje cjevovoda. Moramo definirati sljedeće:

- Vertex Shader: kod za renderiranje vrhova objekta
- Fragment Shader: kod za renderiranje boje i tekstura za objekt
- Program: objekt koji sadrži shader-e koji se žele koristiti za crtanje objekata

U nastavku slijedi primjer osnovnih shader-a:

private final String vertexShaderCode = "uniform mat4 uMVPMatrix;" + "attribute vec4 vPosition;" + "void main() {" + " gl_Position = uMVPMatrix * vPosition;" + "}"; private final String fragmentShaderCode = "precision mediump float;" + "uniform vec4 vColor;" + "void main() {" + " gl_FragColor = vColor;" + "}"; Ti se stringovi moraju kompajlirati kako bi se objekti mogli prikazati. I to se radi na sljedeći način (primjer za vertex shader): // create a vertex shader type (GLES20.GL_VERTEX_SHADER) // or a fragment shader type (GLES20.GL_FRAGMENT_SHADER) int shader = GLES20.glCreateShader(type); // add the source code to the shader and compile it GLES20.glShaderSource(shader, vertexShaderCode); GLES20.glCompileShader(shader); Daljnja važna stavka je povezivanje shader-a sa programom kako bi on znao koje će koristiti: // create empty OpenGL Program mProgram = GLES20.glCreateProgram(); // add the vertex shader to program GLES20.glAttachShader(mProgram, vertexShader); // create OpenGL program executable GLES20.glLinkProgram(mProgram); // This is in the draw method. // Add program to OpenGL environment GLES20.glUseProgram(mProgram); U nastavku ću se vratiti na shader-e, kada budem prikazivao kako se definira boja za svaki vrh posebno.

Objekti

Kreiranje objekata i nije takom jednostavno kako se čini, ali proći ću kroz neke glavne dijelove. Svaki vrh trebamo definirati u jednom polju koje ćemo naknadno predati programu kako bi ih mogao nacrtati. Za neke objekte za koje znamo koliko nam treba vrhova to nije problem, jer odmah znamo koliko nam veliko polje treba biti. Dok za neke objekte kao što je valjak, ipak ne znamo koliko će nam vrhova trebati da ga nacrtamo. U nastavku ću pokazati kako sam punio vrhove za bazu valjka.

float step = (float) (2.0 * Math.PI) / num; for (float i = 0; i <= 2 * Math.PI + step; i += step) { float a = (float) Math.cos(i) * r; float b = (float) Math.sin(i) * r; if (a == r || a == -r) { b = 0.0f; } objectCoordsList.add(a); objectCoordsList.add(h); objectCoordsList.add(b); }Kada sve elemente iz liste prebacimo u polje (objectOrders), slijedi sljedeći dio u kojem moramo odrediti kojim se redom spajaju vrhovi. Što je kod nas lako napraviti, jer se spajaju onim redom kojim su i upisani. Kada imamo i to riješeno moramo ta dva polja povezati sa buffer-ima koje dajemo programu da ih ispiše. // initialize vertex byte buffer for shape coordinates // multiply with 4 because our vertices have 4 number which // describes them ByteBuffer bb = ByteBuffer.allocateDirect(objectCoords.length * 4); bb.order(ByteOrder.nativeOrder()); vertexBuffer = bb.asFloatBuffer(); vertexBuffer.put(objectCoords); vertexBuffer.position(0); Sada u draw metodi povežemo pozicije iz vertex shadera-a, sa vertexBuffer-om: // get handle to vertex shader's vPosition member mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition"); // Enable a handle to the triangle vertices GLES20.glEnableVertexAttribArray(mPositionHandle); // Prepare the triangle coordinate data GLES20.glVertexAttribPointer(mPositionHandle, COORDS_PER_VERTEX, GLES20.GL_FLOAT, false, vertexStride, vertexBuffer); Povežemo i boju sa objektom: // get handle to fragment shader's vColor member mColorHandle = GLES20.glGetUniformLocation(mProgram, "vColor"); // Set color for drawing the triangle GLES20.glUniform4fv(mColorHandle, 1, color, 0); Te na kraju iscrtamo objekt pomoću nekih od draw metoda, kao što je npr glDrawElements: GLES20.glDrawElements(GLES20.GL_TRIANGLE_FAN, drawOrder.length, GLES20.GL_UNSIGNED_SHORT, drawListBuffer); Baza valjkaPlašt valjkaStozac

Postavljanje boja u shader-ima

Kako OpenGL ES 2.0 koristi shader-e za renderiranje objekata, to se može iskoristiti kako bi svakome vrhu dodijelili posebnu boju koje interpolira sa susjednom bojom. I to radimo tako da svaku boju dodamo također u jedno polje koje kasnije predajemo programu kako bi znao što iscrtati. Na gotovo isti nači kao što zna di su pozicije vrhova. Ali krenimo redom. Prve preinake su na vertex shader-u.

private final String vertexShaderCode = "uniform mat4 uMVPMatrix;" + "attribute vec4 vPosition;" + "attribute vec4 aColor;" + "varying vec4 vColor;" + "void main() {" + " gl_Position = uMVPMatrix * vPosition;" + " vColor = aColor;" + "}";Sada u draw metodi kao što je gore već objašnjeno povežemo atribut aColor iz vertex shader-a sa programom kak bi znao koje sve boje treba korisiti: mColorHandle = GLES20.glGetAttribLocation(mProgram, "aColor"); GLES20.glEnableVertexAttribArray(mPositionHandle); GLES20.glVertexAttribPointer(mColorHandle, COORDS_PER_COLOR, GLES20.GL_FLOAT, false, colorStride, colorBuffer); Te maknemo prethodni dio koda (naveden u dijelu Objekti) koji je određivao boju i to je to.

Kocka boje Kocka boje2
Projekcija

Još jedna vrlo važna stvar je ostala ne pojašnjena, a to je projekcija objekta. Točnije matrice projekcije i transformacije koje utječu na objekt. Kao što ste već mogli vidjeti u vertex shaer-u je definirana jedna martica:

uniform mat4 uMVPMatrix; Te se ona množi sa vPosition kako bi dobili konačno vrijednost pozicije vrha: gl_Position = uMVPMatrix * vPosition; Osim ovog dijela u draw metodi je potrebno doći do te matrice i povezati je sa matricom transformacije koju šaljemo iz Renderera: // get handle to shape's transformation matrix mMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix"); // Apply the projection and view transformation GLES20.glUniformMatrix4fv(mMVPMatrixHandle, 1, false, mvpMatrix, 0); U nastavku slijede dijelovi koda koje je potrebno implementirati u Rendereru iz kojeg matricu transformacije šaljemo u draw metodu svakog objekta. Sve se transformacije moraju napraviti u određenom redoslijedu kako bi dobili na kraju ono što želimo. Evo jedan primjer, na kojem će biti jasnije: // translate the cylinder up the y axis a bit higher then the height of // the cone, the rotate it for 90 degrees on the z axis and add an // rotation over the x axis Matrix.setIdentityM(mModelMatrix, 0); Matrix.translateM(mModelMatrix, 0, 0, 0.8f, 0); Matrix.setRotateM(mRotationMatrix, 0, -90, 0, 0, 1.0f); mTempMatrix = mModelMatrix.clone(); Matrix.multiplyMM(mModelMatrix, 0, mTempMatrix, 0, mRotationMatrix, 0); mTempMatrix = mMVPMatrix.clone(); Matrix.multiplyMM(mMVPMatrix, 0, mTempMatrix, 0, mModelMatrix, 0); Matrix.setRotateM(mRotationMatrix, 0, mAngle, 1.0f, 0, 0); mTempMatrix = mMVPMatrix.clone(); Matrix.multiplyMM(mMVPMatrix, 0, mTempMatrix, 0, mRotationMatrix, 0); mCylinder.draw(mMVPMatrix, colorWhite);Ako želimo da nam se objekt prvo translatira pa rotira oko neke osi, onda ih tako i moramo množiti. To zna dosta zakomplicirati stvari, ali kada se dovoljno uđe u "štos" nema većih problema. Posebno treba paziti da novokreirana polja (matrice) postaviti na 0. Kloniranje matrica u koje želimo da bude rezultat je sigurniji način i primjer dobre prakse.

Ostale postavke

Bilo bi dobro navesti koje je stvari (dobro) potrebno imati u onDrawFrame metodi u Rendereru. A to su pozivi metoda glClear i postavljanje pozcije kamere sa setLookAtM:

// Draw background color (Clear buffer from all other colors) GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT); // Set the camera position (View matrix) Matrix.setLookAtM(mViewMatrix, 0, xPosition, yPostion, zPosition, 0f, 0f, 0f, 0f, 1.0f, 0.0f); Dok se kamera projekcija (frustum) navodi u onSurfaceChanged metodi: // this projection matrix is applied to object coordinates // in the onDrawFrame() method Matrix.frustumM(mProjectionMatrix, 0, -ratio, ratio, -1, 1, 3, 9); Na kraju je korišteno sve što je objašnjeno na ovoj stranici kako bi se izradio vjetromjer kao što je prikazano na slici.

Vjetromjer
Kontakt
Email: drazen.bandic@foi.hr