如何在Android中使用OpenGL ES媒体效果
准备
为了开始本教程,您必须具备:
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;