查看: 2955|回复: 0
打印 上一主题 下一主题

[WEBGL] WebGL教程9:深度缓存、透明度和混合 【转】

[复制链接]
.    

3797

主题

11

听众

5万

积分

首席设计师

Rank: 8Rank: 8

纳金币
32328
精华
41

活跃会员 优秀版主 荣誉管理 论坛元老

跳转到指定楼层
楼主
发表于 2012-8-27 11:28:46 |只看该作者 |倒序浏览
欢迎来到WebGL教程的第八节课,这节课的内容基于NeHe OpenGL教程的第8节改编的。在这节课上,我们将会介绍混合,并且稍微介绍一下这个相当有用的深度缓存是如何工作的。
    下面的视频就是我们这节课将会完成的最终效果。
[media=swf?VideoIDS=XMzA1MjQ2NDI4&embedid=NTkuNTYuMjI2Ljc4Ajc2MzExNjA3AgI%3D&wd=,640,480]http://static.youku.com/v1.0.0261/v/swf/loader.swf?VideoIDS=XMzA1MjQ2NDI4&embedid=NTkuNTYuMjI2Ljc4Ajc2MzExNjA3AgI%3D&wd=[/media]

    你将会看到一个半透明并且缓慢旋转的立方体,看上去是用有色玻璃制作的。你还可以像上节课中一样调节光照。
    你可以点击画布下边的复选框以开启或者关闭混合模式和透明效果。你还可以调节alpha 的参数(这个我们将在稍后解释),当然,还有光线明暗。
    下面我们来看看它是怎么工作的……
    惯例声明:本系列的教程是针对那些已经具备相应编程知识但没有实际3D图形经验的人的;目标是让学习者创建并运行代码,并且明白代码其中的含义,从而可以快速地创建自己的3D Web页面。如果你还没有阅读前一课,请先阅读前一课的内容吧。因为本课中我只会讲解那些与前一课中不同的新知识。
    另外,我编写这套教程是因为我在独立学习WebGL,所以教程中可能(非常可能)会有错误,所以还请风险自担。尽管如此,我还是会不断的修正bug和改正其中的错误的,所以如果你发现了教程中的错误,请告诉我。
    有两种方法可以获得上面实例的代码:在实例的独立页面中选择“查看源代码”;或者点击这里下载我们为您准备好的压缩包。
    深度缓存(Depth Buffer)
    当你命令WebGL绘制物体的时候,必须要经过某些必要的步骤。按等级高低排序:
    • 在所有的顶点上运行顶点着色器以绘制出物体所在的位置。
    • 线性地在顶点之间进行插值运算,这样做是告诉顶点哪些片元(这个时候,你可以把片元当做像素对待)需要上色。
    • 对于每个片元来说,运行片元着色器以绘制出它的颜色。
    • 把它写入帧缓冲。
    最终,帧缓冲就是屏幕上显示出来的内容。但是当你需要绘制两个物体时,又会是怎样的呢?比如,当你需要绘制两个大小一样的正方形,一个中心位于(0,0,-5),另一个中心位于(0,0,-10),又会是怎样的呢?你肯定不希望第二个正方形覆盖在第一个正方形上,因为它距离更远,应当被挡住的。
    WebGL正是运用深度缓存来处理这样的情况的。当片元着色器处理完片元以及RGBA颜色值,并把它们写入帧缓冲时,也会将与Z值相关的深度值储存在里面,但是这个值并不完全与Z值相同。(这不奇怪,因为深度缓存经常也被叫做Z缓存。)
    为什么我说“相关的”呢?是这样的,WebGL总是将所有的Z值按从0到1顺序排列,0为最近,1为最远。其实这些事情在drawScene函数一开始,当我们调用***并创建投影矩阵时,就背着我们发生了。现在,你所需要知道的就是,Z-buffer的值越大,物体距离就越远,这一点与我们通常的坐标系统是相反的。
    好了,这些就是深度缓存。你应该还记得我们在第一课中初始化WebGL的代码,有这样一行。
1     gl.enable(gl.DEPTH_TEST);



    这行代码是给WebGL系统一个说明,告诉系统在把一段新的片元写入帧缓冲时,系统需要做些什么,基本上的意思是“注意深度缓存”。它会和另一个WebGL设置深度函数一起协同发生作用。实际上这个函数默认拥有一个合适的值,如果我们将它设置为默认值,它会是这样的:
1     gl.depthFunc(gl.LESS);



    这个函数的意思是:如果片元的Z值小于当前值,将使用新的值,而不是原有的值。这个深度检测,和其他实现他的代码一起,会给我们一个合理的***行为——近处的物体会掩盖远处的物体。(你还可以尝试着将深度函数中的参数设为其他不同的值,尽管我想这些值并不太常用。)
    混合(Blending)
    混合是上述这一过程的另一个替代方案。通过深度检测,我们使用深度函数来判断是否用新的片元替换现有片元。当我们使用混合时,我们使用一个混合函数,把现有片元和新片元的颜色组合到一起,创建一个全新的片元,接着将它写入缓冲区内。
    让我们来看一下代码。大部分代码是和第七课中的代码相同的,最重要的部分几乎都在drawScene的一小段代码中。首先,我们先来看一下混合复选框是否被选取。
436         var blending = document.getElementById("blending").checked;



    如果被选取,我们将设置一个函数用来组合这两个片元。


437         if (blending) {

438            gl.blendFunc(gl.SRC_ALPHA, gl.ONE);



    这些参数定义了混合是如何完成的。这需要一定技巧,但是并不困难。首先,我们需要定义两个术语:源片元(source fragment)和目标片元(destination fragment)。其中源片元是我们正在绘制的片元,而目标片元是已经存在于帧缓冲之中的。gl.blendFunc函数中的第一个参数定义了source factor,而第二个参数,定义了,这些factor是在混合函数中使用的一些数字。在这节课的例子中,我们指定用源片元的alpha值作为source factor,而destination factor则是一个常量1。当然,也有其他可能性,如果将源色指定为SRC_COLOR,你最后得到的source factor会是单独的红、绿、蓝和alpha值,其中每个都与原始的RGBA的分量相等。
    现在,让我们来假设一下如果WebGL正在试图用计算出一个片元的颜色,这个片元有一个既存的目标片元和一个即将被加入的源片元,目标片元的RGBA值为 (Rd, Gd, Bd, Ad),源片元的值为(Rs, Gs, Bs, As)。
    另外,假设RGBA的source factor是(Sr, Sg, Sb, Sa),而destination factor是(Dr, Dg, Db, Da)。
    对于每个颜色分量,WebGL将会进行以下运算。
    Rresult = Rs * Sr + Rd * Dr

    Gresult = Gs * Sg + Gd * Dg

    Bresult = Bs * Sb + Bd * Db

    Aresult = As * Sa + Ad * Da
    为了简单起见,这里我们仅会对红色分量进行运算。
    Rresult = Rs * As + Rd
    通常来说,这并不是创建透明物体的最理想方法。但当开启光照时,它确是解决问题的一个好方法。还有一件需要强调的事情是,混合并不是透明!相对于其他技术,它仅仅是一种能够实现透明效果的技巧。我在学习Nehe时,我花了好大功夫才弄懂这些,所以,请原谅我在这里过于强调这一点。
    好啦,继续我们的课程。
439             gl.enable(gl.BLEND);



    这一段很简单,和其他许多WebGL中的特性一样,混合是默认关闭的。所以,在这里我们必须启用它。
440             gl.disable(gl.DEPTH_TEST);



    这里有一个很有趣的现象,那就是在开启混合的同时,我们必须要关闭深度检测。如果我们不这么做,混合效果将会出现在某些地方,但是在另一些地方却又不会出现。例如,当我们在绘制一个立方体时,假设我们正巧先绘制立方体的背面,这样,立方体的背面就会被写入帧缓冲之中;接着,当正面完成时,正面就会出现在背面之前,并带有混合效果,这也是我们需要的效果。但是,相反如果我们先绘制正面,然后绘制背面,这时背面将会在我们运行混合函数之前,在深度检测中被丢弃,显然这不是我们希望得到的效果。
    眼尖的读者应该会在上面的混合函数中注意到这一点,混合很依赖绘制物体的顺序,这一点是在我们前几节课中都没有遇到过的。我们将在后面详细介绍,让我们先来讲完最后一点代码。
441             gl.uniform1f(shaderProgram.alphaUniform, parseFloat(document.getElementById("alpha").value));



    现在,我们正从页面中的文本框里加载一个alpha值,并且将它传递给着色器。这是因为我们作为纹理使用的图像没有自带alpha通道(它只有RGB通道,所以每个像素来说alpha值都固定为1)。所以,alpha值最好是可以调节的,这样我们就能看出它是如何影响图像的。
    drawScene函数中的其余部分是用于当混合被关闭时,进行一般运算的。
442         } else {

443            gl.disable(gl.BLEND);

444            gl.enable(gl.DEPTH_TEST);

445        }



    在片元着色器中还有一些小的改动,那就是处理纹理时将使用到alpha值,参见第18行代码。
11

12    precision mediump float;

13

14

15    varying vec2 vTextureCoord;

16    varying vec3 vLightWeighting;

17

18    uniform float uAlpha;

19

20    uniform sampler2D uSampler;

21

22    void main(void) {

23        vec4 textureColor = texture2D(uSampler, vec2(vTextureCoord.s, vTextureCoord.t));

24        gl_FragColor = vec4(textureColor.rgb * vLightWeighting, textureColor.a * uAlpha);

25    }



    有变化的代码就这么多了!
    好了,我们现在来讲一讲绘制顺序。示例中的透明效果十分出色——看上去很像彩色玻璃吧。 但现在我们再来看看它,这次我们改变一下光线的方向,让光线从Z轴正方向射入,也就是把文本框中的“-”号都去掉。恩,看上去很棒,但是,那种可以乱真的彩色玻璃效果却消失了。
    为什么呢?这是因为在原来的光照处理中,立方体朝后的那一面,也就是背对光源的那一面一般都是昏暗的。也就是说那一面的R、G、B的值都十分小。所以,在进行运算时:
    Rresult = Rs * Ra + Rd
    那一面看上去就不会那么明显。换句话说,我们的光照处理让后侧可见度较低。如果我们改变光照效果,让前侧可见度较低的话,那我们的透明效果看起来就不那么好了。
    但是,我们要如何才能得到一个“合适”的透明效果呢?OpenGL FAQ告诉我们,需要使用SRC_ALPHA 作为source factor,用ONE_MINUS_SRC_ALPHA作为destination factor。但是还有一个问题,源片元和目标片元的运算方式不同,所以,我们将会十分依赖绘制物体的顺序。这也引出了我认为是OpenGL或WebGL中的一个不光彩的地方;好再让我们来看一下OpenGL的FAQ:
    当使用深度缓存时,你必须十分注意呈现图元的顺序。按照从后到前的顺序,完全不透明的图元应当首先被呈现,接着是部分透明的图元;如果你没有按照此顺序呈现图元,深度检测会屏蔽那些原本借助于透明图元才可以显示的图元。
    深度缓存只能保证最近的面。而透明物体,实际上需要的是保存多层面数据,才能重现正确的效果。所以需要先画不透明的物体,然后再画透明的物体;同时,透明物体需要先按由远到近排好顺序才行。 如果你先画了透明的,后画不透明的,显然是不对的。因为即使不透明的更近,也不能通过深度检测屏蔽掉不透明的,所以必须先画不透明的。透明的物体,也需要由远到近来画。这样一层层blend上去,才是正确的结果。如果先blend了近的,那再blend远的时候,实际结果是错的。
    如果在开启depth test的情况下,先画一个透明物体,那如果它后面有一个不透明物体,而且又是后画的话,depth test会使得后面的物体画不上去。
    大概就是这样了。使用混合来做出透明效果是十分有技巧、并且十分繁琐的,但是只要你能像控制光照一样,控制场景中的其他大部分对象,我们就能很容易地实现我们需要的正确效果。仅仅能够正确地绘制物体还不够,为了让物体看上去更好看,我们还必须用特定的顺序去绘制它们。
    值得一提的是,混合是一项非常有用的技术,还可以用来实现许多其他的效果,下一节课你将会看到。那么,这一节课所讲的内容就到此为止了。这节课上我们讲了深度缓存,还有如何通过混合来做出透明效果。
    如果你有任何问题、评论或者修正,都请告诉我!特别是这一课,当我第一次学习时,我感觉这是NeHe的教程中最难理解的一部分。
    下节课我们将改进代码结构,不再使用麻烦的全局变量,并实现多个不同物体在场景中的运动。
分享到: QQ好友和群QQ好友和群 腾讯微博腾讯微博 腾讯朋友腾讯朋友 微信微信
转播转播0 分享淘帖0 收藏收藏0 支持支持0 反对反对0
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

手机版|纳金网 ( 闽ICP备2021016425号-2/3

GMT+8, 2024-9-21 16:24 , Processed in 0.756601 second(s), 32 queries .

Powered by Discuz!-创意设计 X2.5

© 2008-2019 Narkii Inc.

回顶部