OpenGL是一种包含了一系列可以操作图形图像函数的图形编程接口的规范标准,它规范了每个函数该如何执行以及它们的输出值,OpenGL ES是OpenGL的一个精简子集,主要用于手持和嵌入式设备,OpenGL ES很大程度上兼容了OpenGL,主要是删除了一些OpenGL的一些冗余接口和引入一些新功能提高处理手持设备和嵌入式设备的特定性能和功耗限制,因此在写此博客时主要是与OpenGL的对比过程中学习的领悟,学习OpenGL对于开发OpenGL ES Android程序应该能够让更好的知道是如何实现,而不仅仅停留在怎么实现上吧(看完《OpenGL ES应用开发实践指南 Android卷》的我貌似知道了OpenGL如何初步实现简单的Android程序,但是好像总是有很多地方知其然不知其所以然),其实写博客的目的也只是为了反思自己学了些什么内容,就当写日记了吧。
学习时所学习的资料是learnOpenGl网站(这个网站上的示例貌似都是使用的C实现的,我会尽量努力将它改成Java实现吧),结合学习过OpenGL ES的前辈给出的学习建议和在学习网站上的内容之前看过的《OpenGL ES应用开发实践指南 Android卷》
另外网站大部分知识基于OpenGL 3.3,正好对应的是OpenGL ES3.0
private final float[] vertexData = {
...}
FloatBuffer vertexArray = ByteBuffer.allocateDirect(vertexData.length * 4) //分配一块本地内存块,分配时需要知道分配多少字节,每个浮点数占4字节
.order(ByteOrder.nativeOrder()) //告诉字节缓冲区按照本地字节序列组织内容,这个顺序并不重要,重要的是全平台采用相同的顺序
.asFloatBuffer() //可以得到一个反映底层字节的FloatBuffer实例,直接使用浮点数
.put(vertexData); //把数组数据从JVM中复制到本地内存中,通常情况下,进程结束,内存释放
//创建对象,同时将生成的对象id保存在buffers数组中
final int buffers[] = new int[1];
glGenBuffers(buffers.length, buffers, 0);
//判断对象创建是否成功,如果创建失败buffers数组中会存入0,否则,则会存入对象id
if(buffers[0] == 0) {
//TODO:throw exception or log
}
bufferId = buffers[0];
//对象绑定至上下文的目的位置
glBindBuffer(GL_ARRAY_BUFFER, buffers[0]);
//对对象进行需要的设置:此处将之前定义的顶点数据复制到缓冲的内存中
glBufferData(GL_ARRAY_BUFFER, vertexArray.capacity() * 4, vertexArray, GL_STATIC_DRAW);
//解绑,解绑即将上下文目的位置与0绑定即可
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBufferData(int target, int size, Buffer data, int usage)
是一个专门用来将用户定义的数据复制到当前绑定缓冲对象的函数,第一个参数指定缓冲区类型,顶点缓冲区用GL_ARRAY_BUFFER常量表示,索引缓冲区用GL_ELEMENT_ARRAY_BUFFER常量表示;第二个参数指定数据的大小;第三个参数指定缓冲区需要存储的数据;第四个参数指定对缓冲区对象期望的使用模式,常见的形式有三种:GL_STATIC_DRAW:数据不会变或几乎不会变;GL_DYNAMIC_DRAW:数据会被改变很多;GL_STREAM_DRAW:数据每次绘制时都会改变。当绘制内容位置数据不会改变,每次渲染调用时都保持原样,最好使用第一个,它也是这个函数最常使用参数,如果缓冲中的数据将频繁被改变,使用的类型就会使用后两者中的一个,这样能确保显卡把数据放在能够高速写入的内存部分vertexArray = new VertexArray(new float[] {
-1, 1, 1, //index = 0
1, 1, 1, //index = 1
-1, -1, 1,
1, -1, 1,
-1, 1, -1,
1, 1, -1,
-1, -1, -1,
1, -1, -1
}
indexArray = ByteBuffer.allocateDirect(6 * 6)
.put(new byte[] {
//前面
1, 3, 0,
0, 3, 2,
//后面
4, 6, 5,
5, 6, 7,
//左面
0, 2, 4,
4, 2, 6,
//右面
5, 7, 1,
1, 7, 3,
//上面
5, 1, 4,
4, 1, 0,
//下面
6, 2, 7,
7, 2, 3
})
final int buffers[] = new int[1];
glGenBuffers(buffers.length, buffers, 0);
if(buffers[0] == 0) {
//TODO:throw exception or log
}
bufferId = buffers[0];
//对象绑定至上下文的目的位置,此处上下文类型是索引缓冲对象
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffers[0]);
//对对象进行需要的设置:此处将之前定义的索引数组数据复制到缓冲的内存中
glBufferData(GL__ELEMENT_ARRAY_BUFFER, indexArray.capacity() * 4, indexArray, GL_STATIC_DRAW);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
gl_Position = vec4(a_Positon.x, a_Position.y, a_Position.z, 1.0f);
实现顶点着色器的输出;xyz分量的默认值为0.0f,w分量的默认值为1.0fattribute vec4 a_Position;
void main() {
gl_Positon = a_Position;
}
precision mediump float;
uniform vec4 u_Color;
void main() {
gl_FragColor = u_Color;
}
StringBuilder body = new StringBuilder();
try {
InputStream inputStream = context.getResources().openRawResources(resourceId);
InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
String nextLine = null;
while((nextLine = bufferedReader.readLine()) != null) {
body.append(nextLine);
body.append('\n');
}
} catch (IOException e) {
//TODO:
} catch (Resources.NotFoundException e) {
//TODO:
}
return body.toString;
glGetShaderiv(int shader, int pname, int[] params, int offset)
,第一个参数传入编译过的着色器对象,第二个参数传入指定检查 编译状态 的常量GL_COMPILE_STATUS,第三个须传入一个数组,用于保存编译状态,OpenGL ES Java实现中对于检查状态结果的保存通常存放在一个数组中,因此类似场景中,经常可以看到第一只包含一个元素的数组,用于保存状态结果,第四个参数是一个偏移值,存放在数组的第几个元素中final int shaderObjectId = glCreateShader(type); //创建着色器对象,type传入着色器类型,如果为顶点着色器,传入GL_VERTEX_SHADER,片段着色器传入GL_FRAGMENT_SHADER;
if(shaderObjectId == 0) {
//TODO:
}
glShaderSource(shaderObjectId, shaderCode); //绑定着色器对象和源代码,shaderCode传入上一段代码读取的着色器编码的字符串
glCompileShader(shaderObjectId); //编译
//检查编译结果,如果编译失败,及时抛出异常或打log
final int[] compileStatus = new int[1];
glGetShaderiv(shaderObjectId, GL_COMPILE_STATUS, compileStatus, 0);
if(compileStatus[0] == 0) {
//TODO:
}
return shaderObjectId; //返回编译好的着色器对象id,供后续使用
final int programObjectId = GLCreateProgram();
if(programObjectId == 0) {
//TODO:
}
glAttachShader(programObjectId, vertexShaderId); //将顶点着色器与新建的对象绑定
glAttachShader(programObjectId, fragmentShaderId); //将片段着色器与新建的对象绑定
glLinkProgram(programObjectId); //链接
final int[] linkStatus = new int[1];
glGetProgramiv(programObjectId, GL_LINK_STATUS, linkStatus, 0);
if(linkStatus[0] == 0) {
glDeleteProgram(programObjectId);
//TODO:
}
//验证新创建的程序对象包含的执行段在给定当前的OpenGL状态下是否可执行
glValidateProgram(programObjectId);
final int[] validateStatus = new int[1];
glGetProgramiv(programObjectId, GL_VALIDATE_STATUS, validateStatus, 0);
if(validateStatus[0] == 0) {
//TODO:
}
return programObjectId;
attributeLocation = glGetAttribLocation(programObjectId, a_Position);
获取,第二个参数指定每个属性有几个分量组成,这里显然是x、y、z三个分量,如果是上述着色器代码,由于定义的a_Position是vec4,则这个参数应设为4,同时每个顶点应该对应4个浮点数,第三个参数指定数据类型,浮点数,第四个参数指定是否希望数据标准化,如果设置为true,所有数据被映射到-1到1之间,一般只有数据类型为整型时,才有意义,第五个参数指定步长,只有在顶点数组中包含多个特性是才有意义,譬如顶点数组有每个顶点位置坐标和顶点rgb值组成,对于为OpenGL指定顶点位置属性是,则需跳过rgb值,会设置为(3+3)*4,此处由于仅一个属性,因此设置为0,OpenGL即可自动判断步长,第六个参数告诉OpenGL从哪里读取数据,调用这个函数之前,第一行代码移动数组的指针的位置指针会影响这个参数读取的位置;第三行代码是一个使能函数,启用参数表示的顶点属性,顶点属性默认是禁用的,因此需要靠使能告诉OpenGL去哪儿寻找对应属性所需数据。floatBuffer.position(dataOffset);
glVertexAttribPointer(attributeLocation, componentCounnt, GL_FLOAT, false, stride, floatBuffer);
glEnableVertexAttribArray(attributeLocation);
glUseProgram(shaderProgram); //激活链接后的着色器对象
glDrawArrays();*
glDrawArrays(GL_TRIANGLES, offset, componentCount);
;第二种情况glDrawElments(GL_TRIANGLES, componentConunt, GL_UNSIGNED_BYTE, indexArray);
对于第三个参数指定将索引数组解释为无符号数,第四个参数指定索引数组;第三种情况glDrawElements(GL_TRIANGLES, componentConunt, GL_UNSIGNED_BYTE, offset);
以上是OpenGL中一个简单图形生成的基本流程,能够简单的建立对于OpenGL绘制图形的抽象概念,接下来将继续深入针对每一个点进行学习。。。。。。。。。。。。