我正在参加「掘金·启航计划」
使用GLSurfaceView
GLSurfaceView继承自SurfaceView同样也是在View基础上具有创建独立Surface能力。另外GLSurfaceView又在SurfaceView上实现了使用OpenGL的能力,可通过它创造出OpenGL环境在Surface进行绘制。因此学习使用GLSurfaceView也是学习OpenGL ES重要环节之一。
如下是GLSurfaceView最基础使用方法:
- 继承
GLSurfaceView创建PointGLRenderer PointGLRenderer构造方法中配置EGL相关初始化SceneRenderer继承Renderer主要实现三个回调方法:Surface创建回调、Surface窗口大小回调、渲染绘制刷新回调。- 实际中最重要部分就是
Point对象实现,它主要负责顶点着色器、片元着色器以及GL程序实现和运行。
public class PointGLRenderer extends GLSurfaceView {
SceneRenderer mRenderer;//自定义渲染器的引用
Point point;
public PointGLRenderer(Context context) {
super(context);
this.setEGLContextClientVersion(3);
mRenderer = new SceneRenderer();
this.setRenderer(mRenderer);
this.setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
}
private class SceneRenderer implements Renderer {
public void onDrawFrame(GL10 gl) {
//清除深度缓冲与颜色缓冲
GLES30.glClear(GLES30.GL_DEPTH_BUFFER_BIT | GLES30.GL_COLOR_BUFFER_BIT);
point.drawSelf();
}
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
//设置屏幕背景色RGBA
GLES30.glClearColor(1, 1, 1, 1.0f); // 白色
//创建对象
point = new Point(PointGLRenderer.this);
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
GLES30.glViewport(0, 0, width, height);
}
}
}
GLSurfaceView的简单使用就是这样,只需要知道其内部已经帮助开发者封装实现GL环境。(当然开发者也可以自行实现EGL环境使开发过程更可控,但绝大多数情况下GLSurfaceView已满足开发需求)
点线面
OpenGL中绘制是支持点、线、三角形,其中三角形可构成面。
首先大致先了解OpenGL环境下坐标系情况,原点位置(0,0)在中心,左上角坐标为(-1,1),右下角坐标为(-1,-1),如图所展示只是2维空间上xy坐标,另外z坐标暂时还未涉及,等涉及到3维空间投影模式再做介绍。
| 坐标系 | 坐标系上绘制 |
|---|---|
点绘制
绘制点信息需要配置点坐标信息。例如下面代码中点坐标配置vertices[],其中第一个坐标信息是(-0.9f, 0.9f,0),对应的坐标系是(x,y,z)。由于坐标系x轴从左到右是[-1,1],y轴从上到下是[1,-1],z轴暂时先不考虑。因此第一个点坐标信息所在位置大致是在左上角。另外colors[]设定了每个点的颜色
public void initVertexData()//初始化顶点数据的方法
{
// 点坐标
float vertices[] = new float[]{//顶点坐标数组
-0.9f, 0.9f , 0, //1
0.9f, 0.9f , 0,
-0.9f, -0.9f, 0,
0.9f, -0.9f , 0,
};
mVertexBuffer = BufferUtil.creatFloatBuffer(vertices);
// 点颜色
float colors[] = new float[]{//顶点颜色数组
1, 0, 0, 0,// // 红
0, 1, 0, 0, // 绿
0, 0, 1, 0, // 蓝
1, 0, 1, 0, // 紫
};
mColorBuffer = BufferUtil.creatFloatBuffer(colors);
}
顶点着色器代码部分,输入值aPosition和aColor分别代表顶点位置和顶点颜色
#version 300 es
layout (location = 0) in vec3 aPosition; //顶点位置
layout (location = 1) in vec4 aColor; //顶点颜色
out vec4 vColor; //用于传递给片元着色器的变量
void main()
{
gl_Position = vec4(aPosition,1); //根据总变换矩阵计算此次绘制此顶点位置
vColor = aColor;//将接收的颜色传递给片元着色器
gl_PointSize=10.0;//点大小
}
对应的在Java代码层获取到aPosition和aColor的引用,在加载脚本信息后获取maPositionHandle和maColorHandle表示为aPosition和aColor的引用。
//初始化着色器的方法
public void initShader(GLSurfaceView mv) {
//加载顶点着色器的脚本内容
mVertexShader = ShaderUtil.loadFromAssetsFile("samplexs/samplex1/vertex_samplex_1.vsh", mv.getResources());
//加载片元着色器的脚本内容
mFragmentShader = ShaderUtil.loadFromAssetsFile("samplexs/samplex1/frag_samplex_1.fsh", mv.getResources());
//基于顶点着色器与片元着色器创建程序
mProgram = ShaderUtil.createProgram(mVertexShader, mFragmentShader);
//获取程序中顶点位置属性引用
maPositionHandle = GLES30.glGetAttribLocation(mProgram, "aPosition");
//获取程序中顶点颜色属性引用
maColorHandle = GLES30.glGetAttribLocation(mProgram, "aColor");
}
取到引用之后分别将顶点位置数据和颜色数据送入到管线中并在GL环境下开启引用可用,最后使用绘制点方法绘制点信息。
//将顶点位置数据送入渲染管线
GLES30.glVertexAttribPointer(
maPositionHandle, 3,
GLES30.GL_FLOAT, false,
3 * 4, mVertexBuffer);
//将顶点颜色数据送入渲染管线
GLES30.glVertexAttribPointer(
maColorHandle, 4,
GLES30.GL_FLOAT, false,
4 * 4, mColorBuffer);
//启用顶点位置数据数组
GLES30.glEnableVertexAttribArray(maPositionHandle);
//启用顶点颜色数据数组
GLES30.glEnableVertexAttribArray(maColorHandle);
// 五个点 1
GLES30.glDrawArrays(GLES30.GL_POINTS, 0, 4);
mVertexBuffer是顶点坐标,因为每个顶点由xyz三个坐标组成因此size为3,又因为数据类型是FLOAT,每个数据是4位因此stride为3 * 4;maColorHandle是颜色数据,同理因为每个色值由RGBA四个组成,因此size为4,又因为数据类型是FLOAT因此stride为4 * 4。因为·GLES30.glDrawArrays(GLES30.GL_POINTS, 0, 4)坐标点数组是4个同时起始下标为0,因此是从0开始绘制一共绘制4个点,绘制形式是POINTS
线绘制
绘制线的方法和绘制点在步骤上基本一样,不同点在于glDrawArrays的绘制模式不同,且绘制线有多种形式。 在此之前已知点绘制是xyz三个坐标确定,点颜色绘制是有rgba确定,线的绘制也是如此(因为由点组成线)。在绘制线之前修改vertices[]和colors[]数据源,绘制线需要增加更多的坐标信息。
public void initVertexData()//初始化顶点数据的方法
{
float vertices[] = new float[]{//顶点坐标数组
-0.8f, 0.4f, 0, //2
-0.7f, 0.4f, 0,
-0.8f, 0.3f, 0,
-0.7f, 0.3f, 0,
-0.6f, 0.4f, 0,//3
-0.5f, 0.4f, 0,
-0.6f, 0.3f, 0,
-0.5f, 0.3f, 0,
-0.4f, 0.4f, 0,//4
-0.3f, 0.4f, 0,
-0.4f, 0.3f, 0,
-0.3f, 0.3f, 0,
};
mVertexBuffer = BufferUtil.creatFloatBuffer(vertices);
float colors[] = new float[]{//顶点颜色数组
1, 0, 0, 0,//2 // 红
0, 1, 0, 0, // 绿
0, 0, 1, 0, // 蓝
1, 0, 1, 0, // 紫
1, 0, 0, 0,//3 // 红
0, 1, 0, 0, // 绿
0, 0, 1, 0, // 蓝
1, 0, 1, 0, // 紫
1, 0, 0, 0,//4// 红
0, 1, 0, 0, // 绿
0, 0, 1, 0, // 蓝
1, 0, 1, 0, // 紫
};
mColorBuffer = BufferUtil.creatFloatBuffer(colors);
}
线的绘制提供了三种模式:GLES30.GL_LINES、GLES30.GL_LINE_STRIP、GLES30.GL_LINE_LOOP。
- GLES30.GL_LINES会根据顶点顺序两两一组为线段进行绘制。
- GLES30.GL_LINE_STRIP会根据顶点顺序依次连接组成线段进行绘制。
- GLES30.GL_LINE_LOOP和GLES30.GL_LINE_STRIP绘制形式一样,区别在于最后一个顶点和第一个顶点会相连形成线段环。
// 四个点 2
GLES30.glDrawArrays(GLES30.GL_POINTS, 0, 4);
// 两条线
GLES30.glDrawArrays(GLES30.GL_LINES, 0, 4);
// 四个点 3
GLES30.glDrawArrays(GLES30.GL_POINTS, 4, 4);
// 两条线
GLES30.glDrawArrays(GLES30.GL_LINE_STRIP, 4, 4);
// 四个点 4
GLES30.glDrawArrays(GLES30.GL_POINTS, 8, 4);
// 两条线
GLES30.glDrawArrays(GLES30.GL_LINE_LOOP, 8, 4);
PS: GLES30.glLineWidth(5) 可以设置线段宽度
面绘制
由于在OepnGL环境下只有绘制三角形的方法,因此绘制面其实就是绘制三角形。此外和线绘制一样,三角形绘制也有多种模式可选:GLES30.GL_TRIANGLES、GLES30.GL_TRIANGLE_STRIP、GLES30.GL_TRIANGLE_FAN。
- GLES30.GL_TRIANGLES会按照顶点顺序每3个组成三角形进行绘制。因此在绘制一个矩形时会有两对顶点是重合关系,存在顶点冗余。
- GLES30.GL_TRIANGLE_STRIP会按照顶点顺序依次组成三角形。例如N个顶点则绘制N-2个三角形。此方法会节省顶点空间。
- GLES30.GL_TRIANGLE_FAN按照第一个顶点作为中心点,其他顶点作为边缘点绘制出扇形三角形。
矩形面设定vertices[]配置,GLES30.GL_TRIANGLES绘制方式因为顶点重合所以需要6个顶点绘制一个矩形;GLES30.GL_TRIANGLE_STRIP绘制方式优化了顶点数量,可以用4个顶点绘制一个矩形;GLES30.GL_TRIANGLE_FAN不同于以上两种顶点坐标需要以顺时针或者逆时针顺序组成矩形。具体代码如下所示:
public void initVertexData()//初始化顶点数据的方法
{
// 五个点坐标
float vertices[] = new float[]{//顶点坐标数组
-0.8f, 0.4f, 0, //2
-0.7f, 0.4f, 0,
-0.8f, 0.3f, 0,
-0.7f, 0.4f, 0,
-0.8f, 0.3f, 0,
-0.7f, 0.3f, 0,
-0.6f, 0.4f, 0,//3
-0.5f, 0.4f, 0,
-0.6f, 0.3f, 0,
-0.5f, 0.3f, 0,
-0.4f, 0.4f, 0,//4
-0.4f, 0.3f, 0,
-0.3f, 0.3f, 0,
-0.3f, 0.4f, 0,
};
mVertexBuffer = BufferUtil.creatFloatBuffer(vertices);
}