OpenGL第7~8章
裁剪,光栅化和隐藏面消除
光栅化是将对象处理成片元的过程。
必须完成的两件事;第一,使每个集合对象经过图形系统
第二,给颜色缓存中要显示的每个像素附一个颜色值。
图形绘制系统的4个主要任务
建模—>几何处理à光栅化à片元处理à帧缓存
建模:得到一组表示几何对象的顶点数据。
几何处理:作用于顶点数据,确定哪些几何对象会在屏幕上显示,并把灰度值和颜色值赋给对象的顶点。经过一系列的变换矩阵的变换处理
片元处理:只有光栅化之后的片元处理阶段才会用到纹理值,
第8课 合成技术:go over blending
这节课会先浏览一遍合成技术,同时会粗略的看看深度缓存(depth buffer)是怎样工作的。
深度缓存
从第二节课我们知道,当你告诉WebGL去画东西的时候,会有一个大概的过程,
1运行顶点渲染器来计算每个顶点的位置
2在顶点间进行线性插值,来告诉系统哪些片段需要被画。
3对每一个片元运行片段渲染器来计算它的颜色
4写入帧缓存
因此,帧缓存是最后需要被执行的。但是如果要画两个物体呢?例如,如果你画一个中心在(0,0,-5)的正方形和一个中心在(0,0,-10)的正方形,你不会想要画第二个正方形并把第一个覆盖掉,因为它在很远的地方并且应该被覆盖。
WebGL使用深度缓存来处理这种情况。当片元经过片元渲染器处理之后写入帧缓存中,同一般的RGBA颜色值一样,它也会存储一个与片元Z值有关的深度值。
为什么说“与Z值相关”呢?WebGL希望所有的Z值的范围都在0~1之内,0表示最近的(closese)1表示最远的(furthese away)。这些之前都被我们之前在drawScene函数内调用的perspective函数中的投影矩阵隐藏了。到目前你所需要知道的就是Z-buffer值越大,离的越远。这和我们平常的坐标刚好相反(越远Z坐标越小,是负数)。
那么这就是深度缓存了。现在,你可能还记得我们从第一节课就开始用来初始化WebGL上下文的一段代码:
gl.enable(gl.DEPTH_TEST);
这是在告诉系统在把片元写入帧缓存时要做些什么。也就是“注意下深度缓存”。他和另一系统设置the depth function 一起联合使用。它通常有一个默认值,但如果我们要设置它的值的话可以这样设置:
gl.depthFunc(gl.less);
这意味着,“如果我们的片元有一个比当前Z值还要小的Z值,那么就使用新的这个片元,抛弃当前片元。”这些设置及使用它的这些代码就能够为我们提供足够多的behavior了。
合成
合成可以简单的认为是上述过程的另一个选择。我们使用depth function 和depth-testing来决定是否要用新片元替换当前的片元。而如果我们使用合成方法时,我们会用一个合成函数(blending function)来组合当前片元和新片元的颜色并产生一个新的片元,之后把这个新片元写入缓存。
现在我们来看看这段代码。这些代码和上一节课几乎完全相同,所有重要的部分都是在drawScene函数的短的片段里面。首先,我们检查合成复选框是否被选择。
var blending=document.getElementById(“blending”).checked;
如果是,就设置一个用来组合两个片元的函数:
if(blending){
gl.blendFunc(gl.SRC_ALPHA,gl.ONE);
这个函数的参数决定怎样来合成。这需要技巧,但不会很难。第一步,我们先决定两个要求:片元的来源是我们正在画的这个,目标片元是已经在帧缓存。gl.blendFunc的第一个参数决定源因子,第二个决定了目标因子。这些因子是在合成函数中使用的数字。这样,我们就可以说源因子是源片元的alpha值,而目标因子是一个为1的常量。当然也有其他可能,例如,如果你使用SRC_COLOR来确定源的颜色,则源因子的RGBA值就和初始的RGBA组件一样。%>_<%-_-!-_-|||=_=-_-#
假设WebGL试图计算一个片元的颜色(Rd, Gd, Bd, Ad),新来的片元的RGBA值为(Rd, Gd, Bd, Ad);源因子的RGBAz值是(Sr, Sg, Sb, Sa),目标因子的RGBA是(Dr, Dg, Db, Da)。
对于每一个颜色组件,系统将会进行下面这些运算:
Rresult = Rs * Sr + Rd * Dr
Gresult = Gs * Sg + Gd * Dg
Bresult = Bs * Sb + Bd * Db
Aresult = As * Sa + Ad * Da
所以,在这种情况下,我们可以得到(只给出红色组件):
Rresult = Rs * As + Rd
这不是获得透明对象的一个好方法,但是恰好能很好的工作在lighting 的情况下。这一点很值得强调。合成和透明是不同的,他只是一个能够获得透明效果的许多方法中的一个。
好的,现在继续:
gl.enable(gl.BLEND);
如同WebGL中的许多东西一样,合成默认是关掉的,所以我们需要先打开。
gl.disable(gl.DEPTH_TEST);
这个就更有趣了,我们必须先关掉深度测试。不关掉会怎样呢?举个例子,假如我们画一个立方体,如果我们先画背面然后再画正面,这样正面会被合成在背面上来,这就没有问题,但如果我们先画正面,背面由于离我们更远就会在合成之前由于深度测试而被无视掉。
读者们可能会注意到合成对画图的顺序有很大倚赖,这在之前是我们未曾注意的。这个等会儿再说,我们先完成剩下的这些代码:
gl.uniform1f(shaderProgram.alphaUniform, parseFloat(document.getElementById("alpha").value));
在这里我们从JS中获得一个alpha值,并将其导入渲染器中。这是因为我们使用的纹理没有它自己的alpha通道(它只有RGB,所以每一个像素的alpha值都是1)。
drawScene中剩下的就只是一些关掉合成后所要处理的必要代码。
} else {
gl.disable(gl.BLEND);
gl.enable(gl.DEPTH_TEST);
}
片元渲染器代码中也有一些小的修改以便于使用alpha值。
#ifdef GL_ES
precision highp float;
#endif
varying vec2 vTextureCoord;
varying vec3 vLightWeighting;
uniform float uAlpha;
uniform sampler2D uSampler;
void main(void) {
vec4 textureColor = texture2D(uSampler, vec2(vTextureCoord.s, vTextureCoord.t));
gl_FragColor = vec4(textureColor.rgb * vLightWeighting, textureColor.a * uAlpha);
}
以上就是代码中的所以改变了。
现在回头来看下画图顺序。从这个例子里面我们能得到很好的透明效果,看起来就像真的教堂玻璃一样。现在重新试一下,改变光照方向使方向从Z轴正方向过来——只需要把“-”去掉就行了,看起来也不错,但是真实感完全没有 了。
原因在于在初始光照条件下,立方体背面总是模糊的,也就是说背面的RGB值很小,所以等式:
Rresult = Rs * Ra + Rd
的计算结果就不是那么强烈。也就是说,我们刚好获得了使背面可见性更低的光照。如果我们把光照转到正面,正面的可见性就降低,透明效果就没那么好了。
那么怎样解决这个问题呢?OpenGL FAQ(常见问题)说你需要用一个SRC_ALPHA的源因子,和一个ONE_MINUS_SRC_ALPHA的目标因子。但是由于源片元和目标片元的处理方式的不同,对画图顺序还是会有依赖。
所以用合成来处理透明度是需要技巧的,但如果你可以有效控制场景,就像这节课里控制光源方向,你就可以适当地使用这个功能,当然你需要注意画图顺序。
幸运的是,合成在其他方面还是很有用的,你将会在下节课中看到。