iOS开发OpenGL ES - 透明度、混合和多重纹理

透明度*
纹理中可以使用个包含透明度元素的GL_RGBA格式来指定每个纹素的透明度。通常一个或者更多个纹素会结合灯光和顶点颜色来决定每个片元的最终颜色和透明度。(灯光在后面会讲到)。每个片元产生的透明度会影响片元怎么与一个帧缓存内的现存内容相混合。

当纹理计算出来一个完全不透明的最终片元颜色时,这个片元颜色会简单地替换任何在帧缓存的像素颜色渲染缓存内现存的对应的像素颜色。如果计算出来的片元颜色部分透明或者全透明,OpenGL ES会使用一个混合函数来混合片元颜色与像素颜色渲染缓存内对应的像素。这也就是在iOS项目中对于可滑动列表视图尽量不使用ClearColor或者透明度喂0的原因。如果视图较多的话会增加渲染时长,fps严重下降,以致出现卡顿等影响用户体验的问题。

通过调用glEnable(GL_BLEND)函数来开启混合,然后通过调用glBlendFunc(GLenum sfactor, GLenum dfactor)来设置混合函数。sfactor参数用于指定每个片元的最终颜色元素是怎么影响混合的。dfactor参数用于指定在目标帧缓存中已经存在的颜色元素会怎么影响混合。最常用的混合函数配置是设置sfactor为GL_SRC_ALPHA,设置dfactor为GL_ONE_MINUS_SRC_ALPHA,如下代码:

glEnable(GL_BLEND);
 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

其中GL_SRC_ALPHA用于让源片元的透明度元素挨个与其他的片元颜色相乘。GL_ONE_MINUS_SRC_ALPHA用于让源片元的透明度元素与在帧缓存中的正被更新的像素的颜色元素相乘。最后的结果是:如果片元的透明度为0,那么没有片元的颜色会出现在帧缓存中。如果片元的透明度为1,那么片元的颜色会完全替代在帧缓存中对应的像素颜色。介于0.0-1.0之间的透明度意味着片元颜色的一部分会被帧缓存内对应的像素颜色的的一部分来产生一个混合的结果。当使用glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)时,在帧缓存中的最终颜色是用下面的方程式计算的:

混合
使用glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)会与iOS Core Graphics的"正常混合模式"产生相同的结果。如下显示结果:

树叶纹理与在帧缓存的像素颜色渲染缓存中的黑色像素混合,因此在树叶纹理包含透明纹素的每个地方黑色像素都保持不变。然后使用一个虫子图片的第二个纹理与像素颜色渲染缓存相混合,最终的渲染结果为虫子纹理在树叶纹理之上,树叶纹理在黑色背景之上。

下面开始代码,首先还是定义顶点数据

static const SceneVertex vertices[] =
{
      // 位置坐标            // 纹理坐标
    {{-1.0f, -0.6f, 0.0f}, {0.0f, 0.0f}},      // 第一个三角形
    {{ 1.0f, -0.6f, 0.0f}, {1.0f, 0.0f}},
    {{-1.0f,  0.6f, 0.0f}, {0.0f, 1.0f}},
    {{ 1.0f, -0.6f, 0.0f}, {1.0f, 0.0f}},      // 第二个三角形
    {{-1.0f,  0.6f, 0.0f}, {0.0f, 1.0f}},
    {{ 1.0f,  0.6f, 0.0f}, {1.0f, 1.0f}},
};

定义两个三角形合成一个矩形已完整的显示出贴图

在将顶点数据发送到GPU内存缓存之后我们需要加载图像,从图像加载纹理时较上篇有所不同,这里应用了一个NSDictionary对象来设定选项。这里的GLKTextureLoaderOriginBottomLeft与对应的布尔值YES是为了设置GLKit的GLKTextureLoader类垂直翻转图像数据,这个操作可以抵消图像原点与OpenGL ES标准原点之间的差异。

NSString *leavesImagePath = [[NSBundle mainBundle] pathForResource:@"leaves" ofType:@"jpg"];
CGImageRef leavesImageRef = [UIImage imageWithContentsOfFile:leavesImagePath].CGImage;    
self.textureInfo0 = [GLKTextureLoader textureWithCGImage:leavesImageRef options:[NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES],GLKTextureLoaderOriginBottomLeft, nil] error:NULL];
    
NSString *beetleImagePath = [[NSBundle mainBundle] pathForResource:@"beetle" ofType:@"jpg"];
CGImageRef beetleImageRef = [UIImage imageWithContentsOfFile:beetleImagePath].CGImage;
self.textureInfo1 = [GLKTextureLoader textureWithCGImage:beetleImageRef options:[NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES],GLKTextureLoaderOriginBottomLeft, nil] error:NULL];

下面是- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect方法中的调整。把相同的几何图形渲染了两次,第一次使用了树叶纹理,第二次使用了虫子。混合发生在每次被一个纹理着色的一个片元与在像素颜色渲染缓存中已存在的像素颜色混合的时候。

self.baseEffect.texture2d0.name = self.textureInfo0.name;
self.baseEffect.texture2d0.target = self.textureInfo0.target;
[self.baseEffect prepareToDraw];
glDrawArrays(GL_TRIANGLES, 0, 6);
    
self.baseEffect.texture2d0.name = self.textureInfo1.name;
self.baseEffect.texture2d0.target = self.textureInfo1.target;
[self.baseEffect prepareToDraw];
glDrawArrays(GL_TRIANGLES, 0, 6);

GLkit的baseEffect是由第一个纹理设定的,同时缓存数据被绘制。然后baseEffect由第二个纹理设定。同时缓存数据被再次绘制。这两个过程中也伴随着与像素颜色渲染缓存的混合。绘图的顺序决定了哪一个纹理会出现在另一个之上。在当前情况下是虫子在树叶的上面。反之则颠倒。

需要源码请移步GitHubLearnOpenGL ES brach02

多重纹理
很多有用的可视效果可以通过把片元颜色与在像素颜色渲染缓存中现存的颜色相混合来实现。但是这个技术有两个缺点:每次显示更新时几何图形必须要被渲染一次或多次,混合函数需要从像素颜色渲染缓存读取颜色数据以便与片元颜色混合。然后结果被写回帧缓存。当带有透明度数据的多个纹理层叠式,每个纹理的像素颜色渲染缓存的颜色会被再次读取、混合、重写。通过多次读写像素颜色渲染缓存来创建一个最终的渲染像素的过程叫做多通道渲染。如往常一样,内存访问限制了性能,因此多通道渲染时次优的。接下来将介绍多重纹理以避免多通道渲染的大部分缺陷。

所有的现代GPU都能够同时从至少两个纹理缓存中取样纹素。GLKit的GLKBaseEffect类同时支持两种纹理。执行纹素取样和混合的硬件组件叫做一个纹理单元或者一个取样器。如果你的应用需要超过两个纹理单元,在确定一个单独的通道中可以结合多少个纹理之前,可以使用以下代码:

GLint iUnits;
glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &iUnits);
printf("%zd",iUnits);

多重纹理引入了另一个组合配置选项。为了帮助降低复杂性。iOS5中的GLKit的GLKEffectPropertyTexture类提供了3中常见的多重纹理模式:GLKTextureEnvModeReplace、GLKTextureEnvModeModulate、GLKTextureEnvModeDecal。GLKEffectPropertyTexture默认使用GLKTextureEnvModeModulate。这种模式几乎总是产生最好的结果。GLKTextureEnvModeModulate模式会让所有为灯光和其他效果计算出来的颜色与从纹理取样的颜色相混合。详细解释可以看下官方文档

GLKEffectPropertyTexture的envMode属性用于配制混合模式。本例中还是加载两个纹理,但是不再需要明确地启动与帧缓存的像素颜色渲染缓存的混合。相反,baseEffect的第二个纹理属性texture2d1被设置为使用GLKTextureEnvModeDecal模式,这种模式会使用一个与glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)类似的方法来混合第二个与第一个纹理

self.baseEffect.texture2d1.envMode = GLKTextureEnvModeDecal;
NSString *leavesImagePath = [[NSBundle mainBundle] pathForResource:@"leaves" ofType:@"jpg"];
CGImageRef leavesImageRef = [UIImage imageWithContentsOfFile:leavesImagePath].CGImage;
    
self.textureInfo0 = [GLKTextureLoader textureWithCGImage:leavesImageRef options:[NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES],GLKTextureLoaderOriginBottomLeft, nil] error:NULL];
    
self.baseEffect.texture2d0.name = self.textureInfo0.name;
self.baseEffect.texture2d0.target = self.textureInfo0.target;
    
    
NSString *beetleImagePath = [[NSBundle mainBundle] pathForResource:@"beetle" ofType:@"jpg"];
CGImageRef beetleImageRef = [UIImage imageWithContentsOfFile:beetleImagePath].CGImage;
self.textureInfo1 = [GLKTextureLoader textureWithCGImage:beetleImageRef options:[NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES],GLKTextureLoaderOriginBottomLeft, nil] error:NULL];
    
self.baseEffect.texture2d1.name = self.textureInfo1.name;
self.baseEffect.texture2d1.target = self.textureInfo1.target;
self.baseEffect.texture2d1.envMode = GLKTextureEnvModeDecal;

再看下glkView:(GLKView *)view drawInRec中的修改:

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);
    
glEnableVertexAttribArray(GLKVertexAttribTexCoord1);
glVertexAttribPointer(GLKVertexAttribTexCoord1, 2, GL_FLOAT, GL_FALSE, sizeof(SceneVertex), NULL + textureOffset);
[self.baseEffect prepareToDraw];
glDrawArrays(GL_TRIANGLES, 0, 6);

看一下最终效果图:

最后附上源码LearnOpenGL ES brach 03

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

推荐阅读更多精彩内容