NDK OpenGL ES 3.0 開發(十一):模板測試

該原創文章首發於微信公衆號:字節流動

OpenGL ES 模板測試

在這裏插入圖片描述
模板測試與深度測試類似,主要作用是利用模板緩衝區(Stencil Buffer)所保存的模板值決定當前片段是否被丟棄,且發生於深度測試之前。

圖片來源:https://learnopengl.com/Advanced-OpenGL/Stencil-testing

模板測試一般步驟:

  • 啓用模板測試,開啓模板緩衝寫入glStencilMask(0xFF)
  • 執行渲染操作,更新模板緩衝區;
  • 關閉模板緩衝寫入glStencilMask(0x00)
  • 執行渲染操作,利用模板緩衝區所保存的模板值確定是否丟棄特定片段。

啓用模板測試 glEnable(GL_STENCIL_TEST);, 清空模板緩衝區 glClear( GL_STENCIL_BUFFER_BIT);

控制模板緩衝區是否可以進行寫入:

// 0xFF == 0b11111111
// 模板值與它進行按位與運算結果是模板值,模板緩衝可寫
glStencilMask(0xFF); 

// 0x00 == 0b00000000 == 0
// 模板值與它進行按位與運算結果是0,模板緩衝不可寫
glStencilMask(0x00); 

模板測試的配置函數 glStencilFuncglStencilOp

void glStencilFunc(GLenum func, GLint ref, GLuint mask);
  • func:設置模板測試操作。這個測試操作應用到已經儲存的模板值和 glStencilFunc的 ref 值上,可用的選項是:
    GL_NEVER、GL_LEQUAL、GL_GREATER、GL_GEQUAL、GL_EQUAL、GL_NOTEQUAL、GL_ALWAYS ;
  • ref:指定模板測試的引用值。模板緩衝區中的模板值會與這個值對比;
  • mask:指定一個遮罩,在模板測試對比引用值和儲存的模板值前,對它們進行按位與(and)操作,初始設置爲 1 。
glStencilFunc(GL_EQUAL, 1, 0xFF);
// 表示當一個片段模板值等於(GL_EQUAL)引用值1,片段就能通過測試被繪製了,否則就會被丟棄。

glStencilFunc(GL_ALWAYS, 1, 0xFF);
// 表示所有片段模板測試總是通過。

glStencilOp 主要用於控制更新模板緩衝區的方式。

void glStencilOp(GLenum sfail, GLenum dpfail, GLenum dppass);
  • sfail: 如果模板測試失敗將如何更新模板值;
  • dpfail: 如果模板測試通過,但是深度測試失敗時將如何更新模板值;
  • dppass: 如果深度測試和模板測試都通過,將如何更新模板值。

參數可選操作:

操作 描述
GL_KEEP 保持現有的模板值
GL_ZERO 將模板值置爲 0
GL_REPLACE 將模板值設置爲用 glStencilFunc 函數設置的ref值
GL_INCR 如果模板值不是最大值就將模板值 +1
GL_INCR_WRAP 與 GL_INCR 一樣將模板值 +1 ,如果模板值已經是最大值則設爲 0
GL_DECR 如果模板值不是最小值就將模板值 -1
GL_DECR_WRAP 與 GL_DECR 一樣將模板值 -1 ,如果模板值已經是最小值則設爲最大值
GL_INVERT 按位反轉當前模板緩衝區的值

繪製物體輪廓是模板測試的常見應用,其步驟一般如下:

  • 啓動深度測試和模板測試,清空模板緩衝和深度緩衝;
  • 在繪製物體前,設置 glStencilFunc(GL_ALWAYS, 1, 0xFF);,用 1 更新物體將被渲染的片段對應的模板值;
  • 渲染物體,寫入模板緩衝區;
  • 關閉模板寫入和深度測試;
  • 將物體放大一定比例;
  • 使用一個不同的片段着色器用來輸出一個純顏色(物體輪廓顏色);
  • 再次繪製物體,設置 glStencilFunc(GL_NOTEQUAL, 1, 0xFF) 當片段的模板值不爲 1 時,片段通過測試進行渲染;
  • 開啓模板寫入和深度測試。

關鍵實現的代碼片段:

//啓動深度測試和模板測試,清空模板和深度緩衝
glClear(GL_STENCIL_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glEnable(GL_DEPTH_TEST);
glEnable(GL_STENCIL_TEST);

glStencilFunc(GL_ALWAYS, 1, 0xFF); //所有片段都要寫入模板緩衝
glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);//若模板測試和深度測試都通過了,將片段對應的模板值替換爲1
glStencilMask(0xFF);

//繪製物體
glBindVertexArray(m_VaoId);
glUseProgram(m_ProgramObj);
glUniform3f(m_ViewPosLoc, 0.0f, 0.0f, 3.0f);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, m_TextureId);
glUniform1i(m_SamplerLoc, 0);
UpdateMatrix(m_MVPMatrix, m_ModelMatrix, m_AngleX, m_AngleY , 1.0, glm::vec3(0.0f,  0.0f,  0.0f), ratio);
glUniformMatrix4fv(m_MVPMatLoc, 1, GL_FALSE, &m_MVPMatrix[0][0]);
glUniformMatrix4fv(m_ModelMatrixLoc, 1, GL_FALSE, &m_ModelMatrix[0][0]);
glDrawArrays(GL_TRIANGLES, 0, 36);
glBindVertexArray(0);

glStencilFunc(GL_NOTEQUAL, 1, 0xFF);//當片段的模板值不爲 1 時,片段通過測試進行渲染

//禁用模板寫入和深度測試
glStencilMask(0x00);
glDisable(GL_DEPTH_TEST);

//繪製物體輪廓
glBindVertexArray(m_VaoId);
glUseProgram(m_OutlineProgramObj);
//放大 1.05 倍
UpdateMatrix(m_MVPMatrix, m_ModelMatrix, m_AngleX, m_AngleY, 1.05, glm::vec3(0.0f,  0.0f,  0.0f), ratio);
glUniformMatrix4fv(m_MVPMatLoc, 1, GL_FALSE, &m_MVPMatrix[0][0]);
glUniformMatrix4fv(m_ModelMatrixLoc, 1, GL_FALSE, &m_ModelMatrix[0][0]);
glDrawArrays(GL_TRIANGLES, 0, 36);
glBindVertexArray(0);

//開啓模板寫入和深度測試
glStencilMask(0xFF);
glEnable(GL_DEPTH_TEST);
glDisable(GL_STENCIL_TEST);

另外需要注意,在使用 GLSurfaceView 時,新的 API 默認沒有配置模板緩衝區,需要使用 setEGLConfigChooser 配置模板緩衝區。

public MyGLSurfaceView(Context context, AttributeSet attrs) {
    super(context, attrs);
    this.setEGLContextClientVersion(2);
    mGLRender = new MyGLRender();
    /*If no setEGLConfigChooser method is called,
    then by default the view will choose an RGB_888 surface with a depth buffer depth of at least 16 bits.*/
    setEGLConfigChooser(8, 8, 8, 8, 16, 16);//最後 2 個參數表示分別配置 16 位的深度緩衝區和模板緩衝區
    setRenderer(mGLRender);
    setRenderMode(RENDERMODE_WHEN_DIRTY);

}

利用模板測試繪製物體輪廓效果圖:
在這裏插入圖片描述

實現代碼路徑:
NDK_OpenGLES_3_0

聯繫與交流

我的公衆號
在這裏插入圖片描述
我的微信
我的微信

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章