因为最近的项目需要用到openGL ES 2.0,折腾了半天,查了网上一大堆资料,都没找到系统的openGL ES 2.0的学习资料。经过这半天,总算自己写了一个可以画出三角形和正方形的代码,顺便对网上一堆的代码封装了下。嗯,开始说吧。
首先感谢这篇教程:
http://hukai.me/android-training-course-in-chinese/graphics/opengl/environment.html
以及它的android官方原文:https://developer.android.com/training/graphics/opengl/shapes.html
不想往下看的同学,源码在这里:
http://download.csdn.net/detail/code_better/9592177
其他的我就不多说了,想说的都在注释里:
项目结构图:
先在manifest.xml里加上:
<uses-feature
android:glEsVersion="0x00020000"
android:required="true" />
MainActivity:
/**
* GLSurfaceView相当于画布
* 主要绘画工作都在Renderer里
*/
public class MainActivity extends AppCompatActivity {
private GLSurfaceView glSurfaceView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//创建一个GLSurfaceView实例
glSurfaceView = new MyGLSurfaceView(this);
//将glSurfaceView设置为activity的ContentView.
setContentView(glSurfaceView);
}
@Override
protected void onPause() {
super.onPause();
if (glSurfaceView != null) {
glSurfaceView.onPause();
}
}
@Override
protected void onResume() {
super.onResume();
if (glSurfaceView != null) {
glSurfaceView.onResume();
}
}
}
MyGLSurfaceView:
class MyGLSurfaceView extends GLSurfaceView {
public MyGLSurfaceView(Context context) {
super(context);
// 创建一个OpenGL ES 2.0 context,非常重要
setEGLContextClientVersion(2);
//设置Renderer到GLSurfaceView
setRenderer(new MyGL20Renderer());
// 只有在绘制数据改变时才绘制view
//此设置会阻止绘制GLSurfaceView的帧,直到你调用了requestRender(),这样会非常高效
// setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
}
}
MyGL20Renderer:
public class MyGL20Renderer implements GLSurfaceView.Renderer {
//声明一个三角形对象
Triangle triangle = null;
//声明一个正方形对象
Square square = null;
public void onSurfaceCreated(GL10 unused, EGLConfig config) {
//设置背景的颜色
GLES20.glClearColor(0.5f, 0.5f, 0.5f, 1.0f);
triangle = new Triangle();
square = new Square();
}
public void onDrawFrame(GL10 unused) {
// 重绘背景色
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
//绘制三角形
// triangle.draw();
//绘制矩形
square.draw();
}
public void onSurfaceChanged(GL10 unused, int width, int height) {
GLES20.glViewport(0, 0, width, height);
}
}
Util:
/**
* Created by 炜贤 on 2016/8/1.
* <p/>
* 画一个图形:需要至少一个顶点着色器来绘制一个形状,以及一个片段着色器为该形状上色。
* 这些着色器必须被编译然后添加到一个OpenGL ES Program当中,并利用它来绘制形状。
*/
public class Util {
/**
* float类型大小为4个字节
*/
private static final int LENGTH = 4;
private final String vertexShaderCode =
"attribute vec4 vPosition;" +
"void main() {" +
" gl_Position = vPosition;" +
"}";
private final String fragmentShaderCode =
"precision mediump float;" +
"uniform vec4 vColor;" +
"void main() {" +
" gl_FragColor = vColor;" +
"}";
public int loadShader(int type, String shaderCode) {
// 创建一个vertex shader类型(GLES20.GL_VERTEX_SHADER)
// 或一个fragment shader类型(GLES20.GL_FRAGMENT_SHADER)
int shader = GLES20.glCreateShader(type);
// 将源码添加到shader并编译它
GLES20.glShaderSource(shader, shaderCode);
GLES20.glCompileShader(shader);
return shader;
}
/**
* 顶点着色器(Vertex Shader):用来渲染形状顶点的OpenGL ES代码
*
* @return 顶点着色器
*/
public int getVertexShader() {
return loadShader(GLES20.GL_VERTEX_SHADER, vertexShaderCode);
}
/**
* 片段着色器(Fragment Shader):使用颜色或纹理渲染形状表面的OpenGL ES代码。
*
* @return 片段着色器
*/
public int getFragmentShader() {
return loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentShaderCode);
}
/**
* 程式(Program):一个OpenGL ES对象,包含了你希望用来绘制一个或更多图形所要用到的着色器
* 程式(program)是用来装配着色器的(个人理解)
*
* @return 程式
*/
public int getProgram() {
//获取顶点着色器
int vertexShader = getVertexShader();
//获取片段着色器
int fragmentShader = getFragmentShader();
int program = GLES20.glCreateProgram(); // 创建空的OpenGL ES Program
GLES20.glAttachShader(program, vertexShader); // 将vertex shader添加到program
GLES20.glAttachShader(program, fragmentShader); // 将fragment shader添加到program
GLES20.glLinkProgram(program); // 创建可执行的 OpenGL ES program
// 添加program到OpenGL ES环境中
GLES20.glUseProgram(program);
return program;
}
/**
* @param coords_per_vertex 每个顶点的坐标数
* @param vertexBuffer 浮点缓冲区
* @param color 颜色数组,数组的四个数分别为图形的RGB值和透明度
*/
public void draw(int coords_per_vertex, FloatBuffer vertexBuffer, float color[]) {
//获取程式
int program = getProgram();
//得到处理到顶点着色器的vPosition成员
int vPositionHandler = GLES20.glGetAttribLocation(program, "vPosition");
// 启用一个指向图形的顶点数组的handle
GLES20.glEnableVertexAttribArray(vPositionHandler);
// 准备坐标数据
GLES20.glVertexAttribPointer(vPositionHandler, coords_per_vertex,
GLES20.GL_FLOAT, false,
LENGTH * coords_per_vertex, vertexBuffer);
// 得到处理到片段着色器的vPosition成员
int mColorHandle = GLES20.glGetUniformLocation(program, "vColor");
// 设置颜色
GLES20.glUniform4fv(mColorHandle, 1, color, 0);
// 绘制三角形比较简单,这里采用glDrawArrays方法(默认是逆时针方向)
GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, 3);
// 禁用指向图形的顶点数组
GLES20.glDisableVertexAttribArray(vPositionHandler);
}
/**
*
* @param coords_per_vertex 每个顶点的坐标数
* @param vertexBuffer 浮点缓冲区
* @param color 颜色数组,数组的四个数分别为图形的RGB值和透明度
* @param drawOrder 绘制顶点的顺序(按逆时针方向)
* @param drawListBuffer 绘图顺序顶点的缓冲区
*/
public void draw(int coords_per_vertex, FloatBuffer vertexBuffer, float color[], short drawOrder[], ShortBuffer drawListBuffer) {
//获取程式
int program = getProgram();
//得到处理到顶点着色器的vPosition成员
int vPositionHandler = GLES20.glGetAttribLocation(program, "vPosition");
// 启用一个指向图形的顶点数组的handle
GLES20.glEnableVertexAttribArray(vPositionHandler);
// 准备坐标数据
GLES20.glVertexAttribPointer(vPositionHandler, coords_per_vertex,
GLES20.GL_FLOAT, false,
LENGTH * coords_per_vertex, vertexBuffer);
// 得到处理到片段着色器的vPosition成员
int mColorHandle = GLES20.glGetUniformLocation(program, "vColor");
// 设置颜色
GLES20.glUniform4fv(mColorHandle, 1, color, 0);
// 绘制图形
GLES20.glDrawElements(GLES20.GL_TRIANGLES, drawOrder.length, GLES20.GL_UNSIGNED_SHORT, drawListBuffer);
// 禁用指向图形的顶点数组
GLES20.glDisableVertexAttribArray(vPositionHandler);
}
}
Triangle:
/**
* 三角形
*/
public class Triangle {
private FloatBuffer vertexBuffer;
// 设置每个顶点的坐标数
static final int COORDS_PER_VERTEX = 3;
// 设置三角形顶点数组
static float triangleCoords[] = { // 默认按逆时针方向顺序绘制
0.0f, 0.622008459f, 0.0f, // 顶
-0.5f, -0.311004243f, 0.0f, // 左底
0.5f, -0.311004243f, 0.0f // 右底
};
// 设置图形的RGB值和透明度
float color[] = {0.63671875f, 0.76953125f, 0.22265625f, 1.0f};
/**
* 初始化
* 流程:创建一个顶点的缓冲区空间,然后将其作为一个浮点的缓冲区,
* 然后将坐标加到这个缓冲区中,然后将读指针指向第一个位置
*/
public Triangle() {
// 初始化顶点字节缓冲区,用于存放形状的坐标,
ByteBuffer bb = ByteBuffer.allocateDirect(
// (每个浮点数占用4个字节)
triangleCoords.length * 4);
// 设置使用设备硬件的原生字节序
bb.order(ByteOrder.nativeOrder());
// 将ByteBuffer作为一个浮点缓冲区
vertexBuffer = bb.asFloatBuffer();
// 把坐标都添加到FloatBuffer中
vertexBuffer.put(triangleCoords);
// 设置buffer从第一个坐标开始读
vertexBuffer.position(0);
}
/**
* 绘图
*/
public void draw(){
new Util().draw(COORDS_PER_VERTEX,vertexBuffer,color);
}
}
Square:
/**
* 正方形
*/
public class Square {
//顶点缓冲区
private FloatBuffer vertexBuffer;
//绘图顺序顶点缓冲区
private ShortBuffer drawListBuffer;
// 每个顶点的坐标数
static final int COORDS_PER_VERTEX = 3;
//正方形四个顶点的坐标
static float squareCoords[] = {-0.5f, 0.5f, 0.0f, // top left
-0.5f, -0.5f, 0.0f, // bottom left
0.5f, -0.5f, 0.0f, // bottom right
0.5f, 0.5f, 0.0f}; // top right
private short drawOrder[] = {0, 1, 2, 0, 2, 3}; // 顶点的绘制顺序
// 设置图形的RGB值和透明度
float color[] = {0.63671875f, 0.76953125f, 0.22265625f, 1.0f};
public Square() {
// initialize vertex byte buffer for shape coordinates
ByteBuffer bb = ByteBuffer.allocateDirect(
// (坐标数 * 4)
squareCoords.length * 4);
bb.order(ByteOrder.nativeOrder());
vertexBuffer = bb.asFloatBuffer();
vertexBuffer.put(squareCoords);
vertexBuffer.position(0);
// 为绘制列表初始化字节缓冲
ByteBuffer dlb = ByteBuffer.allocateDirect(
// (对应顺序的坐标数 * 2)short是2字节
drawOrder.length * 2);
dlb.order(ByteOrder.nativeOrder());
drawListBuffer = dlb.asShortBuffer();
drawListBuffer.put(drawOrder);
drawListBuffer.position(0);
}
/**
* 绘图
*/
public void draw() {
new Util().draw(COORDS_PER_VERTEX, vertexBuffer, color, drawOrder, drawListBuffer);
}
}
这个画出来的三角形和正方形的效果并不是我们预想的的那样,那是因为投影的问题,请看我之后的博客吧,谢谢!