iOS开发OpenGL ES - 纹理

概述:纹理是一个用来保存图像的颜色元素值的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里面

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容