3.5、透明度、混合和多重纹理

OpenGL ES提供了如此多的选项用来保存、映射,以及配置纹理,以至于这个主题常常会压垮程序员。关于纹素颜色格式、纹理环境函数、取样模式,以及细节级别存在一个天文数字的组合和排列。本节甚至会介绍更多的选项,因此为了保持这个主题在可管理范围内,专注于现代最常用的用例是必要的。

可以使用包含透明度元素的GL_RGBA纹理格式来指定每个纹素的透明度。图3-6显示了一个拥有透明度颜色元素值的纹理,因此棋盘格图案可以透过半透明的纹素显示出来。通常-一个或者更多个纹素会结合灯光和顶点颜色来决定每个片元的最终颜色和透明度。第4章会介绍灯光。每个片元产生的透明度都会影响片元怎么与一个帧缓存内的现存内容相混合。

图 3-6

当纹理计算出来一个完全不透明的最终片元颜色时,这个片元颜色会简单地替换任何在帧缓存的像素颜色渲染缓存内现存的对应的像素颜色。还有更有意思的混合方式。如果计算出来的片元颜色部分透明或者全透明,OpenGL ES会使用一个混合 函数来混合片元颜色与像素颜色渲染缓存内对应的 像素。

通过调用glEnable(GL__BLEND)函数来开启混合。然后通过调用glBlendFunc(sourceFactor,destinationFactor)来设置混合函数。sourceFactor 参数用于指定每个片元的最终颜色元素是怎么影响混合的。destinationFactor参数用于指定在目标帧缓存中已经存在的颜色元素会怎么影响混合。最常用的混合函数配置是设置sourceFactor为GL_SRC_ALPHA,设置destinationFactor为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用于让源片元的透明度元素(1.0) 与在帧缓存内的正被更新的像素的颜色元素相乘。结果是,如果片元的透明度值为0,那么没有片元的颜色会出现在帧缓存中。如果片元的透明度值为1,那么片元的颜色会完全替代在帧缓存中的对应的像素颜色。介于0.0到1.0之间的透明度值意味着片元颜色的一部分会被添加到帧缓存内对应的像素颜色的一部分中来产生一个混合的结果。当使用 gIBlendFunc(GL_SRC_ALPHA, GL_ONEMINUS_SRC_ALPHA) 时,在帧缓存中的最 终颜色是用下面的方程式计算的:

Red(final) = Alpha(fragment) * Red (fragment) + (1.0 - Alpha(fragment)) * Red(frame buffer),
Green(final) = Alpha(fragment) * Green (fragment) + (1.0 - Alpha(fragment)) * Green(frame buffer),
Blue(final) = Alpha(fragment) * Blue (fragment) + (1.0 - Alpha(fragment)) * Blue(frame buffer),
Alpha(final) = Alpha(fragment) * Alpha (fragment) + (1.0 - Alpha(fragment)) * Alpha(frame buffer),

3.5.1 在OpenGLES_Ch3_4 示例中混合片元颜色

使用glBlendFunc(GLSRC_ALPHA, GL ONE_MINUS_SRC_ALPHA)会与iOS Core Graphics的“正常混合模式”产生相同的结果。图3-7是例子OpenGLES_Ch3_4产生的显示结果。图3-7中的树叶纹理与在帧缓存的像素颜色渲染缓存中的黑色像素混合,因此在树叶纹理包含透明纹素的每个地方黑色像素都保持不变。然后使用一个虫子图片的第二个纹理会与像素颜色渲染缓存相混合。每当虫子纹理的纹素是透明的时候,在帧缓存内的已存的颜色就保持不变。最终的渲染结果为虫子纹理在树叶纹理之上,树叶纹 理在黑色背景之上。

图 3-7

注意

在图3-7中显示的层效果揭示了iOs Core Ani-mation技术的基本原理。每个Core Animation层使用一个对应的OpenGL ES的像素颜色渲染缓存来保存像素颜色数据。每个层的像素颜色数据作为一个OpenGL ES纹理缓存,并且纹理缓存会使用glBlendFunc(GL_SRC_ALPHA, GLONE_MINUS_SRC_ALPHA)函数来与后帧缓存混合在一起。


例子OpenGLES_Ch3_4 在例子OpenGLES_Ch3_3中的“-viewDidLoad” 方法的实现中添加了下面的粗体代码。该代码会加载第二个纹理并开启与像素颜色渲染缓存的混合。


// setup texture0
CGImageRef imageRef0 =
[[UIImage imageNamed:@"leaves.gif"I CGImage];

self.textureInfo0 = [GLKTextureLoader
        textureWithCGImage:imageRefo
                    options:[NSDictionary dictionaryWithobjectsAndKeys:
        [NSNumber numberWithBool :YES ],
        GLKTextureLoaderoriginBottomLeft, ni1]
        error :NULL];


    // setup texturel
    CGImageRef imageRef1 =
    [[UIImage imageNamed:@"beet1e.png"] CGImage];
    self.textureInfol = [GLKTextureLoader
    textureWithCGImage: imageRefl
    options: [NSDictionary dictionaryWithobjectsAndKeys:
    [NSNumber numberWithBool:YES] ,
    GLKTextureLoaderoriginBottomLeft, nil]
    error :NULL] ;

    //Enable fragment blending with Frame Buffer contents
    glEnable (GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

相比上一个例子的另一个小变化是在从图像加载纹理时应用了一个NSDictionary对象来设定选项。在这个例子中,GLKTextureLoaderOriginBottomLeft键与布尔值YES 搭配是为了命令GLKit的GLKTextureLoader类垂直翻转图像数据。这个翻转可以抵消图像的原点与OpenGL ES标准原点之间的差异。


[ NSDictionary dictionaryWithobjectsAndKeys:
[ NSNumber numberWithBool:YES ],GLKTextureLoaderoriginBottomLeft, nil];

例子OpenGLES_Ch3_4把相同的几何图形渲染了两次:第一次使用了一个纹理,第二次使用了另一个。混合发生在每次被一个纹理着色的一个片元与在像素颜色渲染缓存 中已存的像素颜色混合的时候。下 面来自OpenGLES_Ch3_4 的“-glkView:drawInRect: ”实现的摘录显示了相关的代码:


self.baseEffect.texture2d0.name = self.textureInfo0.name;
self.baseEffect.texture2d0.target = self.textureInfo0.target;
[self .baseEffect prepareToDraw];


// Draw triangles using the first three vertices in the
// currently bound vertex buffer
[self.vertexBuffer drawArrayWithMode:GL_TRIANGLES startVertexIndex:0 numberOfVertices:6];

self.baseEffect.texture2d0.name = self.textureInfol.name;
self.baseEffect.texture2d0.target = self.textureInfo1.target;
[self.baseEffect prepareToDraw];


// Draw triangles using the first three vertices in the
// currently bound vertex buffer
[ self.vertexBuffer drawArrayWithMode:GL_TRIANGLES
startVertexIndex:0
numberOfVertices:6];

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

3.5.2 示例OpenGLES_Ch3_5中的多重纹理

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

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

GLint iUnits;
g1GetIntegerv(GL_ MAX_ TEXTURE UNITS,&iUnits);

例子OpenGLES_Ch3_5产生的输出与OpenGLES_Ch3_4相同,除了树叶和虫子纹理被混合在了一个通道中,而且与像素颜色渲染缓存的内容混合来产生结果片元颜色的过程只会在每次显示更新中发生一次。

多重纹理引人了另一个组合组配置选项。为了帮助降低复杂性,iOS5中的GLKit的GLKEffectPropertyTexture类操作了3种常见的多重纹理模式:GLKTextureEnvModeReplaceGLKTextureEnvMode Modulate,以及GLKTextureEnvModeDecalGLKEffectPropertyTexture默认使用GLKTextureEnvModeModulate模式,这种模式几乎总是产生最好的结果。GLKTextureEnvModeModulate模式会让所有的为灯光和其他效果计算出来的颜色与从一个纹理取样的颜色相混合。

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

self.baseEffect.texture2d1.envMode = GLKTextureEnvModeDecal;

需要为所有的纹理单元的使用提供纹理坐标。多重纹理支持为每个纹理使用不同的纹理坐标。在本书例子中SceneVertex数据类型可以被扩展来保存每个顶点的纹理坐标的多重集合,但是OpenGLES_Ch3_5配置OpenGL ES 2.0为两个纹理使用相同的纹理坐标。下面的来自例子OpenGLES_Ch3_5的“-glkView:drawInRect:” 方法的完整实现显示了具体步骤。粗体的代码突出了每个纹理单元的纹理坐标的设定:

- (void)glkView: (GLKView * )view drawInRect: (CGRect)rect {

    // Clear back frame buffer (erase previous drawing)
    [(AGLKContext * )view.context clear:GL_COLOR_BUFFER_BIT];

    [self.vertexBuffer prepareToDrawWithAttrib:GLKVertexAttribPosition
    numberOfCoordinates:3
    attriboffset:offsetof(SceneVertex, positionCoords)
    shouldEnable:YES];

    [self.vertexBuffer prepareToDrawWithAttrib:GLKVertexAttribTexCoord0
    numberOfCoordinates:2
    attriboffset:offsetof (SceneVertex, textureCoords)
    shouldEnable:YES];

    [self.vertexBuffer prepareToDrawWithAttrib:GLKVertexAttribTexCoordl
    numberOfCoordinates:2
    attriboffset :offsetof (SceneVertex, textureCoords)
    shouldEnable:YES ];

    [self.baseBffect prepareToDraw];
    // Draw triangles using the first three vertices in the
    // currently bound vertex buffer
    [self.vertexBuffer drawArrayWithMode:GL _TRIANGLES
    startVertexIndex:0
    numberOfVertices:6];
}

3.5.3在OpenGLES_Ch3_6 示例中自定义纹理

多重纹理的强大和灵活性在使用自定义的利用OpenGLESShadingLanguage的OpenGL ES 2.0片元程序时会变得更加明显。一个额外的例子,在OpenGLES_Ch3_6 中,首先用一个由GLKit的GLKBaseEffect在后台自动生成的Shading Language程序绘制一个立方体,然后使用下面的自定义顶点和片元的Shading Language程序绘制第二 个立方体。现在不用担心没有学过Shading Language,但是如果你很好奇,可以摆弄一下OpenGLES_Ch3_6,看看它是怎么工作的。

//
// Shader . vsh
// TestShaders

attribute vec4 aPosition;
attribute vec3 aNormal;
attribute vec2 aTextureCoordo;
attribute vec2 aTextureCoord1;
varying lowp vec4 vColor;
varying 1owp vec2 vTextureCoordo;
varying lowp vec2 vTextureCoordl;
uniform mat4 uModelViewProjectionMatrix;
uniform mat3 uNormalMatrix;


void main() {
    vec3 eyeNormal = normalize(uNormalMatrix * aNormal);
    vec3 lightPosition = vec3(0.0, 0.0, 1.0);
    vec4 diffuseColor = vec4(0.7, 0.7, 0.7, 1.0);
    float nDotVP = max(0.0, dot(eyeNormal, normalize(lightPosition)));
    vColor = vec4( (diffuseColor * nDotVP).xyz, diffuseColor.a);
    vTextureCoord0 = aTextureCoord0.st;
    vTextureCoord1 = aTextureCoord1.st;
    g1_ Position = uModelViewProjectionMatrix * aPosition;

}

一个片元着色器是一个由GPU执行的,用来完成计算当前渲染缓存中的每个片元的最终颜色所需要的运算的简短程序。包含片元着色器程序的文件通常使用“.fsh"文 件扩展名。


// Shader . fsh
// TestShaders

uniform sampler2D uSamp1er0;
uniform sampler2D uSampler1;
varying lowp vec4 vColor;
varying lowp vec2 vTextureCoord0;
varying lowp vec2 vTextureCoordl;

void main() {
    // first texture Modulate, second texture Decal
    lowp vec4 color0 = texture2D( uSampler0, vTextureCoord0);
    lowp vec4 color1 = texture2D(uSampler1, vTextureCoord1);
    g1_ FragColor = mix(color0, color1, color1.a)  * vColor;
}

相比其他的纹理混合配置方法,GLShadingLanguage程序往往是既简短又更加自文档化的。但是,OpenGLESShadingLanguage是一个复杂到值得用整本书来论述的主题,例如OpenGL Shading Language (第3版),由Randi J. Rost、Bill Licea-Kane、Dan Ginsburg、John M. Kessenich、Barthold Lichtenbelt、Hugh Malan以及Mike Weiblen所著。本书会逐步地讲解一些概念同时在几个章节中做例子演示,但是本书不是一本完整的参考书。

results matching ""

    No results matching ""