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值設置3,和尺子分處不同的平面,無z-fighting問題
設置合適的near和far值
在創建相機的時候,會有near
和far
兩個參數,用來設置相機的近平面和遠平面。這個兩個參數其實和深度緩沖(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=0
,polygonOffsetUnits=1.0
- 當面和近平面(near)、遠平面(far)有一個明顯的角度時,這時候就需要一個較大的偏移和一個較小但非零的偏移因子。這是因為要分開兩個交叉的面要比分開兩個重合的面要更大的偏移。你可以設置如
polygonOffsetFactor=0.75
,polygonOffsetUnits=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