最近在学习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中绘制立方体
构造函数中利用两个方法完成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个点。