如何在Android中使用OpenGL ES媒体效果

Android的媒体效果框架允许开发人员轻松地将各种令人印象深刻的视觉效果应用到照片或视频中。作为这个媒体效果的框架,它使用GPU来处理图像处理的过程,它只接收OpenGL的纹理作为输入。在本教程中,您将学习如何使用OpenGL ES2.0将图像资源转换为纹理,以及如何使用帧对图像应用不同的处理效果。

准备

为了开始本教程,您必须具备:

1.支持Android开发的IDE。如果没有,可以从Android开发者网站下载最新版本的Android studio。

2.运行在Android4.0上的安卓手机,GPU支持OpenGL ES2.0。

3.了解OpenGL的基础知识

设置OpenGL ES环境

创建GLSurfaceView

为了显示OpenGL图形,您需要使用GLSurfaceView类,就像任何其他视图子类一样。您可以通过在布局xml文件中定义它或在代码中创建一个实例,将它添加到您的活动或片段中。

在本教程中,我们使用GLSurfaceView作为我们活动中的唯一视图,因此为了简单起见,我们在代码中创建了一个GLSurfaceView的实例,并将其传递到setContentView中,这样它将填充您的整个手机屏幕。活动中的onCreate方法如下:

& ltcode class = " hljs " java = " " & gt受保护的void onCreate(Bundle saved instancestate){

super . oncreate(savedInstanceState);

GLSurfaceView视图=新的GLSurfaceView(this);

setContentView(视图);

} & lt/code & gt;

因为媒体特效的框架只支持OpenGL ES2.0及以上,2;

& lt代码avrasm="" class="hljs " >view . seteglcontextclientversion(2);& lt/code & gt;

为了确保GLSurfaceView仅在必要时呈现,我们在setRenderMode方法中设置了它:

& lt代码avrasm="" class="hljs " >view . setrendermode(GLSurfaceView。render mode _ WHEN _ DIRTY);& lt/code & gt;

创建渲染器

Renderer负责在GLSurfaceView中呈现内容。

创建类实现接口GLSurfaceView。Renderer,这里我们打算将这个类命名为EffectsRenderer,添加构造函数并在接口中覆盖抽象方法,如下:

& ltcode class = " hljs " java = " " & gt公共类EffectsRenderer实现GLSurfaceView。渲染器{

公共效果呈现器(上下文context){

super();

}

@覆盖

public void onSurfaceCreated(GL 10 GL,EGLConfig config) {

}

@覆盖

public void onSurfaceChanged(GL 10 GL,int width,int height) {

}

@覆盖

公共void onDrawFrame(GL10 gl) {

}

} & lt/code & gt;

回到活动并调用setRenderer方法,让GLSurfaceView使用我们创建的渲染器:

& ltcode class = " hljs " cs = " " & gtview . set renderer(new effects renderer(this));& lt/code & gt;

写入清单文件

如果您想将应用程序发布到Google Store,请将以下句子添加到AndroidManifest.xml文件中:

& ltcode class = " hljs " xml = " " & gt& ltuses-feature Android:gles version = " 0x 00020000 " Android:required = " true " & gt;& lt/uses-feature & gt;& lt/code & gt;

这将确保您的应用程序只能安装在支持OpenGL ES2.0的设备上。现在,OpenGL环境已经就绪。

创建一个OpenGL平面

定义顶点

GLSurfaceView无法直接显示照片。照片应转换成纹理,并应用于OpenGL广场。在本教程中,我将创建一个有4个顶点的2D平面。为简单起见,我将使用矩形。现在,创建一个新的类Square,并用它来表示形状。

& ltcode class = " hljs " cs = " " & gt公共类广场{

} & lt/code & gt;

默认OpenGL系统的坐标系中的原点在中心,因此四个角的坐标可以表示为:

左下角:(-1,-1)右下角:(1,-1)右上角:(1,1)左上角:(-1,1)。

我们用OpenGL画的所有物体都要用三角形来确定。为了画一个正方形,我们需要两个有公共边的三角形,这意味着这些三角形的坐标应该是:

三角形1: (-1,-1),(1,-1),和(-1,1)三角形2: (1)

创建一个浮点数组来表示这些顶点:

& ltcode class = " hljs " cpp = " " & gt私有浮点顶点[] = {

-1f,-1f,

1f,-1f,

-1f,1f,

1f,1f

};& lt/code & gt;

为了在正方形上定位纹理,需要确定纹理的顶点坐标,并创建另一个数组来表示纹理的顶点坐标:

& ltcode class = " hljs " cpp = " " & gt私有浮点textureVertices[] = {

0f,1f,

1f,1f

0f,0f,

1f,0f

};& lt/code & gt;

创建缓冲区

这些坐标数组应转换成字节缓冲区,然后在使用OpenGL之前定义:

& ltcode class = " hljs " cs = " " & gt私有浮动缓冲区verticesBuffer

私有FloatBuffer textureBuffer& lt/code & gt;

在initializeBuffers方法中初始化这些缓冲区:使用byte buffer . allocated direct创建缓冲区,因为float是4个字节,那么我们需要的字节数组长度应该是float的4倍。

下面使用ByteBuffer.nativeOrder方法来定义基础本地平台上的字节顺序。使用asFloatBuffer方法将ByteBuffer转换为FloatBuffer。创建FloatBuffer后,我们调用put方法将float数组放入缓冲区。最后,我们调用position方法来确保我们从缓冲区的开头开始读取。

& lt代码avrasm="" class="hljs " >私有void initializeBuffers(){

byte buffer buff = byte buffer . allocated direct(vertices . length * 4);

buff . order(byte order . native order());

vertices buffer = buff . asfloatbuffer();

verticesBuffer.put(顶点);

vertices buffer . position(0);

buff = byte buffer . allocatedirect(texture vertices . length * 4);

buff . order(byte order . native order());

texture buffer = buff . asfloatbuffer();

texture buffer . put(texture vertices);

texture buffer . position(0);

} & lt/code & gt;

创建着色器

Shader只是一个简单的C程序,运行在GPU中的每个单独的顶点上。在本教程中,我们使用两种着色器:顶点着色器和片段着色器。

顶点着色器代码:

& ltcode class = " hljs " glsl = " " & gt属性vec4 aPosition

属性vec2 aTexPosition

变化的vec2 vTexPosition

void main() {

gl _ Position = aPosition

vTexPosition = aTexPosition

};& lt/code & gt;

片段着色器代码

class = " hljs " glsl = " " & gt精密中间浮动;

均匀样品uTexture

变化的vec2 vTexPosition

void main() {

GL _ frag color = texture 2d(ut exture,vt exposition);

};& lt/code & gt;

如果你了解OpenGL,那么这段代码你应该很熟悉。如果看不懂这段代码,可以参考OpenGL文档。这里有一个简明的解释:

顶点着色器负责绘制单个顶点。APosition是一个绑定到FloatBuffer的变量,包含这些顶点的坐标。类似地,at exposure是一个绑定到FloatBuffer的变量,包含纹理的坐标。Gl_Position是在OpenGL中创建的变量,代表每个顶点的vTexPosition,vt exposition是一个数组变量,其值传递给片段着色器。

在本教程中,片段着色器负责正方形的着色。它使用texture2D方法从纹理中拾取颜色,并使用OpenGL中创建的变量gl_FragColor为剪辑分配颜色。

在这个类中,着色器的代码应该转换为字符串。

& ltcode class = " hljs " java = " " & gt私有最终字符串vertexShaderCode =

属性vec4 aPosition+

属性vec2 aTexPosition+

变化的vec2 vTexPosition+

void main() { +

gl _ Position = aPosition+

vTexPosition = aTexPosition+

};

私有最终字符串片段ShaderCode =

精密中间浮动;+

均匀样品uTexture+

变化的vec2 vTexPosition+

void main() { +

GL _ frag color = texture 2d(ut exture,vt exposition);+

};& lt/code & gt;

创建程序

创建一个新的方法initializeProgram来创建一个编译和链接着色器的OpenGL程序。

使用glCreateShader创建一个shader对象,并以int的形式返回一个指针。要创建顶点着色器,请将GL_VERTEX_SHADER传递给它。类似地,要创建片段着色器,请将GL_FRAGMENT_SHADER传递给它。下面使用glShaderSource方法将相应的着色器代码与着色器相关联。用glCompileShader编译着色器代码。

在编译了着色器的代码之后,创建了一个新程序glCreateProgram。与glCreateShader类似,它也以int的形式返回一个指针。调用glAttachShader方法将着色器附加到程序。最后,调用link程序进行链接。

代码:

& ltcode class = " hljs " cs = " " & gtprivate int vertexShader

private int fragmentShader

private int程序;

私有void initializeProgram(){

vertex shader = gles 20 . glcreateshader(gles 20。GL _顶点_着色器);

gles 20 . glshadersource(vertexShader,vertexShaderCode);

gles 20 . glcompileshader(vertexShader);

fragment shader = gles 20 . glcreateshader(gles 20。GL _ FRAGMENT _着色器);

gles 20 . glshadersource(fragmentShader,fragmentShaderCode);

gles 20 . glcompileshader(fragmentShader);

program = gles 20 . glcreateprogram();

GLES20.glAttachShader(程序,vertex shader);

GLES20.glAttachShader(程序,fragmentShader);

GLES20.glLinkProgram(程序);

} & lt/code & gt;

你可能会发现Opengl的方法(以gl开头)都在GLES20类中,因为我们用的是OpenGL ES2.0,如果用的是更高版本,我们会用这些类:GLES30,GLES31。

画一个形状

现在定义draw方法,使用我们之前定义的点和着色器进行绘制。

你需要做的是:

1.使用glBindFramebuffer方法创建一个帧缓冲区对象(FBO)。

2.如前所述,调用glUseProgram创建程序。

3.将GL_BLEND传递给glDisable方法以在呈现期间禁用颜色混合。

4.调用glGetAttribLocation来获取变量aPosition和aTexPosition的句柄。

5.使用glVertexAttribPointer将aPosition和Atexposure的句柄连接到它们各自的顶点Buffer和textureBuffer。

6.使用glBindTexture方法将纹理(作为draw方法的参数传入)绑定到片段着色器。

7.调用glClear方法清除GLSurfaceView的内容。

8.最后用glDrawArrays方法画两个三角形(也就是正方形)。

代码:

& lt代码avrasm="" class="hljs " >公共空绘制(内部纹理){

gles 20 . glbindframebuffer(gles 20。GL_FRAMEBUFFER,0);

GLES20.glUseProgram(程序);

GLES20.glDisable(GLES20。GL _ BLEND);

int position handle = gles 20 . glgetattributelocation(program,a position);

int texture handle = gles 20 . glgetuniformlocation(program,ut exture);

int texturePositionHandle = gles 20 . glgetattributelocation(program,at exposition);

GLES20 . glvertexattributepointer(texturePositionHandle,2,gles 20。GL_FLOAT,false,0,texture buffer);

gles 20 . glenablevertexattribarray(texturepositonhandle);

glActiveTexture(GLES20。GL _ texture 0);

GLES20.glBindTexture(GLES20。GL_TEXTURE_2D,纹理);

gles 20 . gluniform 1i(texture handle,0);

GLES20 . glvertexattributepointer(position handle,2,gles 20。GL_FLOAT,false,0,vertices buffer);

gles 20 . glenablevertexattribarray(position handle);

GLES20.glClear(GLES20。GL _ COLOR _ BUFFER _ BIT);

GLES20.glDrawArrays(GLES20。GL_TRIANGLE_STRIP,0,4);

} & lt/code & gt;

向构造函数添加初始化方法:

& ltcode class = " hljs " cs = " " & gt公共广场(){

initialize buffers();

initialize program();

} & lt/code & gt;

渲染OpenGL平面和纹理

现在我们的渲染器什么也没做,我们需要改变它来渲染我们之前创建的平面。

首先,让我们创建一个位图,在res/drawable文件夹中添加一张照片,我将其命名为forest.jpg,并使用BitmapFactory将照片转换为位图。此外,还存储了照片的大小。

按如下方式更改EffectsRenderer的构造函数,

& ltcode class = " hljs " java = " " & gt私人位图照片;

private int photoWidth,photoHeight

公共效果呈现器(上下文context){

super();

photo = bitmapfactory . decode resource(context . get resources()、r . drawable . forest);

photoWidth = photo . getwidth();

photoHeight = photo . getheight();

} & lt/code & gt;

创建一个新方法,generateSquare,将位图转换为纹理,初始化Square对象。还需要一个数组来保存对纹理的引用,用glGenTextures初始化数组,用glBindTexture方法激活位置0处的纹理。

现在,调用glTexParameteri来设置不同的级别,以确定如何渲染纹理。

将GL_TEXTURE_MIN_FILTER(校正功能)和GL_TEXTURE_MAG_FILTER(放大功能)设置为GL_LINEAR,保证画面在拉伸时是平滑的。

将GL_TEXTURE_WRAP_S和GL_TEXTURE_WRAP_T设置为GL_CLAMP_TO_EDGE,以确保纹理不会重复。

最后调用texImage2D方法将位图放入纹理中,实现方法如下:

& lt代码avrasm="" class="hljs " >private int纹理[]= new int[2];

私人广场广场;

私有void generateSquare(){

GLES20.glGenTextures(2,纹理,0);

GLES20.glBindTexture(GLES20。GL_TEXTURE_2D,textures[0]);

GLES20.glTexParameteri(GLES20。2D纹理,GLES20。GL_TEXTURE_MIN_FILTER,GLES20。GL _ LINEAR);

GLES20.glTexParameteri(GLES20。2D纹理,GLES20。GL_TEXTURE_MAG_FILTER,GLES20。GL _ LINEAR);

GLES20.glTexParameteri(GLES20。2D纹理,GLES20。GL_TEXTURE_WRAP_S,GLES20。GL _ CLAMP _ TO _ EDGE);

GLES20.glTexParameteri(GLES20。2D纹理,GLES20。GL_TEXTURE_WRAP_T,GLES20。GL _ CLAMP _ TO _ EDGE);

LUtils.texImage2D(GLES20。GL_TEXTURE_2D,0,照片,0);

Square = new Square();

} & lt/code & gt;

当GLSurfaceView的大小改变时,调用onSurfaceChanged方法,我们需要调用glViewPort来确认新的大小。调用glClearColor使其变黑,然后调用generateSquare重新初始化纹理和平面。

& ltcode class = " hljs " java = " " & gt@覆盖

public void onSurfaceChanged(GL 10 GL,int width,int height) {

GLES20.glViewport(0,0,width,height);

GLES20.glClearColor(0,0,0,1);

generate square();

} & lt/code & gt;

最后,调用onDrawFrame中的draw方法:

& ltcode class = " hljs " java = " " & gt@覆盖

公共void onDrawFrame(GL10 gl) {

square . draw(textures[0]);

} & lt/code & gt;