写在前面

记录一下 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 Getting started/05 Shaders/]

我这里记录一下,着色器的编译过程:

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

OpenGL ES 入门的更多相关文章

  1. OpenGL ES入门09-GLSL实现常见特效 [转]

    本文转自简书,原文地址http://www.jianshu.com/p/e4a8c83cd37 本文是关于OpenGL ES的系统性学习过程,记录了自己在学习OpenGL ES时的收获. 这篇文章的目 ...

  2. OpenGL ES入门详解

     http://blog.csdn.net/wangyuchun_799/article/details/7736928 1.决定你要支持的OpenGL ES的版本.目前,OpenGL ES包含1.1 ...

  3. IOS 中openGL使用教程1(openGL ES 入门篇 | 搭建openGL环境)

    OpenGL版本 iOS系统默认支持OpenGl ES1.0.ES2.0以及ES3.0 3个版本,三者之间并不是简单的版本升级,设计理念甚至完全不同,在开发OpenGL项目前,需要根据业务需求选择合适 ...

  4. Android OpenGL ES 入门系列(二) --- 环境搭建

    转载请注明出处 本文出自Hansion的博客 本章介绍如何使用GLSurfaceView和GLSurfaceView.Renderer完成在Activity中的最简单实现. 1.在AndroidMan ...

  5. Android OpenGL ES 入门系列(一) --- 了解OpenGL ES的前世今生

    转载请注明出处 本文出自Hansion的博客 OpenGL ES (OpenGL for Embedded Systems) 是 OpenGL 三维图形 API 的子集,针对手机.PDA和游戏主机等嵌 ...

  6. iOS开发-OpenGL ES入门教程1

    http://www.jianshu.com/p/750fde1d8b6a 这里是一篇新手教程,环境是Xcode7+OpenGL ES 2.0,目标写一个OpenGL ES的hello world.O ...

  7. IOS 中openGL使用教程2(openGL ES 入门篇 | 绘制一个多边形)

    在上一篇我们学习了如何搭建IOS下openGL的开发环境,接下来我们来学习如何绘制一个多边形. 在2.0之前,es的渲染采用的是固定管线,何为固定管线,就是一套固定的模板流程,局部坐标变换 -> ...

  8. IOS 中openGL使用教程3(openGL ES 入门篇 | 纹理贴图(texture)使用)

    在这篇文章中,我们将学习如何在openGL中使用纹理贴图. penGL中纹理可以分为1D,2D和3D纹理,我们在绑定纹理对象的时候需要指定纹理的种类.由于本文将以一张图片为例,因此我们为我们的纹理对象 ...

  9. IOS 中openGL使用教程4(openGL ES 入门篇 | 离屏渲染)

    通常情况下,我们使用openGL将渲染好的图片绘制到屏幕上,但有时候我们不想显示处理结果,这时候就需要使用离屏渲染了. 正常情况下,我们将屏幕,也就是一个CAEAGLLayer对象作为渲染目标,离屏渲 ...

随机推荐

  1. python接口自动化5-session关联

    前言 我们不难发现浏览器中存在着cookie缓存等,但我们在python中如果像浏览器这样的缓存,我们就很难的需要关联cookie或会话了. 但python的requests库,就封装了Session ...

  2. IT兄弟连 Java语法教程 赋值运算符

    从本书之前的内容中就一直在使用赋值运算符.现在是正式介绍赋值运算符的时候了.赋值运算符是单个等号”=“.在Java中,赋值运算符的工作方式与所有其它计算机语言相同.它的一般形式如下: var = ex ...

  3. nginx-配置文件样例

    1, 总配置文件 user nobody nobody; worker_processes ; worker_rlimit_nofile ; error_log /etc/nginx-idfa/log ...

  4. ros相机标定

    没有经过校准的camera拍摄的图片是有畸变的.如下图: 而我们希望得到的图片是这样的 ros中提供了一个程序camera_calibration帮助我们去做校准. 具体怎么校准参考 https:// ...

  5. java基础(20):Map、可变参数、Collections

    1. Map接口 1.1 Map接口概述 我们通过查看Map接口描述,发现Map接口下的集合与Collection接口下的集合,它们存储数据的形式不同,如下图. Collection中的集合,元素是孤 ...

  6. arcgis api for javascript 学习(四) 地图的基本操作

    1.文章讲解的为地图的平移.放大.缩小.前视图.后视图以及全景视图的基本功能操作 2.主要用到的是arcgis api for javascript中Navigation的用法,代码如下: <! ...

  7. 【Gradle】Gradle构建脚本基础

    Gradle构建脚本基础 Settings文件 在Gradle中,定义了一个设置文件,用于初始化以及工程树的配置.设置文件的默认名为settings.gradle,放在根工程目录下. 设置文件大多数的 ...

  8. python:html元素解析

    说明 主要是总结我通过python实现html解析的一个初步的思路和记录实现基础html解析的代码.本解析方式仅仅 只是实现了html按元素解析的功能,具体元素的分类获取还得需要再进行进一步的优化. ...

  9. PHP将数组转字符串

    implode(',',$arr) //将数组转字符串 $arr = [ 'a'=>1, 'b'=>2, 'c'=>3, ]; $arr_string = implode(',',$ ...

  10. markdown的语法

    目录 md格式是一个纯文本格式,对于一个程序员来说,用md格式代替word.txt等格式用来写说明文档或者blog,目前github以及CSDN都支持md格式书写blog了. md格式的语法: 无序列 ...