一、Activity中初始化GLSurfaceView,setMyRenderer
setEGLContextClientVersion():设置需要使用的OpenGL ES的版本;
setRenderer:声明一个Render,对渲染的操作都在Render里面
setRenderMode:
设置渲染模式,默认是不断地渲染,即 RENDERMODE_CONTINUOUSLY;RENDERMODE_WHEN_DIRTY只在onSurfaceCreated时会渲染一次,之后只有调用 GLSurfaceView.requestRender() 方法时才会进行渲染。
@Override
protected void onStart() {
super.onStart();
mGLSurfaceView = findViewById(R.id.gl_surface_view);
//设置OpenGL ES的版本2.0
mGLSurfaceView.setEGLContextClientVersion(2);
mGLSurfaceView.setRenderer(new MyRenderer(getApplicationContext()));
// 设置渲染的模式
mGLSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
}
二、MyRenderer类实现 GLSurfaceView.Renderer
实现GLSurfaceView.Renderer接口的三个方法:
public void onSurfaceCreated(GL10 gl10, EGLConfig eglConfig);
public void onSurfaceChanged(GL10 gl10, int i, int i1);
public void onDrawFrame(GL10 gl10);
(1)创建着色器程序
着色器程序需要在surface创建完成后才能进行创建。
surface创建完成后会回调进onSurfaceCreated方法,可以在onSurfaceCreated方法中对Renderer进行初始化。
- 通过*GLES20.glCreateProgram()*获取着色器程序;
- glAttachShader( int program, int shader ) 编译顶点着色器代码于片元着色器代码,并与着色器程序建立链接;
- *GLES20.glGetAttribLocation(mProgramId, A_POSITION)*获取顶点着色器中的参数“a_Position”与“a_Color”的地址;
- *GLES20.glVertexAttribPointer( int indx, int size, int type, boolean normalized, int stride, java.nio.Buffer ptr );可以为顶点着色器中的参数“a_Position”进行赋值声明,
indx代表上一步获取的地址,
size表示一个顶点在数组中占几位,代码中只是用了xy坐标,所以长度为2,
type表示顶点所使用的数据类型,代码中使用的是浮点型对应GLES20.GL_FLOAT,
normalized指定在访问定点数据值时是应将其标准化(GL_TRUE)还是直接转换为定点值(GL_FALSE)
stride表示当前顶点到下一个顶点需要偏移的字节位数,代码中 = [(坐标位数 + 颜色位数) 每一位的字节长度 ]
ptr 表示传入的顶点数组转换的float类型的byteBuffer
(2)设置显示窗口
在onSurfaceChanged回调方法中会返回当前SurfaceView的宽高;
通过 glViewport( int x, int y, int width, int height ) 设置显示窗口;x,y分别代表窗口向X和Y方向的偏移量,X值增长则图像向右偏移,反之向左,y变量控制上下偏移,width和height 则控制窗口的宽高。
(3)绘制三角形、正方形
在*onDrawFrame(GL10 gl10)*中开始进行图形绘制:
顶点数组中共有7个顶点,绘制三角形从索引0开始取三个,绘制四边形从3开始取4个,OpenGL如何知道每个顶点要取多少位数据,是在前面GLES20.glVertexAttribPointer是声明了顶点数据的步长信息。
//绘制三角形
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_FAN,0,3);
//绘制四边形
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_FAN,3,4);
glDrawArrays( int mode, int first, int count );
- mode:指定要渲染的图元类型。 (GL_POINTS,GL_LINE_STRIP,GL_LINE_LOOP,GL_LINES,GL_TRIANGLE_STRIP,GL_TRIANGLE_FAN和GL_TRIANGLES)
GL_POINTS:GL_POINTS绘制的点比较小不宜看清,可以再顶点着色器代码中设置OpenGL的内置变量gl_PointSize加粗点的显示效果,也可以通过获取参数“a_Position”与“a_Color”类似步骤传入变量赋值给gl_PointSize实现动态调整点的大小。
private static final String VERTEX_SHADER =
“attribute vec4 a_Position;\n” +
“attribute vec4 a_Color;\n” +
“varying vec4 v_Color;\n” +
“void main() {\n” +
" v_Color = a_Color;\n" +
" gl_Position = a_Position;\n" +
" gl_PointSize = 30.0;\n" +
“}”;
GL_LINE_STRIP:
GL_LINE_LOOP:
GL_LINES:
GL_TRIANGLE_STRIP:
GL_TRIANGLE_FAN:
GL_TRIANGLES:
- first:指定已启用阵列中的起始索引。
- count:指定要渲染的索引数。
import android.content.Context;
import android.opengl.GLES20;
import android.opengl.GLSurfaceView;
import android.util.Log;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
public class MyRenderer implements GLSurfaceView.Renderer {
private static final String TAG = "XJ_MyRenderer";
private Context mContext;
private int mProgramId;
private static final String VERTEX_SHADER =
"attribute vec4 a_Position;\n" +
"attribute vec4 a_Color;\n" +
"varying vec4 v_Color;\n" +
"void main() {\n" +
" v_Color = a_Color;\n" +
" gl_Position = a_Position;\n" +
"}";
private static final String FRAGMENT_SHADER =
"precision mediump float;\n" +
"varying vec4 v_Color;\n" +
"void main() {\n" +
" gl_FragColor = v_Color;\n" +
"}";
//单个顶点坐标的长度(<=着色器中定义的向量维度)
private final static int COORDINATE_LENGTH = 2;
//单个顶点颜色的长度
private final static int COLOR_LENGTH = 3;
//浮点类型占用的字节数
private final static int BYTES_FOR_FLOAT = 4;
//需要向GLSL顶点着色器输入的变量名
private static final String A_POSITION = "a_Position";
private static final String A_COLOR = "a_Color";
//STRIDE是一个顶点的字节偏移(顶点坐标xy+颜色rgb)
private final int STRIDE = (COORDINATE_LENGTH+ COLOR_LENGTH )* BYTES_FOR_FLOAT;
private FloatBuffer mVertexData;
/**
* ^
* |1
* |
* |
* -1 |0 1
* ------------------------------>
* |
* |
* |
* |-1
*/
public MyRenderer(Context context) {
mContext = context;
//顶点数组
float[] TRIANGLE_COORDS = {
//坐标 //颜色
0.25f, 0.25f, 1f,0.5f,0.5f,
0.25f, 0.75f, 0.5f, 1f,0.5f,
0.75f, 0.75f, 0.5f, 0.5f,1f,
-0.25f, -0.25f, 1f,0.5f,0.5f,
-0.75f, -0.25f, 0.5f, 1f,0.5f,
-0.75f, -0.75f, 0.5f, 0.5f,1f,
-0.25f, -0.75f, 1f, 0.5f,1f
};
//通过nio ByteBuffer把设置的顶点数据加载到内存
mVertexData = ByteBuffer
.allocateDirect(TRIANGLE_COORDS.length * BYTES_FOR_FLOAT) //需要多少字节内存
.order(ByteOrder.nativeOrder())//大小端排序
.asFloatBuffer()
.put(TRIANGLE_COORDS);//设置数据
}
private void initRenderer() {
//创建着色器程序
mProgramId = MyShaderLoader.loadProgram(VERTEX_SHADER, FRAGMENT_SHADER);
Log.d(TAG, "initRenderer: mProgramId = " + mProgramId );
int aPosition = GLES20.glGetAttribLocation(mProgramId, A_POSITION);
Log.i(TAG, "initRenderer: aPosition = "+aPosition);
mVertexData.position(0);
GLES20.glVertexAttribPointer(aPosition,
COORDINATE_LENGTH,//用几个偏移描述一个顶点
GLES20.GL_FLOAT,//顶点数据类型
false,
STRIDE,//一个顶点需要多少个字节偏移
mVertexData//分配的buffer
);
//开启顶点着色器的attribute
GLES20.glEnableVertexAttribArray(aPosition);
int aColor = GLES20.glGetAttribLocation(mProgramId, A_COLOR);
mVertexData.position(COORDINATE_LENGTH);
GLES20.glVertexAttribPointer(aColor,COLOR_LENGTH,GLES20.GL_FLOAT,false,STRIDE,mVertexData);
GLES20.glEnableVertexAttribArray(aColor);
}
@Override
public void onSurfaceCreated(GL10 gl10, EGLConfig eglConfig) {
Log.i(TAG, "onSurfaceCreated!");
initRenderer();
}
@Override
public void onSurfaceChanged(GL10 gl10, int i, int i1) {
Log.i(TAG, "onSurfaceChanged!");
GLES20.glViewport(0, 0, i, i1);
}
@Override
public void onDrawFrame(GL10 gl10) {
//清屏
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
//绘制三角形
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_FAN,0,3);
//绘制四边形
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_FAN,3,4);
}
}
三、加载着色器以及程序的工具类
着色器句柄、程序句柄、变量句柄类似于在GPU中对应对象的地址。
import android.opengl.GLES20;
import android.util.Log;
public class MyShaderLoader {
private static final String TAG = "XJ_MyShaderLoader";
private static int loadShader(int type, String codeStr) {
//1. 根据类型(顶点着色器、片元着色器)创建着色器,拿到着色器句柄
int shader = GLES20.glCreateShader(type);
Log.i(TAG, "compileShaderCode: type=" + type + " shaderId=" + shader);
if (shader > 0) {
//2. 设置着色器代码 ,shader句柄和code进行绑定
GLES20.glShaderSource(shader, codeStr);
//3. 编译着色器,
GLES20.glCompileShader(shader);
//4. 查询编译状态
int[] status = new int[1];
GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, status, 0);
Log.i(TAG, "loadShader: status[0]=" + status[0]);
//如果失败,释放资源
if (status[0] == 0) {
GLES20.glDeleteShader(shader);
return 0;
}
}
return shader;
}
public static int loadProgram(String verCode, String fragmentCode) {
//1. 创建Shader程序,获取到program句柄
int programId = GLES20.glCreateProgram();
if(programId == 0){
Log.e(TAG, "loadProgram: glCreateProgram error!" );
return 0;
}
Log.d(TAG, "loadProgram: programId = " + programId );
//2. 根据着色器语言类型和代码,attach着色器
GLES20.glAttachShader(programId, loadShader(GLES20.GL_VERTEX_SHADER, verCode));
GLES20.glAttachShader(programId, loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentCode));
//3. 链接
GLES20.glLinkProgram(programId);
//4. 使用
GLES20.glUseProgram(programId);
return programId;
}
}