Android OpenGL 開始篇

我常給一些人的建議:如果條件不錯,就不要來做程序員了,因爲這不是人乾的事!程序員睡覺的時候也是清潔工人開始掃馬路的時候!

廢話不多說,自己也是作爲學習筆記而已,也是督促自己,因爲如果僅僅運行一個例子很簡單.自己研究這個當然也是需要應用到一定背景下的.

android APP如果需要使用opengl製圖,如果在java層實現,一般是GLSurfaceView來顯示出opengl製圖,GLSurfaceView的特點:

1.管理一個平面,這個平面是一個特殊的內存塊,它可以和android視圖系統混合.
2.管理一個EGL顯示,它能夠讓OpenGL渲染到一個平面.
3.接受一個用戶提供的實際顯示的Renderer對象.
4.使用一個專用線程去渲染從而和UI線程解耦.
5.支持on-demand  和連續的渲染.
6.可選的包,追蹤 和/或者錯誤檢查這個渲染器的OpenGL調用.

一個GLSurfaceView  一定要注意,這個activity的paused  和resumed 兩種狀態.當這個activity處於pauses  狀態時,GLSurfaceView  客戶端需要去調用 onPause() 方法,當activity處於resumed 狀態時,GLSurfaceView  客戶端也需要去調用 onResume() 方法.

下面新建一個使用opengl的工程,在android studio中.

<1> : 新建一個DurianOpenGL1工程,下面直接貼代碼:

package org.durian.durianopengl1;

import android.app.Activity;
import android.opengl.GLSurfaceView;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;

import org.durian.durianopengl1.gl.Durian3DRender;
import org.durian.durianopengl1.gl.DurianGLRender;
import org.durian.durianopengl1.gl.DurianPhotoRender;
import org.durian.durianopengl1.gl.DurianRotateRender;
import org.durian.durianopengl1.gl.DurianTextureRender;
import org.durian.durianopengl1.input.DurianInputSurfaceView;
import org.durian.durianopengl1.texturefilter.DurianTextureFilterSurfaceView;


public class DurianMainActivity extends Activity {

    private GLSurfaceView glview;
    private DurianInputSurfaceView glinputview;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        glview=new GLSurfaceView(this);
        //glinputview=new DurianInputSurfaceView(this);
        //glview.setRenderer(new DurianPhotoRender(this)/*new DurianTextureRender(this)*//*new Durian3DRender(this)*//*new DurianRotateRender(this)*//*new DurianGLRender(this)*/);
        glview.setRenderer(new DurianGLRender(this));
        setContentView(/*new DurianTextureFilterSurfaceView(this)*//*glinputview*/glview/*R.layout.activity_durian_main*/);
    }

    @Override
    protected void onResume() {
        super.onResume();
        glview.onResume();
    }

    @Override
    protected void onPause() {
        super.onPause();
        glview.onPause();
    }
}

注意上面Activity的onResume和onPause兩個週期,GLSurfaceView也需要根據Activity做相應的調整.

下面是渲染器Renderer:

package org.durian.durianopengl1.gl;

import android.content.Context;
import android.opengl.GLSurfaceView;
import android.opengl.GLU;

import org.durian.durianopengl1.draw2d.Square;
import org.durian.durianopengl1.draw2d.Triangle;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

/**
 * Created by Administrator on 2016/4/11.
 */
public class DurianGLRender implements GLSurfaceView.Renderer {

    private Context mContext;

    private Triangle triangle;
    private Square square;

    private org.durian.durianopengl1.draw2d.color.Triangle ctriangle;
    private org.durian.durianopengl1.draw2d.color.Square csquare;

    public DurianGLRender(Context context){
        mContext=context;

        triangle=new Triangle();
        square=new Square();

        //ctriangle=new org.durian.durianopengl1.draw2d.color.Triangle();
        //csquare=new org.durian.durianopengl1.draw2d.color.Square();

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

        gl.glClearColor(0.0f,0.0f,0.0f,1.0f);
        gl.glClearDepthf(1.0f);
        gl.glEnable(GL10.GL_DEPTH_TEST);
        gl.glDepthFunc(GL10.GL_LEQUAL);
        gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT,GL10.GL_NICEST);
        gl.glShadeModel(GL10.GL_SMOOTH);
        gl.glDisable(GL10.GL_DITHER);

    }

    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {
        if(height==0){
            height=1;
        }
        float aspect=(float)width/height;

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

        gl.glMatrixMode(GL10.GL_PROJECTION);
        gl.glLoadIdentity();
        GLU.gluPerspective(gl,45,aspect,0.1f,100.0f);

        gl.glMatrixMode(GL10.GL_MODELVIEW);
        gl.glLoadIdentity();

    }

    @Override
    public void onDrawFrame(GL10 gl) {

        gl.glClear(GL10.GL_COLOR_BUFFER_BIT|GL10.GL_DEPTH_BUFFER_BIT);

        gl.glLoadIdentity();
        gl.glTranslatef(-3.0f,0.0f,-6.0f);
        triangle.draw(gl);

        gl.glTranslatef(3.0f,0.0f,1.0f);
        square.draw(gl);

        gl.glTranslatef(-5.0f,0.0f,-6.0f);
        //ctriangle.draw(gl);

        gl.glTranslatef(5.0f,0.0f,1.0f);
        //csquare.draw(gl);

    }

}


這個類中其中有一個Triangle和Square的類,一個是繪製邊型,一個是方形的.

實現Renderer類就要實現它的三個基本方法.

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

這個方法在Renderer創建的時候就會運行,一般在生命週期中只會運行一次.

這個方法一般是做一些初始化的工作.

gl.glClearColor(0f, 0f, 0f, 0f);

設置清除屏幕時所用的顏色。如果您對色彩的工作原理不清楚的話,我快速解釋一下。色彩值的範圍從0.0f到1.0f。0.0f代表最黑的情況,1.0f就是最亮的情況。glClearColor 後的第一個參數是Red Intensity(紅色分量),第二個是綠色,第三個是藍色。最大值也是1.0f,代表特定顏色分量的最亮情況。最後一個參數是Alpha值。當它用來清除屏幕的時候,我們不用關心第四個數字。現在讓它爲0.0f。我會用另一個教程來解釋這個參數。
通過混合三種原色(紅、綠、藍),您可以得到不同的色彩。希望您在學校裏學過這些。因此,當您使用glClearColor(0.0f,0.0f,1.0f,0.0f),您將用亮藍色來清除屏幕。如果您用 glClearColor(0.5f,0.0f,0.0f,0.0f)的話,您將使用中紅色來清除屏幕。不是最亮(1.0f),也不是最暗 (0.0f)。要得到白色背景,您應該將所有的顏色設成最亮(1.0f)。要黑色背景的話,您該將所有的顏色設爲最暗(0.0f)。我們在這裏設置屏幕爲黑色.

gl.glClearDepthf(1.0f);
        gl.glEnable(GL10.GL_DEPTH_TEST);
        gl.glDepthFunc(GL10.GL_LEQUAL);
這三行是關於depth buffer(深度緩存)的。將深度緩存設想爲屏幕後面的層。深度緩存不斷的對物體進入屏幕內部有多深進行跟蹤。我們本節的程序其實沒有真正使用深度緩存,但幾乎所有在屏幕上顯示3D場景OpenGL程序都使用深度緩存。它的排序決定那個物體先畫。這樣您就不會將一個圓形後面的正方形畫到圓形上來。深度緩存是OpenGL十分重要的部分.

gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT,GL10.GL_NICEST);
這裏告訴OpenGL我們希望進行最好的透視修正。這會十分輕微的影響性能。但使得透視圖看起來好一點.
gl.glShadeModel(GL10.GL_SMOOTH);

啓用smooth shading(陰影平滑).陰影平滑通過多邊形精細的混合色彩,並對外部光進行平滑,在以後的課程中會看到他的效果.

gl.glDisable(GL10.GL_DITHER);

關閉服務器端GL功能,在GL中很多都是一對一對的,比如這個的另一個gl.glEnable(...).


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

當發生繪製變化的時候運行.比如橫屏切換到豎屏.

gl.glViewport(0,0,width,height);
此方法用來設定可見區域,即OpenGL應把渲染之後的圖形繪製在窗體的哪個部位,當視見區域是整個窗體時,OpenGL將把渲染結果繪製到整個窗口.

gl.glMatrixMode(GL10.GL_PROJECTION);
這個函數其實就是對接下來要做什麼進行一下聲明,也就是在要做下一步之前告訴計算機我要對“什麼”進行操作了,這個“什麼”在glMatrixMode的“()”裏的選項(參數)有3種模式: GL_PROJECTION 投影, GL_MODELVIEW 模型視圖, GL_TEXTURE 紋理.
gl.glLoadIdentity();

重設視圖模型變換 , 用於觀測創建的物體.

GLU.gluPerspective(gl,45,aspect,0.1f,100.0f);
參考:http://blog.chinaunix.net/uid-24448954-id-3059473.html


@Override
    public void onDrawFrame(GL10 gl)
這個纔是真正繪製圖形的地方,類似於自定義的View的onDraw方法.
gl.glClear(GL10.GL_COLOR_BUFFER_BIT|GL10.GL_DEPTH_BUFFER_BIT);

將緩存清除爲預先的設置值.

gl.glTranslatef(-3.0f,0.0f,-6.0f);

這個方法是位移操作,三個參數分別是x,y,z左邊,(0,0,0)通過前面的glLoadIdentity將被設置在平面中間位置,z的正方向是從屏幕指向上(或者屏幕外).


下面的實現邊形的類:

package org.durian.durianopengl1.draw2d;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;

import javax.microedition.khronos.opengles.GL10;

/**
 * Created by Administrator on 2016/4/11.
 */
public class Triangle {

    private FloatBuffer vertexBuffer;
    private ByteBuffer indexBuffer;

    private float[] vertices={
        0.0f,1.0f,0.0f,
            -1.0f,-1.0f,0.0f,
            1.0f,-1.0f,0.0f
    };
    private byte[] indices={0,1,2};

    public Triangle(){

        ByteBuffer vbb=ByteBuffer.allocateDirect(vertices.length*4);
        vbb.order(ByteOrder.nativeOrder());
        vertexBuffer=vbb.asFloatBuffer();
        vertexBuffer.put(vertices);
        vertexBuffer.position(0);

        indexBuffer=ByteBuffer.allocateDirect(indices.length);
        indexBuffer.put(indices);
        indexBuffer.position(0);

    }

    public void draw(GL10 gl){

        gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
        gl.glVertexPointer(3,GL10.GL_FLOAT,0,vertexBuffer);

        gl.glDrawElements(GL10.GL_TRIANGLES,indices.length,GL10.GL_UNSIGNED_BYTE,indexBuffer);
        gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);

    }

}

邊是由兩點一線組成,上面是三邊形,那麼就有三個座標,由於opengl是空間座標,所以座標是由x,y,z三項組成:

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

上面每一行分別對應是x,y,z

構造方法體中:

ByteBuffer vbb=ByteBuffer.allocateDirect(vertices.length*4);
        vbb.order(ByteOrder.nativeOrder());
        vertexBuffer=vbb.asFloatBuffer();
        vertexBuffer.put(vertices);
        vertexBuffer.position(0);
opengl對於頂點操作實際是在native層完成的,內存分配也是在native完成的,所以上面是完成頂點的數據的內存分配
gl.glEnableClientState
gl.glDisableClientState

這兩個一般是成對出現,可以控制管道(pipeline)開關
下圖顯示了OpenGL ES 1.x 固定管道的結構圖:

管道“工序”大致可以分爲 Transformation Stage 和 Rasterization Stage兩大步。

  OpenGL ES 支持的基本圖形爲 點Point, 線Line, 和三角形Triangle ,其它所有複製圖形都是通過這幾種基本幾何圖形組合而成。

  在發出繪圖指令後,會對頂點(Vertices)數組進行指定的座標變換或光照處理。

  頂點處理完成後,通過Rasterizer 來生成像素信息,稱爲”Fragments“ 。

  對於Fragment 在經過Texture Processing, Color Sum ,Fog 等處理並將最終處理結果存放在內存中(稱爲FrameBuffer)。

  OpenGL 2.0可以通過編程來修改藍色的步驟,稱爲Programmable Shader.

以上管道中工序可以通過設置來打開或關閉某些功能(比如無需霧化Fog處理),並可以爲某個工序設置參數,比如設置Vertext Array.

gl.glVertexPointer(3,GL10.GL_FLOAT,0,vertexBuffer);

第一個參數代表每個點或者頂點用幾個座標表示,本例中使用三個xyz。你也可以指定兩個只用xy,這樣話,z就爲0.需要注意的是,第一個參數並不代表有幾個點,如果你想要使用20個點來代表一個三角形,這裏你需要傳入的不是20而是2或3.
第二個參數表示座標值應當被解析爲float類型(這樣OpenGL就可以知道每個值佔用幾位).

第三個參數可以稱爲“步長”,代表每個點之間有幾位分割。本例中,0代表一個點挨着一個點,有時候你可能會在點的後面定義顏色,這時,你應該指出每個顏色佔用的位長,以便OpenGL在解析時跳過這段長度.

gl.glDrawElements(GL10.GL_TRIANGLES,indices.length,GL10.GL_UNSIGNED_BYTE,indexBuffer);


第一個參數代表你想要畫的幾何圖形(GL_TRIANGLE_STRIP代表一個三角strip)。其他可能的選項包括GL_POINTS,GL_LINE_STRIP,GL_LINES,GL_LINE_LOOP,GL_TRIANGLES和G:_TRIANGLE_FAN.

第二個參數:glDrawElements其它的參數可以讓你重用預定義的帶你。比如說,一個四邊形包括四個點,每一個四邊形可以用兩個三角形組合而來。如果你想使用兩個三角形組合成一個四邊形,你有必要去定義6個點嗎?當然不是,你只需要定義四個點,然後引用6次畫出兩個三角形就可以了,這種處理被稱爲“indexing into the point buffer”如下所示:
Points:(p1,p2,p3,p4)
Draw indices(p1,p2,p3,   p2,p3,p4)
知道了這些,glDrawElements的第二個參數就好理解了,它代表了index buffer中有幾個座標標示(可以理解爲點).

第三個參數代表index 數組中值的類型,是GL_UNSIGNED_SHORT還是GL_UNSIGNED_BYTE。
最後一個參數引用index buffer.

OK,基本操作和解釋已經相當清楚了.

另外一個方形類就不過多解釋了;

package org.durian.durianopengl1.draw2d;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;

import javax.microedition.khronos.opengles.GL10;

/**
 * Created by Administrator on 2016/4/11.
 */
public class Square {

    private FloatBuffer vertexBuffer;

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

    public Square(){

        ByteBuffer vbb=ByteBuffer.allocateDirect(vertices.length*4);
        vbb.order(ByteOrder.nativeOrder());
        vertexBuffer=vbb.asFloatBuffer();
        vertexBuffer.put(vertices);
        vertexBuffer.position(0);

    }

    public void draw(GL10 gl){

        gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
        gl.glVertexPointer(3,GL10.GL_FLOAT,0,vertexBuffer);

        gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP,0,vertices.length/3);
        gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);

    }

}



















發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章