Come utilizzare OpenGL ES nelle app Android

Quasi tutti i telefoni Android disponibili sul mercato oggi dispongono di un'unità di elaborazione grafica o di una GPU in breve. Come suggerisce il nome, questa è un'unità hardware dedicata alla gestione dei calcoli che sono solitamente correlati alla grafica 3D. Come sviluppatore di app, puoi utilizzare la GPU per creare grafica e animazioni complesse che funzionano a frame rate molto alti.

Al momento sono disponibili due diverse API che puoi utilizzare per interagire con la GPU di un dispositivo Android: Vulkan e OpenGL ES. Mentre Vulkan è disponibile solo su dispositivi con Android 7.0 o versioni successive, OpenGL ES è supportato da tutte le versioni di Android.

In questo tutorial, ti aiuterò a iniziare a utilizzare OpenGL ES 2.0 nelle app Android.

Prerequisiti

Per poter seguire questo tutorial, avrai bisogno di:

  • l'ultima versione di Android Studio
  • un dispositivo Android che supporta OpenGL ES 2.0 o versioni successive
  • una versione recente di Blender o qualsiasi altro software di modellazione 3D

1. Che cos'è OpenGL ES?

OpenGL, che è l'abbreviazione di Open Graphics Library, è un'API indipendente dalla piattaforma che consente di creare grafica 3D con accelerazione hardware. OpenGL ES, abbreviazione di OpenGL per Embedded Systems, è un sottoinsieme dell'API.

OpenGL ES è un'API di livello molto basso. In altre parole, non offre alcun metodo che ti permetta di creare o manipolare rapidamente oggetti 3D. Invece, mentre si lavora con esso, ci si aspetta che tu gestisca manualmente compiti come la creazione dei singoli vertici e facce di oggetti 3D, il calcolo di varie trasformazioni 3D e la creazione di diversi tipi di shader.

Vale anche la pena ricordare che Android SDK e NDK insieme consentono di scrivere codice relativo a OpenGL ES in Java e C.

2. Impostazione del progetto

Poiché le API OpenGL ES fanno parte del framework Android, non è necessario aggiungere alcuna dipendenza al progetto per poterle utilizzare. In questo tutorial, tuttavia, utilizzeremo la libreria IO di Apache Commons per leggere il contenuto di alcuni file di testo. Pertanto, aggiungilo come a compilare dipendenza dal modulo dell'app build.gradle file:

compilare 'commons-io: commons-io: 2.5'

Inoltre, per impedire agli utenti di Google Play che non dispongono di dispositivi che supportano la versione OpenGL ES, è necessario installare l'app, aggiungere quanto segue tag al file manifest del tuo progetto:

3. Crea una tela

Il framework Android offre due widget che possono fungere da canvas per la tua grafica 3D: GLSurfaceView e TextureView. La maggior parte degli sviluppatori preferisce l'utilizzo GLSurfaceView, e scegliere TextureView solo quando intendono sovrapporre la loro grafica 3D a un'altra vista widget di. Per l'app che creeremo in questo tutorial, GLSurfaceView sarà sufficiente.

Aggiungere un GLSurfaceView il widget per il file di layout non è diverso dall'aggiunta di altri widget.

Nota che abbiamo reso la larghezza del nostro widget uguale alla sua altezza. Ciò è importante perché il sistema di coordinate OpenGL ES è un quadrato. Se è necessario utilizzare una tela rettangolare, ricordarsi di includere le proporzioni durante il calcolo della matrice di proiezione. Imparerai cosa è una matrice di proiezione in un secondo momento.

Inizializzazione a GLSurfaceView widget all'interno di Attività la classe è semplice come chiamare il findViewById () metodo e passando il suo id ad esso.

mySurfaceView = (GLSurfaceView) findViewById (R.id.my_surface_view);

Inoltre, dobbiamo chiamare il setEGLContextClientVersion () metodo per specificare esplicitamente la versione di OpenGL ES che useremo per disegnare all'interno del widget.

mySurfaceView.setEGLContextClientVersion (2);

4. Creare un oggetto 3D

Sebbene sia possibile creare oggetti 3D in Java codificando manualmente le coordinate X, Y e Z di tutti i loro vertici, farlo è molto macchinoso. L'utilizzo di strumenti di modellazione 3D è invece molto più semplice. Blender è uno di questi strumenti. È open source, potente e molto facile da imparare.

Accendi Blender e premi X per eliminare il cubo predefinito. Quindi, premere Shift-A e selezionare Maglia> Torus. Ora abbiamo un oggetto 3D abbastanza complesso composto da 576 vertici.

Per poter utilizzare il toro nella nostra app per Android, dobbiamo esportarlo come file OBJ di Wavefront. Pertanto, andare a File> Esporta> Wavefront (.obj). Nella schermata successiva, dai un nome al file OBJ, assicurati che il file Triangulate Faces e Mantieni ordine dei vertici le opzioni sono selezionate e premere il tasto Esporta OBJ pulsante.

Ora puoi chiudere Blender e spostare il file OBJ nei tuoi progetti di Android Studio risorse cartella.

5. Analizzare il file OBJ

Se non l'hai già notato, il file OBJ che abbiamo creato nel passaggio precedente è un file di testo, che può essere aperto utilizzando qualsiasi editor di testo.

Nel file, ogni riga che inizia con una "v" rappresenta un singolo vertice. Allo stesso modo, ogni riga che inizia con una "f" rappresenta una singola faccia triangolare. Mentre ogni linea di vertici contiene le coordinate X, Y e Z di un vertice, ciascuna linea di fronte contiene gli indici di tre vertici, che insieme formano una faccia. Questo è tutto ciò che devi sapere per analizzare un file OBJ.

Prima di iniziare, creare una nuova classe Java chiamata Torus e aggiungi due Elenco oggetti, uno per i vertici e uno per i volti, come le sue variabili membro.

Torus di classe pubblica Lista privata verticesList; Elenco privato facesList; public Torus (Context context) verticesList = new ArrayList <> (); facesList = new ArrayList <> (); // Più codice va qui

Il modo più semplice per leggere tutte le singole righe del file OBJ è utilizzare il comando Scanner classe e il suo nextLine () metodo. Durante il looping delle linee e il popolamento dei due elenchi, è possibile utilizzare il comando Stringa La classe di inizia con() metodo per verificare se la linea corrente inizia con una "v" o una "f".

// Apre il file OBJ con Scanner Scanner scanner = new Scanner (context.getAssets (). Open ("torus.obj")); // Passa in rassegna tutte le sue linee mentre (scanner.hasNextLine ()) String line = scanner.nextLine (); if (line.startsWith ("v")) // Aggiungi la linea vertice alla lista dei vertici verticesList.add (linea);  else if (line.startsWith ("f")) // Aggiungi la linea del viso alla lista delle facce facesList.add (linea);  // Chiudi lo scanner scanner.close (); 

6. Creare oggetti buffer

Non è possibile passare direttamente gli elenchi di vertici e facce ai metodi disponibili nell'API OpenGL ES. È necessario prima convertirli in oggetti buffer. Per memorizzare i dati delle coordinate del vertice, avremo bisogno di a FloatBuffer oggetto. Per i dati del volto, che consiste semplicemente di indici dei vertici, a ShortBuffer l'oggetto sarà sufficiente.

Di conseguenza, aggiungere le seguenti variabili membro a Torus classe:

private FloatBuffer verticesBuffer; ShortBuffer privato facesBuffer;

Per inizializzare i buffer, dobbiamo prima creare a ByteBuffer oggetto usando il allocateDirect () metodo. Per il buffer dei vertici, allocare quattro byte per ogni coordinata, con le coordinate come numeri in virgola mobile. Una volta il ByteBuffer oggetto è stato creato, puoi convertirlo in a FloatBuffer chiamando il suo asFloatBuffer () metodo.

// Crea buffer per vertici ByteBuffer buffer1 = ByteBuffer.allocateDirect (verticesList.size () * 3 * 4); buffer1.order (ByteOrder.nativeOrder ()); verticesBuffer = buffer1.asFloatBuffer ();

Allo stesso modo, creane un altro ByteBuffer oggetto per il buffer delle facce. Questa volta, allocare due byte per ogni indice dei vertici perché gli indici sono corto senza firma letterali. Inoltre, assicurati di utilizzare il asShortBuffer () metodo per convertire il ByteBuffer oggetto a ShortBuffer.

// Crea buffer per facce ByteBuffer buffer2 = ByteBuffer.allocateDirect (facesList.size () * 3 * 2); buffer2.order (ByteOrder.nativeOrder ()); facesBuffer = buffer2.asShortBuffer ();

Il popolamento del buffer dei vertici implica il looping dei contenuti di verticesList, estraendo le coordinate X, Y e Z da ciascun elemento e chiamando il tasto mettere() metodo per mettere i dati all'interno del buffer. Perché verticesList contiene solo stringhe, dobbiamo usare il parseFloat () per convertire le coordinate da stringhe a galleggiante valori.

for (String vertice: verticesList) String coords [] = vertex.split (""); // Dividi per spazio float x = Float.parseFloat (coords [1]); float y = Float.parseFloat (coords [2]); float z = Float.parseFloat (coords [3]); verticesBuffer.put (x); verticesBuffer.put (y); verticesBuffer.put (z);  verticesBuffer.position (0);

Nota che nel codice sopra abbiamo usato il posizione() metodo per ripristinare la posizione del buffer.

Il popolamento del buffer delle facce è leggermente diverso. Devi usare il parseShort () metodo per convertire ciascun indice dei vertici in un valore breve. Inoltre, poiché gli indici partono da uno anziché da zero, è necessario ricordare di sottrarre uno da essi prima di inserirli nel buffer.

for (String face: facesList) String vertexIndices [] = face.split (""); short vertex1 = Short.parseShort (vertexIndices [1]); short vertex2 = Short.parseShort (vertexIndices [2]); short vertex3 = Short.parseShort (vertexIndices [3]); facesBuffer.put ((short) (vertex1 - 1)); facesBuffer.put ((short) (vertex2 - 1)); facesBuffer.put ((short) (vertex3 - 1));  facesBuffer.position (0);

7. Crea shader

Per essere in grado di rendere il nostro oggetto 3D, dobbiamo creare uno shader di vertici e uno shader di frammenti per esso. Per ora, puoi pensare a uno shader come un programma molto semplice scritto in un linguaggio simile a C chiamato OpenGL Shading Language o GLSL in breve.

Un vertex shader, come avrai intuito, è responsabile della gestione dei vertici di un oggetto 3D. Un framment shader, chiamato anche pixel shader, è responsabile della colorazione dei pixel dell'oggetto 3D.

Passaggio 1: creare uno shader di vertici

Crea un nuovo file chiamato vertex_shader.txt all'interno del tuo progetto res / raw cartella.

Un vertex shader deve avere un attributo variabile globale al suo interno per ricevere i dati di posizione dei vertici dal proprio codice Java. Inoltre, aggiungere un uniforme variabile globale per ricevere una matrice di proiezione della vista dal codice Java.

Dentro il principale() funzione del vertex shader, è necessario impostare il valore di GL_POSITION, una variabile integrata GLSL che decide la posizione finale del vertice. Per ora, puoi semplicemente impostare il suo valore sul prodotto di uniforme e attributo variabili globali.

Di conseguenza, aggiungi il seguente codice al file:

attributo posizione vec4; matrice mat4 uniforme; void main () gl_Position = matrix * position; 

Passaggio 2: crea un frammento shader

Crea un nuovo file chiamato fragment_shader.txt all'interno del tuo progetto res / raw cartella.

Per mantenere questo tutorial breve, ora creeremo uno shader di frammenti molto minimalista che assegna semplicemente il colore arancione a tutti i pixel. Per assegnare un colore a un pixel, all'interno di principale() funzione di un framment shader, puoi usare il gl_FragColor variabile integrata.

galleggiante mediocre di precisione; void main () gl_FragColor = vec4 (1, 0.5, 0, 1.0); 

Nel codice precedente, la prima riga che specifica la precisione dei numeri in virgola mobile è importante perché uno shader di frammenti non ha alcuna precisione predefinita per loro.

Passaggio 3: compilare gli shader

Indietro nel Torus classe, ora devi aggiungere codice per compilare i due shader che hai creato. Prima di farlo, tuttavia, è necessario convertirli da risorse non elaborate a stringhe. Il IOUtils la classe, che fa parte della libreria IO di Apache Commons, ha a accordare() metodo per fare proprio questo. Il seguente codice mostra come usarlo:

// Converti vertex_shader.txt in una stringa InputStream vertexShaderStream = context.getResources (). OpenRawResource (R.raw.vertex_shader); String vertexShaderCode = IOUtils.toString (vertexShaderStream, Charset.defaultCharset ()); vertexShaderStream.close (); // Converti fragment_shader.txt in una stringa InputStream fragmentShaderStream = context.getResources (). OpenRawResource (R.raw.fragment_shader); String fragmentShaderCode = IOUtils.toString (fragmentShaderStream, Charset.defaultCharset ()); fragmentShaderStream.close ();

Il codice degli shader deve essere aggiunto agli oggetti shader di OpenGL ES. Per creare un nuovo oggetto shader, usa il glCreateShader () metodo del GLES20 classe. A seconda del tipo di oggetto shader che vuoi creare, puoi passare GL_VERTEX_SHADER o GL_FRAGMENT_SHADER ad esso. Il metodo restituisce un numero intero che funge da riferimento all'oggetto shader. Un oggetto shader appena creato non contiene alcun codice. Per aggiungere il codice shader all'oggetto shader, è necessario utilizzare glShaderSource () metodo.

Il codice seguente crea oggetti shader sia per il vertex shader che per il framment shader:

int vertexShader = GLES20.glCreateShader (GLES20.GL_VERTEX_SHADER); GLES20.glShaderSource (vertexShader, vertexShaderCode); int fragmentShader = GLES20.glCreateShader (GLES20.GL_FRAGMENT_SHADER); GLES20.glShaderSource (fragmentShader, fragmentShaderCode);

Ora possiamo passare gli oggetti shader al glCompileShader () metodo per compilare il codice che contengono.

GLES20.glCompileShader (Vertexshader); GLES20.glCompileShader (fragmentShader);

8. Crea un programma

Durante il rendering di un oggetto 3D, non si usano direttamente gli shader. Invece, li si collega a un programma e si utilizza il programma. Pertanto, aggiungere una variabile membro al Torus classe per memorizzare un riferimento a un programma OpenGL ES.

programma privato int;

Per creare un nuovo programma, usa il glCreateProgram () metodo. Per attaccare gli oggetti shader di vertice e di frammento, usa il glAttachShader () metodo.

programma = GLES20.glCreateProgram (); GLES20.glAttachShader (programma, vertexShader); GLES20.glAttachShader (programma, fragmentShader);

A questo punto, puoi collegare il programma e iniziare a usarlo. Per fare ciò, utilizzare il glLinkProgram () e glUseProgram () metodi.

GLES20.glLinkProgram (programma); GLES20.glUseProgram (programma);

9. Disegna l'oggetto 3D

Con gli shader ei buffer pronti, abbiamo tutto ciò che serve per disegnare il nostro toro. Aggiungi un nuovo metodo al Torus classe chiamata disegnare:

public void draw () // Il codice dell'illustrazione va qui

In una fase precedente, all'interno del vertex shader, abbiamo definito a posizione variabile per ricevere i dati di posizione dei vertici dal codice Java. È giunto il momento di inviare i dati di posizione dei vertici ad esso. Per fare ciò, dobbiamo prima ottenere un handle per il posizione variabile nel nostro codice Java usando il glGetAttribLocation () metodo. Inoltre, l'handle deve essere abilitato utilizzando il glEnableVertexAttribArray () metodo.

Di conseguenza, aggiungi il seguente codice all'interno di disegnare() metodo:

int position = GLES20.glGetAttribLocation (programma, "posizione"); GLES20.glEnableVertexAttribArray (posizione);

Puntare il posizione gestire il nostro buffer di vertici, dobbiamo usare il glVertexAttribPointer () metodo. Oltre al buffer dei vertici stesso, il metodo prevede il numero di coordinate per vertice, il tipo di coordinate e l'offset di byte per ciascun vertice. Perché abbiamo tre coordinate per vertice e ogni coordinata è a galleggiante, l'offset del byte deve essere 3 * 4.

GLES20.glVertexAttribPointer (posizione, 3, GLES20.GL_FLOAT, false, 3 * 4, verticiBuffer);

Il nostro vertex shader si aspetta anche una matrice di proiezione della vista. Sebbene tale matrice non sia sempre necessaria, l'utilizzo di una ti consente di avere un controllo migliore su come viene eseguito il rendering del tuo oggetto 3D.

Una matrice di proiezione della vista è semplicemente il prodotto delle matrici di visualizzazione e di proiezione. Una matrice di visualizzazione consente di specificare le posizioni della fotocamera e il punto che sta guardando. Una matrice di proiezione, d'altra parte, ti consente non solo di mappare il sistema di coordinate quadrate di OpenGL ES sullo schermo rettangolare di un dispositivo Android, ma anche di specificare i piani vicini e lontani del frustum di visualizzazione.

Per creare le matrici, puoi semplicemente crearne tre galleggiante matrici di dimensioni 16:

float [] projectionMatrix = new float [16]; float [] viewMatrix = new float [16]; float [] productMatrix = new float [16];

Per inizializzare la matrice di proiezione, è possibile utilizzare il frustumM () metodo del Matrice classe. Si aspetta la posizione dei piani sinistro, destro, inferiore, superiore, vicino e lontano. Poiché la nostra area di disegno è già un quadrato, puoi utilizzare i valori -1 e 1 per i piani sinistro e destro e inferiore e superiore. Per i piani di clip vicini e lontani, sentiti libero di sperimentare con valori diversi.

Matrix.frustumM (projectionMatrix, 0, -1, 1, -1, 1, 2, 9);

Per inizializzare la matrice di visualizzazione, utilizzare il setLookAtM () metodo. Si aspetta le posizioni della telecamera e il punto che sta guardando. Sei di nuovo libero di sperimentare con valori diversi.

Matrix.setLookAtM (viewMatrix, 0, 0, 3, -4, 0, 0, 0, 0, 1, 0);

Infine, per calcolare la matrice del prodotto, utilizzare il multiplyMM () metodo.

Matrix.multiplyMM (productMatrix, 0, projectionMatrix, 0, viewMatrix, 0);

Per passare la matrice del prodotto al vertex shader, devi ottenere un handle per il suo matrice variabile usando il glGetUniformLocation () metodo. Una volta ottenuto l'handle, è possibile puntarlo sulla matrice del prodotto utilizzando il glUniformMatrix () metodo.

int matrix = GLES20.glGetUniformLocation (programma, "matrice"); GLES20.glUniformMatrix4fv (matrice, 1, false, productMatrix, 0);

Devi aver notato che non abbiamo ancora usato il buffer delle facce. Ciò significa che non abbiamo ancora detto a OpenGL ES come collegare i vertici per formare triangoli, che serviranno come facce del nostro oggetto 3D.

Il glDrawElements () metodo consente di utilizzare il buffer facce per creare triangoli. Come argomenti, si aspetta il numero totale di indici dei vertici, il tipo di ciascun indice e il buffer delle facce.

GLES20.glDrawElements (GLES20.GL_TRIANGLES, facesList.size () * 3, GLES20.GL_UNSIGNED_SHORT, facesBuffer);

Infine, ricorda di disabilitare il attributo gestore abilitato in precedenza per passare i dati del vertice al vertex shader.

GLES20.glDisableVertexAttribArray (posizione);

10. Crea un renderer

Nostro GLSurfaceView il widget ha bisogno di a GLSurfaceView.Renderer oggetto di essere in grado di rendere grafica 3D. Puoi usare il setRenderer () associare un renderer con esso.

mySurfaceView.setRenderer (new GLSurfaceView.Renderer () // Più codice va qui);

Dentro il onSurfaceCreated () metodo del renderer, è necessario specificare la frequenza di rendering della grafica 3D. Per ora, rendiamo solo quando l'immagine 3D cambia. Per fare ciò, passa il RENDERMODE_WHEN_DIRTY costante al setRenderMode () metodo. Inoltre, inizializza una nuova istanza di Torus oggetto.

@Override public void onSurfaceCreated (GL10 gl10, EGLConfig eglConfig) mySurfaceView.setRenderMode (GLSurfaceView.RENDERMODE_WHEN_DIRTY); torus = new Torus (getApplicationContext ()); 

Dentro il onSurfaceChanged () metodo del renderer, puoi definire la larghezza e l'altezza della tua vista usando il glViewport () metodo.

@Override public void onSurfaceChanged (GL10 gl10, int width, int height) GLES20.glViewport (0,0, larghezza, altezza); 

Dentro il onDrawFrame () metodo del renderer, aggiungere una chiamata al disegnare() metodo del Torus classe per disegnare effettivamente il toro.

@Override public void onDrawFrame (GL10 gl10) torus.draw (); 

A questo punto, puoi eseguire la tua app per vedere il toro arancione.

Conclusione

Ora sai come usare OpenGL ES nelle app Android. In questo tutorial, hai anche imparato come analizzare un file OBJ di Wavefront ed estrarre i dati di vertice e di faccia da esso. Ti suggerisco di generare altri oggetti 3D usando Blender e provare a renderli nell'app.

Sebbene ci concentriamo solo su OpenGL ES 2.0, comprendiamo che OpenGL ES 3.x è retrocompatibile con OpenGL ES 2.0. Ciò significa che se preferisci utilizzare OpenGL ES 3.x nella tua app, puoi semplicemente sostituire il file GLES20 classe con il GLES30 o GLES31 classi.

Per saperne di più su OpenGL ES, è possibile fare riferimento alle sue pagine di riferimento. E per saperne di più sullo sviluppo di app per Android, assicurati di dare un'occhiata ad alcuni dei nostri tutorial qui su Envato Tuts+!

  • Come iniziare con il kit di sviluppo nativo di Android

    Con il lancio di Android Studio 2.2, lo sviluppo di applicazioni Android che contengono codice C ++ è diventato più facile che mai. In questo tutorial, ti mostrerò come ...
    Ashraff Hathibelagal
    androide
  • Android Things: input / output periferici

    Android Things ha una capacità unica di connettersi facilmente ai componenti elettronici esterni con l'API Peripheral e il supporto dei dispositivi integrato. In questo articolo…
    Paul Trebilcox-Ruiz
    SDK Android
  • Come proteggere un'app per Android

    In questo articolo, daremo un'occhiata ad alcune delle migliori pratiche che è possibile seguire per creare un'app Android sicura. Questo significa un'app che non perde ...
    Ashraff Hathibelagal
    androide
  • Codifica un'app per Android con Flutter e Dart

    Flutter di Google è un framework di sviluppo di app multipiattaforma che utilizza il linguaggio di programmazione Dart. In questo tutorial, ti presenterò le basi di ...
    Ashraff Hathibelagal
    androide