專業(yè)維護網(wǎng)站的公司中國最新疫情最新消息
模板測試(Stencil Test):
當(dāng)片段著色器處理完一個片段之后,模板測試(Stencil Test)會開始執(zhí)行,和深度測試一樣,它也可能會丟棄片段。接下來,被保留的片段會進入深度測試,它可能會丟棄更多的片段。模板測試是根據(jù)又一個緩沖來進行的,它叫做模板緩沖(Stencil Buffer),我們可以在渲染的時候更新它來實現(xiàn)一些很有意思的效果。
一個模板緩沖中,(通常)每個模板值(Stencil Value)是8位的。所以每個像素/片段一共能有256種不同的模板值。我們可以將這些模板值設(shè)置為我們想要的值,然后當(dāng)某一個片段有某一個模板值的時候,我們就可以選擇丟棄或是保留這個片段了。
模板緩沖的一個簡單的例子如下:
模板緩沖首先會被清除為0,之后在模板緩沖中使用1填充了一個空心矩形。場景中的片段將會只在片段的模板值為1的時候會被渲染(其它的都被丟棄了)。?
每個窗口庫都需要為你配置一個模板緩沖。GLFW自動做了這件事,所以我們不需要告訴GLFW來創(chuàng)建一個,但其它的窗口庫可能不會默認(rèn)給你創(chuàng)建一個模板庫,所以記得要查看庫的文檔。
測試和混合發(fā)生在渲染流程中的最后一個階段,在這個階段里,GPU主要的工作是逐片元操作,將片元的顏色以某種形式合并,得到最終在屏幕上顯示的像素顏色。
在webgl中的測試有裁剪測試、透明的測試、模板測試以及深度測試。這幾個測試都是高度可配置的,測試流程如下圖:
模板緩沖:?
通常用戶在啟用模板緩沖的時候,會將整個模板緩沖中的所有片段模板值設(shè)置為0,從而丟棄所有的片段,然后再設(shè)置特定區(qū)域的模板值以及比較函數(shù)。GPU會讀取用戶設(shè)置的模板值,然后將該值和模板緩沖中該位置的模板值,按比較函數(shù)進行比較,最終決定是保留還是舍棄該片段,形成遮罩效果。在模板測試中有兩個很重要的方法是stencilFunc和stencilOp,stencilFunc用來控制stencil的測試方式,得出測試結(jié)果。stencilOp根據(jù)結(jié)果決定要如何處理緩沖中的數(shù)據(jù)。
glStencilFunc?:
glStencilFunc(GLenum func, GLint ref, GLuint mask)一共包含三個參數(shù):
func
:設(shè)置模板測試函數(shù)(Stencil Test Function)。這個測試函數(shù)將會應(yīng)用到已儲存的模板值上和glStencilFunc函數(shù)的ref
值上。可用的選項有:GL_NEVER、GL_LESS、GL_LEQUAL、GL_GREATER、GL_GEQUAL、GL_EQUAL、GL_NOTEQUAL和GL_ALWAYS。它們的語義和深度緩沖的函數(shù)類似。ref
:設(shè)置了模板測試的參考值(Reference Value)。模板緩沖的內(nèi)容將會與這個值進行比較。mask
:設(shè)置一個掩碼,它將會與參考值和儲存的模板值在測試比較它們之前進行與(AND)運算。初始情況下所有位都為1。
測試的時候,ref會先和mask做與運算,再將
模板緩沖中的值與mask做與運算,最后把這兩個與運算的值,代入比較函數(shù)得出結(jié)果。
所有的比較函數(shù)如下:
舉個例子:
glStencilFunc(gl.GEQUAL, 1, 0xFF)
?先將參考值1與0xff做與運算得到運算結(jié)果:1,將運算結(jié)果再與"模板緩沖和0xff進行與運算的值"進行比較。判斷是否滿足前者大于后者,如果是的話模板測試成功,否則失敗。
mask值設(shè)為0xff的時候,就等于直接拿參考值和模板緩沖值做比較。
想要禁用模板也可以將mask設(shè)置為0x00
經(jīng)歷了glStencilFunc之后,我們就知道模板測試是不是通過。接下來就要對模板緩沖進行操作,這就需要?glStencilOp這個函數(shù)了。
glStencilOp:
glStencilOp(GLenum sfail, GLenum dpfail, GLenum dppass)一共包含三個選項,我們能夠設(shè)定每個選項應(yīng)該采取的行為:
sfail
:模板測試失敗時采取的行為。dpfail
:模板測試通過,但深度測試失敗時采取的行為。dppass
:模板測試和深度測試都通過時采取的行為。
每個選項都可以選用以下的其中一種行為:
行為 | 描述 |
---|---|
gl.KEEP | 保持當(dāng)前儲存的模板值 |
gl.ZERO | 將模板緩沖值設(shè)置為0 |
gl.REPLACE | 將模板緩沖區(qū)值設(shè)置為glStencilFunc函數(shù)設(shè)置的ref 值 |
gl.INCR | 如果模板緩沖值小于最大值則將模板值加1 |
gl.INCR_WRAP | 與GL_INCR一樣,但如果模板緩沖值超過了最大值則歸零 |
gl.DECR | 如果模板緩沖值大于最小值則將模板值減1 |
gl.DECR_WRAP | 與GL_DECR一樣,但如果模板緩沖值小于0則將其設(shè)置為最大值 |
gl.INVERT | 按位翻轉(zhuǎn)當(dāng)前的模板緩沖值 |
默認(rèn)情況下glStencilOp是設(shè)置為(gl.KEEP, gl.KEEP, gl.KEEP)
的,所以不論測試的結(jié)果是什么,模板緩沖都會保留它的值。默認(rèn)的行為不會更新模板緩沖,所以如果你想寫入模板緩沖的話,你需要至少對其中一個選項設(shè)置不同的值。
通常我們這樣設(shè)置:glStencilOp(gl
..KEEP, gl.KEEP,gl.REPLACE
),測試失敗時保持原有值(KEEP
),測試通過的參考值替換模板緩沖值(REPLACE
)
在webgl中模板測試默認(rèn)是處于禁用狀態(tài),使用時需要手動開啟,我們采用gl.enable來開啟:
gl.enable(gl.STENCIL_TEST);
注意,和顏色和深度緩沖一樣,我們也需要在每幀繪制之前清除模板緩沖。
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT | gl.STENCIL_BUFFER_BIT);
如果想自己在webgl上寫模板測試,需要在獲取gl上下文對象時傳入stencil的請求:
const gl = canvas.getContext("webgl",{stencil:true});
深度測試(Depth Test):
深度測試可以幫助實現(xiàn)3D渲染的物體遮擋效果 ,
深度緩沖就像顏色緩沖(Color Buffer)(儲存所有的片段顏色)一樣,在每個片段中儲存了信息,并且(通常)和顏色緩沖有著一樣的寬度和高度。深度緩沖是由窗口系統(tǒng)自動創(chuàng)建的,它會以16、24或32位float的形式儲存它的深度值。在大部分的系統(tǒng)中,深度緩沖的精度都是24位的。
?當(dāng)深度測試被啟用的時候,OpenGL會將一個片段的深度值與深度緩沖的內(nèi)容進行對比。OpenGL會執(zhí)行一個深度測試,如果這個測試通過了的話,深度緩沖將會更新為新的深度值。如果深度測試失敗了,片段將會被丟棄。
深度緩沖是在片段著色器運行之后(以及模板測試運行之后)在屏幕空間中運行的。屏幕空間坐標(biāo)與通過OpenGL的glViewport所定義的視口密切相關(guān),并且可以直接使用GLSL內(nèi)建變量gl_FragCoord從片段著色器中直接訪問。gl_FragCoord的x和y分量代表了片段的屏幕空間坐標(biāo)(其中(0, 0)位于左下角)。gl_FragCoord中也包含了一個z分量,它包含了片段真正的深度值。z值就是需要與深度緩沖內(nèi)容所對比的那個值。
深度測試默認(rèn)是禁用的,所以如果要啟用深度測試的話,我們需要用GL_DEPTH_TEST選項來啟用它:
glEnable(GL_DEPTH_TEST);
當(dāng)它啟用的時候,如果一個片段通過了深度測試的話,OpenGL會在深度緩沖中儲存該片段的z值;如果沒有通過深度緩沖,則會丟棄該片段。如果你啟用了深度緩沖,你還應(yīng)該在每個渲染迭代之前使用GL_DEPTH_BUFFER_BIT來清除深度緩沖,否則你會仍在使用上一次渲染迭代中的寫入的深度值:
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
如果在某些情況下你會需要對所有片段都執(zhí)行深度測試并丟棄相應(yīng)的片段,但不希望更新深度緩沖。也就是你在使用一個只讀的(Read-only)深度緩沖。OpenGL允許我們禁用深度緩沖的寫入,只需要設(shè)置它的深度掩碼(Depth Mask)設(shè)置為GL_FALSE
就可以了:
glDepthMask(GL_FALSE);
注意這只在深度測試被啟用的時候才有效果。
深度測試函數(shù)
OpenGL允許我們修改深度測試中使用的比較運算符。這允許我們來控制OpenGL什么時候該通過或丟棄一個片段,什么時候去更新深度緩沖。我們可以調(diào)用glDepthFunc函數(shù)來設(shè)置比較運算符(或者說深度函數(shù)(Depth Function)):
glDepthFunc(GL_LESS);
這個函數(shù)接受下面表格中的比較運算符:
函數(shù) | 描述 |
---|---|
GL_ALWAYS | 永遠(yuǎn)通過深度測試 |
GL_NEVER | 永遠(yuǎn)不通過深度測試 |
GL_LESS | 在片段深度值小于緩沖的深度值時通過測試 |
GL_EQUAL | 在片段深度值等于緩沖區(qū)的深度值時通過測試 |
GL_LEQUAL | 在片段深度值小于等于緩沖區(qū)的深度值時通過測試 |
GL_GREATER | 在片段深度值大于緩沖區(qū)的深度值時通過測試 |
GL_NOTEQUAL | 在片段深度值不等于緩沖區(qū)的深度值時通過測試 |
GL_GEQUAL | 在片段深度值大于等于緩沖區(qū)的深度值時通過測試 |
默認(rèn)情況下使用的深度函數(shù)是GL_LESS,它將會丟棄深度值大于等于當(dāng)前深度緩沖值的所有片段。
?如果想自己在webgl上寫深度測試,需要在獲取gl上下文對象時傳入depth的請求:
const gl = canvas.getContext("webgl",{stencil:true,depth:true});
Creator中使用深度、模板測試:
打開TS引擎源碼文件cocos\gfx\webgl\webgl-swapchain.ts(低版本打開cocos\gfx\webgl\webgl-device.ts)
有個initStates函數(shù),引擎在這里初始化depth、stencil state:
function initStates (gl: WebGLRenderingContext) {gl.activeTexture(gl.TEXTURE0);gl.pixelStorei(gl.PACK_ALIGNMENT, 1);gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1);gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);gl.bindFramebuffer(gl.FRAMEBUFFER, null);// rasterizer stategl.enable(gl.SCISSOR_TEST);gl.enable(gl.CULL_FACE);gl.cullFace(gl.BACK);gl.frontFace(gl.CCW);gl.disable(gl.POLYGON_OFFSET_FILL);gl.polygonOffset(0.0, 0.0);// depth stencil stategl.enable(gl.DEPTH_TEST);gl.depthMask(true);gl.depthFunc(gl.LESS);gl.depthRange(0.0, 1.0);gl.stencilFuncSeparate(gl.FRONT, gl.ALWAYS, 1, 0xffff);gl.stencilOpSeparate(gl.FRONT, gl.KEEP, gl.KEEP, gl.KEEP);gl.stencilMaskSeparate(gl.FRONT, 0xffff);gl.stencilFuncSeparate(gl.BACK, gl.ALWAYS, 1, 0xffff);gl.stencilOpSeparate(gl.BACK, gl.KEEP, gl.KEEP, gl.KEEP);gl.stencilMaskSeparate(gl.BACK, 0xffff);gl.disable(gl.STENCIL_TEST);// blend stategl.disable(gl.SAMPLE_ALPHA_TO_COVERAGE);gl.disable(gl.BLEND);gl.blendEquationSeparate(gl.FUNC_ADD, gl.FUNC_ADD);gl.blendFuncSeparate(gl.ONE, gl.ZERO, gl.ONE, gl.ZERO);gl.colorMask(true, true, true, true);gl.blendColor(0.0, 0.0, 0.0, 0.0);
}
以上初始化的配置,主要針對3D對象,因為2D對象大多數(shù)包含透明像素,因此2D管線不需要進行深度測試,比如:對于builtin-sprite.effect這類2d shader中對應(yīng)深度測試部分,都被默認(rèn)關(guān)閉了:
除了可以在effect文件中初始化深度模板測試 , creator編輯器還可以在material文件中修改:
所有深度、模板測試相關(guān)的配置都可以在Pipeline States下修改。
下面我們做個案例:
?如圖,結(jié)合相機位置和右下角相機拍攝出的畫面來看,由于長方體柱子的深度值比其他模型的深度值要小,(由于深度測試函數(shù)是GL_LESS,深度值小的通過測試)使得長方體背后的模型的片段被剔除了,因此長方體遮擋住了其他物體(包括地面)。接下來我們對長方體的深度測試做一些配置修改,再看看顯示效果:
1.關(guān)閉長方體模型材質(zhì)中的的深度測試:
關(guān)閉長方體的深度測試表現(xiàn)的效果:?
?長方體關(guān)閉了深度測試后,沒有了深度值,在它和其他模型重疊的地方,會被有深度的模型片段填充。