深度排序与alpha混合

原文:

https://blogs.msdn.microsoft.com/shawnhar/2009/02/18/depth-sorting-alpha-blended-objects/

翻译:李现民

最后修改:2012-07-03

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

当绘制一个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
  • 重绘物体,这时只有物体的最近端才会被混合到颜色缓冲中
时间: 2024-10-10 21:00:08

深度排序与alpha混合的相关文章

深度排序与alpha混合 【转】

  翻译:李现民 最后修改:2012-07-03 原文:Depth sorting alpha blended objects 先说个题外话,本来我想回答在 Creators Club论坛上的一个常见问题,但(意外的是)我竟然没能从网上找到一个令人满意的答案. 问题本身很简单,但答案却有些复杂: “为什么我的透明物体的绘制顺序是错误的,或者为什么它们的一部分不见了?” 当绘制一个3D场景的时候,将图形按深度排序非常重要,只有这样靠近摄像机的物体才能被绘制在(离摄像机)更远的物体的上面.我们不会希

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

原文:Alpha混合物体的深度排序 先说个题外话, 本来我想解答一下最近Creators Club论坛上经常出现的一个问题, 意外的是在网上竟然找不到什么全面的答案.. 这是个有着复杂答案的简单问题: “为什么我的透明物体的绘制顺序不对, 或者有些不见了?” 当绘制一个3D场景时, 对图形进行深度排序是非常重要的, 这样离镜头近才画在远处物体的前面. 我们不会希望看到远处的山把近在眼前的建筑给挡住了! 如今有三种深度排序方法得到了广泛的应用: 深度缓冲 (也叫做 z-buffering) 油画家

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

原文:D3D深度测试和Alpha混合 1.       深度测试 a)         深度缓冲区:屏幕上每个像素点的深度信息的一块内存缓冲区.D3D通过比较当前绘制的像素点的深度和对应深度缓冲区的点的深度值来决定是否绘制当前像素. b)        D3DPRESENT_PARAMETERS. AutoDepthStencilFormat = D3DFMT_D16 表示深度值由16位二进制表示 开启深度测试:pDevice->SetRenderState( D3DRS_ZENABLE, TR

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

为了更深刻的理解透明度,我们还需要学习一下深度排序,简单来说就是物体被渲染的先后顺序. Unity允许我们通过代码来控制某个特定物体渲染到屏幕的顺序.这个做法类似于photoshop中图层的概念. 开始之前,准备工作还是新建Shader Material,准备测试场景.为了对比 是需要使用2个材质球的. 打开我们的shader,稍微编辑一下即可看到效果.过程简单如下: Shader "91YGame/DepthSort" { Properties { _MainTex ("B

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

第17章 三维游戏模型的载入 主要是如何从3ds max中导出.X文件,以及如何从X文件加载三维模型到DirextX游戏程序里.因为复杂的3D物体,要用代码去实现,那太反人类了,所以我们需要一些建模软件. 对于3ds max,要到出.X文件,要装个Panda插件.然后就是作者推荐的一个3D模型资源网站:http://www.cgmodel.com/. 网格模型接口ID3DXMesh 这个接口表示网格,继承自ID3DXBaseMesh.ID3DXMesh接口中的D3DXCreateMesh()可用

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

本系列主要参考<Unity Shaders and Effects Cookbook>一书(感谢原书作者),同时会加上一点个人理解或拓展. 这里是本书所有的插图.这里是本书所需的代码和资源(当然你也可以从官网下载). ========================================== 分割线 ========================================== 写在前面 为了让我们真正明白透明度,我们需要了解一下深度排序,或者说,对象的绘制顺序.Unity允许

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

显然DirectDraw是Windows下写2D图形程序的最好选择,虽然Direct3D也可以写,但是没DirectDraw简单方便,特别对于初学者,一来就接触那么多函数和参数总不是件愉快的事,所以我的文章主要结合我做的工作,谈谈DirectDraw编程中一些比较关键的技术,大多是我自己想出来的.我想先声明,我的文章可以任意转载,源代码可以任意使用和修改. 由于我是业余时间写的文章,所以只能每次发表一篇,希望我的工作可以为大家的游戏增光添彩,同时我的文章主要面向有基本C++,DirectDraw

Unity3D教程宝典之Shader篇:第十三讲 Alpha混合

转载自风宇冲Unity3D教程学院 Alpha Blending,中文译作Alpha混合 Blending就是控制透明的.处于光栅化的最后阶段. 这里例如我们给一个模型贴一个材质,那么在某个点计算出来颜色值称为源,而该点之前累积的颜色值,叫目标. 语法 Blend Off     不混合 Blend SrcFactor DstFactor  SrcFactor是源系数,DstFactor是目标系数 最终颜色 = (Shader计算出的点颜色值 * 源系数)+(点累积颜色 * 目标系数) 属性(往

3D软引擎之深度排序

花了不少时间去实现了这个功能,大多问题都出现在低级错误,看来以后要提醒十二分精神!错误的原因是: <span style="color: rgb(255, 255, 255); font-family: Arial; font-size: 14px; line-height: 22px;"> </span>void CTriangle2DUtils::DrawSolidGeneralClipZOrder( D3DXVECTOR3 p0, D3DXVECTOR3