##.GlSurfaceView
简介:
GlSurfaceView继承自SurfaceView,额外提供了一些能力:1.支持用OpenGl对图像渲染后显示到Surface上,2.提供了渲染器类Render,渲染器中有Surface的生命周期回调接口,以及每一帧图像的渲染接口。3.提供了单独的GlThrad线程,设置渲染器后,该线程会启动,初始化EGL环境,并在线程中根据状态变化做Render中各个API接口的回调。
##.OpenGl简单:
OpenGl是一个图形处理的标准接口库,它是跨平台的。手机由于性能相对较弱,难以支持OpenGl的全部功能,所以Android中提供OpenGl的子集OpenGl ES。Android中为图像提供OpenGl渲染能力并显示的控件是GlSurfaceView。在GlSurfaceView中GLThread会创建EGL环境,EGL是OpenGl和Android本地窗口系统之间的接口,OpenGl通过EGL来实现界面绘制。而GlSurfaceView会自动将EGL环境配置好,将OpenG渲染l处理后的图像绘制到界面上。
##.EGL简介:
OpenGl是一套跨平台的接口,它与各个平台本地窗口系统之间的交互,是借助于一个中间控制层,这个中间控制层就是EGL。 EGL也有自己的一套标准API,由各个平台的系统来完成其具体实现。EGL是OpenGL和本地窗口体系进行联系的桥梁,负责管理OpenGL的运行状态、渲染图像到本地窗口或缓冲区等功能。在Android系统中也是一样,OpenGL的每一步处理,都需要依赖于EGL提供的这些相关功能支持,所以必须先创建EGL环境,才能正常进行OpenGL处理。不过Android中GLSurfaceView会自己在GLThread中完成EGL环境的初始化,使用GLSurfaceView时,开发者并不需要自己来初始化EGL环境。
#.内部机制分析:
GLThread:调用SurfaceView的setRender()后,SurfaceView内部会创建GLThread线程,并在线程内部 创建EGL环境。GLThread的核心代码在 guardedRun()中,里面是一个while(true){...}死循环,进行EGL环境的管理并根据条件判断调用 Render的相关方法, 如Render的 onSurfaceCreated(), onSurfaceChanged()和onDrawFrame()方法等。因为OpenGl ES的绘制依赖EGL环境,所以必须在这个线程中进行。EglHelper:负责为GLThread创建EGL环境。GLSurfaceView:继承了SurfaceView的所有特性,负责提供Surface并管理Surface的状态。
##.重要API
&&.设置渲染器
TestRender render = new TestRender();
//为GlSurfaceView设置渲染器Render
//这一步很关键,当设置渲染器后,会触发GlThread线程启动,然后创建EGL环境,
// 并在线程中根据条件判断,来对应调用Render的各个接口
mGlSurfaceView.setRenderer(render);
&&.设置渲染模式
/*设置GLThread的渲染方式,该值会影响GLThread内部while(true){}死循环内部的条件判断
1.Render.RENDERMODE_WHEN_DIRTY:表示被动渲染,需要手动调用相应方法来触发渲染。
设置该值,GLThread在死循环死循环中不会自动调用Render.onDrawFrame()
只有在调用requestRender()或者onResume()等方法后才会改变死循环中的判断条件,调用一次Render.onDrawFrame()
2.Render.RENDERMODE_CONTINUOUSLY:表示持续渲染,该值为GLThread的默认渲染模式
设置该值时,GLThread在死循环中会不断的自动调用Render.onDrawFrame()。因此会比较消耗手机性能。*/
//当需要固定的刷新频率时,例如30帧/s,应该用Render.RENDERMODE_WHEN_DIRTY模式,然后手动控制刷新。
//注意:必须在设置Render之后才能设置,否则会报错
setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
&&.当调用requestRender()时,会手动在GLThread中触发一次Render.onDrawFrame()调用,进行一次绘制操作。
&&.由于操作OpenGL环境只能通过GL线程,所以GLSurfaceView为我们提供了queueEvent(Runnable r)这个方法,GLThread在内部死循环while(true){…}中,会调用这个传入的Runnable的run()方法。
&&.onPause()/onResume()
这两个方法实际回去调用GLThread的onPause()/onResume()。
onPause()会停止GLThread中的绘制,不再回调Render中的回到接口。
onResume()会恢复GLThread中的绘制,恢复回调Render中的回到接口。
(看源码,onPause()时GLThread会销毁EGL环境,onResume()时又会恢复EGL环境。)
#.代码示例与讲解(见代码注释)
public class TestGlSurfaceView extends GLSurfaceView {
public TestGlSurfaceView(Context context) {
this(context, null);
}
public TestGlSurfaceView(Context context, AttributeSet attrs) {
super(context, attrs);
// 使用OpenGL ES 2.0
setEGLContextClientVersion(2);
}
@Override
public void setRenderer(Renderer renderer) {
super.setRenderer(renderer);
/*设置GLThread的渲染方式,该值会影响GLThread内部while(true){}死循环内部的条件判断
1.Render.RENDERMODE_WHEN_DIRTY:表示被动渲染,需要手动调用相应方法来触发渲染。
设置该值,GLThread在死循环死循环中不会自动调用Render.onDrawFrame()
只有在调用requestRender()或者onResume()等方法后才会改变死循环中的判断条件,调用一次Render.onDrawFrame()
2.Render.RENDERMODE_CONTINUOUSLY:表示持续渲染,该值为GLThread的默认渲染模式
设置该值时,GLThread在死循环中会不断的自动调用Render.onDrawFrame()。因此会比较消耗手机性能。*/
//当需要固定的刷新频率时,例如30帧/s,应该用Render.RENDERMODE_WHEN_DIRTY模式,然后手动控制刷新。
//注意:必须在设置Render之后才能设置,否则会报错
setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
}
/**
* 该方法会触发GLThread恢复运行
*/
@Override
public void onResume() {
super.onResume();
}
/**
* 该方法会触发GLThread进入等待状态
*/
@Override
public void onPause() {
super.onPause();
}
}
//Render中一般是用OpenGl做来图形图像处理,需要了解Android提供的OpenGL ES用法
public class TestRender implements GLSurfaceView.Renderer {
private final String TAG = getClass().getSimpleName();
private Triangle mTriangle;
private Square mSquare;
public TestRender(){
}
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
ILog.d(TAG, "surfaceCreated()");
//surface被创建后需要做的处理
//这里是设置背景颜色
GLES20.glClearColor(0.0f,0.0f,0.0f,1.0f);
// 初始化一个三角形
mTriangle = new Triangle();
// 初始化一个正方形
mSquare = new Square();
}
// 渲染窗口大小发生改变或者屏幕方法发生变化时候回调
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
ILog.d(TAG, "surfaceChanged()");
// 设置OpenGL视口的位置与大小, 最终图像会被输出到视口上显示
// 在此处对应的是在GlSurfaceView的Surface上现实在哪个区域
// 该API定义时的坐标方向与OpenGl二维图形中坐标方向相同,
// 以(0,0)为的左下角,向右为x轴正方向,向上为y轴正方向.
// 最终输出的画面会等比例缩放到在这个区域上,但设置的区域超出实际Surface的部分不会显示。
GLES20.glViewport(0,0,width,height);
}
@Override
public void onDrawFrame(GL10 gl) {
ILog.d(TAG, "onDrawFrame()");
// mTriangle.draw();
mSquare.draw();
}
}
public class Triangle {
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;" +
"}";
private FloatBuffer vertexBuffer;
// 数组中每个顶点的坐标数
static final int COORDS_PER_VERTEX = 3;
static float triangleCoords[] = { // 按照逆时针方向:
0.0f, 0.622008459f, 0.0f, // top
-0.5f, -0.311004243f, 0.0f, // bottom left
0.5f, -0.311004243f, 0.0f // bottom right
};
// 设置颜色RGBA(red green blue alpha)
float color[] = { 0.63671875f, 0.76953125f, 0.22265625f, 1.0f };
private final int mProgram;
public Triangle() {
// 为存放形状的坐标,初始化顶点字节缓冲
ByteBuffer bb = ByteBuffer.allocateDirect(
// (坐标数 * 4 )float 占四个字节
triangleCoords.length * 4);
// 使用设备的本点字节序
bb.order(ByteOrder.nativeOrder());
// 从ByteBuffer创建一个浮点缓冲
vertexBuffer = bb.asFloatBuffer();
// 把坐标加入FloatBuffer中
vertexBuffer.put(triangleCoords);
// 设置buffer,从第一个坐标开始读
vertexBuffer.position(0);
int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER,
vertexShaderCode);
int fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER,
fragmentShaderCode);
// 创建一个空的 OpenGL ES Program
mProgram = GLES20.glCreateProgram();
// 将vertex shader 添加到 program
GLES20.glAttachShader(mProgram, vertexShader);
// 将fragment shader 添加到 program
GLES20.glAttachShader(mProgram, fragmentShader);
// 创建一个可执行的 OpenGL ES program
GLES20.glLinkProgram(mProgram);
}
private int mPositionHandle;
private int mColorHandle;
private final int vertexCount = triangleCoords.length / COORDS_PER_VERTEX;
private final int vertexStride = COORDS_PER_VERTEX * 4; // 4 bytes per vertex
public void draw() {
// 将program 添加到 OpenGL ES 环境中
GLES20.glUseProgram(mProgram);
// 获取指向vertex shader的成员vPosition的句柄
mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");
// 启用一个指向三角形的顶点数组的句柄
GLES20.glEnableVertexAttribArray(mPositionHandle);
// 准备三角形的坐标数据
GLES20.glVertexAttribPointer(mPositionHandle, COORDS_PER_VERTEX,
GLES20.GL_FLOAT, false,
vertexStride, vertexBuffer);
// 获取指向fragment shader的成员vColor的句柄
mColorHandle = GLES20.glGetUniformLocation(mProgram, "vColor");
// 设置三角形的颜色
GLES20.glUniform4fv(mColorHandle, 1, color, 0);
// 画三角形
GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, vertexCount);
// 禁用指向三角形的定点数组
GLES20.glDisableVertexAttribArray(mPositionHandle);
}
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;
}
}
public class Square {
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;" +
"}";
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 }; // 绘制顶点的顺序
// 设置颜色RGBA(red green blue alpha)
// float color[] = { 0.63671875f, 0.76953125f, 0.22265625f, 1.0f };
float color[] = { 0.15671875f, 0.65953125f, 0.84265625f, 1.0f };
private final int mProgram;
public Square() {
// initialize vertex byte buffer for shape coordinates
ByteBuffer bb = ByteBuffer.allocateDirect(
// (# of coordinate values * 4 bytes per float)
squareCoords.length * 4);
bb.order(ByteOrder.nativeOrder());
vertexBuffer = bb.asFloatBuffer();
vertexBuffer.put(squareCoords);
vertexBuffer.position(0);
// initialize byte buffer for the draw list
ByteBuffer dlb = ByteBuffer.allocateDirect(
// (# of coordinate values * 2 bytes per short)
drawOrder.length * 2);
dlb.order(ByteOrder.nativeOrder());
drawListBuffer = dlb.asShortBuffer();
drawListBuffer.put(drawOrder);
drawListBuffer.position(0);
int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER,
vertexShaderCode);
int fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER,
fragmentShaderCode);
// 创建一个空的 OpenGL ES Program
mProgram = GLES20.glCreateProgram();
// 将vertex shader 添加到 program
GLES20.glAttachShader(mProgram, vertexShader);
// 将fragment shader 添加到 program
GLES20.glAttachShader(mProgram, fragmentShader);
// 创建一个可执行的 OpenGL ES program
GLES20.glLinkProgram(mProgram);
}
private int mPositionHandle;
private int mColorHandle;
// private final int vertexCount = squareCoords.length / COORDS_PER_VERTEX;
private final int vertexStride = COORDS_PER_VERTEX * 4; // 4 bytes per vertex
private float[] mMVPMatrix=new float[16];
private int mMatrixHandler;
public void draw() {
//将程序加入到OpenGLES2.0环境
GLES20.glUseProgram(mProgram);
//获取变换矩阵vMatrix成员句柄
mMatrixHandler= GLES20.glGetUniformLocation(mProgram,"vMatrix");
//指定vMatrix的值
GLES20.glUniformMatrix4fv(mMatrixHandler,1,false,mMVPMatrix,0);
//获取顶点着色器的vPosition成员句柄
mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");
//启用三角形顶点的句柄
GLES20.glEnableVertexAttribArray(mPositionHandle);
//准备三角形的坐标数据
GLES20.glVertexAttribPointer(mPositionHandle, COORDS_PER_VERTEX,
GLES20.GL_FLOAT, false,
vertexStride, vertexBuffer);
//获取片元着色器的vColor成员的句柄
mColorHandle = GLES20.glGetUniformLocation(mProgram, "vColor");
//设置绘制三角形的颜色
GLES20.glUniform4fv(mColorHandle, 1, color, 0);
//绘制三角形
// GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, vertexCount);
//索引法绘制正方形
GLES20.glDrawElements(GLES20.GL_TRIANGLES, drawOrder.length, GLES20.GL_UNSIGNED_SHORT, drawListBuffer);
//禁止顶点数组的句柄
GLES20.glDisableVertexAttribArray(mPositionHandle);
}
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;
}
}
//Activity中的调用
public class TestGlSurfaceViewActivity extends BaseActivity {
@Override
protected BaseActivityPresenter getPresenter() {
return null;
}
private TestGlSurfaceView mGlSurfaceView;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.test_gl_surfaceview_activity);
mGlSurfaceView = (TestGlSurfaceView) findViewById(R.id.gl_surface_view);
TestRender render = new TestRender();
//为GlSurfaceView设置渲染器Render
//这一步很关键,当设置渲染器后,会触发GlThread线程启动,然后创建EGL环境,
// 并在线程中根据条件判断,来对应调用Render的各个接口
mGlSurfaceView.setRenderer(render);
}
}
主要参考: 初识:Android中的GLSurfaceView - 简书
初识Android OpenGL ES - 简书
(声明:部分图片获取自网络,这里只是用于学习分享,侵删!)