翻译:李现民

最后修改:2012-07-03

原文:Depth sorting alpha blended objects

先说个题外话,本来我想回答在 Creators Club论坛上的一个常见问题,但(意外的是)我竟然没能从网上找到一个令人满意的答案。

问题本身很简单,但答案却有些复杂:

“为什么我的透明物体的绘制顺序是错误的,或者为什么它们的一部分不见了?”

当绘制一个3D场景的时候,将图形按深度排序非常重要,只有这样靠近摄像机的物体才能被绘制在(离摄像机)更远的物体的上面。我们不会希望远方的山脉被绘制在近在眼前的建筑物的上面!

当前得到广泛应用的深度排序技术有三种:

  1. 深度缓冲(又叫 z-buffering)

  2. 画家算法

  3. 背面剔除

不幸的是,每种技术都有它的局限性。为了获得好的绘制结果,大多数游戏需要依赖于以上三种技术的组合。

1 深度缓冲

深度缓冲是一种简单而有效的办法,并且当你只绘制不透明物体时,其绘制结果非常完美,但该方法无法处理透明的物体!

这是因为深度缓冲算法仅仅跟踪记录了到目前为止所绘制的最近像素点,对不透明物体而言这足够了。举例说来,如果我们需要绘制两个三角形,A和B:

如果我们按照先B后A的顺序绘制,则深度缓冲会发现来自于A的新像素点比之前绘制的来自于B的像素点要近,因此直接覆盖绘制就可以了。如果我们按照相反的
顺序绘制(先A后B),则深度缓冲会发现来自于B的像素点比已经绘制的来自于于A的像素点要远,因此将会直接丢弃它们。无论哪种情况下我们都会得到正确的
结果:A在上面,而B在后面被隐藏。

但如果物体(几何体)是透明的怎么办?也就是物体B部分可以见时(透过物体A的半透明三角面片)。如果我们按照先B后A的顺序绘制,仍然会得到正确的结
果,但反之就会出错了。在第二种情况下,深度缓冲会首先从B得到一个像素点,然后发现已经绘制了某个来自于A的更近的像素点,但却不知道如何处理这种情
况。它仅有两种选择是:绘制B上的像素点(结果将是错误的,因为这会将更远处的B混合到更近的A之上,但alpha
blending的顺序是不可交换的)或者直接将B整个丢弃。这很不好!

结论:深度缓冲对不透明物体是完美的,但对透明物体却没什么用。

2 画家算法

既然深度缓冲算法无法以错误的顺序正确绘制透明物体,那么一定存在一个简单的修正办法,对吧?只要我们总是保证以正确的顺序绘制就可以了!我们首先将场景
中的所有物体排序,这样我们就可以先绘制远处的物体,然后在其上绘制更近一些的物体,这样就可以保证前面示例中的B物体总是在A物体之前绘制。

不幸的是,这说起来容易做起来难。在很多情况下将对象排序是不够的。例如,A和B相互交叉的情况该怎么办?

这种情况很可能发生:比如说A是一个玻璃杯而B是一个放在里面的玻璃珠。现在根本无法以正确的方式对它们进行排序,因为A的一部分比B更近,但另一部分却更远。

我们甚至不需要使用两个单独的物体重现这个问题。组成玻璃杯的那些三角面片怎么处理呢?为了使结果看起来是正确的,我们需要在绘制玻璃杯的正面之前先绘制其背面。因此仅仅将物体进行排序还不够:我们真正需要的是排序每一个三角面片。

困难在于,将每个三角面片都排序的代价是极其昂贵的!而且即使我们可以承受这种代价,这也不足以保证在所有情况下都可以得到正确的绘制结果。比如两个透明的三角形相互交叉的情况如何处理?

没有办法对这些三角形排序,因为我们需要将B的上半部分绘制在A的前面,同时将其下半部分绘制在A的后面。唯一的解决办法是在检测这种情况发生时将这些三角形在它们相交的地方拆分,但这种做法的代价过于高昂了。

结论:画家算法需要你对排序的粒度作出权衡。如果仅仅对少量大型物体进行排序,则算法会非常快但精确度不高;反之,如果对大量小型物体进行排序(极限情况是对三角面片排序),则算法会很慢但会更加精确。

3 背面剔除

人们通常不认为背面剔除是一种排序技术,但事实上它是的确是一种重要的(排序)方法。它的局限性在于仅仅适用于凸面体。

考虑一个简单的凸面体,比如一个球体或一个立方体。无论你从哪个角度观察它,每一个屏幕像素都会被精确的覆盖两次:一次被物体的前面覆盖,另一次是被它的
背面覆盖。如果使用背面剔除丢弃物体背面的三角面片,那么就只剩下前面的了。哈哈!如果每一个屏幕像素只被覆盖一次,那你自动就会获得完美的alpha
blending结果,而不需要任何排序。

当然,大多数游戏不会仅仅绘制球体或立方体:),所以背面剔除本身并不是一个完整的解决方案。

结论:背面剔除对凸面体是完美的,但对于其它的就无能为力了。

4 我该如何让游戏看起来更好一些?

最常用的方法是:

  1. 设置 DepthBufferEnable 与 DepthBufferWriteEnable 为 true

  2. 绘制所有的不透明物体(几何体)

  3. 保持 DepthBufferEnable=true,但修改 DepthBufferWriteEnable=false

  4. 将物体按它与摄像机之间的距离进行排序,然后以从后向前的顺序绘制

这种方法依赖于前述三种排序技术的组合:

  • 不透明物体使用深度缓冲排序

  • 透明物体与不透明物体仍然会被深度缓冲处理(所以你永远不会透过一个不透明物体看到一个透明物体)

  • 画家算法按物体的相对关系对透明物体排序(如果两个透明物体相交的话会引起排序错误)

  • 依赖背面剔除对单个透明物体上的所有三角面片进行排序(如果透明物体不是凸面体则会引起排序错误)

结果并非完美,但却非常有效并易于实现,而且对大多数游戏而言已经足够好了。

有很多方法可以用于改进排序的精确度:

避免alpha blending!
的不透明物体越多,排序就越容易,也越精确。你真的需要在每个地方都使用alpha
blending嘛?如果你的关卡设计需要在玻璃窗上再加一层,那么是否可以考虑修改设计以便实现起来更加容易呢?如果你正在使用alpha
blending实现诸如树木之类的裁剪(cut-out)图形,是否可以考虑使用alpha
test替代?就是简单地考虑接受/拒绝两种情况,这样被接受的像素点由于是不透明的,因而仍然可以使用深度缓冲排序。

放松,不要紧张。也许排序错误实际上并不那么糟糕呢?也许你可以试着调整显卡(使alpha通道更加柔和,更加半透明化一些)使排序错误看起来并不那么明显。在我们的3D粒子采样中就使用了这种方法,我们并没有尝试对单个烟雾中的粒子排序,而是挑选了一个粒子纹理使它看起来是OK的。如果你将烟雾纹理换成更加不透明的,那么排序错误就会变得比较明显了。

如果你的alpha混合模型不是凸面体,也许你可以试着将它们改的更加“凸”一些呢?即使不是完美的凸面体,只要它们越接近凸面体,排序错误就会越少。考虑将复杂的模型拆分成可独立排序的多个部件。比如一个人体模型无论如何都不是凸面体,但如果你把它拆分成躯干、头、手臂等,那么每一部分都可以近似认为是凸面体了。

如果你的纹理遮罩(texture masks)基本上是用于开/关裁剪(cut-outs)的,只是边缘部分有一些透明的像素用于反走样,你可以使用双pass绘制技术:

  • Pass 1:绘制不透明部分:关闭alpha blending,并且alpha test只接受100%不透明的区域,深度缓冲开启(补充:深度写入开启)

  • Pass 2:绘制边缘部分:开启alpha blending,并且alpha test只接受alpha < 1的像素,深度缓冲开启,深度写入关闭

以将物体绘制两次为代价,这种方法为纹理中间不透明部分提供了100%正确的深度缓冲排序,以及相对精确的半透明边缘部分排序。这是一种很好的方法,既对纹理裁剪的边缘部分做了一些反走样,同时也利用了深度缓冲的优点避免了对单个树木或草叶进行额外的排序。我们在广告牌采样中使用了这种技术:请参考Billboard.fx中的注释与effect passes部分。

使用z
prepass。当你需要淡出一个正常状态下不透明的物体而又不想透过它自己的近端部分看到它的远端部分时,这是一种非常好的技术。假如从右边观察一个人
体。如果它是玻璃做的,那么你会期望透过它的右手臂看到躯干和左手臂。但是如果在整个淡出过程中它是一个实体人的话(不透明,也许是幽灵,或者正在传送,
又或者被杀死后正在重生),你会期望只看到透明的右手臂部分,以及它后面的背景,而不会同时看到躯干与左手臂。要达到这种效果需要:

  • 设置 ColorWriteChannels=None,并启用深度缓冲

  • 绘制物体到深度缓冲(但不影响颜色缓冲)

  • 设置 ColorWriteChannels=All, DepthBufferFunction=Equal,并启用alpha blending

  • 重绘物体,这时只有物体的最近端才会被混合到颜色缓冲中

深度排序与alpha混合 【转】的更多相关文章

  1. 深度排序与alpha混合

    原文: https://blogs.msdn.microsoft.com/shawnhar/2009/02/18/depth-sorting-alpha-blended-objects/ 翻译:李现民 ...

  2. 【转载】Alpha混合物体的深度排序

    原文:Alpha混合物体的深度排序 先说个题外话, 本来我想解答一下最近Creators Club论坛上经常出现的一个问题, 意外的是在网上竟然找不到什么全面的答案.. 这是个有着复杂答案的简单问题: ...

  3. Unity3D ShaderLab 修改渲染队列进行深度排序

    Unity3D ShaderLab 修改渲染队列进行深度排序 为了更深刻的理解透明度,我们还需要学习一下深度排序,简单来说就是物体被渲染的先后顺序. Unity允许我们通过代码来控制某个特定物体渲染到 ...

  4. D3D中深度测试和Alpha混合的关系

    我在学习D3D的深度测试和Alpha混合的时候,有一些遗憾.书上提供的例子里说一定要先渲染不透明物体,再渲染透明物体,对渲染状态的设置也有特殊要求.我看的很晕.自己查图形学的书,上网找资料,结果还是糊 ...

  5. 【Unity Shaders】Transparency —— 使用渲染队列进行深度排序

    本系列主要参考<Unity Shaders and Effects Cookbook>一书(感谢原书作者),同时会加上一点个人理解或拓展. 这里是本书所有的插图.这里是本书所需的代码和资源 ...

  6. 【转载】D3D深度测试和Alpha混合

    原文:D3D深度测试和Alpha混合 1.       深度测试 a)         深度缓冲区:屏幕上每个像素点的深度信息的一块内存缓冲区.D3D通过比较当前绘制的像素点的深度和对应深度缓冲区的点 ...

  7. 《逐梦旅程 WINDOWS游戏编程之从零开始》笔记8——载入三维模型&Alpha混合技术&深度测试与Z缓存

    第17章 三维游戏模型的载入 主要是如何从3ds max中导出.X文件,以及如何从X文件加载三维模型到DirextX游戏程序里.因为复杂的3D物体,要用代码去实现,那太反人类了,所以我们需要一些建模软 ...

  8. 【STM32H7教程】第56章 STM32H7的DMA2D应用之刷色块,位图和Alpha混合

    完整教程下载地址:http://www.armbbs.cn/forum.php?mod=viewthread&tid=86980 第56章       STM32H7的DMA2D应用之刷色块, ...

  9. DirectDraw打造极速图形引擎(Alpha混合)

    显然DirectDraw是Windows下写2D图形程序的最好选择,虽然Direct3D也可以写,但是没DirectDraw简单方便,特别对于初学者,一来就接触那么多函数和参数总不是件愉快的事,所以我 ...

随机推荐

  1. 异常 ndroid.view.InflateException: Binary XML file line #8: Error inflating class com.ouyang.test.MyView

    发现自定义view时出现ndroid.view.InflateException: Binary XML file line #8: Error inflating class com.ouyang. ...

  2. Django one

    WEB-Django: Http协议: http协议:超文本传输协议,基于TCP/IP通信协议来传递数据 特点: 1.灵活:允许传输任意类型的数据对象.正在传输的类型有Content-Type标记 2 ...

  3. windows :Tomcat免安装版环境变量配置 + jdk配置

    1.  下载后解压,我解压的目录为:D:\Tomcat\apache-tomcat-9.0.1-windows-x64 2.  安装jdk和jre, 并配置环境变量: 2.1 用户变量新建JAVA_H ...

  4. 【转】javascript操作Select标记中options集合

    先来看看options集合的这几个方法:options.add(option)方法向集合里添加一项option对象:options.remove(index)方法移除options集合中的指定项:op ...

  5. 九度oj 题目1368:二叉树中和为某一值的路径

    题目描述: 输入一颗二叉树和一个整数,打印出二叉树中结点值的和为输入整数的所有路径.路径定义为从树的根结点开始往下一直到叶结点所经过的结点形成一条路径. 输入: 每个测试案例包括n+1行: 第一行为2 ...

  6. POJ 1952

    BUY LOW, BUY LOWER Time Limit: 1000MS   Memory Limit: 30000K Total Submissions: 7748   Accepted: 267 ...

  7. HDU——1058Humble Numbers(找规律)

    Humble Numbers Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others) T ...

  8. bzoj[Usaco2008 Nov]mixup2 混乱的奶牛 状压dp

    [Usaco2008 Nov]mixup2 混乱的奶牛 Time Limit: 10 Sec  Memory Limit: 162 MBSubmit: 1204  Solved: 698[Submit ...

  9. 2-sat 问题 【例题 Flags(2-sat+线段树优化建图)】

    序: 模拟赛考了一道 2-sat 问题.之前从来没听过…… 考完才发现其实这个东东只要一个小小的 tarjan 求强连通分量就搞定了. 这个方法真是巧妙啊,拿来讲讲. What is it? [・_・ ...

  10. 标准C程序设计七---70

    Linux应用             编程深入            语言编程 标准C程序设计七---经典C11程序设计    以下内容为阅读:    <标准C程序设计>(第7版) 作者 ...