本章将学习OpenGL ES中点和线的绘制,在绘制之前需要先了解这些:坐标系统;着色器;GLSL(OpenGL ES Shading Language),OpenGL ES着色语言;
坐标系统
和android布局的坐标不同,OpenGL是一个右手坐标系。简单来说,就是正x轴在你的右手边,正y轴朝上,而正z轴是朝向后方的。想象你的屏幕处于三个轴的中心,则正z轴穿过你的屏幕朝向你。坐标系画起来如下:
如果你想要绘制一个在屏幕中心的点时,那么这个点的坐标就是:0,0,0,当你绘制的是不是三维图像时可以无视z轴(你设置0或者1都不会影响显示效果)。坐标的最大值是1,如果你的坐标值大于1的话,这个坐标是无法显示的。
可以参考https://learnopengl-cn.github.io/01%20Getting%20started/08%20Coordinate%20Systems/来学习坐标系统,当然还有一些概念,例如正射投影,透视投影,我们暂时用不到用到的时候我们再来学习。
着色器
从我们输入数据到显示图像,这一流程可以查看下图,
其中需要我们来写是顶点着色器和片段着色器的代码。
GLSL
GLSL是一种类C语言,用来编写顶点着色程序和片段着色程序。GLSL语言还是比较复杂的,OpenGL ES2.0的GLSL官方文档地址是https://www.khronos.org/registry/OpenGL/specs/es/2.0/GLSL_ES_Specification_1.00.pdf,如果英文水平比较高可阅读参考,否则可用参考http://colin1994.github.io/2017/11/11/OpenGLES-Lesson04/,http://colin1994.github.io/2017/11/12/OpenGLES-Lesson05这两篇文章,写的还是不错的。
变量类型
变量类别 | 变量类型 | 描述 |
---|---|---|
空 | void | 用于无返回值的函数或空的参数列表 |
标量 | float, int, bool | 浮点型,整型,布尔型的标量数据类型 |
浮点型向量 | float, vec2, vec3, vec4 | 包含1,2,3,4个元素的浮点型向量 |
整数型向量 | int, ivec2, ivec3, ivec4 | 包含1,2,3,4个元素的整型向量 |
布尔型向量 | bool, bvec2, bvec3, bvec4 | 包含1,2,3,4个元素的布尔型向量 |
矩阵 | mat2, mat3, mat4 | 尺寸为2x2,3x3,4x4的浮点型矩阵 |
纹理句柄 | sampler2D, samplerCube | 表示2D,立方体纹理的句柄 |
限定符
限定符 | 描述 |
---|---|
< none: default > | 局部可读写变量,或者函数的参数 |
const | 编译时常量,或只读的函数参数 |
attribute | 由应用程序传输给顶点着色器的逐顶点的数据 |
uniform | 在图元处理过程中其值保持不变,由应用程序传输给着色器 |
varying | 由顶点着色器传输给片段着色器中的插值数据 |
先说几个会用到内置的变量,
gl_Position:只能用在顶点着色程序中,用来定义需要写入的顶点。
gl_PointSize:定义顶点的大小,单位是像素。设置的越大,点就越大,当然显示的点时正方形的。
gl_FragColor :用于片段着色程序中,用来设置图像的颜色。
下面先写顶点着色程序和片段着色程序,
String vertexShaderCode =
"attribute vec4 aPosition;" +
"void main() {" +
" gl_Position = aPosition;" +
" gl_PointSize = 19.0;" +
"}";
String fragmentShaderCode =
"precision mediump float;" +
"uniform vec4 vColor;" +
"void main() {" +
" gl_FragColor = vColor;" +
"}";
然后我们定义一个顶点的数组,
private final float[] triangleCoords = {
-0.9f, 0.9f, 0.0f,
-0.9f, 0.8f, 0.0f,
-0.8f, -0.1f, 0.0f,
-0.6f, -0.5f, 0.0f,
-0.5f, -0.8f, 0.0f,
-0.4f, 0.4f, 0.0f,
-0.2f, 0.1f, 0.0f,
-0.0f, 0.5f, 0.0f,
0.1f, 0f, 0.0f,
0.3f, -0.5f, 0.0f,
0.4f, -0.2f, 0.0f,
0.6f, -0.5f, 0.0f,
0.9f, -0.6f, 0.0f,
0.9f, -0.9f, 0.0f,
};
定义一个颜色的数组,用来绘制颜色,四个数值分别表示rbga。
// 0 到1 代表 0- 256 如 181 用0.70703125f
private float[] color = {0.70703125f, 0.10546875f, 0.84375f, 1.0f};
在上篇文章中我们显示了一个窗口,现在我们绘制点和线,在onDrawFrame中进行绘制
首先我们加载着色器,加载着色器的代码如下
public static int loadShader(int shaderType, String source) {
int shader = GLES20.glCreateShader(shaderType);
checkGlError("glCreateShader type=" + shaderType);
GLES20.glShaderSource(shader, source);
GLES20.glCompileShader(shader);
int[] compiled = new int[1];
GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0);
if (compiled[0] == 0) {
LogUtil.e("Could not compile shader " + shaderType + ":" + GLES20.glGetShaderInfoLog(shader));
GLES20.glDeleteShader(shader);
shader = 0;
}
return shader;
}
主要的几个方法
glCreateShader:创建shader,参数值:GLES20.GL_VERTEX_SHADER(顶点着色器)或GLES20.GL_FRAGMENT_SHADER(片段着色器),返回创建的shader值。
glShaderSource:加载着色器代码,参数为create的shader值和GLSL代码。
glCompileShader:加载着色器完成。
glGetShaderiv:获取加载完成的状态,主要用来检测是否加载完成,如果加载失败了则使用glDeleteShader来删除错误的shader。
checkGlError是封装好的检测是否有错误的方法,内容如下
public static void checkGlError(String op) {
int error = GLES20.glGetError();
if (error != GLES20.GL_NO_ERROR) {
String msg = op + ": glError 0x" + Integer.toHexString(error);
LogUtil.e(msg);
throw new RuntimeException(msg);
}
}
使用glCreateProgram创建一个空的程序,返回一个非0值。使用glAttachShader来连接当前的程序和创建好的shader值,最后使用glLinkProgram来完成链接program,如果不需要Program后可以使用glDeleteProgram删除program。封装好之后的代码如下
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();
checkGlError("glCreateProgram");
if (program == 0) {
LogUtil.e("Could not create program");
}
GLES20.glAttachShader(program, vertexShader);
checkGlError("glAttachShader");
GLES20.glAttachShader(program, pixelShader);
checkGlError("glAttachShader");
GLES20.glLinkProgram(program);
int[] linkStatus = new int[1];
GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0);
if (linkStatus[0] != GLES20.GL_TRUE) {
LogUtil.e("Could not link program: ");
LogUtil.e(GLES20.glGetProgramInfoLog(program));
GLES20.glDeleteProgram(program);
program = 0;
}
return program;
}
封装了一个OpenGLUtil工具类来进行shader的加载,便于使用。
加载完成后进行顶点的绘制,完整的onDrawFrame代码如下
@Override
public void onDrawFrame(GL10 gl) {
super.onDrawFrame(gl);
int shaderProgram = OpenGLUtil.createProgram(vertexShaderCode, fragmentShaderCode);
GLES20.glUseProgram(shaderProgram);
int positionHandle = GLES20.glGetAttribLocation(shaderProgram, "aPosition");
GLES20.glEnableVertexAttribArray(positionHandle);
FloatBuffer vertexBuffer = OpenGLUtil.createFloatBuffer(triangleCoords);
GLES20.glVertexAttribPointer(positionHandle, 3, GLES20.GL_FLOAT,
false, 3 * 4, vertexBuffer);
int colorHandle = GLES20.glGetUniformLocation(shaderProgram, "vColor");
GLES20.glUniform4fv(colorHandle, 1, color, 0); // 设置颜色
// 画点
GLES20.glDrawArrays(GLES20.GL_POINTS, 0, 13); // 画点
// 设置线宽
GLES20.glLineWidth(18);
// 画线,不连续的线,例如:有1,2,3,4四个点,1和2是一条线,3,4是一条线
GLES20.glDrawArrays(GLES20.GL_LINES, 2, 4);
// 画线,封闭的线,例如:有1,2,3,4四个点,1,2,3,4,1会连接2,2连接3,3连接4,4连接1
GLES20.glDrawArrays(GLES20.GL_LINE_LOOP, 6, 4);
// 画线,不封闭的线
GLES20.glDrawArrays(GLES20.GL_LINE_STRIP, 10, 4);
GLES20.glDisableVertexAttribArray(positionHandle);
}
一些主要方法
glUseProgram:使用创建好的program
glGetAttribLocation:查询由程序指定的先前链接的程序对象,返回aPosition变量顶点属性的索引
glEnableVertexAttribArray:启用由索引指定的顶点属性数组。
createFloatBuffer:封装的生成FloatBuffer代码。
glVertexAttribPointer:指定顶点数组的位置和数据格式,几个参数:index,索引;size,每个顶点的数,当前是3;type,类型,float,short等等,当前是float;normalized,设置为false;stride,顶点间的偏移,每个顶点3个float值,每个float是4个字节;pointer,顶点的集合。
glGetUniformLocation:获取片段着色器中属性的索引。
glUniform4fv:修改统一变量或统一变量数组的值。4fv表示 vec4,同样的方法有很多:glUniform3fv,glUniform3iv等等,可以参考官网的glUniform文档。
glDrawArrays:开始绘制。参数mode类型:有GL_POINTS(点),GL_LINES(线),GL_LINE_STRIP,GL_LINE_LOOP,GL_TRIANGLE_STRIP,GL_TRIANGLE_FAN和GL_TRIANGLE;参数first:开始位置;count:总数。
glDisableVertexAttribArray:禁用顶点属性数组,当绘制完成后可以调用。
显示效果如下
源码地址https://github.com/jklwan/OpenGLProject/blob/master/sample/src/main/java/com/chends/opengl/view/window/PointLineView.java
这一章了解了基本的OpenGL ES图像的绘制方式,下一章会来学习面的显示。