本文共 3379 字,大约阅读时间需要 11 分钟。
最近在学习opengl es,其中弄了一个小Demo,画了个天空盒,并在场景里加了个立方体,下面主要介绍下画立方体的流程。
一、在Android中使用opengl es,主要是使用GLSurfaceView和GLSurfaceView.Renderer。
GLSurfaceView继承自SurfaceView,通过该类来使用opengl es,为Android提供view。
通过setContentView(mGLSurfaceView)来实现的,这里我用MySurfaceView对GLSurfaceView进行了一下封装,放到自己建的类中。
二、连通了opengl es和Android的view,那么具体的内容将由GLSurfaceView.Renderer来提供,它是渲染器,也是我们要实现的一个接口,完成它的三个方法:
a. onSurfaceCreated,在Surface创建的时候调用
b.onSurfaceChanged, 在Surface改变的的时候调用
c. onDrawFrame, 在Surface上绘制的时候调用,也代表着画面的每一帧
在自己建立的MySurfaceView类中使用内部类SceneRenderer来实现Renderer接口
针对要实现的三个方法进行介绍,直接上代码,代码中注释很清晰,主要说一下图中后两行,使用的矩阵后面在介绍,而最后一行,该类将在onDrawFrame中完成立方体的绘制
下面这一段代码就运行在onDrawFrame中绘制立方体
下面是重点的内容部分,也是使用opengl es绘制的流程
构造函数中利用两个方法完成2项准备工作,第一个是准备模型(立方体)的顶点坐标和颜色坐标,只给出了一部分,立方体一个面的位置坐标和颜色坐标。
final float cubePosition[] =
{ // Front face -1.0f, 1.0f, 1.0f, -1.0f, -1.0f, 1.0f, 1.0f, 1.0f, 1.0f, -1.0f, -1.0f, 1.0f, 1.0f, -1.0f, 1.0f, 1.0f, 1.0f, 1.0f,……,};
final float[] cubeColor =
{ // Front face (red) 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f,…….,};
mCubePositions = ByteBuffer.allocateDirect(cubePosition.length * 4)
.order(ByteOrder.nativeOrder()).asFloatBuffer(); mCubePositions.put(cubePosition).position(0); mCubeColors = ByteBuffer.allocateDirect(cubeColor.length * 4) .order(ByteOrder.nativeOrder()).asFloatBuffer(); mCubeColors.put(cubeColor).position(0);有了坐标之后将这些坐标放到mCubePositions和mCubeColors两个buffer中,可以理解为opengl es在画立方体时,从buffer的开始位置取坐标开始绘制。
另一个主要工作就是准备shader,为Vertex Shader 和 Fragment Shader ,分别是顶点着色器和片段着色器,是可编程管线中两部分,可以理解为通过流水线上的两个位置,我们要完成的一些工作,向管线中提供相应所需要的东西。
vertex_tex_S.sh和frag_tex_S.sh是两个脚本语言,加载之后创建程序,然后定位到我们要提供信息的地方,即程序中的三个索引id(你也可以用0,1,2,3…来表示),位置、mvp(模型-视图-透视)矩阵,颜色。
下面的一大段就是创建并链接着色器的过程,从上面给出的脚本语言来创建,创建之后再glAttachShader,顶点着色器和片段着色器都要创建和glAttachShader,然后再将他们GLES20.glLinkProgram(program),这是opengl中固定的流程。
public static int createProgram(String vertexSource, String fragmentSource) { //加载顶点着色器 int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource); if (vertexShader == 0) { return 0; } //加载片元着色器 int pixelShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource); if (pixelShader == 0) { return 0; } //创建程序 int program = GLES20.glCreateProgram(); //若程序创建成功则向程序中加入顶点着色器与片元着色器 if (program != 0) { //向程序中加入顶点着色器 GLES20.glAttachShader(program, vertexShader); checkGlError("glAttachShader"); //向程序中加入片元着色器 GLES20.glAttachShader(program, pixelShader); checkGlError("glAttachShader"); //链接程序 GLES20.glLinkProgram(program); //存放链接成功program数量的数组 int[] linkStatus = new int[1]; //获取program的链接情况 GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0); //若链接失败则报错并删除程序 if (linkStatus[0] != GLES20.GL_TRUE) { Log.e("ES20_ERROR", "Could not link program: "); Log.e("ES20_ERROR", GLES20.glGetProgramInfoLog(program)); GLES20.glDeleteProgram(program); program = 0; } } return program; } //检查每一步操作是否有错误的方法 public static void checkGlError(String op) { int error; while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) { Log.e("ES20_ERROR", op + ": glError " + error); throw new RuntimeException(op + ": glError " + error); } }
最后一步就是画立方体了,先是使用创建好的着色器程序,然后传递给一个mvp矩阵,该矩阵就是将立方体显示在手机屏幕上,首先画出的是在立方体自己坐标系下的model矩阵,需要变换到世界坐标系,这里世界坐标系和立方体自己坐标系重合,然后再变换到相机坐标系中(通过view的矩阵),最后在变换到屏幕坐标系中(使用透视矩阵),变换的过程使用矩阵相乘的形式。图中已用红线表示,从buffer中取数据方法指定标示的地方,最后就是开始绘图了,由于采用三角面开始画,每个面6个点,画两个三角形,6个面36个点。