Cum se utilizează efecte media Android cu OpenGL ES

Cadrul Android Efecte media permite dezvoltatorilor să aplice cu ușurință o mulțime de efecte vizuale impresionante fotografiilor și clipurilor video. Pe măsură ce cadrul utilizează GPU-ul pentru a efectua toate operațiile de procesare a imaginilor, acesta poate accepta texturile OpenGL ca intrări. În acest tutorial, veți învăța cum să utilizați OpenGL ES 2.0 pentru a converti o resursă trasabilă într-o textură și apoi să utilizați cadrul pentru a aplica diferite efecte.

Cerințe preliminare

Pentru a urma acest tutorial, trebuie să aveți:

  • un IDE care suportă dezvoltarea aplicațiilor Android. Dacă nu aveți unul, obțineți cea mai recentă versiune de Android Studio de pe site-ul Android Developer.
  • un dispozitiv care rulează Android 4.0+ și are un GPU care acceptă OpenGL ES 2.0.
  • o înțelegere de bază a OpenGL.

1. Configurarea mediului OpenGL ES

Pasul 1: Creați un GLSurfaceView

Pentru a afișa grafica OpenGL în aplicația dvs., trebuie să utilizați a GLSurfaceView obiect. Ca oricare alta Vedere, îl puteți adăuga la un Activitate sau Fragment definind-o într-un fișier XML cu aspect sau creând o instanță a acestuia în cod.

În acest tutorial, veți avea un a GLSurfaceView obiect ca fiind singurul Vedere în tine Activitate. Prin urmare, crearea acestuia în cod este mai simplă. Odată creat, trimiteți-l la setContentView astfel încât să umple întregul ecran. Ta Activitate„s onCreate metoda ar trebui să arate astfel:

protejat void onCreate (Bundle savedInstanceState) super.onCreate (savedInstanceState); Vizualizare GLSurfaceView = GLSurfaceView nou (acest lucru); setContentView (vizualizare); 

Deoarece cadrul Media Efecte acceptă numai OpenGL ES 2.0 sau o versiune ulterioară, treceți valoarea 2 la setEGLContextClientVersion metodă.

view.setEGLContextClientVersion (2);

Pentru a vă asigura că GLSurfaceView redă conținutul său numai atunci când este necesar, trece valoarea RENDERMODE_WHEN_DIRTY la setRenderMode metodă.

view.setRenderMode (GLSurfaceView.RENDERMODE_WHEN_DIRTY);

Pasul 2: Creați un Renderer

A GLSurfaceView.Renderer este responsabil pentru desenarea conținutului GLSurfaceView.

Creați o clasă nouă care implementează GLSurfaceView.Renderer interfață. O să chem această clasă EffectsRenderer. După adăugarea unui constructor și suprimarea tuturor metodelor interfeței, clasa ar trebui să arate astfel:

clasa publică EffectsRenderer implementează GLSurfaceView.Renderer public EffectsRenderer (Context context) super ();  @Override public void onSurfaceCreated (GL10 gl, EGLCconfig)  @Overide public void onSurfaceChanged GL10 gl, int width, int height  @Override public void peDrawFrame (GL10 gl) 

Întoarce-te la tine Activitate și apelați setRenderer astfel încât GLSurfaceView utilizează funcția de randare personalizată.

view.setRenderer (noul EffectsRenderer (acest lucru));

Pasul 3: Editați Manifestul

Dacă intenționați să publicați aplicația pe Google Play, adăugați următoarele AndroidManifest.xml:

Acest lucru vă asigură că aplicația dvs. poate fi instalată numai pe dispozitivele care acceptă OpenGL ES 2.0. Mediul OpenGL este acum gata.

2. Crearea unui plan OpenGL

Pasul 1: Definirea vârfurilor

GLSurfaceView nu poate afișa direct o fotografie. Fotografia trebuie transformată într-o textură și aplicată mai întâi într-o formă OpenGL. În acest tutorial, vom crea un plan 2D care are patru noduri. Din motive de simplitate, hai să o facem pătrat. Creați o nouă clasă, Pătrat, pentru a reprezenta pătratul.

clasa publica publica 

Sistemul implicit de coordonate OpenGL își are originea în centrul său. Ca rezultat, coordonatele celor patru colțuri ale pieței noastre, ale căror laturi sunt două unități lung, va fi:

  • colțul din stânga jos la (-1, -1)
  • colțul din dreapta jos la (1, -1)
  • colțul din dreapta sus la (1, 1)
  • colțul din stânga sus la (-1, 1)

Toate obiectele pe care le desenăm folosind OpenGL ar trebui să fie alcătuite din triunghiuri. Pentru a desena pătratul, avem nevoie de două triunghiuri cu o margine comună. Aceasta înseamnă că coordonatele triunghiurilor vor fi:

triunghiul 1: (-1, -1), (1, -1) și (-1, 1)
triunghiul 2: (1, -1), (-1, 1) și (1, 1)

Creeaza o pluti pentru a reprezenta aceste vârfuri.

vertexurile plutitoare private [] = -1f, -1f, 1f, -1f, -1f, 1f, 1f, 1f,;

Pentru a plasa textura pe pătrat, trebuie să specificați coordonatele vârfurilor texturii. Texturile urmează un sistem de coordonate în care valoarea coordonatei y crește pe măsură ce mergeți mai sus. Creați o altă matrice pentru a reprezenta vârful texturii.

textura plutitoare privatăVerturi [] = 0f, 1f, 1f, 1f, 0f, 0f, 1f, 0f;

Pasul 2: Creați obiecte tampon

Rețelele de coordonate trebuie să fie transformate în buffere de octeți înainte ca OpenGL să le poată folosi. Să declare aceste tampoane mai întâi.

privat FloatBuffer verticesBuffer; privat FloatBuffer textureBuffer;

Scrie codul pentru a inițializa aceste tampoane într-o metodă nouă numită initializeBuffers. Folosește ByteBuffer.allocateDirect metoda de creare a tamponului. Pentru că a pluti utilizări 4 octeți, trebuie să multiplicați dimensiunea matricelor cu valoarea 4.

Apoi, utilizați ByteBuffer.nativeOrder pentru a determina ordinea byte a platformei native care sta la baza, și setați ordinea tampoanelor la acea valoare. Folosește asFloatBuffer - metoda de conversie ByteBuffer exemplu în FloatBuffer. După FloatBuffer este creat, utilizați a pune metoda de încărcare a matricei în tampon. În cele din urmă, utilizați poziţie pentru a vă asigura că memoria tampon este citită de la început.

Conținutul mesajului initializeBuffers metoda ar trebui să arate astfel:

private void initializeBuffers () ByteBuffer buff = ByteBuffer.allocateDirect (vertices.length * 4); buff.order (ByteOrder.nativeOrder ()); verticesBuffer = buff.asFloatBuffer (); verticesBuffer.put (nodurile); verticesBuffer.position (0); Buff = ByteBuffer.allocateDirect (textureVertices.length * 4); buff.order (ByteOrder.nativeOrder ()); textureBuffer = buff.asFloatBuffer (); textureBuffer.put (textureVertices); textureBuffer.position (0); 

Pasul 3: Creați Shaders

Este timpul să vă scrieți propriile shadere. Shaderele nu sunt decât programe simple C care sunt gestionate de GPU pentru a procesa fiecare vertex individual. Pentru acest tutorial, trebuie să creați două shadere, un shader de vârf și un fragment de shader.

Codul C pentru shaderul de vertex este:

atribut vec4 aPosition; atribut vec2 aTexPosition; variabile vec2 vTexPosition; void principală () gl_Position = aPosition; vTexPosition = aTexPosition; ;

Codul C pentru fragmentul shader este:

precizie, cu capăt mediu; proba uniformă de eșantionare2D; variabile vec2 vTexPosition; void principal () gl_FragColor = textură2D (uTexture, vTexPosition); ;

Dacă știți deja OpenGL, acest cod trebuie să vă fie cunoscut deoarece este comun pe toate platformele. Dacă nu, pentru a înțelege aceste programe, trebuie să vă referiți la documentația OpenGL. Iată o scurtă explicație pentru a începe:

  • Shaderul vârfului este responsabil pentru desenarea nodurilor individuale. o pozitie este o variabilă care va fi legată de FloatBuffer care conține coordonatele nodurilor. asemănător, aTexPosition este o variabilă care va fi legată de FloatBuffer care conține coordonatele texturii. gl_Position este o variabilă OpenGL încorporată și reprezintă poziția fiecărui vârf. vTexPosition este a variabil variabilă, a cărei valoare este pur și simplu transferată către shader-ul fragmentului.
  • În acest tutorial, fragmentul shader este responsabil pentru colorarea pătratului. Acesta preia culorile din textura folosind texture2D și le atribuie fragmentului utilizând o variabilă încorporată numită gl_FragColor.

Codul shader trebuie reprezentat ca Şir obiecte din clasă.

privat final String vertexShaderCode = "atv vec4 aPosition;" + "atribut vec2 aTexPosition;" + "variază vec2 vTexPosition;" + "void principală () " + "gl_Position = aPoziție;" + "vTexPosition = aTexPosition;" + ""; fragmentul final al fragmentului StringShaderCode = "floare de precizie mediump"; + "eșantion unic de eșantionare2D"; + "variază vec2 vTexPosition;" + "void main () " + "gl_FragColor = textură2D (uTexture, vTexPosition);" + "";

Pasul 4: Creați un program

Creați o nouă metodă numită initializeProgram pentru a crea un program OpenGL după compilarea și conectarea shaderelor.

Utilizare glCreateShader pentru a crea un obiect shader și a trimite o referință la acesta sub forma unui int. Pentru a crea un shader de vertex, treceți valoarea GL_VERTEX_SHADER la el. În mod similar, pentru a crea un shader de fragment, treceți valoarea GL_FRAGMENT_SHADER la el. Următoarea utilizare glShaderSource pentru a asocia codul shader corespunzător cu shader. Utilizare glCompileShader pentru a compila codul shader.

După ce ați compilat ambele shadere, creați un nou program folosind glCreateProgram. Ca și cum  glCreateShader, acest lucru returnează și o int ca referință la program. Apel glAttachShader pentru a atașa shaderele la program. În cele din urmă, sună glLinkProgram pentru a conecta programul.

Metoda și variabilele asociate ar trebui să arate astfel:

private int vertexShader; private int fragmentShader; programul privat int; privat void initializeProgram () vertexShader = GLES20.glCreateShader (GLES20.GL_VERTEX_SHADER); GLES20.glShaderSource (vertexShader, vertexShaderCode); GLES20.glCompileShader (vertexShader); fragmentShader = GLES20.glCreateShader (GLES20.GL_FRAGMENT_SHADER); GLES20.glShaderSource (fragmentShader, fragmentShaderCode); GLES20.glCompileShader (fragmentShader); program = GLES20.glCreateProgram (); GLES20.glAttachShader (program, vertexShader); GLES20.glAttachShader (program, fragmentShader); GLES20.glLinkProgram (program);  

S-ar putea să fi observat că metodele OpenGL (metodele prefixate cu gl) aparțin clasei GLES20. Acest lucru se datorează faptului că folosim OpenGL ES 2.0. Dacă doriți să utilizați o versiune superioară, atunci va trebui să utilizați clasele GLES30 sau GLES31.

Pasul 5: Desenați Piața

Creați o nouă metodă numită a desena pentru a desena efectiv pătratul folosind vârfurile și shaderele pe care le-am definit mai devreme.

Iată ce trebuie să faceți în această metodă:

  1. Utilizare glBindFramebuffer pentru a crea un obiect tampon numit cadru (adesea numit FBO).
  2. Utilizare glUseProgram pentru a începe să folosiți programul pe care tocmai l-am conectat.
  3. Treceți valoarea GL_BLEND la glDisable pentru a dezactiva amestecarea culorilor în timpul redării.
  4. Utilizare glGetAttribLocation pentru a obține un mâner variabilelor o pozitie și aTexPosition menționate în codul shader de vârfuri.
  5. Utilizare glGetUniformLocation pentru a obține un mâner la constanta uTexture menționate în codul shader de fragment.
  6. Folosește glVertexAttribPointer să asociați o pozitie și aTexPosition se mânerează cu verticesBuffer si textureBuffer respectiv.
  7. Utilizare glBindTexture pentru a lega textură (trecut ca argument la a desena metoda) la shader-ul fragmentului.
  8. Goliți conținutul GLSurfaceView utilizând glClear.
  9. În cele din urmă, utilizați glDrawArrays metoda de a trage efectiv cele două triunghiuri (și astfel pătratul).

Codul pentru a desena metoda ar trebui să arate astfel:

public void draw (textura int) GLES20.glBindFramebuffer (GLES20.GL_FRAMEBUFFER, 0); GLES20.glUseProgram (program); GLES20.glDisable (GLES20.GL_BLEND); int positionHandle = GLES20.glGetAttribLocație (program, "aPoziție"); int textureHandle = GLES20.glGetUniformLocation (program, "uTexture"); int texturePositionHandle = GLES20.glGetAttribLocation (program, "aTexPosition"); GLES20.glVertexAttribPointer (texturăPositionHandle, 2, GLES20.GL_FLOAT, falsă, 0, texturaBuffer); GLES20.glEnableVertexAttribArray (texturePositionHandle); GLES20.glActiveTexture (GLES20.GL_TEXTURE0); GLES20.glBindTexture (GLES20.GL_TEXTURE_2D, textura); GLES20.glUniform1i (texturăHandle, 0); GLES20.glVertexAttribPointer (pozițiaHandle, 2, GLES20.GL_FLOAT, false, 0, verticesBuffer); GLES20.glEnableVertexAttribArray (positionHandle); GLES20.glClear (GLES20.GL_COLOR_BUFFER_BIT); GLES20.glDrawArrays (GLES20.GL_TRIANGLE_STRIP, 0, 4);  

Adăugați un constructor la clasă pentru a inițializa bufferele și programul în momentul creării obiectului.

public Square () initializeBuffers (); initializeProgram (); 

3. Realizarea planului și texturii OpenGL

În prezent, editorul nostru nu face nimic. Trebuie să schimbăm acest lucru astfel încât acesta să poată face planul pe care l-am creat în pașii anteriori.

Dar, mai întâi, să creăm a Bitmap. Adăugați o fotografie la proiectul dvs. res / drawable pliant. Se numește fișierul pe care îl folosesc forest.jpg. Folosește BitmapFactory pentru a converti fotografia într-o Bitmap obiect. De asemenea, stocați dimensiunile Bitmap obiect în variabile separate.

Schimbați constructorul EffectsRenderer astfel încât să aibă următorul conținut:

fotografie bitmap privată; private photoWidth, photoHeight; public EffectsRenderer (Context context) super (); photo = BitmapFactory.decodeResource (context.getResources (), R.drawable.forest); photoWidth = photo.getWidth (); photoHeight = photo.getHeight (); 

Creați o nouă metodă numită generateSquare pentru a converti bitmap-ul într-o textura și a inițializa a Pătrat obiect. De asemenea, veți avea nevoie de o serie de numere întregi pentru a menține referințe la texturile OpenGL. Utilizare glGenTextures pentru a inițializa matricea și glBindTexture pentru a activa textura la index 0.

Apoi, utilizați glTexParameteri pentru a stabili diferite proprietăți care să decidă cum este redată textura:

  • A stabilit GL_TEXTURE_MIN_FILTER (funcția de minificare) și GL_TEXTURE_MAG_FILTER (funcția de mărire) la GL_LINEAR pentru a vă asigura că textura pare netedă, chiar și atunci când este întinsă sau strânsă.
  • A stabilit GL_TEXTURE_WRAP_S și GL_TEXTURE_WRAP_T la GL_CLAMP_TO_EDGE astfel încât textura să nu se repete niciodată.

În cele din urmă, utilizați texImage2D metoda de hartă a Bitmap la textura. Punerea în aplicare a directivei generateSquare metoda ar trebui să arate astfel:

int int textures [] = int int [2]; pătrat privat; void privat generateSquare () GLES20.glGenTexturi (2, texturi, 0); GLES20.glBindTexture (GLES20.GL_TEXTURE_2D, texturi [0]); GLES20.glTexParameteri (GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR); GLES20.glTexParameteri (GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR); GLES20.glTexParameteri (GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE); GLES20.glTexParameteri (GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE); GLUtils.texImage2D (GLES20.GL_TEXTURE_2D, 0, fotografie, 0); pătrat = pătrat nou (); 

Ori de câte ori dimensiunile GLSurfaceView schimba onSurfaceChanged metodă a Renderer se numește. Aici trebuie să sunați glViewPort pentru a specifica noile dimensiuni ale ferestrei de vizualizare. De asemenea, sunați glClearColor pentru a picta GLSurfaceView negru. Apoi, sună generateSquare pentru a reinitializa texturile și avionul.

@Overide public void onSurfaceChanged (GL10 gl, lățime int, int înălțime) GLES20.glViewport (0,0, lățime, înălțime); GLES20.glClearColor (0,0,0,1); generateSquare (); 

În cele din urmă, apelați Pătrat obiecte a desena în interiorul metodei onDrawFrame metodă a Renderer.

@Override public void peDrawFrame (GL10 gl) square.draw (texturi [0]); 

Acum puteți să rulați aplicația dvs. și să vedeți fotografia pe care ați ales-o ca fiind redată ca textură OpenGL într-un avion.

4. Folosind cadrul de efecte media

Codul complex pe care l-am scris până acum era doar o condiție prealabilă pentru utilizarea cadrului Media Efecte. Acum este momentul să începeți să utilizați cadrul propriu-zis. Adăugați următoarele câmpuri la dvs. Renderer clasă.

efecte privateContextContextContext; efect efectiv privat;

Inițializați effectContext folosind câmpul EffectContext.createWithCurrentGlContext. Este responsabil pentru gestionarea informațiilor despre efectele vizuale din interiorul unui context OpenGL. Pentru a optimiza performanța, aceasta trebuie apelată o singură dată. Adăugați următorul cod la începutul paginii onDrawFrame metodă.

dacă (effectContext == null) effectContext = EfectContext.createWithCurrentGlContext (); 

Crearea unui efect este foarte simplă. Folosește effectContext pentru a crea un EffectFactory și utilizați EffectFactory pentru a crea un Efect obiect. O dată Efect obiect este disponibil, puteți apela aplica și să transmită o referire la textura originală, în cazul nostru texturi [0], împreună cu o referință la un obiect textura gol, în cazul nostru este texturi [1]. După aplica se numește metoda, texturi [1] va conține rezultatul Efect.

De exemplu, pentru a crea și a aplica alb-negru efect, iată codul pe care trebuie să-l scrieți:

void privat griScaleEffect () EffectFactory factory = efectContext.getFactory (); efect = factory.createEffect (EffectFactory.EFFECT_GRAYSCALE); efect.apply (texturi [0], photoWidth, photoHeight, texturi [1]); 

Apelați această metodă în onDrawFrame și treci texturi [1] la Pătrat obiecte a desena metodă. Ta onDrawFrame metoda ar trebui să aibă următorul cod:

@Override public void peDrawFrame (GL10 gl) if (effectContext == null) efectContext = EfectContext.createWithCurrentGlContext ();  dacă (efect! = null) effect.release ();  griScaleEffect (); square.draw (texturi [1]); 

eliberare metoda este folosită pentru a elibera toate resursele deținute de un Efect. Când rulați aplicația, ar trebui să vedeți următorul rezultat:

Puteți utiliza același cod pentru a aplica alte efecte. De exemplu, iată codul care trebuie aplicat film documentar efect:

document void privat privatEffect () EffectFactory factory = effectContext.getFactory (); efect = factory.createEffect (EffectFactory.EFFECT_DOCUMENTARY); efect.apply (texturi [0], photoWidth, photoHeight, texturi [1]); 

Rezultatul arată astfel:

Unele efecte iau parametri. De exemplu, efectul de ajustare a luminozității are a strălucire parametru care ia un parametru pluti valoare. Poți să folosești setParameter pentru a modifica valoarea oricărui parametru. Următorul cod vă arată cum să îl utilizați:

luminozitatea privată voidEffect () EffectFactory factory = effectContext.getFactory (); efect = factory.createEffect (EffectFactory.EFFECT_BRIGHTNESS); efect.setParametru ("luminozitate", 2f); efect.apply (texturi [0], photoWidth, photoHeight, texturi [1]); 

Efectul va face ca aplicația dvs. să producă următorul rezultat:

Concluzie

În acest tutorial, ați învățat cum să utilizați Media Effects Framework pentru a aplica diferite efecte fotografiilor dvs. În timp ce ați făcut acest lucru, ați învățat cum să desenați un plan folosind OpenGL ES 2.0 și să aplicați diverse texturi.

Cadrul poate fi aplicat atât fotografiilor, cât și videoclipurilor. În cazul videoclipurilor, pur și simplu trebuie să aplicați efectul pentru cadrele individuale ale videoclipului în onDrawFrame metodă.

Ați văzut deja trei efecte în acest tutorial și cadrul are mai multe zeci pentru a vă experimenta. Pentru a afla mai multe despre ele, consultați site-ul Web al dezvoltatorului Android.


Cod