iOS开发学习OpenGL ES系列 -- 纹理

上一篇通过重新定义顶点坐标,我们绘制了一个立方体,表面设置为rgb颜色。在实际的开发中,很多3D模型都是非常漂亮的,那是由于有各种漂亮贴图的缘故,这一篇在前面的学习基础上,增加物体的表面贴图。使得物体看起来更加自然。

纹理是一个用来保存图像颜色元素值的OpenGL ES缓存。可以使用任何图像生成纹理缓存,使用纹理之前需要先加载图片生成纹理。然后指定纹理坐标才可以在着色器中获取相应的纹理像素点颜色了。

首先看一下纹理坐标。

纹理坐标系有一个命名为S和T的2D轴。在一个纹理中无论有多少个纹素,纹理的尺寸在S轴T轴上永远是在0.0到1.0。

在每个顶点的X、Y、Z坐标被转换成视口坐标系后,GPU会设置转换生成的三角形内的每个像素颜色。转换几何形状数据为帧缓存中的颜色像素的渲染步骤叫做点阵化,每个颜色像素叫做片元。当OpenGL ES没有使用纹理时,GPU会根据包含该片元的对象的顶点的颜色来计算每个片元的颜色。当设置了使用纹理后,GPU会根据在当前绑定的纹理缓存中的纹素来计算每个片元的颜色。

程序需要指定怎么对齐纹理和顶点,以便让GPU知道每个片元的颜色由哪些纹素决定。这个对齐叫做映射,是通过扩展每个顶点保存的数据来实现的:除来X、Y、Z坐标,每个顶点还给出了U和V坐标值。每个U坐标会映射顶点在视口中的最终位置到纹理中的沿着S轴的一个位置。V坐标映射到T轴。
纹理映射到转换后的顶点

每个顶点的U和V坐标会附加到每个顶点的视口坐标系中的最终位置。

我们修改原来的顶点坐标,并扩充出U、V坐标值。

static GLfloat vertices[] = {
   
    // X轴+
    0.5,  0.5, -0.5,  1, 0, 0,  1, 1,
    0.5, -0.5, -0.5,  1, 0, 0,  1, 0,
    0.5,  0.5,  0.5,  1, 0, 0,  0, 1,
    0.5,  0.5,  0.5,  1, 0, 0,  0, 1,
    0.5, -0.5,  0.5,  1, 0, 0,  0, 0,
    0.5, -0.5, -0.5,  1, 0, 0,  1, 0,
    
    // X轴-
   -0.5,  0.5, -0.5,  1, 0, 0,  0, 1,
   -0.5, -0.5, -0.5,  1, 0, 0,  0, 0,
   -0.5, -0.5,  0.5,  1, 0, 0,  1, 0,
   -0.5, -0.5,  0.5,  1, 0, 0,  1, 0,
   -0.5,  0.5,  0.5,  1, 0, 0,  1, 1,
   -0.5,  0.5, -0.5,  1, 0, 0,  0, 1,
    
    // Y轴+
    -0.5,  0.5, -0.5,  0, 1, 0, 0, 0,
    -0.5,  0.5,  0.5,  0, 1, 0, 1, 0,
     0.5,  0.5,  0.5,  0, 1, 0, 1, 1,
     0.5,  0.5,  0.5,  0, 1, 0, 1, 1,
     0.5,  0.5, -0.5,  0, 1, 0, 0, 1,
    -0.5,  0.5, -0.5,  0, 1, 0, 0, 0,
    
    // Y轴-
    -0.5, -0.5, -0.5,  0, 1, 0, 0, 0,
    -0.5, -0.5,  0.5,  0, 1, 0, 1, 0,
     0.5, -0.5,  0.5,  0, 1, 0, 1, 1,
     0.5, -0.5,  0.5,  0, 1, 0, 1, 1,
     0.5, -0.5, -0.5,  0, 1, 0, 0, 1,
    -0.5, -0.5, -0.5,  0, 1, 0, 0, 0,
    
    // Z轴+
    -0.5,  0.5,  0.5,  0, 0, 1, 0, 1,
    -0.5, -0.5,  0.5,  0, 0, 1, 0, 0,
     0.5, -0.5,  0.5,  0, 0, 1, 1, 0,
     0.5, -0.5,  0.5,  0, 0, 1, 1, 0,
     0.5,  0.5,  0.5,  0, 0, 1, 1, 1,
    -0.5,  0.5,  0.5,  0, 0, 1, 0, 1,
    
    // Z轴-
    -0.5,  0.5, -0.5,  0, 0, 1, 0, 1,
    -0.5, -0.5, -0.5,  0, 0, 1, 0, 0,
     0.5, -0.5, -0.5,  0, 0, 1, 1, 0,
     0.5, -0.5, -0.5,  0, 0, 1, 1, 0,
     0.5,  0.5, -0.5,  0, 0, 1, 1, 1,
    -0.5,  0.5, -0.5,  0, 0, 1, 0, 1,
};

每个面上的每个三角形的每个顶点指定纹理坐标点。如下图这样:

坐标定义好之后需要发送顶点数据到着色器,所以着色器代码需要添加接受属性:

// 接受uv值
attribute vec2 uv;

// 输出到片段着色器
varying vec2 fragUV;

然后启用shader中的uv属性

GLuint uvAttribLocation = glGetAttribLocation(program, "uv");
glEnableVertexAttribArray(uvAttribLocation);

为uv指定数据读取规范:

glVertexAttribPointer(uvAttribLocation, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), NULL + 6 * sizeof(GLfloat));

指定了纹理坐标,我们还需要生成纹理并且绑定到着色器属性上,才能使用。

先看一下生成纹理,可以使用GLKit提供的生成方法:

// 读取bundle文件
NSString *textureFile = [[NSBundle mainBundle] pathForResource:@"wall" ofType:@"jpg"];
    
// 生成GLKTextureInfo对象
texture = [GLKTextureLoader textureWithContentsOfFile:textureFile options:nil error:nil];

这里的GLKTextureLoader类是苹果GLKit框架提供的实用工具类,可简化从各种文件格式加载OpenGL或OpenGL ES纹理数据。
里面提供了初始化新纹理加载器对象或者从文件加载纹理、从内存中创建纹理、从URL加载纹理和从CGImages创建纹理方法。
这里并不会直接创建GLKTextureInfo对象,而通过GLKTextureLoader创建的OpenGL纹理数据返回GLKTextureInfo纹理信息对象供我们使用,它的name属性即是opengl上下文的纹理名称,以便我们用来和OpenGL进行交互。
需要详细了解的可以在苹果官方文档查阅

生成纹理之后需要传递到shader中进行绑定。我们在片段着色器中添加uniform sampler2D texture,其中sampler2D是纹理参数类型。然后通过texture2D函数采样fragUV坐标下的texture像素颜色。返回值赋值给gl_FragColor:

precision highp float;

varying lowp vec2 fragUV;
uniform sampler2D texture;

void main(void) {
    gl_FragColor = texture2D(texture, fragUV);
}

重新定义好了fragment shader,我们回到项目中进行纹理绑定:

// 绑定纹理到GL_TEXTURE_2D
glBindTexture(GL_TEXTURE_2D, texture.name);

// 获取texture位置
GLuint textureUniformLocation = glGetUniformLocation(program, "texture");

// 将0传递给texture
glUniform1i(textureUniformLocation, 0);

OpenGL ES中可以开启8个通道,通道0是默认开启,这里我们只设置一层纹理。所以不需要再开启更多通道,直接将0传递给textureUniformLocation即可。

到这立方体的纹理已经设置好了,看下最终效果:
cube.gif

补充:
大部分的OpenGL ES实现要么需要、要么受益于使用尺寸为2的幂的纹理。本例中的图片为512x512,边长均为2的n次幂,这个尺寸符合OpenGL ES的要求。一个4x64的纹理是有效的,一个128x128的纹理可以工作良好。一个1x64的纹理也可以。一个200x200的纹理要么不工作,要么根据使用的OpenGL ES版本在渲染时导致效率低下。

参考:OpenGL ES应用开发实践指南 iOS卷

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

推荐阅读更多精彩内容