Android中你可以在JNI层或者java层使用OpenGL ES,JNI需要自己去调用OpenGL的库方法;java层你只需要使用GLSurfaceView组件,你就可以使用OpenGL的一些方法,来进行绘图工作了,但是这里OpenGL与GLSurfaceView是绑定在一块的,同生共死。
插入一个疑问,Android中有了view为什么有设计出surfaceView呢?
先使用java层的openGL ES
因为openGL是一个native的库,所以很多数据都是需要放到jni层去处理,在后面的很多代码细节都可以发现
glSurfaceView = findViewById(R.id.gl_surface);
glSurfaceView.setEGLContextClientVersion(2);
glSurfaceView.setRenderer(new GLRender(this));
//RENDERMODE_WHEN_DIRTY 触发式手动刷新 RENDERMODE_CONTINUOUSLY 持续刷新 固定周期刷新
glSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
这里我们需要知道openGL是如何来进行画画的,OpenGL只能画三种图像:点、线和三角形,任何复杂的图像都可以由这三种图像构成;openGL是如何画出这三种图像的呢?
通过坐标点和着色器,openGL的坐标系是三维坐标系,常规的数学坐标系,屏幕中心是原点,坐标系 如下图:
着色器shader分为顶点着色器和片着色器,这两个好比就是画笔和填充桶的关系
综上,我们开发需要的东西:
需要注意的openGL要的是ByteFloat类型而不是基础的float类型,大小端字节序问题
private float[] vertexData = new float[]{
//三角形
-0.5f, 0,
0.5f, 0,
0, 1,
//歪三角形
-0.5f, 0,
0.5f, 0,
1, -1,
//线
-0.5f , 0f,
0.5f , 0f,
//点
0, 0.5f
};
private void init(){
//千万不要弄成allocate方法了,这个方法产生的内存在java堆上,openGL拿不到的
floatBuffer = ByteBuffer.allocateDirect(vertexData.length * 4)
.order(ByteOrder.nativeOrder())
.asFloatBuffer();
floatBuffer.put(vertexData);
floatBuffer.position(0);
}
type对应GLES20.GL_VERTEX_SHADER、GLES20.GL_FRAGMENT_SHADER;
拿到shader还需要链接OpenGL代码程序,才能使用其功能
/**
* 获取编译的sourceCode和shader
* @param type
* @param sourceCode
* @return
*/
public static int getCompileShader(int type, String sourceCode){
//创建着色器
int glShaderId = GLES20.glCreateShader(type);
if(glShaderId == 0){
LogHelper.log_i("create shader error!");
return 0;
}
/**
* 加入我们定义opengl的源码,在raw文件加下
* 编译源码,为什么要这么做?
* openGL的所有功能都是JNI层实现的,java层最终都是在jvm中运行的,jni是拿不到数据
* 所以我们编写的源码,经过编译在jni产生openGL想要的源码
*/
GLES20.glShaderSource(glShaderId, sourceCode);
GLES20.glCompileShader(glShaderId);
int[] compileStatus = new int[1];
GLES20.glGetShaderiv(glShaderId, GLES20.GL_COMPILE_STATUS, compileStatus, 0);
if(compileStatus[0] == 0){
LogHelper.log_i("compile source code error!");
return 0;
}
return glShaderId;
}
/**
* 编译了源码只是我们要的功能,这些功能需要使用OpenGL的东东,所以需要链接openGL program
* @param vertexShader
* @param fragShader
* @return
*/
public static int linkOpenGLProgram(int vertexShader, int fragShader){
int glProgram = GLES20.glCreateProgram();
if(glProgram == 0){
LogHelper.log_i("create openGL program error!");
return 0;
}
GLES20.glAttachShader(glProgram, vertexShader);
GLES20.glAttachShader(glProgram, fragShader);
GLES20.glLinkProgram(glProgram);
int[] linkStatus = new int[1];
GLES20.glGetProgramiv(glProgram,GLES20.GL_LINK_STATUS, linkStatus, 0);
if(linkStatus[0] == 0){
LogHelper.log_i("link openGL source code error!");
return 0;
}
return glProgram;
}
需要放到Render的onDrawFrame回调方法中去
glUniform4f(fragGLColor, 1.0f, 1.0f, 1.0f, 1.0f):CPU向GPU传递的4个颜色值RGBA,fragGLColor是片元着色器内部的颜色变量
glDrawArrays画画的图形及坐标数组及偏移
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
GLES20.glUniform4f(fragGLColor, 1.0f, 1.0f, 1.0f, 1.0f);
GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, 6); //绘制三角形 三角形坐标是我们定义数组的0~6位置
//绘制线
GLES20.glUniform4f(fragGLColor, 1.0f, 0.0f, 0.0f, 1.0f);
GLES20.glDrawArrays(GLES20.GL_LINES, 6, 2);
//绘制点 glUniform4f uniform是不可变类型变量 4f 参数有4个浮点型,该函数主要用于CPU向GPU发送4个颜色值RGBA
GLES20.glUniform4f(fragGLColor, 0f, 0f, 0f, 0f);
GLES20.glDrawArrays(GLES20.GL_POINTS, 8, 1);
源码库链接
常规的画一个独立的三角形使用GLES20.GL_TRIANGLE标志位进行画画,这种作画需要你指定三角形的3个点坐标,但是一些复杂图形由三角形组合而成的,没有必要一一去列举,可以服用很多坐标,下面两种就是对点和边的服用
这种方式,第一个点位中心复用点,其他的数据都会复用这个点,看一组数据,详细的解释看代码注释
/**
* Triangle Fan三角形的另一种方式,请看下面前两列数据,
* 第一列为中心点,后面第二行与第三行和第一行形成三角形;
* 第三行、第四行与第一行又形成一个三角形 ,依次类推,规律有点类似为123、134、145等
* 后三列数据是颜色值RGB
*/
0, 0,
-0.5f, -0.5f,
0.5f, -0.5f,
0.5f, 0.5f,
-0.5f, 0.5f
直接看数据更容易理解:
/**
* triangle strip是另一种三角形组成,图形画法如下
* 123行构成三角形
* 23行的点构成边,与第4行点形成三角形;也就是
* 123三角、234三角、345三角
*/
-0.25f, 0,
-0.5f, 0.25f,
-0.5f, -0.25f,
-0.75f, 0
省去调用FragmentShader的颜色变量再近些赋值的操作,直接在读取的Position位置坐标时,指定颜色值范围,openGL内部直接读取颜色值再自行赋值给FragmentShader颜色
precision mediump float;
varying vec4 v_Color;
void main() {
gl_FragColor = v_Color;
}
===========================文件分割线=============================
attribute vec4 a_Position;
attribute vec4 a_Color;
varying vec4 v_Color;
void main(){
gl_Position = a_Position;
gl_PointSize = 20.f; //指定点的大小 不然划出来看不见
v_Color = a_Color;
}
private final int POS_LEN = 2;
private final int COLOR_LEN = 3;
private final int STRIDE = (POS_LEN+COLOR_LEN) * 4;
private float[] vertexData = new float[]{
/**
* Triangle Fan 后面3列是颜色值
*/
0, 0, 1f, 1f, 1f,
-0.5f, -0.5f, 0.7f, 0.7f, 0.7f,
0.5f, -0.5f, 0.7f, 0.7f, 0.7f,
0.5f, 0.5f, 0.7f, 0.7f, 0.7f,
-0.5f, 0.5f, 0.7f, 0.7f, 0.7f,
/**
* triangle strip
*/
-0.25f, 0, 1f, 1f, 1f,
-0.5f, 0.25f,0.7f, 0.7f, 0.7f,
-0.5f, -0.25f,0.7f, 0.7f, 0.7f,
-0.75f, 0, 0.7f, 0.7f, 0.7f
};
glPos = GLES20.glGetAttribLocation(glProgram, "a_Position");
glColor = GLES20.glGetAttribLocation(glProgram, "a_Color");
//指明数据的分布样式 POS_LEN=2 COLOR_LEN=3
GLES20.glVertexAttribPointer(glPos, POS_LEN, GLES20.GL_FLOAT, false, STRIDE, floatBuffer);
GLES20.glEnableVertexAttribArray(glPos);
GLES20.glVertexAttribPointer(glColor, COLOR_LEN, GLES20.GL_FLOAT, false, STRIDE, floatBuffer);
GLES20.glEnableVertexAttribArray(glColor);
@Override
public void onDrawFrame(GL10 gl) {
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_FAN, 0, 5);
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 5, 4);
}
颜色值的原理,实际上是从数据中读到a_color,a_Color赋值给v_Color,c_Color在赋值给gl_FragColor;