概述:纹理是一个用来保存图像的颜色元素值的OpenGL ES缓存。当把纹理应用到几何图形中后,会使渲染的场景显得更自然,会使三角形的复杂组合像是真实的物体而不只是有颜色的面。
当用一个图像初始化一个纹理缓存之后,在这个图像中的每个像素变成了纹理中的一个纹素。与像素类似,纹素保存颜色数据。像素和纹素之间的差别很微妙:像素通常表示计算机屏幕上的一个实际颜色点,因此,像素通常被用来作为一个测量单位,而纹素存在于一个虚拟的没有尺寸的数学坐标种,下图显示了OpenGL ES纹理坐标系中的一个纹理:纹理坐标系有一个命名为S和T的2D轴。在一个纹理中无论有多少个纹素,纹理的尺寸永远是在S轴上从0.0到1.0,在T轴上从0.0到1.0。从一个1像素高64像素宽的图像初始化来的纹理会沿着整个T轴有1纹素,沿S轴有64纹素。
注意:本例中使用的是2D纹理,但是一些OpenGL 实现还会支持1D和3D纹理。一个1D的纹理就相当于沿着T轴只有1纹素的2D纹理,因此一个64纹素的1D纹理与尺寸为64x1的图像初始化来的2D纹理是相同的。3D纹理就是一个层饼,一个在拥有R、S和T轴的坐标系中沿着R轴堆叠的多个2D纹理的层饼。1D和3D纹理对于某些特定的应用来说是非常方便的,但是2D纹理是迄今为止最常见的。
在上一篇的代码基础上,我们做一些修改就可以实现纹理贴图效果:
首先,纹理坐标被加入到SceneVertex类型的声明中:
typedef struct {
// 位置
GLKVector3 positionCoords;
// 纹理
GLKVector2 textureCoords;
} SceneVertex;
纹理坐标定义了几何图形中的每个顶点的纹理映射,下面更新一下顶点数据数组:
static const SceneVertex vertices[] = {
// 位置 纹理
{{ 0.0, 0.5, 0.0}, {0.5,1.0}},
{{ -0.5, -0.5, 0.0}, {0.0,0.0}},
{{ 0.5, -0.5, 0.0}, {1.0,0.0}},
};
下面需要使用GLKit提供的GLKTextureLoader类,这个类用于将一个纹理图像加载到OpenGL ES纹理缓存中。
NSString *imagePath = [[NSBundle mainBundle] pathForResource:@"wall" ofType:@"jpg"];
CGImageRef imageRef = [UIImage imageWithContentsOfFile:imagePath].CGImage;
/*
GLKTextureLoader的extureWithCGImage方法接受一个CGImageRef创建一个新的包含CGImageRef的像素数据的纹理缓存。
options参数接受一个存储了指定GLKTextureLoader怎么解析加载的图像数据的NSDictionary。
GLKTextureLoader会自动调用glTexParameteri() 方法来为创建的纹理缓存设置取样和循环模式(后期会做详细介绍)
*/
GLKTextureInfo *textureInfo = [GLKTextureLoader textureWithCGImage:imageRef options:nil error:NULL];
/*
GLKBaseEffect对象提供了对于使用纹理做渲染的内建的支持。在纹理缓存被创建以后,设置baseEffect的texture2d0属性使用一个新的纹理缓存
GLKTextureInfo的target属性指定被配置的纹理缓存的类型
*/
self.baseEffect.texture2d0.name = textureInfo.name;
self.baseEffect.texture2d0.target = textureInfo.target;
GLKTextureInfo 类封装了创建的纹理缓存相关信息,包括尺寸以及是否包含MIP贴图。在本例中只需要缓存的标识符、名字和用于纹理的目标。
正如前面一片那样,我们把顶点数据发送到GPU缓存之后需要告诉GPU如何使用这些数据。
// 内置宏,可以直接使用
GLsizei textureOffset = offsetof(SceneVertex, textureCoords);
// 激活GLKVertexAttribTexCoord0属性
glEnableVertexAttribArray(GLKVertexAttribTexCoord0);
/*
告诉OpenGL ES每个顶点数据如何使用。
第一个参数:指示当前绑定的缓存包含每个顶点的位置信息,也就是给哪个顶点属性赋值
第二个参数:指定每个位置又3个数据部分
第三个参数:告诉OpenGL ES每个部分都保存为一个浮点类型的值
第四个参数:告诉OpenGL ES小数点固定数据是否可以被改变,本例中没有使用小数点固定的数据,因此赋值为GL_FALSE
第五个参数:可以称为"步幅",指定了没哥顶点的保存需要多少个字节。简单点就是指定了GPU从一个顶点的内存碍事转到下一个顶点的内存开始位置需要跳过多少字节
第六个参数:告诉OpenGL ES可以从当前绑定的顶点缓存的开始位置访问顶点数据
与前面不同的是,本次的纹理坐标包含2个部分,所以第二个参数赋值为2,由于现在顶点数据中包含顶点的位置坐标X、Y、Z和纹理坐标U、V。
所以纹理坐标应该从顶点坐标后面开始取。
*/
glVertexAttribPointer(GLKVertexAttribTexCoord0, 2, GL_FLOAT, GL_FALSE, sizeof(SceneVertex), NULL + textureOffset);
通过上述修改,运行起来就可以得到想要的带纹理的图形了:稍微修改一下渲染色:
self.baseEffect.constantColor = GLKVector4Make(1.0, // red
1.0, // green
1.0, // blue
1.0); // alpha
最后还是附上完整代码,方便大家参考:
#import "ViewController.h"
typedef struct {
GLKVector3 positionCoords;
GLKVector2 textureCoords;
} SceneVertex;
@interface ViewController () {
// 声明GLuint类型变量,用于存放本例中顶点数据的缓存标识符
GLuint vertexBufferID;
}
@property (nonatomic, strong) GLKBaseEffect *baseEffect;
@end
@implementation ViewController
static const SceneVertex vertices[] = {
{{ 0.0, 0.5, 0.0},{0.5,1.0}},
{{ -0.5, -0.5, 0.0},{0.0,0.0}},
{{ 0.5, -0.5, 0.0},{1.0,0.0}},
};
- (void)viewDidLoad {
[super viewDidLoad];
// 将当前view强制转换为GLKView
GLKView *view = (GLKView *)self.view;
// 断言,检测用storyboard加载的视图是否是GLKView
NSAssert([view isKindOfClass:[GLKView class]], @"View controller's View is not a GLKView");
// 初始化context为OpenGL ES 2.0
view.context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
// 在任何其他的OpenGL ES配置或者渲染发生前,应用的GLKView实例的上下文属性都需要设置为当前
[EAGLContext setCurrentContext:view.context];
/*
初始化baseEffect
GLKBaseEffect 是GLKit提供的另一个内建类。GLKBaseEffect的存在是为了简化OpenGL ES的很多常用操作。GLKBaseEffect隐藏了iOS设备支持的多个OpenGL ES版本之间的差异。
在OpenGL ES 2.0中,如果没有GLKit和GLKBaseEffect类,完成这个简单的例子需要用着色器语言编写一个GPU程序。
GLKBaseEffect会在需要的时候自动地构建GPU程序并极大地简化本书中的例子
*/
self.baseEffect = [[GLKBaseEffect alloc] init];
self.baseEffect.useConstantColor = GL_TRUE;
self.baseEffect.constantColor = GLKVector4Make(1.0, // red
1.0, // green
1.0, // blue
1.0); // alpha
glClearColor(1.0, 1.0, 1.0, 1.0);
glGenBuffers(1, &vertexBufferID);
glBindBuffer(GL_ARRAY_BUFFER, vertexBufferID);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
NSString *imagePath = [[NSBundle mainBundle] pathForResource:@"wall" ofType:@"jpg"];
CGImageRef imageRef = [UIImage imageWithContentsOfFile:imagePath].CGImage;
GLKTextureInfo *textureInfo = [GLKTextureLoader textureWithCGImage:imageRef options:nil error:NULL];
self.baseEffect.texture2d0.name = textureInfo.name;
self.baseEffect.texture2d0.target = textureInfo.target;
}
- (void)update{
}
- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect {
/*
iOS的OpenGL中里有2个着色器, 一个是GLKBaseEffect,为了方便OpenGL ES 1.0转移到2.0的通用着色器。 一个是OpenGL ES 2.0新添加的可编程着色器,使用跨平台的着色语言。实例化基础效果实例,如果没有GLKit与GLKBaseEffect类,就需要为这个简单的例子编写一个小的GPU程序,使用2.0的Shading Language,而GLKBaseEffect会在需要的时候自动的构建GPU程序。这里使用GLKBaseEffect来做着色器
“prepareToDraw”方法,是让“效果Effect”针对当前“Context”的状态进行一些配置,它始终把“GL_TEXTURE_PROGRAM”状态定位到“Effect”对象的着色器上。此外,如果Effect使用了纹理,它也会修改“GL_TEXTURE_BINDING_2D”。
*/
[self.baseEffect prepareToDraw];
// 前两行为渲染前的“清除”操作,清除颜色缓冲区和深度缓冲区中的内容。
glClear(GL_COLOR_BUFFER_BIT);
//glBindBuffer(GL_ARRAY_BUFFER, vertexBufferID);
// 启动顶点缓存渲染操作
glEnableVertexAttribArray(GLKVertexAttribPosition);
/*
告诉OpenGL ES每个顶点数据如何使用。
第一个参数:指示当前绑定的缓存包含每个顶点的位置信息,也就是给哪个顶点属性赋值
第二个参数:指定每个位置又3个数据部分
第三个参数:告诉OpenGL ES每个部分都保存为一个浮点类型的值
第四个参数:告诉OpenGL ES小数点固定数据是否可以被改变,本例中没有使用小数点固定的数据,因此赋值为GL_FALSE
第五个参数:可以称为"步幅",指定了没哥顶点的保存需要多少个字节。简单点就是指定了GPU从一个顶点的内存碍事转到下一个顶点的内存开始位置需要跳过多少字节
第六个参数:告诉OpenGL ES可以从当前绑定的顶点缓存的开始位置访问顶点数据
*/
GLsizei positionOffset = offsetof(SceneVertex, positionCoords);
glVertexAttribPointer(GLKVertexAttribPosition, 3, GL_FLOAT, GL_FALSE, sizeof(SceneVertex), NULL + positionOffset);
GLsizei textureOffset = offsetof(SceneVertex, textureCoords);
glEnableVertexAttribArray(GLKVertexAttribTexCoord0);
glVertexAttribPointer(GLKVertexAttribTexCoord0, 2, GL_FLOAT, GL_FALSE, sizeof(SceneVertex), NULL + textureOffset);
/*
告诉OpenGL ES如何使用缓存数据之后就可以调用glDrawArrays()函数通过这些数据来绘制图形了
第一个参数:告诉OpenGL ES怎么处理在绑定的顶点缓存内的顶点数据。本例中屎指示OpenGL ES去渲染三角形
第二个参数:指定缓存内的需要渲染的第一个顶点的位置
第三个参数:需要渲染的顶点数量
*/
glDrawArrays(GL_TRIANGLES, 0, 3);
}
在对OpenGL ES的学习之后才写下这些记录,如果有讲解不到位或者是错误的地方还请大家留言指正,谢谢!
最后如果运行这里[self.baseEffect prepareToDraw];报错的话代码已上传至GitHubLearnOpenGL ES brach01里面