说明:Android中1.x已经过时了,且2.0并不兼容1.x。本文以2.0版本为准!
1. 创建GLSurfaceView和Renderer
详细解释在代码中用注释标明了,就不另作介绍了。
GLSurfaceView负责界面展示,GLSurfaceView.Renderer负责界面的渲染逻辑控制。
public class TriangleGLView extends GLSurfaceView {
public TriangleGLView(Context context) {
this(context,null);
}
public TriangleGLView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
private void init() {
//设置OpenGLES版本号2.0
setEGLContextClientVersion(2);
//设置渲染器
setRenderer(new TriangleRenderer(this));
//设置渲染模式
//①RENDERMODE_CONTINUOUSLY:主动绘制,一定的时间间隔自动调用onDrawFrame方法,进行绘制
//②RENDERMODE_WHEN_DIRTY:被动绘制,只有当调用requestRender()方法是,才会进行绘制。
setRenderMode(RENDERMODE_WHEN_DIRTY);
}
private class TriangleRenderer implements Renderer {
private GLSurfaceView mView;
private Triangle mTriangle;
public TriangleRenderer(GLSurfaceView view){
this.mView = view;
}
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
//设置背景色
GLES20.glClearColor(05f,0.5f,0.5f,0.5f);
//开启OpenGLES的某些功能。GL_DEPTH_TEST:深度测试,当绘制3D图形的时候需要开启,我们现在绘制的是平面图形不要开启。
// GLES20.glEnable(GLES20.GL_DEPTH_TEST);
mTriangle = new Triangle(mView);
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
//设置OpenGLES视口。
GLES20.glViewport(0,0,width,height);
float r = (float)width/height;
//设置平截头体,并给投影矩阵赋值。(frustumM方法赋值的投影矩阵为透视投影,见图透视投影)
Matrix.frustumM(Triangle.sProjMatirx,0,
-r,r,-1,1,1,10);
//设置平截头体,并给投影矩阵赋值。(orthoM方法赋值的投影矩阵为正交投影,见图:正交投影)
//Matrix.orthoM(Triangle.sProjMatirx,0,
// -r,r,-1,1,1,10);
//设置摄像机位置(观察点位置),
Matrix.setLookAtM(Triangle.sVMatrix,0,
0,0,3,
0,0,0,
0,1,0);
}
@Override
public void onDrawFrame(GL10 gl) {
//清除颜色缓存,注释部分为深度缓存。
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT/*|GLES20.GL_DEPTH_BUFFER_BIT*/);
mTriangle.draw();
}
}
}
2. 创建顶点着色器(triangle_ver.glsl)
uniform mat4 u_ProjMatirx;//投影矩阵,通过程序传进来。
uniform mat4 u_VMatirx;//摄像机(观察点)矩阵,通过程序传进来。
uniform mat4 u_MMatirx;//变换矩阵,通过程序传进来。
attribute vec4 a_position;//顶点数据,通过程序传进来。
attribute vec4 a_color;//顶点颜色数据,通过程序传进来。
varying vec4 v_color;//传递给片元着色器的顶点颜色数据。
void main() {
//这里解释下总变换矩阵的作用:a_position是物体坐标,
//而展示到屏幕上需要把物体坐标转换为世界坐标(下图会解释变换过程)。
//注意矩阵顺序不能错,矩阵叉乘的特性。
gl_Position = u_ProjMatirx * u_VMatirx * u_MMatirx * a_position;
//将顶点颜色数据通过共享变量,传递给片元着色器。
v_color = a_color;
}
上图摘自OpenGL坐标系统
创建片元着色器
//片元着色器中需要制定精度
precision mediump float;
varying vec4 v_color;
void main() {
//将顶点颜色数据赋给片元着色器内部,进行栅格化。
gl_FragColor = v_color;
}
3.创建三角形类(Triangle)
内容比较多,简单说一下步骤:
- 初始化顶点数据
- 初始化着色器(包含-获取着色器属性(可选))
- 使用OpenGL ES 2.0绘制三角形(包含-变换矩阵初始化,着色器属性赋值)
package com.linuxpara.gles20example.shader;
import android.opengl.GLES20;
import android.opengl.GLSurfaceView;
import android.opengl.Matrix;
import android.util.Log;
import com.linuxpara.gles20example.util.ShaderUtils;
import java.nio.FloatBuffer;
/**
* Date: 2017/12/11
* *************************************************************
* Auther: 陈占洋
* *************************************************************
* Email: [email protected]
* *************************************************************
* Description:
*/
public class Triangle {
private static final String TAG = "Triangle";
//投影矩阵
public static float[] sProjMatirx = new float[16];
//摄像机矩阵
public static float[] sVMatrix = new float[16];
//变化矩阵
public static float[] sMMatrix = new float[16];
private FloatBuffer mVerBuffer;
private int mVerSize;
private FloatBuffer mColorBuffer;
private int mProgram;
private int a_position;
private int a_color;
private int u_mMatirx;
private int u_projMatrix;
private int u_vMatirx;
public Triangle(GLSurfaceView view) {
initVerData();
initShader(view);
}
/**
* 初始化顶点数据
*/
private void initVerData() {
float[] ver = {
//三角形一共三个顶点
-0.5f, -1f, -3f,//第一个顶点的xyz轴坐标。
0.5f, -1f, -3f,//第二个顶点的xyz轴坐标。
0f, 1f, -3f,//第三个顶点的xyz轴坐标。
};
//顶点个数
mVerSize = ver.length / 3;
//顶点缓存
mVerBuffer = ShaderUtils.getFloatBuffer(ver);
float[] color = {
//顶点的颜色
1, 0, 0, 1,//第一个顶点的颜色RGBA
0, 1, 0, 1,//第二个顶点的颜色RGBA
0, 0, 1, 1,//第三个顶点的颜色RGBA
};
//顶点颜色缓存
mColorBuffer = ShaderUtils.getFloatBuffer(color);
}
/**
* 初始化着色器
* @param view
*/
private void initShader(GLSurfaceView view) {
String verSource = ShaderUtils.getSourceFromAsset("triangle_ver.glsl", view.getResources());
String fragSource = ShaderUtils.getSourceFromAsset("triangle_frag.glsl", view.getResources());
mProgram = createProgram(verSource, fragSource);
if (mProgram == 0){
Log.i(TAG, "initShader: 创建着色器程序失败!!!");
}
//获取shader中的投影矩阵变量。
u_projMatrix = GLES20.glGetUniformLocation(mProgram, "u_ProjMatirx");
//获取shader中的摄像机矩阵变量
u_vMatirx = GLES20.glGetUniformLocation(mProgram, "u_VMatirx");
//获取shader中的变换矩阵变量
u_mMatirx = GLES20.glGetUniformLocation(mProgram, "u_MMatirx");
//获取shader中的顶点数据变量
a_position = GLES20.glGetAttribLocation(mProgram, "a_position");
//获取shader中的顶点颜色变量
a_color = GLES20.glGetAttribLocation(mProgram, "a_color");
}
/**
* 创建着色器程序。
* @param verSource
* @param fragSource
* @return
*/
protected int createProgram(String verSource,String fragSource){
int verShader = loadShader(GLES20.GL_VERTEX_SHADER, verSource);
if (verShader == 0){
return 0;
}
int fragShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragSource);
if (fragShader == 0){
return 0;
}
int program = GLES20.glCreateProgram();
if (program != 0){
//依赖
GLES20.glAttachShader(program,verShader);
checkGLError("glAttachShader");
GLES20.glAttachShader(program,fragShader);
checkGLError("glAttachShader");
//链接
GLES20.glLinkProgram(program);
int[] linked = new int[1];
GLES20.glGetProgramiv(program,GLES20.GL_LINK_STATUS,linked,0);
if (linked[0] == 0){
Log.e("ES20_ERROR", "Could not link program: ");
Log.e("ES20_ERROR", GLES20.glGetProgramInfoLog(program));
GLES20.glDeleteProgram(program);
program = 0;
}
}
return program;
}
/**
* 加载着色器
* @param shaderType
* @param shaderSource
* @return
*/
private int loadShader(int shaderType,String shaderSource){
//创建shader
int shader = GLES20.glCreateShader(shaderType);
if (shader != 0){
//加载shader源码
GLES20.glShaderSource(shader,shaderSource);
//编译源码
GLES20.glCompileShader(shader);
int[] complied = new int[1];
//获取shader信息
GLES20.glGetShaderiv(shader,GLES20.GL_COMPILE_STATUS,complied,0);
if (complied[0] == 0){
Log.e("ES20_ERROR", "Could not compile shader " + shaderType + ":");
Log.e("ES20_ERROR", GLES20.glGetShaderInfoLog(shader));
//删除shader
GLES20.glDeleteShader(shader);
shader = 0;
}
}
return shader;
}
public void checkGLError(String op) {
int error = 0;
if ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR){
Log.e("ES20_ERROR", op + ": glError " + error);
throw new RuntimeException(op + ": glError " + error);
}
}
public void draw() {
//指定使用着色器程序。
GLES20.glUseProgram(mProgram);
//初始化变换矩阵
Matrix.setRotateM(sMMatrix,0,
0,
0,0,-1f);
//给shader中u_ProjMatrix变量赋值
GLES20.glUniformMatrix4fv(u_projMatrix,1,
false,sProjMatirx,0);
//给shader中u_VMatirx变量赋值
GLES20.glUniformMatrix4fv(u_vMatirx,1,
false,sVMatrix,0);
//给shader中u_MMatirx变量赋值
GLES20.glUniformMatrix4fv(u_mMatirx,1,
false,sMMatrix,0);
//给shader中a_position变量赋值
GLES20.glVertexAttribPointer(a_position,3,GLES20.GL_FLOAT,
false,0,mVerBuffer);
//给shader中a_color变量赋值
GLES20.glVertexAttribPointer(a_color,4,GLES20.GL_FLOAT,
false,0,mColorBuffer);
//开启顶点数据
GLES20.glEnableVertexAttribArray(a_position);
GLES20.glEnableVertexAttribArray(a_color);
//绘制顶点数据
GLES20.glDrawArrays(GLES20.GL_TRIANGLES,0,mVerSize);
//关闭顶点数据
GLES20.glDisableVertexAttribArray(a_position);
GLES20.glDisableVertexAttribArray(a_color);
}
}
用到的ShaderUtils工具类
代码比较简单,不做过多说明
package com.linuxpara.gles20example.util;
import android.content.res.Resources;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
/**
* Date: 2017/12/11
* *************************************************************
* Auther: 陈占洋
* *************************************************************
* Email: [email protected]
* *************************************************************
* Description:
*/
public class ShaderUtils {
/**
* 获取浮点类型缓存
*
* @param buffer
* @return
*/
public static FloatBuffer getFloatBuffer(float[] buffer) {
FloatBuffer floatBuffer = ByteBuffer.allocateDirect(buffer.length * 4)
.order(ByteOrder.nativeOrder())
.asFloatBuffer()
.put(buffer);
floatBuffer.position(0);
return floatBuffer;
}
/**
* 获取int类型缓存
*
* @param buffer
* @return
*/
public static IntBuffer getIntBuffer(int[] buffer) {
IntBuffer intBuffer = ByteBuffer.allocateDirect(buffer.length * 4)
.order(ByteOrder.nativeOrder())
.asIntBuffer()
.put(buffer);
intBuffer.position(0);
return intBuffer;
}
/**
* 获取字节类型缓存
*
* @param buffer
* @return
*/
public static ByteBuffer getByteBuffer(byte[] buffer) {
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(buffer.length)
.order(ByteOrder.nativeOrder())
.put(buffer);
byteBuffer.position(0);
return byteBuffer;
}
/**
* 从资产目录中获取shader代码
*
* @param fileName
* @return
*/
public static String getSourceFromAsset(String fileName, Resources resources) {
InputStream in = null;
ByteArrayOutputStream baos = null;
String result = "";
try {
in = resources.getAssets().open(fileName);
baos = new ByteArrayOutputStream();
byte[] buf = new byte[1024];
int len = 0;
while ((len = in.read(buf)) != -1) {
baos.write(buf, 0, len);
}
String source = baos.toString("UTF-8");
result = source.replaceAll("\\r\\n", "\n");
} catch (IOException e) {
e.printStackTrace();
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (baos != null) {
try {
baos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return result;
}
}
/**
* 打印数组类型的矩阵
* @param
* @param matrix
* @param column
* @return
*/
public static String printFloatMatrixArray(float[] matrix, int column) {
StringBuilder result = new StringBuilder();
for (int i = 0; i < matrix.length; i++) {
if (i%column == 0){
result.append("\n");
}else {
result.append(",");
}
result.append(matrix[i]);
}
return result.toString();
}
}
补充:以下图片摘自极客学院。
GLES20.glDrawArrays(GLES20.GL_TRIANGLES,0,mVerSize);
中绘制类型解释。
GL_POINTS:绘制独立的点。
GL_LINES:顶点两两连接,为多条线段构成。
GL_LINE_STRIP:绘制一系列线段。
GL_LINE_LOOP:类同上,但是首尾相连,构成一个封闭曲线。
GL_TRIANGLES:每隔三个顶点构成一个三角形,为多个三角形组成。
GL_TRIANGLE_STRIP:每相邻三个顶点组成一个三角形,为一系列相接三角形构成。
GL_TRIANGLE_FAN:以一个点为三角形公共顶点,组成一系列相邻的三角形。