OpenGL ES 入門

寫在前面

記錄一下 OpenGL ES Android 開發的入門教程。邏輯性可能不那麼強,想到哪寫到哪。也可能自己的一些理解有誤。

參考資料:

LearnOpenGL CN
Android官方文檔
《OpenGL ES應用開發實踐指南Android卷》
《OpenGL ES 3.0 編程指南第2版》

一、前言

目前android 4.3或以上支持opengles 3.0,但目前很多運行android 4.3系統的硬件能支持opengles 3.0的也是非常少的。不過,opengles 3.0是向後兼容的,當程序發現硬件不支持opengles 3.0時則會自動調用opengles 2.0的API。Andorid 中使用 OpenGLES 有兩種方式,一種是基於Android框架API, 另一種是基於 Native Development Kit(NDK)使用 OpenGL。本文介紹Android框架接口。

二、繪製三角形實例

本文寫一個最基本的三角形繪製,來說明一下 OpenGL ES 的基本流程,以及注意點。

2.1 創建一個 Android 工程,在 AndroidManifest.xml 文件中聲明使用 opengles3.0

<!-- Tell the system this app requires OpenGL ES 3.0. -->
<uses-feature android:glEsVersion="0x00030000" android:required="true" />

如果程序中使用了紋理壓縮的話,還需進行如下聲明,以防止不支持這些壓縮格式的設備嘗試運行程序。

<supports-gl-texture android:name="GL_OES_compressed_ETC1_RGB8_texture" />
<supports-gl-texture android:name="GL_OES_compressed_paletted_texture" />

2.2 MainActivity 使用 GLSurfaceView

MainActivity.java 代碼:

package com.sharpcj.openglesdemo;

import android.app.ActivityManager;
import android.content.Context;
import android.content.pm.ConfigurationInfo;
import android.opengl.GLSurfaceView;
import android.os.Build;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;

public class MainActivity extends AppCompatActivity {
    private static final String TAG = MainActivity.class.getSimpleName();

    private GLSurfaceView mGlSurfaceView;
    private boolean mRendererSet;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
//        setContentView(R.layout.activity_main);
        if (!checkGlEsSupport(this)) {
            Log.d(TAG, "Device is not support OpenGL ES 2");
            return;
        }
        mGlSurfaceView = new GLSurfaceView(this);
        mGlSurfaceView.setEGLContextClientVersion(2);
        mGlSurfaceView.setEGLConfigChooser(8, 8, 8, 8, 16, 0);
        mGlSurfaceView.setRenderer(new MyRenderer(this));
        setContentView(mGlSurfaceView);
        mRendererSet = true;
    }

    @Override
    protected void onPause() {
        super.onPause();
        if (mRendererSet) {
            mGlSurfaceView.onPause();
        }
    }

    @Override
    protected void onResume() {
        super.onResume();
        if (mRendererSet) {
            mGlSurfaceView.onResume();
        }
    }

    /**
     * 檢查設備是否支持 OpenGLEs 2.0
     *
     * @param context 上下文環境
     * @return 返回設備是否支持 OpenGLEs 2.0
     */
    public boolean checkGlEsSupport(Context context) {
        final ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
        final ConfigurationInfo configurationInfo = activityManager.getDeviceConfigurationInfo();
        final boolean supportGlEs2 = configurationInfo.reqGlEsVersion >= 0x20000
                || (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1
                && (Build.FINGERPRINT.startsWith("generic")
                || Build.FINGERPRINT.startsWith("unknown")
                || Build.MODEL.contains("google_sdk")
                || Build.MODEL.contains("Emulator")
                || Build.MODEL.contains("Andorid SDK built for x86")));
        return supportGlEs2;
    }
}

關鍵步驟:

  • 創建一個 GLSurfaceView 對象
  • 給GLSurfaceView 對象設置 Renderer 對象
  • 調用 setContentView() 方法,傳入 GLSurfaceView 對象。

2.3 實現 SurfaceView.Renderer 接口中的方法

創建一個類,實現 GLSurfaceView.Renderer 接口,並實現其中的關鍵方法

package com.sharpcj.openglesdemo;

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

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

import static android.opengl.GLES30.*;


public class MyRenderer implements GLSurfaceView.Renderer {
    private Context mContext;
    private MyTriangle mTriangle;

    public MyRenderer(Context mContext) {
        this.mContext = mContext;
    }

    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
        mTriangle = new MyTriangle(mContext);
    }

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

    @Override
    public void onDrawFrame(GL10 gl) {
        glClear(GL_COLOR_BUFFER_BIT);
        mTriangle.draw();
    }
}

三個關鍵方法:

  • onSurfaceCreated() - 在View的OpenGL環境被創建的時候調用。
  • onSurfaceChanged() - 如果視圖的幾何形狀發生變化(例如,當設備的屏幕方向改變時),則調用此方法。
  • onDrawFrame() - 每一次View的重繪都會調用

glViewport(0, 0, width, height); 用於設置視口。
glCrearColor(1.0f, 1.0f, 1.0f, 1.0f) 方法用指定顏色(這裏是白色)清空屏幕。
在 onDrawFrame 中調用 glClearColor(GL_COLOR_BUFFER_BIT) ,擦除屏幕現有的繪製,並用之前的顏色清空屏幕。 該方法中一定要繪製一些東西,即便只是清空屏幕,因爲該方法調用後會交換緩衝區,並顯示在屏幕上,否則可能會出現閃爍。該例子中將具體的繪製封裝在了 Triangle 類中的 draw 方法中了。
注意:在 windows 版的 OpenGL 中,需要手動調用 glfwSwapBuffers(window) 來交換緩衝區。

2.4 OpenGL ES 的關鍵繪製流程

創建 MyTriangle.java 類:

package com.sharpcj.openglesdemo;

import android.content.Context;

import com.sharpcj.openglesdemo.util.ShaderHelper;
import com.sharpcj.openglesdemo.util.TextResourceReader;

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

import static android.opengl.GLES30.*;

public class MyTriangle {
    private final FloatBuffer mVertexBuffer;

    static final int COORDS_PER_VERTEX = 3;  // number of coordinates per vertex in this array
    static final int COLOR_PER_VERTEX = 3;  // number of coordinates per vertex in this array

    static float triangleCoords[] = {   // in counterclockwise order:
            0.0f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f,     // top
            -0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f,   // bottom left
            0.5f, -0.5f, 0.0f, 0.0f, 0.0f, 1.0f     // bottom right
    };


    private Context mContext;
    private int mProgram;

    public MyTriangle(Context context) {
        mContext = context;
        // initialize vertex byte buffer for shape coordinates
        mVertexBuffer = ByteBuffer.allocateDirect(triangleCoords.length * 4).order(ByteOrder.nativeOrder()).asFloatBuffer();
        mVertexBuffer.put(triangleCoords);  // add the coordinates to the FloatBuffer
        mVertexBuffer.position(0);  // set the buffer to read the first coordinate

        String vertexShaderCode = TextResourceReader.readTextFileFromResource(mContext, R.raw.simple_vertex_glsl);
        String fragmentShaderCode = TextResourceReader.readTextFileFromResource(mContext, R.raw.simple_fragment_glsl);

        int vertexShader = ShaderHelper.compileVertexShader(vertexShaderCode);
        int fragmentShader = ShaderHelper.compileFragmentShader(fragmentShaderCode);

        mProgram = ShaderHelper.linkProgram(vertexShader, fragmentShader);
    }

    public void draw() {
        if (!ShaderHelper.validateProgram(mProgram)) {
            glDeleteProgram(mProgram);
            return;
        }
        glUseProgram(mProgram);  // Add program to OpenGL ES environment

//        int aPos = glGetAttribLocation(mProgram, "aPos");  // get handle to vertex shader's vPosition member
        mVertexBuffer.position(0);
        glVertexAttribPointer(0, COORDS_PER_VERTEX, GL_FLOAT, false, (COORDS_PER_VERTEX + COLOR_PER_VERTEX) * 4, mVertexBuffer);  // Prepare the triangle coordinate data
        glEnableVertexAttribArray(0);  // Enable a handle to the triangle vertices

//        int aColor = glGetAttribLocation(mProgram, "aColor");
        mVertexBuffer.position(3);
        glVertexAttribPointer(1, COORDS_PER_VERTEX, GL_FLOAT, false, (COORDS_PER_VERTEX + COLOR_PER_VERTEX) * 4, mVertexBuffer);  // Prepare the triangle coordinate data
        glEnableVertexAttribArray(1);

        // Draw the triangle
        glDrawArrays(GL_TRIANGLES, 0, 3);

    }
}

在該類中,我們使用了,兩個工具類:
TextResourceReader.java, 用於讀取文件的類容,返回一個字符串,準確說,它與 OpenGL 本身沒有關係。

package com.sharpcj.openglesdemo.util;

import android.content.Context;
import android.content.res.Resources;
import android.util.Log;

import java.io.BufferedReader;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;

public class TextResourceReader {

    private static String TAG = "TextResourceReader";

    public static String readTextFileFromResource(Context context, int resourceId) {
        StringBuilder body = new StringBuilder();
        InputStream inputStream = null;
        InputStreamReader inputStreamReader = null;
        BufferedReader bufferedReader = null;
        try {
            inputStream = context.getResources().openRawResource(resourceId);
            inputStreamReader = new InputStreamReader(inputStream);
            bufferedReader = new BufferedReader(inputStreamReader);
            String nextLine;
            while ((nextLine = bufferedReader.readLine()) != null) {
                body.append(nextLine);
                body.append("\n");
            }
        } catch (IOException e) {
            throw new RuntimeException("Could not open resource: " + resourceId, e);
        } catch (Resources.NotFoundException nfe) {
            throw new RuntimeException("Resource not found: " + resourceId, nfe);
        } finally {
            closeStream(inputStream);
            closeStream(inputStreamReader);
            closeStream(bufferedReader);
        }
        return body.toString();
    }

    private static void closeStream(Closeable c) {
        if (c != null) {
            try {
                c.close();
            } catch (IOException e) {
                Log.e(TAG, e.getMessage());
            }
        }
    }
}

ShaderHelper.java 着色器的工具類,這個跟 OpenGL 就有非常大的關係了。

package com.sharpcj.openglesdemo.util;

import android.util.Log;

import static android.opengl.GLES30.*;

public class ShaderHelper {
    private static final String TAG = "ShaderHelper";

    public static int compileVertexShader(String shaderCode) {
        return compileShader(GL_VERTEX_SHADER, shaderCode);
    }

    public static int compileFragmentShader(String shaderCode) {
        return compileShader(GL_FRAGMENT_SHADER, shaderCode);
    }

    private static int compileShader(int type, String shaderCode) {
        final int shaderObjectId = glCreateShader(type);
        if (shaderObjectId == 0) {
            Log.w(TAG, "could not create new shader.");
            return 0;
        }
        glShaderSource(shaderObjectId, shaderCode);
        glCompileShader(shaderObjectId);

        final int[] compileStatus = new int[1];
        glGetShaderiv(shaderObjectId, GL_COMPILE_STATUS, compileStatus, 0);
        /*Log.d(TAG, "Results of compiling source: " + "\n" + shaderCode + "\n: "
                + glGetShaderInfoLog(shaderObjectId));*/

        if (compileStatus[0] == 0) {
            glDeleteShader(shaderObjectId);
            Log.w(TAG, "Compilation of shader failed.");
            return 0;
        }
        return shaderObjectId;
    }

    public static int linkProgram(int vertexShaderId, int fragmentShaderId) {
        final int programObjectId = glCreateProgram();
        if (programObjectId == 0) {
            Log.w(TAG, "could not create new program");
            return 0;
        }
        glAttachShader(programObjectId, vertexShaderId);
        glAttachShader(programObjectId, fragmentShaderId);
        glLinkProgram(programObjectId);
        final int[] linkStatus = new int[1];
        glGetProgramiv(programObjectId, GL_LINK_STATUS, linkStatus, 0);
        /*Log.d(TAG, "Results of linking program: \n"
                + glGetProgramInfoLog(programObjectId));*/
        if (linkStatus[0] == 0) {
            glDeleteProgram(programObjectId);
            Log.w(TAG, "Linking of program failed");
            return 0;
        }
        return programObjectId;
    }

    public static boolean validateProgram(int programId) {
        glValidateProgram(programId);
        final int[] validateStatus = new int[1];
        glGetProgramiv(programId, GL_VALIDATE_STATUS, validateStatus, 0);
        /*Log.d(TAG, "Results of validating program: " + validateStatus[0]
                + "\n Log: " + glGetProgramInfoLog(programId));*/
        return validateStatus[0] != 0;
    }
}

着色器是 OpenGL 裏面非常重要的概念,這裏我先把代碼貼上來,然後來講流程。
在 res/raw 文件夾下,我們創建了兩個着色器文件。
頂點着色器,simple_vertex_shader.glsl

#version 330

layout (location = 0) in vec3 aPos;   // 位置變量的屬性位置值爲 0
layout (location = 1) in vec3 aColor; // 顏色變量的屬性位置值爲 1

out vec3 vColor; // 向片段着色器輸出一個顏色

void main()
{
    gl_Position = vec4(aPos.xyz, 1.0);
    vColor = aColor; // 將ourColor設置爲我們從頂點數據那裏得到的輸入顏色
}

片段着色器, simple_fragment_shader.glsl

#version 330
precision mediump float;

in vec3 vColor;

out vec4 FragColor;

void main()
{
    FragColor = vec4(vColor, 1.0);
}

全部的代碼就只這樣了,具體繪製過程下面來說。運行程序,我們看到效果如下:

三、OpenGL 繪製過程


一張圖說明 OpenGL 渲染過程:

我們看 MyTriangle.java 這個類。
要繪製三角形,我們肯定要定義三角形的頂點座標和顏色。(廢話,不然GPU怎麼知道用什麼顏色繪製在哪裏)。
首先我們定義了一個 float 型數組:

static float triangleCoords[] = {   // in counterclockwise order:
            0.0f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f,     // top
            -0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f,   // bottom left
            0.5f, -0.5f, 0.0f, 0.0f, 0.0f, 1.0f     // bottom right
    };

注意:這個數組中,定義了 top, bottom left, bottom right 三個點。每個點包含六個數據,前三個數表示頂點座標,後三個點表示顏色的 RGB 值。

座標系統

可能注意到了,因爲我們這裏繪製最簡單的平面二維圖像,Z 軸座標都爲 0 ,屏幕中的 X, Y 座標點都是在(-1,1)的範圍。我們沒有對視口做任何變換,設置的默認視口,此時的座標系統是以屏幕正中心爲座標原點。 屏幕最左爲 X 軸 -1 , 屏幕最右爲 X 軸 +1。同理,屏幕最下方爲 Y 軸 -1, 屏幕最上方爲 Y 軸 +1。OpenGL 座標系統使用的是右手座標系,Z 軸正方向爲垂直屏幕向外。

3.1 複製數據到本地內存

mVertexBuffer = ByteBuffer.allocateDirect(triangleCoords.length * 4).order(ByteOrder.nativeOrder()).asFloatBuffer();
mVertexBuffer.put(triangleCoords);

這一行代碼,作用是將數據從 java 堆複製到本地堆。我們知道,在 java 虛擬機內存模型中,數組存在 java 堆中,受 JVM 垃圾回收機制影響,可能會被回收掉。所以我們要將數據複製到本地堆。
首先調用 ByteBuffer.allocateDirect() 分配一塊本地內存,一個 float 類型的數字佔 4 個字節,所以分配的內存大小爲 triangleCoords.length * 4 。
調用 order() 指定字節緩衝區中的排列順序, 傳入 ByteOrder.nativeOrder() 保證作爲一個平臺,使用相同的排序順序。
調用 asFloatBuffer() 可以得到一個反映底層字節的 FloatBuffer 類的實例。
最後調用 put(triangleCoords) 把數據從 Android 虛擬機堆內存中複製到本地內存。

3.2 編譯着色器並鏈接到程序

接下來,通過 TextResourceReader 工具類,讀取頂點着色器和片段着色器文件的的內容。

String vertexShaderCode = TextResourceReader.readTextFileFromResource(mContext, R.raw.simple_vertex_shader);
String fragmentShaderCode = TextResourceReader.readTextFileFromResource(mContext, R.raw.simple_fragment_shader);

然後通過 ShaderHelper 工具類編譯着色器。然後鏈接到程序。

int vertexShader = ShaderHelper.compileVertexShader(vertexShaderCode);
int fragmentShader = ShaderHelper.compileFragmentShader(fragmentShaderCode);

mProgram = ShaderHelper.linkProgram(vertexShader, fragmentShader);
ShaderHelper.validateProgram(mProgram);

着色器

着色器是一個運行在 GPU 上的小程序。着色器的文件其實定義了變量,並且包含 main 函數。關於着色器的詳細教程,請查閱:(LearnOpenGL CN 中的着色器教程)[https://learnopengl-cn.github.io/01%20Getting%20started/05%20Shaders/]

我這裏記錄一下,着色器的編譯過程:

3.2.1 創建着色器對象

int shaderObjectId = glCreateShader(type);`    

創建一個着色器,並返回着色器的句柄(類似java中的引用),如果返回了 0 ,說明創建失敗。GLES 中定義了常量,GL_VERTEX_SHADERGL_FRAGMENT_SHADER 作爲參數,分別創建頂點着色器和片段着色器。

3.2.2 編譯着色器

編譯着色器,

glShaderSource(shaderObjectId, shaderCode);
glCompileShader(shaderObjectId);

下面的代碼,用於獲取編譯着色器的狀態結果。

final int[] compileStatus = new int[1];
glGetShaderiv(shaderObjectId, GL_COMPILE_STATUS, compileStatus, 0);
Log.d(TAG, "Results of compiling source: " + "\n" + shaderCode + "\n: "
        + glGetShaderInfoLog(shaderObjectId));

if (compileStatus[0] == 0) {
    glDeleteShader(shaderObjectId);
    Log.w(TAG, "Compilation of shader failed.");
    return 0;
}

親測上面的程序在我手上真機可以正常運行,在 genymotion 模擬器中運行報瞭如下錯誤:

JNI DETECTED ERROR IN APPLICATION: input is not valid Modified UTF-8: illegal start byte 0xfe

網上搜索了一下,這個異常是由於Java虛擬機內部的dalvik/vm/CheckJni.c中的checkUtfString函數拋出的,並且JVM的這個接口明確是不支持四個字節的UTF8字符。因此需要在調用函數之前,對接口傳入的字符串進行過濾,過濾函數,可以上網搜到,這不是本文重點,所以我把這個 log 註釋掉了

Log.d(TAG, "Results of compiling source: " + "\n" + shaderCode + "\n: "
        + glGetShaderInfoLog(shaderObjectId));

3.2.3 將着色器連接到程序

編譯完着色器之後,需要將着色器連接到程序才能使用。

int programObjectId = glCreateProgram();

創建一個 program 對象,並返回句柄,如果返回了 0 ,說明創建失敗。

glAttachShader(programObjectId, vertexShaderId);
glAttachShader(programObjectId, fragmentShaderId);
glLinkProgram(programObjectId);

將頂點着色器個片段着色器鏈接到 program 對象。下面的代碼用於獲取鏈接的狀態結果:

final int[] linkStatus = new int[1];
glGetProgramiv(programObjectId, GL_LINK_STATUS, linkStatus, 0);
/*Log.d(TAG, "Results of linking program: \n"
        + glGetProgramInfoLog(programObjectId));*/
if (linkStatus[0] == 0) {
    glDeleteProgram(programObjectId);
    Log.w(TAG, "Linking of program failed");
    return 0;
}

3.2.4 判斷 program 對象是否有效

在使用 program 對象之前,我們還做了有效性判斷:

glValidateProgram(programId);
final int[] validateStatus = new int[1];
glGetProgramiv(programId, GL_VALIDATE_STATUS, validateStatus, 0);
/*Log.d(TAG, "Results of validating program: " + validateStatus[0]
                + "\n Log: " + glGetProgramInfoLog(programId));*/

如果 validateStatus[0] == 0 , 則無效。

3.3 關聯屬性與頂點數據的數組

首先調用glUseProgram(mProgram) 將 program 對象添加到 OpenGL ES 的繪製環境。

看如下代碼:

mVertexData.position(0); // 移動指針到 0,表示從開頭開始讀取

// 告訴 OpenGL, 可以在緩衝區中找到 a_Position 對應的數據
int aPos = glGetAttribLocation(mProgram, "aPos"); 
glVertexAttribPointer(aPos, COORDS_PER_VERTEX, GL_FLOAT, false, (COORDS_PER_VERTEX + COLOR_PER_VERTEX) * 4, mVertexBuffer);  // Prepare the triangle coordinate data
glEnableVertexAttribArray(aPos);

int aColor = glGetUniformLocation(mProgram, "aColor");
glVertexAttribPointer(1, COORDS_PER_VERTEX, GL_FLOAT, false, (COORDS_PER_VERTEX + COLOR_PER_VERTEX) * 4, mVertexBuffer);  // Prepare the triangle coordinate data
glEnableVertexAttribArray(aColor);

在 OpenGL ES 2.0 中,我們通過如上代碼,使用數據。調用 glGetAttribLocation() 方法,找到頂點和顏色對應的數據位置,第一個參數是 program 對象,第二個參數是着色器中的入參參數名。
然後調用 glVertexAttribPointer() 方法
參數如下(圖片截取自《OpenGL ES應用開發實踐指南Android卷》):

最後調用glEnableVertexAttribArray(aPos); 使 OpenGL 能使用這個數據。

但是你發現,我們上面給的代碼中並沒有調用 glGetAttribLocation() 方法尋找位置,這是因爲,我使用的 OpenGLES 3.0 ,在 OpenGL ES 3.0 中,着色器代碼中,新增了 layout(location = 0) 類似的語法支持。

#version 330

layout (location = 0) in vec3 aPos;   // 位置變量的屬性位置值爲 0
layout (location = 1) in vec3 aColor; // 顏色變量的屬性位置值爲 1

out vec3 vColor; // 向片段着色器輸出一個顏色

void main()
{
    gl_Position = vec4(aPos.xyz, 1.0);
    vColor = aColor; // 將ourColor設置爲我們從頂點數據那裏得到的輸入顏色
}

這裏已經指明瞭屬性在頂點數組中對應的位置,所以在代碼中,可以直接使用 0 和 1 來表示位置。

3.4 繪製圖形

最後調用 glDrawArrays(GL_TRIANGLES, 0, 3) 繪製出一個三角形。
glDrawArrays() 方法第一個參數指定繪製的類型, OpenGLES 中定義了一些常量,通常有 GL_TRIANGLES , GL_POINTS, GL_LINES, GL_TRIANGLE_STRIP, GL_TRIANGLE_FAN 等等類型,具體每種類型代表的意思可以查閱API 文檔。

四、 OpenGL 中的 VAO 和 VBO。

VAO : 頂點數組對象
VBO :頂點緩衝對象

通過使用 VAO 和 VBO ,可以建立 VAO 與 VBO 的索引對應關係,一次寫入數據之後,每次使用只需要調用 glBindVertexArray 方法即可,避免重複進行數據的複製, 大大提高繪製效率。

int[] VBO = new int[2];
int[] VAO = new int[2];

glGenVertexArrays(0, VAO, 0);
glGenBuffers(0, VBO, 0);
glBindVertexArray(VAO[0]);
glBindBuffer(GL_ARRAY_BUFFER, VBO[0]);
glBufferData(GL_ARRAY_BUFFER, triangleCoords.length * 4, mVertexBuffer, GL_STATIC_DRAW);

glVertexAttribPointer(0, COORDS_PER_VERTEX, GL_FLOAT, false, (COORDS_PER_VERTEX + COLOR_PER_VERTEX) * 4, 0);
glEnableVertexAttribArray(0);
glVertexAttribPointer(1, COORDS_PER_VERTEX, GL_FLOAT, false, (COORDS_PER_VERTEX + COLOR_PER_VERTEX) * 4, COORDS_PER_VERTEX * 4);
glEnableVertexAttribArray(1);
glBindVertexArray(VAO[0]);

glGenVertexArrays(1, VAO, 0);
glGenBuffers(1, VBO, 0);
glBindVertexArray(VAO[1]);
glBindBuffer(GL_ARRAY_BUFFER, VBO[1]);
glBufferData(GL_ARRAY_BUFFER, triangleCoords.length * 4, mVertexBuffer2, GL_STATIC_DRAW);

glVertexAttribPointer(0, COORDS_PER_VERTEX, GL_FLOAT, false, (COORDS_PER_VERTEX + COLOR_PER_VERTEX) * 4, 0);
glEnableVertexAttribArray(0);
glVertexAttribPointer(1, COORDS_PER_VERTEX, GL_FLOAT, false, (COORDS_PER_VERTEX + COLOR_PER_VERTEX) * 4, COORDS_PER_VERTEX * 4);
glEnableVertexAttribArray(1);
glBindVertexArray(VAO[1]);


glBindVertexArray(VAO[0]);
glDrawArrays(GL_TRIANGLES, 0, 3);

glBindVertexArray(VAO[1]);
glDrawArrays(GL_TRIANGLES, 0, 3);
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章