OpenGL ES 入門

寫在前面

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

參考資料:android

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

1、前言

目前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框架接口。github

2、繪製三角形實例

本文寫一個最基本的三角形繪製,來講明一下 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 代碼:windows

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 接口,並實現其中的關鍵方法app

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

所有的代碼就只這樣了,具體繪製過程下面來講。運行程序,咱們看到效果以下:

3、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 文檔。

4、 OpenGL 中的 VAO 和 VBO。

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

經過使用 VAO 和 VBO ,能夠創建 VAO 與 VBO 的索引對應關係,一次寫入數據以後,每次使用只須要調用 glBindVertexArray 方法便可,避免重複進行數據的複製, 大大提升繪製效率。

int[] VBO = new int[2];
int[] VAO = new int[2];
glGenVertexArrays(2, VAO, 0);
glGenBuffers(2, 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]);

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);
相關文章
相關標籤/搜索