(转)Uniy半透明物体相互遮挡的渲染问题

转载地址 https://github.com/candycat1992/Unity_Shaders_Book/issues/153
书里的意思是,如果半透明物体开启了深度写入的话,就无法看到在该fragment处后面渲染的fragment,这个主要是为了在半透明物体互相交叉、无法得到正确渲染效果时,我们折中的一种方式。

举个例子,我们来创造一个半透明物体互相交叉的例子:

qq 20170822191617

这两个方块使用的Shader是一样的,但使用了两个各自的材质。为了让效果明显我故意把它们材质的透明度调成不同的值,摄像机靠后的方块透明度较低,靠前的较高。

在不采用其他方法的前提下,如果我们使用书里这种传统的透明混合的方式(指关闭深度写入进行混合)会得到下面的效果:

qq 20170822190453

实际上,这种效果是有问题的。错误的地方在于中间两个方块互相重叠的地方,这个混合效果有问题。我们可以在脑海里重现这个渲染过程:首先Unity会基于物体中心对两个方块进行排序,来实现半透明物体的从后往前渲染的目的。所以,Unity会先渲染方块B、再渲染方块A(参见第一张图里面两个方块在摄像机空间下的位置)。然而在重叠区域,正确的渲染顺序应该是先渲染方块A,因为在这部分区域内,它被方块B覆盖住了,它位于后面。出现错误的本质原因是因为我们的排序都是基于object level的,而不是基于pixel level的排序。

如果此时我们把深度打开,则会得到下面的效果:

qq 20170822190454

你可以一眼就看出来这里面的错误,那就是方块A的重叠部分别完全剔除掉了,这部分完全没有渲染!它被剔除掉的 原因就是因为Unity会先渲染方块B,由于方块B进行了深度写入,所以等到方块A渲染的时候,重叠部分A的像素根本无法通过depth test,会直接被剔除掉。

除了半透明物体之间的问题,尽管理论上来说,理想情况下半透明物体应该在所有不透明物体渲染完之后再渲染,但如果一个不透明物体因为某些原因在某个半透明物体后面被渲染了,也可能会因为半透明物体开启了深度写入而出现非常明显的渲染错误(不透明物体被半透明物体完全遮挡掉没有渲染)。

我觉得看到这里你应该知道我们为什么要关闭深度写入。总结一下的话,可以参考知乎上Milo Yip的回答

对于不透明(opaque)的 fragment,最后只需要看到最接近视点的 fragment。在实时渲染一般是通过深度缓冲来取得这个最近的 fragment。

但对于半透明的 fragment,每一个都会影响最终的结果,需要从远至近地渲染。由于深度缓冲不能用于排序多个 fragment,所以只能用于保存最接近视点的不透明 fragment 深度,远于该深度的透明 fragment 不用渲染,所以仍需要深度测试(depth test)。

由于需要手动排序半透明的物体,写入深度已没有意义。如果能完美地手工排序不透明 fragment,写入深度也没有影响;但若物体在深度方向有重叠,有些 fragment 的顺序会有误,如果开启深度写入,会令到渲染错误很明显,而关掉的话影响较小一点。

当然理想的话,应该用顺序无关透明(order independent transparency, OIT)技术,例如用逐像素链表存储多个 fragment,以像素为单位排序渲染。

简而言之,就是因为,传统的半透明混合总是会在物体交叉、渲染重叠的情况下,出现错误的混合效果。如果开启深度写入,会令到渲染错误很明显,而关掉的话影响较小一点。

回到你的图里,由于你的两个方块摆放规则不存在相互重叠,所以Unity对于两个物体的排序在pixel level也是正确的排序,即每个像素也都是满足从后往前渲染的,所以你不会发现问题。

66 人赞同了该回答

对于不透明(opaque)的 fragment,最后只需要看到最接近视点的 fragment。在实时渲染一般是通过深度缓冲来取得这个最近的 fragment。

但对于半透明的 fragment,每一个都会影响最终的结果,需要从远至近地渲染。由于深度缓冲不能用于排序多个 fragment,所以只能用于保存最接近视点的不透明 fragment 深度,远于该深度的透明 fragment 不用渲染,所以仍需要深度测试(depth test)。

由于需要手动排序半透明的物体,写入深度已没有意义。如果能完美地手工排序不透明 fragment,写入深度也没有影响;但若物体在深度方向有重叠,有些 fragment 的顺序会有误,如果开启深度写入,会令到渲染错误很明显,而关掉的话影响较小一点。

当然理想的话,应该用顺序无关透明(order independent transparency, OIT)技术,例如用逐像素链表存储多个 fragment,以像素为单位排序渲染。



11 人赞同了该回答

首先结论不对。对于半透明的渲染, 是可以同时开启深度测试和深度写入的。方法是使用2个Pass。一个先只负责保证像素级片元深度正确,一个负责渲染。

对于这种交叉物体的渲染,就可以采取这种方式。


深度测试的意义在于舍弃片元与否。

深度写入的意义在于深度测试的基础上,要不要覆盖深度缓冲,即重新设立深度测试的标准。

如果没有配合深度写入来保证像素级的深度关系。


就会出现右边的图形的情况。

回答你这个问题的关键词是“渲染顺序”。


GPU的渲染流程

深度测试的大概流程

混合的大概流程:


2份源码和一个模型你参考体会下:

pan.baidu.com/s/1i5onCp

冯乐乐的《UnityShader入门精要》里面第八章写的挺详细的,可以参考一下。

為什麼半透明模型的渲染要使用深度測試而關閉深度寫入?

找了好幾個說深度測試和深度寫入的區別的文章,看懂了只是混合顏色後把不把深度寫入緩存的區別。但是這和透明模型有什麼關係嗎?


對於不透明(opaque)的 fragment,最後只需要看到最接近視點的 fragment。在實時渲染一般是通過深度緩衝來取得這個最近的 fragment。

但對於半透明的 fragment,每一個都會影響最終的結果,需要從遠至近地渲染。由於深度緩衝不能用於排序多個 fragment,所以只能用於保存最接近視點的不透明 fragment 深度,遠於該深度的透明 fragment 不用渲染,所以仍需要深度測試(depth test)。

由於需要手動排序半透明的物體,寫入深度已沒有意義。如果能完美地手工排序不透明 fragment,寫入深度也沒有影響;但若物體在深度方向有重疊,有些 fragment 的順序會有誤,如果開啟深度寫入,會令到渲染錯誤很明顯,而關掉的話影響較小一點。

當然理想的話,應該用順序無關透明(order independent transparency, OIT)技術,例如用逐像素鏈表存儲多個 fragment,以像素為單位排序渲染。


zwrite會自動把depth信息寫到zbuffer裡面,比如一個物體在z等於80的地方render,另一個物體在z等於100的地方render,深度80的物體會擋住100的。當然這個過程是不會減少overdraw的,完全看運氣或者你設置的渲染順序。

也就是說先渲染了z等於100的物體,就還會再渲染z等於80的物體,儘管前面的物體會擋住後面的。

理解上述,如果是半透明的物體:假設先渲染了z等於80的物體,那麼z等於100的物體就不會渲染了,因為在depth buffer裡面這個物體更遠。

常用辦法是關掉zwrite,增加一個pass來單獨做ztest,再第二個pass裡面來根據sorting信息來渲染


首先結論不對。對於半透明的渲染, 是可以同時開啟深度測試和深度寫入的。方法是使用2個Pass。一個先只負責保證像素級片元深度正確,一個負責渲染。

對於這種交叉物體的渲染,就可以採取這種方式。

深度測試的意義在於捨棄片元與否。

深度寫入的意義在於深度測試的基礎上,要不要覆蓋深度緩衝,即重新設立深度測試的標準。

如果沒有配合深度寫入來保證像素級的深度關係。

就會出現右邊的圖形的情況。

回答你這個問題的關鍵詞是「渲染順序」。

GPU的渲染流程

深度測試的大概流程

混合的大概流程:

2份源碼和一個模型你參考體會下:

http://pan.baidu.com/s/1i5onCpf

馮樂樂的《UnityShader入門精要》裡面第八章寫的挺詳細的,可以參考一下。


想像一下,一個圓環depth是10,圓環後面有個物體,depth是100,你如果zwrite=on,那透過圓環就看不到後面了(zbuffer現在的值是10,測試不過),所以unity中用blend需要關掉zwrite


流水線通常先做深度測試後做Blend。所以,被透明物體遮擋的不透明物體會先被深度測試給剔除掉,而沒有機會去做接下來的Blend。那麼,為了保證結果正確,在繪製透明物體時,禁止其改寫Depth,這樣,就會以不透明物體Depth為準,不透明物體不會被剔除掉,進而有機會與透明物體做接下來的Blend。


http://www.jianshu.com/p/e953e937e210 舉了幾個詳細的例子,可以參考下~


因為半透明的物體是無法遮擋半透明的物體


當時我也有過和LZ一樣的疑問,總結成兩條吧:

關閉Zwrite的目的是:

1.保證半透明的物件不影響不透明的物件,即使在渲染順序出問題的時候

2.兩個互相遮擋的半透明物件,開Zwrite,那麼就只能看到在前面的物件,不開ZWrite,就可以看到兩個物件在混合了,即使混合有可能是有問題的。


有時候也可以不關閉。

從下往上看,假設有個C形的半透明物體需要渲染,在不開啟zwrite的情形下,若C形物體下半部分先渲染,上半部分後渲染,那麼最終顯示的結果是C的上半部分在前面。

猜你喜欢

转载自blog.csdn.net/fdbvm/article/details/80886240
今日推荐