threejs- z-fighting 問題(模型的重疊部位便不停的閃爍起來。這便是Z-Fighting問題)


Z-Buffer

在threejs中,使用深度緩沖(Z-Buffer)來完成場景可見性計算,即確定場景哪部分可見,哪部分不可見。深度緩沖(Z-Buffer)是一個二維數組,其中的每一個元素對應屏幕上的一個像素,如果場景中的兩個模型在同一個像素生成渲染結果,那么圖形處理卡就會比較二者的深度,並且保留距離觀察者較近的物體在該像素點的渲染結果,這樣就形成了近的模型遮擋遠的模型的結果。

上面說到,深度緩沖(Z-Buffer)是一個二維數組,但是數組的元素類型卻可以不同,不同的元素類型代表着不同的精度。這和顏色的精度很像,比如GIF圖像最多用8bit保存一個顏色,也即GIF最多支持256種色彩。以此類推,如果深度緩沖的也用8bit來保存一個像素的深度,那就是說該深度緩存只有256個深度級別。在threejs中只實現了一種深度緩沖,但是在例子中,又實現了一個精度更高的深度緩沖——logarithmicdepthbuffer,可以看示例webgl_camera_logarithmicdepthbuffer

Z-Fighting

當場景中的兩個模型在同一個像素生成的渲染結果對應到一個相同的深度值時,渲染器就不知道該使用哪個模型的渲染結果了,或者說,不知道哪個面在前,哪個面在后,於是便開始“胡作非為”,這次讓這個面在前面,下次讓那個面在前面,於是模型的重疊部位便不停的閃爍起來。這便是Z-Fighting問題。

這里寫圖片描述

(圖片來自:Three.js/WebGL: Large spheres appear broken at intersection

解決 Z-Fighting

要解決Z-Fighting問題,有兩個思路:

  • 讓各模型渲染結果不要在同一個像素出現相同深度值
  • 人為設置渲染順序,這樣即使出現相同深度值,也能正確渲染

這里說一下第二種方法為什么也能解決Z-Fighting,比如有兩個模型A和B,A的渲染順序是0,B的渲染順序是1,既是先渲染A,再渲染B,所以,如果A和B在某個地方出現了相同的深度值,那么后渲染的B會覆蓋掉先渲染的A。下面是按照這兩個思路提出的一些解決辦法。

別讓模型靠得那么近

手動設置一定的偏移即可讓這個問題解決,比如下面兩個例子:

  • 刻度的z值為0,和尺子處於同一平面,會出現z-fighting問題,可以看到刻度文字不停閃爍

    有z-fighting的例子

  • 刻度得z值設置3,和尺子分處不同的平面,無z-fighting問題

    無z-fighting的例子

設置合適的near和far值

在創建相機的時候,會有nearfar兩個參數,用來設置相機的近平面和遠平面。這個兩個參數其實和深度緩沖(Z-Buffer)也密切相關,深度緩沖其實是非線性的,靠近相機的地方精度更高。什么意思呢?假如你的深度緩沖只有10個深度級別,你的相機的near=1,far=100,那么你的深度緩沖可能是這樣的:

深度級別 深度范圍
0 0~1.0
1 1.0~1.1
3 1.1~1.234
4 1.234~1.325
5 1.325~1.55667
6 1.55667~1.9634
7 1.9634~5.434
8 5.434~23.34834
9 23.34834~99.999

(數據是杜撰的)

這樣的非線性深度緩存可能會造成在離相機較遠的地方深度等級的划分過於粗糙,比如上面的深度等級9,離相機的距離從23.34834到99.999的面都屬於同一個深度級別,從上面可以,兩個面對應到同一個深度級別就可能會出現z-fighting,所以,這個深度緩存出現z-fighting的概率還是挺大的。

一般來說,選擇一個稍微大一點的near值效果會明顯,比如把near從0.1設為1。

參考:【Z-Fighting】【Three.js/WebGL: Large spheres appear broken at intersection

設置多邊形偏移(polygon offset)

threejs 的 material 定義了三個多邊形偏移相關的屬性:

  • polygonOffset 是否開啟多邊形偏移
  • polygonOffsetFactor 多邊形偏移因子
  • polygonOffsetUnits 多邊形偏移單位

當發生兩個面深度值相同時,設置了polygonOffset的面便會向前或向后偏移一小段距離,這樣就能區分誰前誰后了。

polygonOffsetFactor和 polygonOffsetUnits的都是正值時,向遠離相機的方向偏移,當兩者都是負值時,向靠近相機的地方偏移。

設置polygonOffsetFactor和 polygonOffsetUnits是有所講究的:

  • 當面和近平面(near)、遠平面(far)幾乎平行的時候,一個很小的偏移就足夠,你可以設置polygonOffsetFactor=0polygonOffsetUnits=1.0
  • 當面和近平面(near)、遠平面(far)有一個明顯的角度時,這時候就需要一個較大的偏移和一個較小但非零的偏移因子。這是因為要分開兩個交叉的面要比分開兩個重合的面要更大的偏移。你可以設置如polygonOffsetFactor=0.75polygonOffsetUnits=4.0

這部分內容很多都來自Z fighting & polygon offset,原文講得更好點。

設置 render order

threejs的Object3D對象定義了一個renderOrder屬性,可以指定對象的渲染順序,按renderOrder從小到大排列,小的先渲染,大的后渲染。

設置完renderOrder之后,就算兩個面有同樣的深度,但是因為有渲染順序,后渲染的面會覆蓋掉先渲染的面。也因為這樣,設置正確的渲染順序很重要。

此外,這種方法更經常用在處理元素透明問題上,詳見transparent-objects-in-threejs

使用 logarithmicDepthBuffer 緩沖

緩沖的級別越多,沖突的概率相應的也就越低,所以,我們可以使用一個精度更高的z緩沖,來代替原有的Z緩沖。對於這個方法,threejs官網已經提供了一個例子webgl_camera_logarithmicdepthbuffer。不過,官網的例子為了演示效果,寫得比較復雜,實際上只需要將logarithmicDepthBuffer參數設為true即可:

var renderer = new THREE.WebGLRenderer({ logarithmicDepthBuffer: true });
  • 1

參考文檔

1、解釋如什么是z-fighting及何用polygon offset 
Z fighting & polygon offset

2、講到了near far 設置的問題 
(1)Three.js/WebGL: Large spheres appear broken at intersection

(2)Z-Fighting

3、解釋了 depth write的使用 
How to use polygonOffset solving Z-fighting poblems

4、講到了解決透明問題的方法,比較全面 
Transparent objects in Threejs


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM