目的:1,为了加深记忆和理解;2,记录疑问以及希望路过大神解答;3,本文为小白文,针对相关API的理解注释希望能给跟我一样的小白一些参考
主要参照大神的文章:
iOS开发-OpenGL ES入门教程1
从零讲解 iOS OpenGL ES 的纹理渲染
OpenGL顶点坐标与纹理坐标
顶点缓存对象VBO
由于本人对于图像学是0理解,对于OpenGL也是只知其名,翻了很多大神的资料还有一些没列出来的是因为找不到浏览记录,在此向各位大佬致谢!!!
我觉得入门的基本点:1,理解坐标系;2,了解渲染流程;3,熟悉各个api工作职责以及参数意义;
个人在照着大神文章撸的时候,为了加深对坐标系的理解,对坐标调整做了练习
//顶点数据,前三个是顶点坐标(x、y、z轴),后面两个是纹理坐标(x,y)
//GL_TRIANGLE_FAN
GLfloat vertexData[] = {
0.5f,0.5f,0.0f, 1.0f,1.0f,//右上
0.5f,-0.5f,0.0f, 1.0f,0.0f,//右下
-0.5f,-0.5f,0.0f, 0.0f,0.0f,//左下
-0.5f,0.5f,0.0f, 0.0f,1.0f, //左上
};
// GL_TRIANGLE_STRIP
// GLfloat vertexData[] = {
// 0.5f,-0.5f,0.0f, 1.0f,0.0f,//右下
// 0.5f,0.5f,0.0f, 1.0f,1.0f,//右上
// -0.5f,-0.5f,0.0f, 0.0f,0.0f,//左下
// -0.5f,0.5f,0.0f, 0.0f,1.0f, //左上
//
// };
上面两组不同的坐标代表的可以用不同的绘制模式得到相同的结果,绘制代码为
//GL_TRIANGLE_FAN 这个参数为绘制模式
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);//使用顶点数组绘制图元
下面介绍一种通过顶点索引数组渲染的方式
// 上下对称
// GLfloat squareVertexData[] =
// {
// 0.5, 0.0, 0.0f, 1.0f, 0.0f, //右中下
// 0.5, 1, 0.0f, 1.0f, 1.0f, //右上
// -0.5, 1.0, 0.0f, 0.0f, 1.0f, //左上
// -0.5, 0, 0.0f, 0.0, 0.0f, //左下
//
// -0.5, 0.0, 0.0f, 0.0f, 1.0f, //左上
// -0.5, -1, 0.0f, 0.0f, 0.0f, //左下
// 0.5, -1, 0.0f, 1.0f, 0.0f, //右下
// 0.5, 0.0, 0.0f, 1.0f, 1.0f, //右上
// };
//
// GLbyte indices[] =
// {
// 0,1,2,
// 2,3,0,
// 4,5,6,
// 6,7,4
// };
在传递数据给GPU的时候要做相应的改变,代码如下
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
绘制的时候
glDrawElements(GL_TRIANGLES, 12, GL_UNSIGNED_BYTE, 0);
疑问:1>如何按照指定的rect渲染,猜测是调整顶点坐标点值的大小?
2>纹理坐标系原点的位置为纹理的左下角,(1,1)为纹理的右上角,而不是屏幕?(这个我的理解应该是这样的,但不确认)
下面环节就是API介绍
GLuint buffer;
/*
创建缓存对象并且返回缓存对象的标示符,第一个为需要创建的缓存数量,第二个为用于存储单一ID或多个ID的GLuint变量或数组的地址
*/
glGenBuffers(1, &buffer);
/*
当缓存对象创建之后,在使用缓存对象之前,我们需要将缓存对象连接到相应的缓存上。
glBindBuffer()有2个参数:target与buffertarget告诉VBO该缓存对象将保存顶点数组数据还是索引数组数据:GL_ARRAY_BUFFER或GL_ELEMENT_ARRAY。任何顶点属性,如顶点坐标、纹理坐标、法线与颜色分量数组都使用GL_ARRAY_BUFFER。用于glDraw[Range]Elements()的索引数据需要使用GL_ELEMENT_ARRAY绑定。注意,target标志帮助VBO确定缓存对象最有效的位置,如有些系统将索引保存AGP或系统内存中,将顶点保存在显卡内存中。当第一次调用glBindBuffer(),VBO用0大小的内存缓存初始化该缓存,并且设置VBO的初始状态,如用途与访问属性。
*/
glBindBuffer(GL_ARRAY_BUFFER, buffer);
/*
将数据拷贝到缓存对象,第一个参数target可以为GL_ARRAY_BUFFER或GL_ELEMENT_ARRAY。size为待传递数据字节数量。第三个参数为源数据数组指针,如data为NULL,则VBO仅仅预留给定数据大小的内存空间。最后一个参数usage标志位VBO的另一个性能提示,它提供缓存对象将如何使用:static、dynamic或stream、与read、copy或draw
*/
glBufferData(GL_ARRAY_BUFFER, sizeof(vertexData), vertexData, GL_STATIC_DRAW);
/*
开启顶点数据,并缓存 默认情况下,出于性能考虑,所有顶点着色器的属性(Attribute)变量都是关闭的,意味着数据在着色器端是不可见的,哪怕数据已经上传到GPU,由glEnableVertexAttribArray启用指定属性,才可在顶点着色器中访问逐顶点的属性数据。glVertexAttribPointer或VBO只是建立CPU和GPU之间的逻辑连接,从而实现了CPU数据上传至GPU。但是,数据在GPU端是否可见,即,着色器能否读取到数据,由是否启用了对应的属性决定,这就是glEnableVertexAttribArray的功能,允许顶点着色器读取GPU(服务器端)数据。
*/
glEnableVertexAttribArray(GLKVertexAttribPosition);
/*
向gpu传入顶点数据的方法,将vbo中的数据传递给顶点数组
参数:
1): index , 指定要修改的顶点属性的索引值
2): size, 指定每个顶点属性的组件数量。必须为1、2、3或者4。初始值为4。(如position是由3个(x,y,z)组成,而颜色是4个(r,g,b,a))
3): type, 指定数组中每个组件的数据类型。可用的符号常量有GL_BYTE, GL_UNSIGNED_BYTE, GL_SHORT,GL_UNSIGNED_SHORT, GL_FIXED, 和 GL_FLOAT,初始值为GL_FLOAT
4): 指定当被访问时,固定点数据值是否应该被归一化(GL_TRUE)或者直接转换为固定点值(GL_FALSE)
5): stride, 指定连续顶点属性之间的偏移量。如果为0,那么顶点属性会被理解为:它们是紧密排列在一起的。初始值为0
6): 指定第一个组件在数组的第一个顶点属性中的偏移量。该数组与GL_ARRAY_BUFFER绑定,储存于缓冲区中。初始值为0;
*/
glVertexAttribPointer(GLKVertexAttribPosition, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat)*5, (GLfloat *)NULL+0);
glEnableVertexAttribArray(GLKVertexAttribTexCoord0);//纹理
glVertexAttribPointer(GLKVertexAttribTexCoord0, 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat)*5, (GLfloat *)NULL+3);
纹理贴图
NSString *filePath = [[NSBundle mainBundle] pathForResource:@"for_test" ofType:@"jpg"];
// GLKTextureLoaderOriginBottomLeft 纹理坐标系是相反的
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:@(1),GLKTextureLoaderOriginBottomLeft, nil];
GLKTextureInfo *textureInfo = [GLKTextureLoader textureWithContentsOfFile:filePath options:options error:nil];
//着色器
self.mEffect = [[GLKBaseEffect alloc] init];
self.mEffect.texture2d0.enabled = GL_TRUE;
self.mEffect.texture2d0.name = textureInfo.name;
疑问:options key为GLKTextureLoaderOriginBottomLeft是不是指纹理坐标系是左下角为原点的意思,值为1又是为啥呢?然后这里纹理坐标系是相反的参照的是UIKit坐标系吗?如果是的化我觉得称之为水平轴翻转180度更适合理解,uikit坐标系是屏幕左上角为原点吧,我理解相反的意思是(0,0)应该对应(1,1)
接下来是渲染绘图环节
- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect{
// 就是用来设置这个 “ 底色 ” 的,即所谓的背景颜色。glClearColor ( ) 只起到Set 的作用,并不Clear 任何
glClearColor(0.3f, 0.6f, 1.0f, 1.0f);
// 是用来清除屏幕颜色,即将屏幕的所有像素点都还原为 “底色
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
//启动着色器
[self.mEffect prepareToDraw];
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);//使用顶点数组绘制图元
// glDrawElements(<#GLenum mode#>, <#GLsizei count#>, <#GLenum type#>, <#const GLvoid *indices#>)--->使用索引数组绘制图元
}
使用顶点索引绘图api为glDrawElements
关于此api请查询百度百科,这里简单贴一下百度百科的参数以及功能释义:
glDrawElements是一个OPENGL的图元绘制函数,从数组中获得数据渲染图元。
函数原型为:
void glDrawElements( GLenum mode, GLsizei count,
GLenum type, const GLvoid *indices);
其中:
mode指定绘制图元的类型,它应该是下列值之一,GL_POINTS, GL_LINE_STRIP, GL_LINE_LOOP, GL_LINES, GL_TRIANGLE_STRIP, GL_TRIANGLE_FAN, GL_TRIANGLES, GL_QUAD_STRIP, GL_QUADS, and GL_POLYGON.
count为以mode类型连接的顶点的总数,且根据不同的mode,count小于或等于单个mode类型图元的顶点数*图元数
type为索引值的类型,只能是下列值之一:GL_UNSIGNED_BYTE, GL_UNSIGNED_SHORT, or GL_UNSIGNED_INT。
indices:指向索引存贮位置的指针
百度百科链接。
网友评论