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

Android的媒体效果框架允许开发者可以很容易的应用多种令人印象深刻的视觉效果到照片或视频之上。作为这个媒体效果的框架,它使用GPU来处理图片处理的过程,它仅仅接收OpenGL的纹理(texture)作为输入。在本次教程中,你将会学习到如何使用OpenGL ES2.0将图片资源转化为纹理,以及如何使用框架为图片应用不同的处理效果。

准备

为了开始本次的教程,你必须具备:

1.一款支持Android开发的IDE,如果你没有的话,可以在Android Developer website下载最新版本的Android studio。

2.一款运行Android4.0之上Android手机,并且GPU支持OpenGL ES2.0

3.对OpenGL的基本知识了解

设置OpenGL ES环境

创建GLSurfaceView

为了显示OpenGL的图形,你需要使用GLSurfaceView类,就像其他任何的View子类意义,你可以将它添加到你的Activity或Fragment之上,通过在布局xml文件中定义或者在代码中创建实例。

在本次的教程中,我们使用GLSurfaceView作为唯一的View在我们的Activity中,因此,为了简便,我们在代码中创建GLSurfaceView的实例并将其传入setContentView中,这样它将会填充你的整个手机屏幕。Activity中的onCreate方法如下:

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    GLSurfaceView view = new GLSurfaceView(this);
    setContentView(view);
}

因为媒体效果的框架仅仅支持OpenGL ES2.0及以上的版本,所以在setEGLContextClientVersion 方法中传入2;

view.setEGLContextClientVersion(2);

为了确保GLSurfaceView仅仅在必要的时候进行渲染,我们在setRenderMode 方法中进行设置:

view.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);

创建Renderer

Renderer负责渲染GLSurfaceView中的内容。

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

public class EffectsRenderer implements GLSurfaceView.Renderer {

    public EffectsRenderer(Context context){
        super();
    }

    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
    }

    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {
    }

    @Override
    public void onDrawFrame(GL10 gl) {
    }
}

回到Activity中调用setRenderer方法,让GLSurfaceView使用我们创建的Renderer:

view.setRenderer(new EffectsRenderer(this));

编写Manifest文件

如果你想要发布你的App到谷歌商店,在AndroidManifest.xml文件中添加如下语句:

<uses-feature android:glEsVersion="0x00020000" android:required="true" />

这会确保你的app只能被安装在支持OpenGL ES2.0的设备之上。现在OpenGL环境准备完毕。

创建一个OpenGL平面

定义顶点

GLSurfaceView是不能直接显示一张照片的,照片首先应该被转化为纹理,应用在OpenGL square之上。在本次教程中,我将创建一个2D平面,并且具有4个顶点。为了简单,我将使用一个长方形,现在,创建一个新的类Square,用它来代表形状。

public class Square {

}

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

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

我们使用OpenGL绘制的所有的物体都应该是由三角形决定的,为了画一个方形,我们需要两个具有一条公共边的三角形,那意味着这些三角形的坐标应该是:

  • triangle 1: (-1, -1), (1, -1), 和 (-1, 1)
  • triangle 2: (1, -1), (-1, 1), 和 (1, 1)

创建一个float数组来代表这些顶点:

private float vertices[] = {
        -1f, -1f,
        1f, -1f,
        -1f, 1f,
        1f, 1f,
};

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

private float textureVertices[] = {
        0f,1f,
        1f,1f,
        0f,0f,
        1f,0f
};

创建缓冲区

这些坐标数组应该被转变为缓冲字符(byte buffer)在OpenGL可以使用之前,接下来进行定义:

private FloatBuffer verticesBuffer;
private FloatBuffer textureBuffer;

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

下面使用ByteBuffer.nativeOrder方法来定义在底层的本地平台上的byte的顺序。使用asFloatBuffer方法将ByteBuffer转化为FloatBuffer,在FloatBuffer被创建后,我们调用put方法来将float数组放入缓冲区,最后,调用position方法来保证我们是由缓冲区的开头进行读取。

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

    buff = ByteBuffer.allocateDirect(textureVertices.length * 4);
    buff.order(ByteOrder.nativeOrder());
    textureBuffer = buff.asFloatBuffer();
    textureBuffer.put(textureVertices);
    textureBuffer.position(0);
}

创建着色器

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

顶点着色器的代码:

attribute vec4 aPosition;
attribute vec2 aTexPosition;
varying vec2 vTexPosition;
void main() {
  gl_Position = aPosition;
  vTexPosition = aTexPosition;
};

片段着色器的代码

precision mediump float;
uniform sampler2D uTexture;
varying vec2 vTexPosition;
void main() {
  gl_FragColor = texture2D(uTexture, vTexPosition);
};

如果你了解OpenGL,那么这段代码对你来说是熟悉的,如果你不能理解这段代码,你可以参考OpenGL documentation。这里有一个简明扼要的解释:

顶点着色器负责绘制单个顶点。aPosition是一个变量被绑定到FloatBuffer上,包含着这些顶点的坐标。相似的,aTexPosition 是一个变量被绑定到FloatBuffer上,包含着纹理的坐标。gl_Position 是一个在OpenGL中创建的变量,代表每一个顶点的位置,vTexPosition是一个数组变量,它的值被传递到片段着色器中。

在本教程中,片段着色器负责square的着色。它使用texture2D方法从纹理中拾取颜色,并且使用一个在OpenGL中被创建的变量gl_FragColor将颜色分配到片段。

在该类中,着色器的代码应该被转化为String。

private final String vertexShaderCode =
        "attribute vec4 aPosition;" +
        "attribute vec2 aTexPosition;" +
        "varying vec2 vTexPosition;" +
        "void main() {" +
        "  gl_Position = aPosition;" +
        "  vTexPosition = aTexPosition;" +
        "}";

private final String fragmentShaderCode =
        "precision mediump float;" +
        "uniform sampler2D uTexture;" +
        "varying vec2 vTexPosition;" +
        "void main() {" +
        "  gl_FragColor = texture2D(uTexture, vTexPosition);" +
        "}";

创建程序

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

使用glCreateShader创建一个着色器对象,并且返回以int为表示形式的指针。为了创建顶点着色器,传递GL_VERTEX_SHADER给它。相似的,为了创建一个片段着色器,传递GL_FRAGMENT_SHADER给它。下面使用glShaderSource方法关联相对应的着色器代码到着色器上。使用glCompileShader编译着色器代码。

在编译了着色器的代码后,创建一段新的的程序glCreateProgram,与glCreateShader相似,它也返回一个以int为表示形式的指针。调用glAttachShader方法附着着色器到程序中,最后,调用glLinkProgram进行链接。

代码:

private int vertexShader;
private int fragmentShader;
private int program;

private 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);
}

你可能会发现,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和aTexPosition的句柄到各自的verticesBuffer和textureBuffer

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

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

8.最后,使用glDrawArrays方法画出两个三角形(也就是方形)

代码:

public void draw(int texture){
    GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);
    GLES20.glUseProgram(program);
    GLES20.glDisable(GLES20.GL_BLEND);

    int positionHandle = GLES20.glGetAttribLocation(program, "aPosition");
    int textureHandle = GLES20.glGetUniformLocation(program, "uTexture");
    int texturePositionHandle = GLES20.glGetAttribLocation(program, "aTexPosition");

    GLES20.glVertexAttribPointer(texturePositionHandle, 2, GLES20.GL_FLOAT, false, 0, textureBuffer);
    GLES20.glEnableVertexAttribArray(texturePositionHandle);

    GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
    GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, texture);
    GLES20.glUniform1i(textureHandle, 0);

    GLES20.glVertexAttribPointer(positionHandle, 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);
}

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

public Square(){
    initializeBuffers();
    initializeProgram();
}

渲染OpenGL平面和纹理

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

首先,让我们创建一个Bitmap,添加一张照片到res/drawable文件夹之下,我把它命名为forest.jpg,使用BitmapFactory将照片转化为Bitmap。另外将照片的尺寸存储下来。

改变EffectsRenderer的构造函数如下,

private Bitmap photo;
private int photoWidth, photoHeight;
public EffectsRenderer(Context context){
    super();
    photo = BitmapFactory.decodeResource(context.getResources(), R.drawable.forest);
    photoWidth = photo.getWidth();
    photoHeight = photo.getHeight();
}

创建一个新的方法generateSquare,将Bitmap转化为纹理,并且出初始化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方法将Bitmap放置到纹理中,实现方法如下:

private int textures[] = new int[2];
private Square square;

private void generateSquare(){
    GLES20.glGenTextures(2, textures, 0);
    GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textures[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, photo, 0);
    square = new Square();
}

当GLSurfaceView的尺寸发生改变时,onSurfaceChanged方法被调用,这时我们需要调用glViewPort确认新的尺寸。调用glClearColor使其变为黑色,接着调用generateSquare重新初始化纹理和平面。

@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
    GLES20.glViewport(0,0,width, height);
    GLES20.glClearColor(0,0,0,1);
    generateSquare();
}

最后在onDrawFrame调用draw方法:

@Override
public void onDrawFrame(GL10 gl) {
    square.draw(textures[0]);
}

最后,你可以运行程序,在手机上看到你选择的图片被渲染出来:

使用媒体效果框架

直到现在为止我们所写的复杂的代码都是为使用媒体效果而做的准备,现在是时候使用这个框架了,在你自己的Renderer类中添加:

private EffectContext effectContext;
private Effect effect;

使用EffectContext.createWithCurrentGlContext初始化effectContext,它负责管理内部一个OpenGL上下文的视觉效果的信息。为优化性能,应该只被调用一次。添加下面的代码到你的onDrawFrame的开头:

if(effectContext==null) {
    effectContext = EffectContext.createWithCurrentGlContext();
}

创建一个效果是十分简单的,使用effectContext来创建一个Effect对象,一旦Effect对象可用,你可以调用apply方法,传递一个引用到原始的纹理中,在本例中是textures[0],随着对空白纹理对象,在本例中是textures[1],在apply方法被调用之后,textures[1]将会包含Effect的结果。

例如,我们使用灰度(grayscale)效果,这是代码:

private void grayScaleEffect(){
    EffectFactory factory = effectContext.getFactory();
    effect = factory.createEffect(EffectFactory.EFFECT_GRAYSCALE);
    effect.apply(textures[0], photoWidth, photoHeight, textures[1]);
}

在onDrawFrame中调用此方法,并将textures[1]传递给Square的draw方法:

@Override
public void onDrawFrame(GL10 gl) {
    if(effectContext==null) {
        effectContext = EffectContext.createWithCurrentGlContext();
    }
    if(effect!=null){
        effect.release();
    }
    grayScaleEffect();
    square.draw(textures[1]);
}

release方法是用来释放Effect所持有的资源,当你运行app时,你可以看到这样的效果:

你可以使用相同的代码应用到一个纪录片效果上(documentary),

private void documentaryEffect(){
    EffectFactory factory = effectContext.getFactory();
    effect = factory.createEffect(EffectFactory.EFFECT_DOCUMENTARY);
    effect.apply(textures[0], photoWidth, photoHeight, textures[1]);
}

看起来像这样

有一些效果需要参数,例如亮度调整的影响,brightness参数是一个float值,你可以使用setParameter方法改变参数值,就像下面的代码:

private void brightnessEffect(){
    EffectFactory factory = effectContext.getFactory();
    effect = factory.createEffect(EffectFactory.EFFECT_BRIGHTNESS);
    effect.setParameter("brightness", 2f);
    effect.apply(textures[0], photoWidth, photoHeight, textures[1]);
}

结果是这样:

总结

在本教程中,你已经学会了如何利用媒体效果框架应用于各种效果到你的照片。这样做的时候,你也学会了如何绘制一个平面利用OpenGL ES 2.0并且应用各种纹理。

该框架可应用于照片和视频,如果是视频的话,你只需将应用效果的方法应用到各帧的onDrawFrame方法中。

你已经看到了本教程中的三种效果,在该框架中还有很多种效果你可以尝试,了解更多的话可以参考Android Developer’s website

原文地址及源码下载

原文来自:How to Use Android Media Effects With OpenGL ES

自己跑了一遍代码,附上下载链接:点击下载

时间: 2024-08-19 11:53:17

如何使用Android中的OpenGL ES媒体效果的相关文章

在Android中使用OpenGL ES进行开发第(一)节:概念先行

一.前期基础是知识储备笔者计划写三篇文章来详细分析OpenGL ES基础的同时也是入门关键的三个点: ①OpenGL ES是什么?与OpenGL的关系是什么?——概念部分 ②使用OpenGL ES绘制2D/3D图形的第一步:定义图形:——运用部分 ③使用OpenGL ES绘制出②步骤中定义好的图形:——运用部分,难点所在 通过这三篇文章的分析,就像给万丈高楼垫定了基石,万丈高楼平地起,后面利用OpenGLES做各种效果,各种变换都是建立在这三步的图形编程理解之上的. 话不多说正文开始 (1)什么

PPAPI中使用OpenGL ES绘图

在PPAPI中使用Chromium的3D图形接口一文中我们介绍了怎么使用PPB_Graphics3D接口,提供了一个简单示例,单机鼠标可以变换插件颜色. foruok原创,如需转载请关注foruok的微信订阅号"程序视界"联系foruok. PPB_Graphics3D是Chromium暴露给PPAPI的3D图形接口,类似衔接Open GL和本地窗口系统的EGL.我们使用PPB_Graphics3D的Create方法来创建context,然后使用PPB_Instance的BindGra

Android中GridView拖拽的效果

最 近看到联想,摩托罗拉等,手机launcher中有个效果,进入mainmenu后,里面的应用程序的图标可以拖来拖去,所以我也参照网上给的代码,写了 一个例子.还是很有趣的,实现的流畅度没有人家的那么好,我只是模仿这种效果,我写的这个拖拽是两个图标之间进行交换,所以,当从一行的某个位置,换到下 一行的另一列的时候,发现有好几个图标都改变位置了,因为是相邻两个交换位置,所以每经过相邻的图标的时候都改变位置.先弄个雏形,以后再更新优化. 转载请标明出处:http://blog.csdn.net/wd

Android学习笔记&mdash;&mdash;OpenGL ES的基本用法、绘制流程与着色器编译

首先声明下,本文为笔者学习<OpenGL ES应用开发实践指南(Android卷)>的笔记,涉及的代码均出自原书,如有需要,请到原书指定源码地址下载. 在Android.iOS等移动平台上,开发者可以使用跨平台应用编程接口创建二维或者三维图形,或进行图像处理和计算机视觉应用,结合两者将能构建丰富有趣的交互体验.前者称为OpenGL,后者称为OpenCV,不过本文主要介绍前者,OpenCV在后续文章中涉及.OpenGL应用于桌面系统的历史已经很长了,但考虑到移动平台的特点(计算能力.性能等),将

android中xml设置Animation动画效果详解

在 android 中, Animation 动画效果的实现可以通过两种方式进行实现,一种是 tweened animation 渐变动画,另一种是 frame by frame animation 画面转换动画. tweened animation 渐变动画有以下两种类型: 1.alpha 渐变透明度动画效果 2.scale 渐变尺寸伸缩动画效果 frame by frame animation 画面转换动画有以下两种类型: 1.translate 画面转换位置移动动画效果 2.rotate

Android中Acitvity跳转动画效果实现

在Activity中为我们提供了overridePendingTransition方法,该方法两个参数参数类型均为资源文件对应id,我们要用到的是anim下的动画效果文件.具体写法如 public class Main2Activity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setC

Android中的沉浸式状态栏效果

无意间了解到沉浸式状态栏,感觉贼拉的高大上,于是就是试着去了解一下,就有了这篇文章.下面就来了解一下啥叫沉浸式状态栏.传统的手机状态栏是呈现出黑色条状的,有的和手机主界面有很明显的区别.这一样就在一定程度上牺牲了视觉宽度,界面面积变小.Google从android kitkat(Android 4.4)开始,给我们开发者提供了一套能透明的系统ui样式给状态栏和导航栏,这样的话就不用向以前那样每天面对着黑乎乎的上下两条黑栏了,还可以调成跟Activity一样的样式,形成一个完整的主题,和IOS7.

Android中GridView拖拽的效果【android进化三十六】

  最 近看到联想,摩托罗拉等,手机launcher中有个效果,进入mainmenu后,里面的应用程序的图标可以拖来拖去,所以我也参照网上给的代码,写了 一个例子.还是很有趣的,实现的流畅度没有人家的那么好,我只是模仿这种效果,我写的这个拖拽是两个图标之间进行交换,所以,当从一行的某个位置,换到下 一行的另一列的时候,发现有好几个图标都改变位置了,因为是相邻两个交换位置,所以每经过相邻的图标的时候都改变位置.先弄个雏形,以后再更新优化. 转载请标明出处:http://blog.csdn.net/

OpenGL ES总结(六)OpenGL ES中EGL

Agenda: EGL是什么? EGL数据类型 EGL在Android中应用 EGL的工作流程 GLSurfaceView与EGL区别 简单Demo EGL是什么? EGL? is an interface between Khronos rendering APIs such as OpenGL ES or OpenVG and the underlying native platform window system. It handles graphics context managemen