Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> 如何使用Android中的OpenGL ES媒體效果

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

編輯:關於Android編程

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文件中添加如下語句:

這會確保你的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]);
}

最後,你可以運行程序,在手機上看到你選擇的圖片被渲染出來:

title=

使用媒體效果框架

直到現在為止我們所寫的復雜的代碼都是為使用媒體效果而做的准備,現在是時候使用這個框架了,在你自己的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時,你可以看到這樣的效果:

title=

你可以使用相同的代碼應用到一個紀錄片效果上(documentary),

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

看起來像這樣

title=

有一些效果需要參數,例如亮度調整的影響,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]);
}

結果是這樣:

title=

總結

在本教程中,你已經學會了如何利用媒體效果框架應用於各種效果到你的照片。這樣做的時候,你也學會了如何繪制一個平面利用OpenGL ES 2.0並且應用各種紋理。

該框架可應用於照片和視頻,如果是視頻的話,你只需將應用效果的方法應用到各幀的onDrawFrame方法中。

你已經看到了本教程中的三種效果,在該框架中還有很多種效果你可以嘗試,了解更多的話可以參考Android Developer’s website。

 

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved