3.1 模板測試和深度測試
一、開始前,先看一下舉例來理解
1。引例
左圖為顏色緩衝區中的一張圖,在模板緩衝區中我們會給這張圖的每一個片元分配一個0-255的數字(8位,預設為0)
中、右圖可以看到,我們修改了一些0為1,透過自定義的一些準則,如輸出模板緩衝區中1對應的片元的顏色;0的不輸出,最後透過模板測試的結果就如右圖所示
2。透過模板測試的應用,實現的效果舉例
①傳送門效果:可以看到左邊傳送門內的景象正是右側的場景
②Minions講解的一些效果,例如3D卡牌效果、偵探鏡效果等
連結:
https://www。
patreon。com/posts/14832
618
③每個正方體面顯示不同場景(每個面作為蒙版來顯示場景)
3。理解
對於上述的例子總結一下,這些效果基本可以歸結為三層組成
以②中的傳送門為例子,三層分別對應:門外場景、門內場景、門
也就是說可以理解為:包括兩層物體/場景、和一層遮罩
二、什麼是模板測試
1。從渲染管線理解
下圖為從片元著色器到FrameBuffer的流程(逐片元操作)
PixelOwnershipTest:
簡單來說就是控制當前螢幕畫素的使用許可權
e。g。:遊戲引擎僅渲染遊戲視窗
ScissorTest(裁剪測試):
在渲染視窗再定義要渲染哪一部分
和裁剪空間一起理解,也就是隻渲染能看到的部分
e。g。只渲染視窗的左下角部分
AlphaTest(透明度測試)
提前設定一個透明度預值
只能實現不透明效果和全透明效果
e。g。 設定透明度a為0。5,如果片元大於這個值就透過測試,如果小於0。5就剔除掉
StencilTest(模板測試)
DepthTest(深度測試)
Blending(透明度混合)
可以實現半透明效果
完成接下來的其他一系列操作後,我們會講合格的片元/畫素輸出到幀緩衝區(FrameBuffer)
逐片元操作是可以配置但不可程式設計的(對應圖中為黃色背景),也就是說是由管線/硬體自身規定好的,我們只能對裡邊的內容進行配置。
2。從邏輯上理解
理解:
referenceValue:當前模板緩衝片元的參考值
stencilBufferValue:模板緩衝區裡的值
中間的comparisonFunction,就是做一個比較
結果:
如果透過,這個片元就進入下一個階段
未透過/拋棄,停止並且不會進入下一個階段,也就是說不會進入顏色緩衝區
總結:就是透過一定條件來判斷這個片元/片元屬性執行保留還是拋棄的操作
3。從書面概念上理解
模板緩衝區-FrameBuffer
模板緩衝區可以為螢幕上的每一個畫素點儲存一個無符號整數值(通常為8位int 0-255)。
這個值的意義根據程式的具體應用而定。
模板測試
渲染過程中,可以用這個值與預先設定好的參考值作(ReferenceValue)比較,根據結果來決定是否更新相應的畫素點的顏色值。
這個比較的過程就稱為
模板測試
。
模板測試在透明度測試之前後,深度測試之前。
如果模板測試透過,相應的畫素點更新,否則不更新。
三、基本原理和使用方法
1。語法表示/結構解釋
Ref
:當前片元的參考值(0-255)
ReadMask
:讀掩碼
WriteMask
:寫掩碼
Comp
:比較操作函式
Pass
:測試透過,之後進行操作(StencilOperation,後邊有詳細講解)
Fail
:測試未透過,也會進行一個操作
ZFail:
模板測試透過,深度測試未透過
2。ComparisonFunction
我們可以根據需求配置
3。StencilOperation 更新值
有不同的更新操作,根據自己的需求進行配置
四、Demo效果展示和講解
1、案例一:3D卡牌效果
注:Unity中模板緩衝區預設都是0
在材質中將ReferenceValue改為0時的效果(模型直接擺放的效果)
課程shader截圖
①蒙版的shader
程式碼截圖
程式碼理解
只有一個int型別的屬性,命名了一個ID
渲染型別為不透明物體,佇列為Geometry+1(預設的不透明物體後進行蒙版的渲染)
ColorMask 顏色遮罩,0就是什麼都不輸出(是高度可配置的,可以改為RGBA(沒有遮罩)、輸出單通道R、G、 B)
ZWrite off 關閉深度寫入,防止顯示的東西被深度剔除(後邊深度測試細講)
Stencil { } 模板測試部分
Ref [_ID] 索引值就是前邊屬性宣告的ID
Comp always //預設比較
Pass replace //預設是keep
Fail、ZFail,不寫的話都是預設值,如程式碼所示
後續就是頂點、片元著色器
顏色給一個half4的就行,因為前邊已經ColorMask0了(什麼都不輸出)
②物體的shader
程式碼截圖
程式碼理解:
除了自身的屬性之外,同樣給了一個ID
渲染型別為不透明物體,佇列為Geometry+2(前邊蒙版後渲染)
Stencil {} 模板測試部分:
Ref [_ID] 同上
Comp equal ,當給定的索引值和當前模板緩衝區的值相等時,才會渲染這個片元。
後續不寫就是預設值
後邊就是自身的光照模型、顏色渲染等
③實現思路梳理
前提:Unity的模板緩衝區的預設值是0
預設物體(mask外邊的物體)的索引值也設為0,Comp設為always,也就是比較是一直透過的,且保持保持模板緩衝區的值不變(不進行模板測試操作的物體渲染完之後,模板緩衝區的值還是0)
然後開始渲染蒙版(mask)
mask的設定Comp也是always,但是不同的是:ID給了1(屬性部分定義),並且Pass的設定為replace,也就是說mask所在的模板緩衝區的值變成了1。
到這裡,總結一下,mask外的物體 值是0,mask的值是1
最後是mask裡邊的物體。
mask的模板測試是這樣的:ID是1,Comp是equal。翻譯一下就是:模板緩衝區的值為1,比較的條件是相等
此時,裡邊物體的模板緩衝區的值是1,外邊物體的模板緩衝區是0,Comp的條件是相等,結果很明顯不相等,這樣的效果就是:除mask顯示的部分,外邊的場景不渲染。
回顧前邊,我們mask的緩衝區的值也為1,通過了測試,所以mask部分(卡牌部分)渲染了出來。
2。案例二:盒子不同面顯示不同場景
實現思路
和卡牌效果類似,一個用蒙版遮罩的物體,盒子每個面用一個蒙版遮罩
同樣利用預設的值為0來做,只是面多了,蒙版和裡邊顯示的物體也多了,ID依次為1、2、3、4
蒙版對應物體同一個ID,最終的效果也就是在對應的蒙版顯示相應的物體
shader
屬性和模板測試部分程式碼截圖
程式碼理解:
屬性中使用了一個內建的列舉,這樣就可以在外邊自己選擇可配置的屬性了
五、模板測試的總結
最重要(用來比較的)兩個值:
當前模板緩衝區值(StencilBufferValue)
、
模板參考值(ReferenceValue)
模板測試主要就是對這兩個值進行特定的比較操作,例如Never、Always、Equal等,具體參考上文的表格
模板測試後,要對模板緩衝區的值進行更新操作,例如Keep,Replace等,具體參考上文表格
模板測試之後,可以透過不同的結果對模板緩衝區做不同的更新操作,例如模板測試成功的操作Pass、模板測試失敗的操作Fail、深度測試失敗的操作ZFail、還有正對正面和背面更新操作Passback,Passfront,Failback等。。。
六、擴充套件及參考資料
1。擴充套件用法
描邊操作
多邊形填充
反射區域控制
shadow volume陰影渲染
2。參考資料
https://
blog。csdn。net/u01104717
1/article/details/46928463
https://
blog。csdn。net/liu_if_el
se/article/details/86316361
https://
gameinstitute。qq。com/co
mmunity/detail/127404
https://
learnopengl-cn。readthedocs。io
/zh/latest/04%20Advanced%20OpenGL/02%20Stencil%20testing/
https://www。
patreon。com/posts/14832
618
https://www。
udemy。com/course/unity-
shaders/
一、什麼是深度測試
幫助我們處理物體的遮擋關係
1。從渲染管線理解
深度測試同樣位於逐片元操作過程中,在模板測試之後,透明度混合之前。
2。從邏輯上理解
理解:
和模板測試差不多,都是透過一個比較來判斷一系列操作
圖1:
如果ZWrite On,且當前深度值和深度緩衝區的值作比較,如果透過就寫入深度,不透過就忽略深度
圖2:
當前深度值和深度緩衝區中的值做比較,如果透過就寫入顏色緩衝區,不透過就不寫入顏色緩衝區
3。從書面概念上理解
所謂深度測試,就是針對當前螢幕上(更準確的說是FrameBuffer)對應的畫素點,將物件自身的深度值與當前深度緩衝區的深度值做比較,如果通過了,這個物件在該畫素點才會將顏色寫入顏色緩衝區
4。從發展上理解
我們要渲染一個場景的話,通常會有多個物體。
首先要
控制渲染順序
畫家演算法:
這是是指油畫的畫法,也就是畫一幅油畫,是從遠處開始畫,然後近處的東西一點點疊加在上面(GAMES系列的課提到過多次)
存在的問題:例如一列物體,最前面的物體最大,站在正前面看只能看到最前面的物體,這樣一來後邊的就不用畫了,不然就是效能浪費。
Z-Buffer演算法:
透過深度緩衝區來控制渲染順序
控制Z-Buffer對深度的儲存
例如:什麼時候更新深度緩衝區、什麼時候使用深度緩衝區
兩個典型的功能:
Z Test
Z Write
具體後邊會講
控制不同型別物體的渲染順序
透明物體
不透明物體
渲染佇列(很有用的概念,後邊會講)
減少OverDraw
Early-Z,一種最佳化手段,後邊會講
Z-cull
Z-check
二、基本原理和使用方法
1。Z-Buffer(深度緩衝區)
和顏色緩衝區一樣,在每個片段中儲存了資訊,並且通常和顏色緩衝有著一樣的寬度和高度
顏色緩衝區
:
就是最終在顯示屏硬體上顯示顏色的GPU視訊記憶體區域了,這個緩衝區儲存了每幀更新後的最終顏色值,圖形流水線經過一系列測試,包括片段丟棄、顏色混合等,最終生成的畫素顏色值就儲存在這裡,然後提交給顯示硬體顯示。
深度緩衝是由視窗系統自動建立的,它會以16、24、32位float形式儲存深度值。大部分系統中深度值是24位的
Z-Buffer中儲存的是當前的深度資訊,對於每個畫素儲存一個深度值。
透過Z-Write 、Z-Test來呼叫Z-Buffer,來達到想要的渲染效果
2。Z Writer(深度寫入)
深度寫入包括兩種狀態
ZWrite On 、 ZWrite Off
當我們開啟深度寫入,物體被渲染時針對物體在螢幕(FrameBuffer)上每個畫素的深度都寫入到深度緩衝區。
關閉深度寫入狀態,物體的深度就不會寫入深度緩衝區。
除了ZWrite的是否寫入深度緩衝區,更重要的是:是否透過深度測試,也就是Z-Test
如果Z-Test都沒透過,也就不會寫入深度了。
也就是說,只有ZTest和ZWrite都可行的情況下才寫入深度緩衝區
綜上,ZWrite有On、Off兩種情況;ZTest有透過、不透過兩種情況,兩者結合的四種情況如下:
3。Z-Test的比較操作
預設:ZWrite On、ZTest LEqual
深度緩衝一開始為無窮大
4。渲染佇列
Unity內建的幾種渲染佇列
:
按照渲染順序從先到後排序,佇列數越小,越先渲染;反之同理。
Unity中設定渲染佇列
:
語法:Tags { “Queue” = “Transparent”}
預設是Geometry
Unity中不透明物體的渲染順序:從前往後
也就是說深度小的先渲染,其次再渲染深度大的
Unity中透明物體的渲染順序:從後往前(類似畫家演算法,會造成OverDraw)
可以在shader的Inspector面板中檢視渲染佇列相關屬性
5。簡述Early-Z技術
是位於三角形遍歷之後、逐片元操作之前的。
傳統的渲染管線中,ZTest是在Blending階段,這時進行深度測試的話,所以物件的畫素著色器都會計算一遍,沒有效能提升,只是為了得到正確的效果,造成了大量的無用計算。(深度測試失敗的片元是已經經過計算的,到這一步不透過被拋棄,那麼前邊的計算就是無用功了)
為了減少這些不必要的計算,現代GPU運用了Early-Z技術,在頂點和片元階段之間(光柵化之後,片元著色器之前)進行一次深度測試(如下左圖黑框部分)。
如果這次深度測試失敗,那就不用在片元著色器中作無關緊要的計算了,這樣一來就會帶來效能提升。
最終的ZTest仍然要進行,以保證正確的遮擋關係。
如右圖前一次的Z-Cull是為了裁剪達到效能最佳化的目的,後一次的Z-check是為了保證正確的遮擋關係。
6。深度值
正確的理解深度值的概念
首先先了解一下模型在渲染管線中的幾次空間變換
模型一開始所在的模型空間:
無深度。
透過M矩陣變換到世界空間,此時模型座標已經變換到了齊次座標(x,y,z,w):
深度存在z分量
。
透過V矩陣變換到視察空間(攝像機空間):
深度存在z分量(線性)
透過P矩陣變換到裁剪空間:
深度緩衝中此空間的z/w中(已經變成了非線性的深度)
最後透過一些投影對映變換到螢幕空間
為什麼深度緩衝區中要儲存一個非線性的深度?
詳細連結
https://
learnopengl-cn。readthedocs。io
/zh/latest/04%20Advanced%20OpenGL/01%20Depth%20testing/
原因1:給近處更多的精度
在深度緩衝區中的深度值是介於 0。0~1。0之間的,從觀察者看到的內容與場景中所有物件的z值作比較。
這些z值可以投影平截頭體(就是視錐)的近平面和遠平面之間的任何值。
//補充:
平截頭體
:又稱視景體、視錐,是三維世界中在螢幕上可見的區域,即虛擬攝像機的視野
下圖中紅框的位置是平截頭體,就是攝像機拍攝的範圍。
貼上維基百科:
https://
zh。wikipedia。org/wiki/%
E8%A7%86%E4%BD%93
要轉換這些檢視空間的z值到[0,1]範圍內,方法之一就是線性轉換,具體如下圖
然而實踐中幾乎不使用線性深度緩衝區,正確的投影特性的非線性深度方程是和1/z成正比的。這樣一來會有如下效果:在Z很近的時候有高精度,Z很遠的時候低精度,這符合我們生活中的情況,具體如下圖
其實可以回想前幾節課中伽馬校正部分對比理解一下,也是根據實際情況(人眼特性),給暗部更多的精度,這裡是近處給更多精度。
另一個原因:深度衝突(Z-Fight)
當兩個平面或三角形緊密相互平行的時候,深度緩衝區不具有足夠的精度來確定哪一個考前。
結果就是這兩個形狀不斷切換順序,導致怪異問題,看起來像是兩個形狀在爭奪靠前的位置。
這就被稱為
深度衝突
。
深度衝突是深度緩衝區的普遍問題,當物件的距離越遠一般越強(因為深度緩衝區在z值非常大的時候沒有很高的精度)
深度衝突無法避免,但是有技巧可以防止出現:
物理上的做法,就是讓物體不要靠得太近。
儘可能的把近平面設定的遠一點。
放棄一些效能來得到更高精度的深度值。
五、Demo效果展示和講解
1。案例一:三個正方體遮擋關係
場景中有三個正方體,並賦予了不同的顏色。正常的情況應該是從前到後依次為藍、綠、紅
圖1詳解:
梳理渲染過程:
沒渲染時,此時Unity的深度緩衝區預設值為無窮大
渲染藍色正方體
相對於預設深度緩衝區的無窮大,肯定是小於等於,所以測試透過
渲染綠色正方體
此時藍色物體位置的深度緩衝區的值已經不是無窮大了,其它位置還是
注:深度緩衝區和顏色緩衝區都是相對於片元來講的(片元可以理解為未完成的一個畫素,還處於渲染管線中的畫素)
綠色正方體進行深度測試,深度測試同樣是LessEqual,並且綠色的深度值比藍色正方體的大。
結果就是:兩個正方體重疊部分是大於深度緩衝區的,也就是測試不透過,所以重疊部分沒有寫入綠色,還是藍色的
沒有重疊部分,深度當然比無窮大小,所以寫入, 渲染出來了綠色正方體未重疊的部分。
紅色同理。
圖2詳解:
梳理渲染過程:
設定:將藍色正方體的深度寫入ZWrite 關掉了;
思路:第一個藍色正方體的渲染時,測試透過,但是並沒有寫入深度。
也就是說,渲染完藍色正方體時,深度緩衝區的值還是無窮大。
這就是藍綠重疊部分,顯示綠色的原因。
圖3詳解:
相較於圖2,只是把綠色正方體的ZTest改為了always
無論是LessEqual還是always,測試都透過,所以效果和圖2一樣
圖4詳解:
將紅色正方體的ZTest也改為了always,這樣一來紅色正方體的深度測試也是一直透過,並且寫入。
因為是從前往後渲染的,所有依次為藍、綠、紅,深度緩衝區中的值也是後邊渲染的
可以理解為後邊遮住前邊的效果。
圖5詳解:
相對於圖4,改變了綠色正方體的渲染佇列為Geometry+1
此時的幀緩衝區面板如下
儘管綠色正方體在紅色正方體前面,因為佇列+1,它的渲染順序變為了紅色正方體後
也就是說,渲染佇列優先順序 > 透明物體的渲染順序(從前到後)
圖6詳解:
相對於圖1,將綠色正方體的ZTest改為了Greater,
也就是說藍色正方體和綠色正方體重疊部分,大於模板緩衝區的部分透過測試,寫入模板緩衝區
結果就是重疊部分為綠色,而未重疊部分的深度當然小於無窮大,所以沒透過測試,自然也就不渲染。
紅色部分正常。
shader截圖
2。案例二:X-Ray效果
實現思路:
分為三部分:前邊的牆、被牆擋住的X-Ray效果部分、高出牆部分的物體
回想一下前邊6張圖,哪張圖是前邊渲染完,後邊渲染顯示在先渲染完前邊的? ——->圖6
也就是說,X-Ray效果部分我們使用到了ZTest :Greater,深度寫入關閉
高出牆體部分是預設的渲染:LessEqual、ZWrite On
shader截圖
程式碼理解
寫CGINCLUDE的好處:將頂點和片元著色器寫在裡邊,在多passshade的時候,直接呼叫就可以了。
X-Ray繪製部分
和之前實現思路相同,ZWrite Off,ZTest Greater
Cull back 是剔除背面,為了最佳化
Blend SrcAlpha One :由於有一個透明的效果,除了上邊的,還需要一步Blend,來做透明度混合
渲染型別和渲染佇列為Transparent
正常繪製部分略
案例三、粒子系統中的深度測試
建立一個粒子系統ParticleSystem,可以看到預設的是透明的
為了理解,我們自己建立一個材質,給到粒子上
此時粒子系統變成了這樣
建立一個shader(Unlit),把粒子的貼圖選上,附到材質上,效果如下(是不透明的),這顯然不是我們要的效果
開啟shader修改程式碼
首先回顧前邊說的:Unity中預設的ZWrite On、ZTest是LessEqual、渲染佇列是Geometry
我們想要讓粒子透明,就需要做如下配置
渲染佇列改為透明物體的渲染佇列:Transparent
ZWrite Off,對於透明物體,是有相互疊加關係的,所以關掉寫入
ZTest 預設(LessEqual),對於透明物體是這樣的:如果透明物體前有不透明物體,此時 透明物體看不到;如果透明物體後面有不透明物體,此時透明物體可以看到。
要渲染透明物體,還要進行Blend操作:Blend One One(加法混合,疊加效果的顯示)
修改完成後效果如下:(正是我們想要的效果)
七、深度測試的總結
最重要的兩個值:當前深度緩衝區的值(ZBufferValue) 和 深度參考值(ReferenceValue)。透過比較操作還實現理想的渲染效果
Unity中的渲染順序
:
先渲染不透明物體(從前到後),再渲染透明物體(從後往前)
透過對ZWrite和ZTest的相互組合配置來控制半透明物體的渲染(關閉深度寫入,快開啟深度測試,透明度混合)
引入Early-Z之後深度測試相關的內容(Z-Cull、Z-Check)
深度緩衝區中儲存的深度值為[0,1]的非線性值
八、擴充套件及參考資料
1。深度測試的擴充套件用法
基於深度的著色
如:湖水的效果
陰影貼圖(shadowma)
比較攝像機空間和燈光空間的深度值得到陰影範圍
透明物體、粒子渲染
透視X-Ray效果
切邊效果
2。參考資料
https://
blog。csdn。net/puppet_ma
ster/article/details/53900568
https://
learnopengl-cn。readthedocs。io
/zh/latest/04%20Advanced%20OpenGL/01%20Depth%20testing/
https://
docs。unity3d。com/cn/201
8。4/Manual/SL-CullAndDepth。html
https://
blog。csdn。net/yangxuan0
261/article/details/79725466
https://
roystan。net/articles/to
on-water。html
《shader入門精要》
《Unity ShaderLab 開發實戰詳解》
補充
深度測試是做特效永遠繞不開的話題
模板測試實際專案中應用的相對較少,建議能不用盡量不用(佔用一個buffer的記憶體?)
作業:根據課程內容,用深度測試和模板測試做些有意思的效果
Emmm目前的段位只能做一些簡單的效果
1。【模板測試部分】嘗試還原一下不同正方體面的不同場景顯示。
實現思路:
也是分為兩個shader,分別是蒙版和物體
蒙版部分和課程內的相同,同樣定義了一個ID方便後邊操作
物體部分在模型著色的基礎上加上了模板測試相關的程式碼
重點也就是ID的設定,第一個面的蒙版和物體ID設為1;第二個面的蒙版和物體ID設為2。僅此而已
效果如下: