OpenGL ES gebruiken in Android-apps

Bijna elke Android-telefoon die momenteel op de markt beschikbaar is, heeft een grafische verwerkingseenheid of kortweg GPU. Zoals de naam al doet vermoeden, is dit een hardware-eenheid speciaal voor het verwerken van berekeningen die meestal gerelateerd zijn aan 3D-afbeeldingen. Als app-ontwikkelaar kunt u de GPU gebruiken om complexe afbeeldingen en animaties te maken die worden uitgevoerd met zeer hoge framesnelheden.

Er zijn momenteel twee verschillende API's die u kunt gebruiken om te communiceren met de GPU van een Android-apparaat: Vulkan en OpenGL ES. Hoewel Vulkan alleen beschikbaar is op apparaten met Android 7.0 of hoger, wordt OpenGL ES door alle Android-versies ondersteund.

In deze zelfstudie help ik u aan de slag met het gebruik van OpenGL ES 2.0 in Android-apps.

voorwaarden

Als je deze zelfstudie wilt kunnen volgen, heb je het volgende nodig:

  • de nieuwste versie van Android Studio
  • een Android-apparaat dat OpenGL ES 2.0 of hoger ondersteunt
  • een recente versie van Blender, of een andere 3D-modelleringssoftware

1. Wat is OpenGL ES?

OpenGL, wat een afkorting is voor Open Graphics Library, is een platformonafhankelijke API waarmee u hardwareversnelde 3D-afbeeldingen kunt maken. OpenGL ES, kort voor OpenGL voor Embedded Systems, is een subset van de API.

OpenGL ES is een zeer low-level API. Met andere woorden, het biedt geen methoden waarmee u snel 3D-objecten kunt maken of manipuleren. In plaats daarvan wordt van u verwacht dat u tijdens het werken handmatige taken beheert, zoals het maken van de afzonderlijke hoekpunten en vlakken van 3D-objecten, het berekenen van verschillende 3D-transformaties en het maken van verschillende soorten shaders..

Het is ook vermeldenswaard dat de Android SDK en NDK samen toestaan ​​om OpenGL ES-gerelateerde code te schrijven in zowel Java als C.

2. Projectinstellingen

Omdat de OpenGL ES-API's deel uitmaken van het Android-framework, hoeft u geen afhankelijkheden aan uw project toe te voegen om deze te kunnen gebruiken. In deze zelfstudie gebruiken we echter de Apache Commons IO-bibliotheek om de inhoud van enkele tekstbestanden te lezen. Voeg het daarom toe als een compileren afhankelijkheid in uw app-modules build.gradle het dossier:

compileer 'commons-io: commons-io: 2.5'

Om Google Play-gebruikers te blokkeren die geen apparaten hebben die de OpenGL ES-versie ondersteunen die u nodig hebt om uw app te installeren, voegt u bovendien het volgende toe: tag aan het manifestbestand van uw project:

3. Maak een canvas

Het Android-framework biedt twee widgets die kunnen fungeren als canvas voor uw 3D-afbeeldingen: GLSurfaceView en TextureView. De meeste ontwikkelaars geven er de voorkeur aan GLSurfaceView, en kies TextureView alleen wanneer ze hun 3D-afbeeldingen op een ander willen weergeven Uitzicht widget. Voor de app die we in deze zelfstudie maken, GLSurfaceView volstaat.

Een toevoegen GLSurfaceView widget naar je layoutbestand verschilt niet van het toevoegen van een andere widget.

Merk op dat we de breedte van onze widget gelijk hebben gemaakt aan de hoogte. Dit is belangrijk omdat het OpenGL ES-coördinatensysteem een ​​vierkant is. Als u een rechthoekig canvas moet gebruiken, vergeet dan niet om de hoogte / breedteverhouding ervan op te nemen tijdens het berekenen van uw projectiematrix. Je zult leren wat een projectiematrix in een latere stap is.

Initialiseren van een GLSurfaceView widget binnen een Activiteit klasse is zo simpel als het aanroepen van de findViewById () methode en het doorgeven van zijn id ernaar.

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

Bovendien moeten we de setEGLContextClientVersion () methode om expliciet de versie van OpenGL ES te specificeren die we zullen gebruiken om in de widget te tekenen.

mySurfaceView.setEGLContextClientVersion (2);

4. Maak een 3D-object

Hoewel het mogelijk is om 3D-objecten in Java te maken door de X-, Y- en Z-coördinaten van al hun hoekpunten met de hand te coderen, is dit erg omslachtig. Het gebruik van 3D-modelleringstools is veel eenvoudiger. Blender is zo'n tool. Het is open source, krachtig en heel gemakkelijk te leren.

Start Blender op en druk op X om de standaardkubus te verwijderen. Druk vervolgens op Shift-A en selecteer Mesh> Torus. We hebben nu een redelijk complex 3D-object bestaande uit 576 hoekpunten.

Om de torus in onze Android-app te kunnen gebruiken, moeten we deze exporteren als een Wavefront-OBJ-bestand. Ga daarom naar Bestand> Exporteren> Wavefront (.obj). In het volgende scherm geef je een naam aan het OBJ-bestand, zorg je ervoor dat de Triangulate Faces en Vertex-volgorde behouden opties zijn geselecteerd en druk op de OBJ exporteren knop.

U kunt Blender nu sluiten en het OBJ-bestand naar uw Android Studio-projecten verplaatsen middelen map.

5. Ontleed het OBJ-bestand

Als u dat nog niet hebt gemerkt, is het OBJ-bestand dat we in de vorige stap hebben gemaakt een tekstbestand dat met elke teksteditor kan worden geopend.

In het bestand vertegenwoordigt elke regel die begint met een "v" een enkele hoekpunt. Op dezelfde manier vertegenwoordigt elke regel die begint met een "f" een enkel driehoekig vlak. Terwijl elke hoeklijn de X-, Y- en Z-coördinaten van een hoekpunt bevat, bevat elke vlaklijn de indexen van drie hoekpunten, die samen een vlak vormen. Dat is alles wat u moet weten om een ​​OBJ-bestand te ontleden.

Maak voordat u begint een nieuwe Java-klasse met de naam Torus en voeg er twee toe Lijst objecten, een voor de hoekpunten en een voor de vlakken, als de ledvariabelen.

publieke klasse Torus privélijst verticesList; privélijst facesList; public Torus (Context-context) verticesList = new ArrayList <> (); facesList = new ArrayList <> (); // Meer code gaat hier

De eenvoudigste manier om alle individuele regels van het OBJ-bestand te lezen is om de Scanner klasse en zijn nextLine () methode. Terwijl u de regels doorloopt en de twee lijsten invult, kunt u de Draad klasse begint met() methode om te controleren of de huidige regel begint met een "v" of een "f".

// Open het OBJ-bestand met een Scanner Scanner-scanner = nieuwe scanner (context.getAssets (). Open ("torus.obj")); // Loop door alle lijnen terwijl (scanner.hasNextLine ()) String line = scanner.nextLine (); if (line.startsWith ("v")) // Voeg vertex-regel toe aan lijst met hoekpunten verticesList.add (regel);  else if (line.startsWith ("f")) // Gezichtslijn toevoegen aan gezichtenlijst facesList.add (regel);  // Sluit de scanner van de scanner.close (); 

6. Maak bufferobjecten

U kunt de lijsten met hoekpunten en vlakken niet direct doorgeven aan de methoden die beschikbaar zijn in de OpenGL ES API. U moet ze eerst omzetten in bufferobjecten. Om de coördinaten van de vertex op te slaan, hebben we een FloatBuffer voorwerp. Voor de gezichtsgegevens, die eenvoudig bestaat uit vertex-indexen, ShortBuffer object zal voldoende zijn.

Voeg de volgende lidvariabelen toe aan de Torus klasse:

private FloatBuffer verticesBuffer; privé ShortBuffer gezichtenBuffer;

Om de buffers te initialiseren, moeten we eerst een maken ByteBuffer object met behulp van de allocateDirect () methode. Voor de hoekpuntenbuffer wijst u vier bytes toe voor elke coördinaat, wat met de coördinaten drijvende kommagetallen zijn. Zodra de ByteBuffer object is gemaakt, u kunt het converteren naar een FloatBuffer door het te bellen asFloatBuffer () methode.

// Maak buffer voor hoekpunten ByteBuffer buffer1 = ByteBuffer.allocateDirect (verticesList.size () * 3 * 4); buffer1.order (ByteOrder.nativeOrder ()); verticesBuffer = buffer1.asFloatBuffer ();

Maak op dezelfde manier een andere ByteBuffer object voor de vlakbuffer. Deze keer, alloceer twee bytes voor elke vertex-index omdat de indexen dat zijn unsigned short letterlijke. Zorg er ook voor dat u de asShortBuffer () methode om het te converteren ByteBuffer bezwaar maken tegen een ShortBuffer.

// Maak buffer voor gezichten ByteBuffer buffer2 = ByteBuffer.allocateDirect (facesList.size () * 3 * 2); buffer2.order (ByteOrder.nativeOrder ()); facesBuffer = buffer2.asShortBuffer ();

Het vullen van de hoekbuffer omvat het doorlopen van de inhoud van verticesList, het extraheren van de X-, Y- en Z-coördinaten van elk item en het aanroepen van de leggen() methode om gegevens in de buffer te plaatsen. Omdat verticesList bevat alleen strings, we moeten de parseFloat () om de coördinaten van strings om te zetten in vlotter waarden.

for (String vertex: verticesList) String coords [] = vertex.split (""); // Splitsen door ruimte float x = Float.parseFloat (coords [1]); zweven y = Float.parseFloat (coords [2]); float z = Float.parseFloat (coords [3]); verticesBuffer.put (x); verticesBuffer.put (y); verticesBuffer.put (z);  verticesBuffer.position (0);

Merk op dat we in de bovenstaande code de positie() methode om de positie van de buffer te resetten.

Het vullen van de gezichtenbuffer is iets anders. U moet de gebruiken parseShort () methode om elke vertex-index in een korte waarde om te zetten. Omdat de indices beginnen met één in plaats van nul, moet u er bovendien rekening mee houden er een af ​​te trekken voordat u ze in de buffer plaatst.

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

7. Creëer Shaders

Om ons 3D-object te kunnen renderen, moeten we hiervoor een vertex-shader en een fragmentshader maken. Voorlopig kun je een arcering zien als een heel eenvoudig programma geschreven in een C-achtige taal met de naam OpenGL Shading Language, of kortweg GLSL.

Een vertex-arcering, zoals u misschien al geraden had, is verantwoordelijk voor het verwerken van de hoekpunten van een 3D-object. Een fragmentshader, ook pixelhader genoemd, is verantwoordelijk voor het inkleuren van de pixels van het 3D-object.

Stap 1: Maak een Vertex Shader

Maak een nieuw bestand met de naam vertex_shader.txt in uw project res / raw map.

Een vertex-arcering moet een attribuut globale variabele erin om vertex positiegegevens van uw Java-code te ontvangen. Voeg daarnaast een toe uniform globale variabele om een ​​view-projectiematrix van de Java-code te ontvangen.

Binnen in de hoofd() functie van de vertex shader, moet u de waarde van instellen gl_position, een ingebouwde GLSL-variabele die de uiteindelijke positie van de vertex bepaalt. Voor nu kun je eenvoudig de waarde ervan instellen op het product van de uniform en attribuut globale variabelen.

Voeg daarom de volgende code toe aan het bestand:

attribuut vec4-positie; uniforme mat4 matrix; void main () gl_Position = matrix * positie; 

Stap 2: maak een Fragment Shader

Maak een nieuw bestand met de naam fragment_shader.txt in uw project res / raw map.

Om deze tutorial kort te houden, zullen we nu een zeer minimalistische fragmentshader creëren die eenvoudig de kleur oranje toewijst aan alle pixels. Om een ​​kleur toe te wijzen aan een pixel, binnen de hoofd() functie van een fragmentshader, kunt u de gl_FragColor ingebouwde variabele.

precisie mediump float; void main () gl_FragColor = vec4 (1, 0.5, 0, 1.0); 

In de bovenstaande code is de eerste regel die de nauwkeurigheid van drijvende-kommagetallen aangeeft belangrijk omdat een fragmentshader geen standaardprecisie voor hen heeft.

Stap 3: compileer de Shaders

Terug in de Torus klasse, moet je nu code toevoegen om de twee shaders te compileren die je hebt gemaakt. Voordat u dit echter doet, moet u ze van onbewerkte bronnen naar strings converteren. De IOUtils class, een onderdeel van de Apache Commons IO-bibliotheek, heeft een toString () methode om precies dat te doen. De volgende code laat zien hoe je het gebruikt:

// Converteer vertex_shader.txt naar een string InputStream vertexShaderStream = context.getResources (). OpenRawResource (R.raw.vertex_shader); String vertexShaderCode = IOUtils.toString (vertexShaderStream, Charset.defaultCharset ()); vertexShaderStream.close (); // Convert fragment_shader.txt naar een string InputStream fragmentShaderStream = context.getResources (). OpenRawResource (R.raw.fragment_shader); String fragmentShaderCode = IOUtils.toString (fragmentShaderStream, Charset.defaultCharset ()); fragmentShaderStream.close ();

De shaders-code moet worden toegevoegd aan OpenGL ES-shader-objecten. Gebruik de. Om een ​​nieuw shader-object te maken glCreateShader () methode van de GLES20 klasse. Afhankelijk van het type arceringobject dat u wilt maken, kunt u slagen GL_VERTEX_SHADER of GL_FRAGMENT_SHADER ernaar toe. De methode retourneert een geheel getal dat dient als een verwijzing naar het shader-object. Een nieuw gemaakt shader-object bevat geen code. Als u de arceringcode aan het arceringobject wilt toevoegen, moet u de glShaderSource () methode.

De volgende code maakt arceringobjecten voor zowel de hoekpuntshader als de fragmentshader:

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

We kunnen de arceringobjecten nu doorgeven aan de glCompileShader () methode om de code die ze bevatten te compileren.

GLES20.glCompileShader (vertexShader); GLES20.glCompileShader (fragmentShader);

8. Maak een programma

Terwijl u een 3D-object rendert, gebruikt u de shaders niet rechtstreeks. In plaats daarvan bevestigt u ze aan een programma en gebruikt u het programma. Voeg daarom een ​​ledvariabele toe aan de Torus klasse om een ​​verwijzing naar een OpenGL ES-programma op te slaan.

privé int-programma;

Gebruik de. Om een ​​nieuw programma te maken glCreateProgram () methode. Gebruik de. Om de vertex en fragment shader-objecten aan te hechten glAttachShader () methode.

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

Op dit punt kunt u het programma koppelen en het gebruiken. Gebruik hiervoor de glLinkProgram () en glUseProgram () methoden.

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

9. Teken het 3D-object

Met de shaders en buffers klaar, hebben we alles wat we nodig hebben om onze torus te trekken. Voeg een nieuwe methode toe aan de Torus klas genoemd trek:

openbare ongeldige trekking () // Tekeningcode gaat hier

In een eerdere stap, binnen de hoekpuntshader, definieerden we a positie variabele om vertex positiegegevens van Java-code te ontvangen. Het is nu tijd om de vertex-positiegegevens ernaar te verzenden. Om dit te doen, moeten we eerst een handvat krijgen voor de positie variabele in onze Java-code met behulp van de glGetAttribLocation () methode. Bovendien moet het handvat worden ingeschakeld met behulp van de glEnableVertexAttribArray () methode.

Voeg daarom de volgende code toe binnen de trek() methode:

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

Om het te wijzen positie omgaan met onze hoekpunten buffer, we moeten de glVertexAttribPointer () methode. Naast de hoekbuffer zelf, verwacht de methode het aantal coördinaten per hoekpunt, het type coördinaten en de byte-offset voor elke hoekpunt. Omdat we drie coördinaten per hoekpunt hebben en elke coördinaat een is vlotter, de byte-offset moet zijn 3 * 4.

GLES20.glVertexAttribPointer (positie, 3, GLES20.GL_FLOAT, false, 3 * 4, hoekpuntenBuffer);

Onze vertex-arcering verwacht ook een matrix voor weergaveprojectie. Hoewel een dergelijke matrix niet altijd nodig is, kunt u er met één een betere controle over hebben hoe uw 3D-object wordt weergegeven.

Een weergaveprojectiematrix is ​​eenvoudig het product van de weergave- en projectiematrices. Een weergavematrix stelt u in staat om de locaties van uw camera en het punt waar het naar kijkt te specificeren. Met een projectiematrix kunt u niet alleen het vierkante coördinatensysteem van OpenGL ES toewijzen aan het rechthoekige scherm van een Android-apparaat, maar ook het nabije en verre vlak van de bekeken afgeknotte kegel opgeven.

Als u de matrices wilt maken, kunt u er eenvoudig drie maken vlotter matrices van grootte 16:

zweven [] projectie Matrix = nieuwe zwevend [16]; zweven [] viewMatrix = nieuwe zweeftekst [16]; zweven [] productMatrix = nieuwe zweeftekst [16];

Om de projectiematrix te initialiseren, kunt u de frustumM () methode van de Matrix klasse. Het verwacht de locaties van de linker-, rechter-, onderste, bovenste, nabije en verre clipvlakken. Omdat ons canvas al een vierkant is, kunt u de waarden gebruiken -1 en 1 voor de linker en rechter en de onderste en bovenste clipvlakken. Voor de nabije en verre clipvlakken, voel je vrij om met verschillende waarden te experimenteren.

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

Als u de weergavematrix wilt initialiseren, gebruikt u de setLookAtM () methode. Het verwacht de posities van de camera en het punt waar het naar kijkt. Je bent weer vrij om met verschillende waarden te experimenteren.

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

Tenslotte, gebruik de. Om de productmatrix te berekenen multiplyMM () methode.

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

Om de productmatrix door te geven aan de vertex-arcering, moet u er een handvat van krijgen Matrix variabele met behulp van de glGetUniformLocation () methode. Zodra u het handvat heeft, kunt u het naar de productmatrix wijzen met behulp van de glUniformMatrix () methode.

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

U moet gemerkt hebben dat we de gezichtenbuffer nog steeds niet hebben gebruikt. Dat betekent dat we OpenGL ES nog steeds niet hebben verteld hoe de hoekpunten moeten worden verbonden om driehoeken te vormen, die als de gezichten van ons 3D-object zullen dienen.

De glDrawElements () methode kunt u de gezichtenbuffer gebruiken om driehoeken te maken. Als zijn argumenten verwacht het het totale aantal vertex-indices, het type van elke index en de vlakbuffer.

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

Vergeet ten slotte niet om het te deactiveren attribuut handler die u eerder hebt ingeschakeld om de hoekpuntgegevens door te geven aan de hoekpuntshader.

GLES20.glDisableVertexAttribArray (positie);

10. Maak een renderer

Onze GLSurfaceView widget heeft een GLSurfaceView.Renderer object om 3D-afbeeldingen te kunnen weergeven. U kunt de setRenderer () om een ​​renderer hieraan te koppelen.

mySurfaceView.setRenderer (nieuwe GLSurfaceView.Renderer () // Meer code gaat hier);

Binnen in de onSurfaceCreated () methode van de renderer, moet u opgeven hoe vaak de 3D-afbeelding moet worden gerenderd. Laten we voorlopig alleen renderen wanneer de 3D-afbeelding verandert. Hiertoe geeft u de RENDERMODE_WHEN_DIRTY constant voor de setRenderMode () methode. Initialiseer ook een nieuw exemplaar van de Torus voorwerp.

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

Binnen in de onSurfaceChanged () methode van de renderer, kunt u de breedte en hoogte van uw viewport definiëren met behulp van de glViewport () methode.

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

Binnen in de onDrawFrame () methode van de renderer, voeg een oproep toe aan de trek() methode van de Torus klasse om de torus daadwerkelijk te tekenen.

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

Op dit punt kun je je app gebruiken om de oranje torus te zien.

Conclusie

U weet nu hoe OpenGL ES moet worden gebruikt in Android-apps. In deze zelfstudie hebt u ook geleerd hoe u een Wavefront-OBJ-bestand analyseert en top en topgegevens eruit haalt. Ik raad aan om nog een paar 3D-objecten te genereren met Blender en ze in de app te renderen.

Hoewel we ons alleen op OpenGL ES 2.0 hebben gericht, begrijp dan dat OpenGL ES 3.x achterwaarts compatibel is met OpenGL ES 2.0. Dat betekent dat als u OpenGL ES 3.x liever gebruikt in uw app, u eenvoudig de GLES20 klasse met de GLES30 of GLES31 klassen.

Voor meer informatie over OpenGL ES, kunt u verwijzen naar de referentiepagina's. En als u meer wilt weten over de ontwikkeling van Android-apps, bekijk dan eens enkele van onze andere handleidingen hier bij Envato Tuts+!

  • Aan de slag met de Native Development Kit van Android

    Met de lancering van Android Studio 2.2 is het ontwikkelen van Android-applicaties die C ++-code bevatten, eenvoudiger dan ooit. In deze zelfstudie laat ik je zien hoe ...
    Ashraff Hathibelagal
    Android
  • Android-dingen: Perifere invoer / uitvoer

    Android Things heeft een unieke mogelijkheid om eenvoudig verbinding te maken met externe elektronische componenten met de Peripheral API en ingebouwde apparaatondersteuning. In dit artikel…
    Paul Trebilcox-Ruiz
    Android SDK
  • Hoe een Android-app te beveiligen

    In dit artikel gaan we enkele praktische tips bekijken die u kunt volgen om een ​​veilige Android-app te maken. Dit betekent een app die niet lekt ...
    Ashraff Hathibelagal
    Android
  • Codering van een Android-app met Flutter en Dart

    Google Flutter is een platformonafhankelijk raamwerk voor de ontwikkeling van apps dat de programmeertaal Dart gebruikt. In deze zelfstudie laat ik je kennismaken met de basisprincipes van ...
    Ashraff Hathibelagal
    Android