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