Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Open GL ES 三角形繪制

Open GL ES 三角形繪制

編輯:關於Android編程

畫三角形是open GL ES中最簡單的入門項目,下面講解具體的流程,方便自己總結工具類,沒有別的意思。

在Android項目中引進open GL

為了使用OpenGL ES 2.0 API,需要添加如下聲明:

創建GLSurfaceView

GLSurfaceView是用來放置圖形view的容器。所有的東西都是繪制在GLSurfaceView上面的,就相當於畫布的概念,
這裡先實現一個GLSurfaceView。擴展自GLSurfaceView,實現自己的MyGLSurfaceView

    public class MyGLSurfaceView extends GLSurfaceView {

        private Context mContext;

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

        public MyGLSurfaceView(Context context, AttributeSet attrs) {
            super(context, attrs);
        }
    }  

創建Render

Renderer類(渲染器類),即 GLSurfaceView.Renderer的實現類,它控制了與它相關聯的 GLSurfaceView 上繪制什麼。
需要實現一下接口:

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

    }

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

    }

    @Override
    public void onDrawFrame(GL10 gl) {

    }
onSurfaceCreated()函數在surface創建的時候調用,所以初始化的工作在裡面完成。 onSurfaceChanged()函數在surface發生改變的時候調用; onDrawFrame()函數是完成surfaceview上面顯示內容的繪制,每一幀的繪制都會去調用。

基礎參數設置

    //給shader中的變量傳參數時候用到的
    private final int mStrideBytes = 7 * 4;    //3 + 4   3表示坐標, 4表示顏色  一共7個float變量,每個變量4字節//一次性讀取 7 x 4個字節
    private final int mPositionOffset = 0;   //頂點坐標的偏移量
    private final int mPositionDataSize = 3;   //3個為一組,表示一個頂點坐標
    private final int mColorOffset = 3;    //顏色數據的變異量為3, 也就是每次讀取數據,從第三個開始是表示顏色的
    private final int mColorDataSize = 4;   //4個數據都是表示顏色的
mStrideBytes是指定buffer在讀取數據的時候一次讀取多少,7表示個數,7個數據, 4表示字節,7*4表示一次讀取多少字節的數據。
比如:
 -0.5f, -0.25f, 0.0f,    //point
1.0f, 0.0f, 0.0f, 1.0f, //color
mPositionOffset表示頂點坐標的偏移量 mPositionDataSize表示頂底每個頂點坐標用多少個數據表示,三個:-0.5f, -0.25f, 0.0f, //point mColorOffset讀取顏色數據時的偏移量,因為頂點坐標用3個數據表示,所以偏移量為3 mColorDataSize表示多少個數據表示一個顏色,4個參數分別為ARGB

頂點坐標和顏色坐標

//數據
    private final float vertexData[] = {
            // X, Y, Z,
            // R, G, B, A
            -0.5f, -0.25f, 0.0f,    //point
            1.0f, 0.0f, 0.0f, 1.0f, //color

            0.5f, -0.25f, 0.0f,     //point
            0.0f, 0.0f, 1.0f, 1.0f, //color

            0.0f, 0.559016994f, 0.0f,//point
            0.0f, 1.0f, 0.0f, 1.0f     //color
    };

可以看到,每組數據有7個,前面3個表示位置坐標,後面4個表示顏色值,可以結合前面的參數設置來理解。
這個數據是程序傳入openGL的數據。

創建Buffer存放數據

    //數據的buffer
    private FloatBuffer mShaderDateBuffer;
    private FloatBuffer getVertexBuffer(float[] data) {
        //先 創建內存地址
        ByteBuffer vbb = ByteBuffer.allocateDirect(data.length * 4);   //每個float是4個字節
        //ByteOrder.nativeOrder()返回本地jvm運行的硬件的字節順序.使用和硬件一致的字節順序可能使buffer更加有效.
        vbb.order(ByteOrder.nativeOrder());
        FloatBuffer vertexBuffer = vbb.asFloatBuffer();
        vertexBuffer.put(data);
        vertexBuffer.position(0);

        return vertexBuffer;
    }

上面代碼中的注釋已經很清楚了,裡面的代碼大多數是固定的寫法。

Matrix的設置

openGL 中有三個類型份額舉證,分別是:
* Model Matrix 模型矩陣
* View Matrix 視圖矩陣
* Projection Matrix 投影矩陣
由於這幾個概念很繞,是個人都要糊弄一會兒才能搞清楚,下面就這幾個矩陣,好好的糊弄糊弄。
所謂的坐標變換,就是將一個坐標系下的坐標,在另外一個坐標系中表示出來。
下圖中世界坐標系下的相機:
坐標系中的相機seooMCwwLDAsMSm89cilz+C7+tf4seq1w7W9tcSho82syrHJ6NbD1dXP4Lv6tcS527Ls1f3P8rrNysC959f4serPtc/CtcQ8YnIgLz4NCnnW4bXEt73P8tK71sKjrCB2ID0gKDAsMSwwLDApLCC0y8qxo6zA+9PDz/LBv7XEsuazy7/J0tS1w7W9IM/gu/rX+LHqz7XPwrXEtdrI/bj21/ix6tbhtcS3vc/yo6x1ID0gbiB4IHYgOzxiciAvPg0KvMbL47XDtb11ID0gKDEsMCwtMSwwKSAs1eLR+dXVz+C7+tfUvLq5ubPJtcTX+LHqz7XOqih1LCB2LCBuLCBQKS4gPC9jb2RlPjwvY29kZT48L3A+DQo8cD48Y29kZT48Y29kZT48aW1nIGFsdD0="照相機坐標系" src="http://image79.360doc.com/DownloadImg/2014/10/2810/46548286_51" title="\" />
這個東西就是將坐標從世界坐標轉換到相機坐標的矩陣,那世界坐標的原點為例(0,0,0,1), 有
原點從世界坐標轉換到相機坐標

在opengl中,數據從用戶構建的局部坐標系,經過一系列的處理,最終渲染在屏幕上面,主要經過了一下過程:

Open gl中只定義了裁剪坐標系、規范化設備坐標系以及屏幕坐標系,而局部坐標系、世界坐標系和相機坐標系是為了
方便用戶的,用戶在OpenGL中的轉換如下:

從坐標來看,就是一下過程

下圖中茶壺在Model Matrix中的定義
Model Matrix下的茶壺
世界坐標系下的茶壺:
世界坐標系下的茶壺
從世界坐標系到相機坐標系

為什麼要將世界坐標系轉化到相機坐標系,我們最終看到的就是相機拍到的,而不是上帝視角下看到的一切(純屬個人理解,不喜勿噴)。
從相機坐標系到裁剪坐標系,通過投影完成的。分為正交投影和透視投影兩種。
最後讀下來還是感覺很暈,的確很暈。那這些東西和上面提到的三個矩陣有什麼關系呢?
基本上就是: Model Matrix是模型坐標系轉換到世界坐標系用,View Matrix就是視圖坐標系,用來轉換到相機坐標系用的,Projection Matrix轉化裁剪坐標系的。

ES中坐標系矩陣的計算

計算模型矩陣,裡面調用了rotateM接口

    private float[] mModelMatrix = new float[16];       //模型矩陣
    private void initModelMatrix() {
        // Do a complete rotation every 10 seconds.
        long time = SystemClock.uptimeMillis() % 10000L;
        float angleInDegrees = (360.0f / 10000.0f) * ((int) time);

        // Draw the triangle facing straight on.
        // 模型矩陣設為單位矩陣
        Matrix.setIdentityM(mModelMatrix, 0);
        // angleInDegrees是旋轉的角度,(0.0f, 0.0f, 1.0f)是模型矩陣
        Matrix.rotateM(mModelMatrix, 0, angleInDegrees, 0.0f, 0.0f, 1.0f);
    }

計算ViewMatrix,需要設置相機的位置,相機觀察的方向,以及相機的觀察正向向量

    private float[] mViewMatrix = new float[16];   //視圖矩陣
    private void initViewMatrix() {
        //放置eye眼睛的位置
        final float eyeX = 0.0f;
        final float eyeY = 0.0f;
        final float eyeZ = 1.5f;

        //設置look方向
        // look也成為center, center到eye所形成的向量,稱為視線方向,與真正的視線看過去的方向相反
        final float lookX = 0.0f;
        final float lookY = 0.0f;
        final float lookZ = -5.0f;

        //設置up坐標
        //eye的位置本身只代表一個坐標而已,但是 look向量和up向量結合右手螺旋准則才能唯一的確定一個坐標系
        //這個坐標系就是eye看到的坐標系,兩者垂直知識為了與常用的三位坐標系一樣
        final float upX = 0.0f;
        final float upY = 1.0f;
        final float upZ = 0.0f;

        //經過計算的到了 viewMatrix,
        Matrix.setLookAtM(mViewMatrix, 0, eyeX, eyeY, eyeZ, lookX, lookY, lookZ, upX, upY, upZ);

    }

投影矩陣,設置關於遠近 以及投影屏幕的大小等屬性。

    private float[] mProjectionMatrix = new float[16];   //投射矩陣
    private void initProjectionMatrix(int width, int height) {
        // Set the OpenGL viewport to the same size as the surface.
        GLES20.glViewport(0, 0, width, height);
        // Create a new perspective projection matrix. The height will stay the same
        // while the width will vary as per aspect ratio.
        final float ratio = (float) width / height;
        final float left = -ratio;
        final float right = ratio;
        final float bottom = -1.0f;
        final float top = 1.0f;
        final float near = 1.0f;
        final float far = 10.0f;

        Matrix.frustumM(mProjectionMatrix, 0, left, right, bottom, top, near, far);
    }

Shader數據傳入

使用工具類:

    public static int compileShader(final int shaderType, final String shaderSource) {
        // 創建shader句柄
        int shaderHandle = GLES20.glCreateShader(shaderType);
        if (shaderHandle != 0) {
            // Pass in the shader source.
            // 使用glShaderSource()分別將頂點著色程序的源代碼字符數組綁定到頂點著色器對象,將片段著色程序的源代碼字符數組綁定到片段著色器對象;
            // 綁定作用
            GLES20.glShaderSource(shaderHandle, shaderSource);

            // Compile the shader.
            // 編譯
            GLES20.glCompileShader(shaderHandle);

            // Get the compilation status.
            // 得到計算結果
            final int[] compileStatus = new int[1];
            GLES20.glGetShaderiv(shaderHandle, GLES20.GL_COMPILE_STATUS, compileStatus, 0);

            // If the compilation failed, delete the shader.
            // 結果審查
            if (compileStatus[0] == 0) {
                Log.e(TAG, "Error compiling shader: " + GLES20.glGetShaderInfoLog(shaderHandle));
                GLES20.glDeleteShader(shaderHandle);
                shaderHandle = 0;
            }
        }

        if (shaderHandle == 0) {
            throw new RuntimeException("Error creating shader.");
        }

        return shaderHandle;
    }

shaderType為兩種,一種是GLES20.GL_VERTEX_SHADER,一種是GLES20.GL_FRAGMENT_SHADER。最終返回的是shader的句柄,後面要用到這個句柄傳入參數,
計算。
鏈接程序
vertexShader

    uniform mat4 u_MVPMatrix;   //應用程序傳入的變換矩陣 ,MVP 是modle view projection的意思,通過這個來計算最終的坐標
    attribute vec4 a_Position;  //應用程序傳入的 頂點的坐標
    attribute vec4 a_Color;     //應用程序傳入的 頂點顏色的坐標

    varying vec4 v_Color;   //這個變量會傳到 fragment shader中處理

    void main() {
        v_Color = a_Color;
        gl_Position = u_MVPMatrix * a_Position;
    }

fragmentShader

    precision mediump float;   //精度

    varying vec4 v_Color;   //這個名字一定要和在 vertex shader中聲明的一樣

    void main() {
        gl_FragColor = v_Color;
    }

變量,這些變量是在shader中定義的,這裡的名稱和c語言中定義的名稱一致。通過這個個名稱獲取變量,傳入參數

private String[] mAttributes = {
            "u_MVPMatrix",
            "a_Position",
            "a_Color"
    };
    public static int createAndLinkProgram(final int vertexShaderHandle, final int fragmentShaderHandle, final String[] attributes) {
        // 創建程序句柄 
        int programHandle = GLES20.glCreateProgram();

        if (programHandle != 0) {
            // Bind the vertex shader to the program.
            // 綁定shader到program
            GLES20.glAttachShader(programHandle, vertexShaderHandle);

            // Bind the fragment shader to the program.
            GLES20.glAttachShader(programHandle, fragmentShaderHandle);

            // Bind attributes
            // 綁定參數到program
            if (attributes != null) {
                final int size = attributes.length;
                for (int i = 0; i < size; i++) {
                    GLES20.glBindAttribLocation(programHandle, i, attributes[i]);
                }
            }

            // Link the two shaders together into a program.
            GLES20.glLinkProgram(programHandle);

            // Get the link status.
            final int[] linkStatus = new int[1];
            GLES20.glGetProgramiv(programHandle, GLES20.GL_LINK_STATUS, linkStatus, 0);

            // If the link failed, delete the program.
            if (linkStatus[0] == 0) {
                Log.e(TAG, "Error compiling program: " + GLES20.glGetProgramInfoLog(programHandle));
                GLES20.glDeleteProgram(programHandle);
                programHandle = 0;
            }
        }

        if (programHandle == 0) {
            throw new RuntimeException("Error creating program.");
        }

        return programHandle;
    }

最後一步,繪制圖形

onSurfaceCreated函數主要是做初始化用的。

    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        //清屏指令
        GLES20.glClearColor(0f, 0f, 0f, 0f);
        //初始化相機的位置
        initViewMatrix();

        initShader();
    }

    private void initShader() {
        int vertexShaderHandle = ShaderHelper.compileShader(GLES20.GL_VERTEX_SHADER, TextResourceReader.readTextFileFromResource(mContext, R.raw.vertex_shader));
        int fragmentShaderHandle = ShaderHelper.compileShader(GLES20.GL_FRAGMENT_SHADER, TextResourceReader.readTextFileFromResource(mContext, R.raw.fragment_shader));

        int programHandle = ShaderHelper.createAndLinkProgram(vertexShaderHandle, fragmentShaderHandle, mAttributes);

        //者三個變量是我們在glsl文件中定義的三個變量,現在鏈接程序之後把他們取出來用,是為了後面賦值
        mMVPMatrixHandle = GLES20.glGetUniformLocation(programHandle, "u_MVPMatrix");
        mPositionHandle = GLES20.glGetAttribLocation(programHandle, "a_Position");
        mColorHandle = GLES20.glGetAttribLocation(programHandle, "a_Color");

        GLES20.glUseProgram(programHandle);
    }

onSurfaceChanged()函數

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

onDrawFrame()繪制

    @Override
    public void onDrawFrame(GL10 gl) {

        GLES20.glClear(GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT);
        initModelMatrix();
        drawFrame(mShaderDateBuffer);

    }
    private void drawFrame(final FloatBuffer frameBuffer) {
        //移動到 表示坐標的起始位置
        frameBuffer.position(mPositionOffset);
        //glVertexAttribPointer(positionSlot, 2, GL_FLOAT, GL_FALSE, stride, pCoords);
        /* 為頂點著色器位置信息賦值,
            1.positionSlot表示頂點著色器位置屬性(即,Position);就是在glsl文件中聲明的attribute變量
            2.表示每一個頂點信息由幾個值組成,這個值必須位1,2,3或4;
            3.GL_FLOAT表示頂點信息的數據類型;
            4.GL_FALSE表示不要將數據類型標准化(即fixed-point);
            5.stride表示數組中每個元素的長度;pCoords表示數組的首地址
        */
        GLES20.glVertexAttribPointer(mPositionHandle, mPositionDataSize, GLES20.GL_FLOAT, false,
                mStrideBytes, frameBuffer);
        //開啟頂點屬性數組
        GLES20.glEnableVertexAttribArray(mPositionHandle);

        //定位到color數據首地址
        frameBuffer.position(mColorOffset);
        GLES20.glVertexAttribPointer(mColorHandle, mColorDataSize, GLES20.GL_FLOAT, false,
                mStrideBytes, frameBuffer);
        //開啟頂點屬性數組
        GLES20.glEnableVertexAttribArray(mColorHandle);

        Matrix.multiplyMM(mMVPMatrix, 0, mViewMatrix, 0, mModelMatrix, 0);

        // This multiplies the modelview matrix by the projection matrix, and stores the result in the MVP matrix
        // (which now contains model * view * projection).
        Matrix.multiplyMM(mMVPMatrix, 0, mProjectionMatrix, 0, mMVPMatrix, 0);

        GLES20.glUniformMatrix4fv(mMVPMatrixHandle, 1, false, mMVPMatrix, 0);
        GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, 3);

    }
GLES20.glVertexAttribPointer(mPositionHandle, mPositionDataSize, GLES20.GL_FLOAT, false,
                mStrideBytes, frameBuffer);

mPositionHandle是GLES20.glGetAttribLocation(programHandle, "a_Position");綁定的變量,mPositionDataSize表示position坐標的數據個數,
也就是多少個數據表示一個坐標,mStrideBytes表示每次讀取多少字節數據。
同樣地,GLES20.glVertexAttribPointer(mColorHandle, mColorDataSize, GLES20.GL_FLOAT, false,mStrideBytes, frameBuffer);
是傳入color數據。
Matrix.multiplyMM是計算MVP矩陣。

glDrawArrays參數詳解

在OpenGl中所有的圖形都是通過分解成三角形的方式進行繪制。
繪制圖形通過GL10類中的glDrawArrays方法實現,

該方法原型:
glDrawArrays(int mode, int first,int count)

*參數1:有三種取值
1.GL_TRIANGLES:每三個頂之間繪制三角形,之間不連接
2.GL_TRIANGLE_FAN:以V0V1V2,V0V2V3,V0V3V4,……的形式繪制三角形
3.GL_TRIANGLE_STRIP:順序在每三個頂點之間均繪制三角形。這個方法可以保證從相同的方向上所有三角形均被繪制。以V0V1V2,V1V2V3,V2V3V4……的形式繪制三角形
*參數2:從數組緩存中的哪一位開始繪制,一般都定義為0
*參數3:頂點的數量

MainActivity

MainActivity中增加opengl版本支持相關代碼。

    //GLSurfaceView
    private MyGLSurfaceView mGLSurfaceView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        initGL();
        final ActivityManager activityManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
        final ConfigurationInfo configurationInfo = activityManager.getDeviceConfigurationInfo();
        final boolean supportsEs2 = configurationInfo.reqGlEsVersion >= 0x20000;
        if (supportsEs2)
        {
            // Request an OpenGL ES 2.0 compatible context.
            mGLSurfaceView.setEGLContextClientVersion(2);
            // Set the renderer to our demo renderer, defined below.
            mGLSurfaceView.setRenderer(new MyRender(this));
        }
        else
        {
            // This is where you could create an OpenGL ES 1.x compatible
            // renderer if you wanted to support both ES 1 and ES 2.
            return;
        }
        setContentView(mGLSurfaceView);
    }
    //初始化 opengl相關
    private void initGL() {
        mGLSurfaceView = new MyGLSurfaceView(this);
    }
    //在下面的兩個方法中,必須有對  GLSurfaceView的處理,當Activity暫停時,在onPause中處理
    //同樣在onResume中有相應的恢復處理
    //下面是最長見的處理方法
    @Override
    protected void onPause() {
        super.onPause();
        mGLSurfaceView.onPause();
    }
    @Override
    protected void onResume() {
        super.onResume();
        mGLSurfaceView.onResume();
    }

總結

畫一個三角形和寫一個Hello World一樣難!!!

References

open GL空間坐標系的理解

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