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