1. 程式人生 > >OpenGL ES 片元操作

OpenGL ES 片元操作

片元著色器後續操作還包括剪裁測試、模板測試、深度測試、混合等,最終才會被送到幀緩衝區。

剪裁測試

剪裁測試可以在渲染時用來限制繪製區域,通過制定一個矩陣進一步限制幀緩衝區可以寫入的畫素,啟用剪裁測試後,繪製不會在整個螢幕(幀緩衝區)進行,而是在指定的矩形區域進行。不在矩形區域中的片元被丟棄,在矩形區域內的片元才能被送往幀緩衝區,實際效果就是在螢幕上開闢了一個小視窗。剪裁矩形確定的範圍內的視口區域的物體才會最終進入幀緩衝區。

使用函式glScissor指定剪裁區域

public static native void glScissor(
        int x, int y,  // 以視口座標指定的剪裁矩形左下角
        int width, // 剪裁矩形畫素寬度
        int height // 剪裁矩形畫素高度
    );

指定剪裁矩形後,需要通過glEnable(GL_SCISSOR_TEST)函式啟用剪裁測試,啟用後所有的渲染操作都只對剪裁矩形生效。

模板測試

模板測試是對每一幀中的每個畫素進行測試,通過和一個模板指定的對應畫素進行對比決定該畫素是否能通過測試,模板測試就是通過模板緩衝區中記錄的模板資訊完成的,首先需要初始化模板緩衝區,這可以通過渲染幾何形狀並制定模板緩衝區的更新方式來完成;接下來就可以使用指定的模板緩衝區中的模板資訊進行其他的繪製了。

模板測試原理,比如模板緩衝區如圖所示,當繪製圖形時設定模板緩衝區值等於1時才繪製的話,那麼接下來繪製的時候就只會在模板緩衝區設定為1的部分繪製,其他位置的片元都會被丟棄。
模板測試原理
使用glEnable(GL_STENCIL_TEST)函式啟用模板測試,同時使用glClear和glClearStencil設定模板緩衝區模板值。

使用函式glStencilFunc控制模板測試的運算子

public static native void glStencilFunc(
        int func, // 指定模板測試的比較函式
        int ref,  // 指定模板測試比較值
        int
mask // 指定在於參考值比較之前與模板緩衝區中的值進行&運算 );

比較函式取值

比較函式 含義
GL_NEVER 從不通過模板測試
GL_ALWAYS 總是通過模板測試
GL_LESS 只有參考值<(模板快取區的值&mask)時才通過
GL_LEQUAL 只有參考值<=(模板快取區的值&mask)時才通過
GL_EQUAL 只有參考值=(模板快取區的值&mask)時才通過
GL_GEQUAL 只有參考值>=(模板快取區的值&mask)時才通過
GL_GREATER 只有參考值>(模板快取區的值&mask)時才通過
GL_NOTEQUAL 只有參考值!=(模板快取區的值&mask)時才通過

設定了模板測試的方式後,還需要設定模板測試通過或是未通過時OpenGL ES將採取什麼樣的操作,使用glStencilOp函式進行設定。

public static native void glStencilOp(
        int fail,  // 指定片元未通過模板測試時,對應模板緩衝區模板值(畫素)的操作
        int zfail,  // 指定片元通過模板測試但沒有通過深度測試時的操作
        int zpass  // 既通過了模板測試又通過了深度測試時執行的操作
    );

對模板緩衝區中的模板值(畫素值)所執行的操作

模板操作 含義
GL_KEEP 保持當前的模板快取區值
GL_ZERO 把當前的模板快取區值設為0
GL_REPLACE 用glStencilFunc函式所指定的參考值替換模板引數值
GL_INCR/GL_INCR_WRAP 增加當前的模板快取區值,已經是最大值保持不變/變為0
GL_DECR/GL_DECR_wrap 減少當前的模板快取區值,已經是最大值保持不變/變為0
GL_INVERT 將當前的模板快取區值進行逐位的翻轉

利用模板測試可以達到剪裁效果,並且可以實現不規則的剪裁。

  • 開啟模板測試,使用glClear方法設定模板快取區中的模板值都為0,然後設定模板測試的操作。
GLES20.glStencilFunc(GLES20.GL_ALWAYS, 1, 1); // 總是通過
GLES20.glStencilOp(GLES20.GL_KEEP, GLES20.GL_KEEP, GLES20.GL_REPLACE); // 通過測試未設定深度測試,認為也通過深度測試,因此是GL_REPLACE,即用glStencilFunction設定的參考值1來代替深度緩衝區中的值

進行上面的設定後再繪製不規則的剪裁區域,繪製玩該剪裁區域後,該區域對應的模板緩衝區的模板值為1,其他區域對應的模板緩衝區中的模板值為0,

  • 重新設定模板測試引數
GLES20.glStencilFunc(GLES20.GL_EQUAL, 1, 1); // 只有模板緩衝區中的模板值為1的地方才被繪製
GLES20.glStencilOp(GLES20.GL_KEEP, GLES20.GL_KEEP, GLES20.GL_KEEP);

進行上面的設定後,只有模板緩衝區中的模板值為1的地方才被繪製,也就是隻有處於該不規則的圖形範圍內的部分會被繪製,其他位置的片元都會被丟棄。

繪製一個矩形,用該矩形產生該區域內的模板緩衝區的資料,然後再設定符合需求的模板引數設定,比如模板值為1時通過測試,再繪製三角形。關鍵程式碼在onDrawFrame函式中

GLES20.glClear( GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_STENCIL_BUFFER_BIT);
Matrix.setIdentityM(mModuleMatrix, 0);
Matrix.rotateM(mModuleMatrix, 0, xAngle, 1, 0, 0);
Matrix.rotateM(mModuleMatrix, 0, yAngle, 0, 1, 0);
Matrix.multiplyMM(mViewProjectionMatrix, 0, mProjectionMatrix, 0, mViewMatrix, 0);
Matrix.multiplyMM(mMVPMatrix, 0, mViewProjectionMatrix, 0, mModuleMatrix, 0);
// 未開啟模板測試
if (!openStencilTest) {
    mShape.drawRanctangle(mMVPMatrix);
    mShape.drawTrangle(mMVPMatrix);
} else {
    // 使用模板測試
    GLES20.glEnable(GLES20.GL_STENCIL_TEST);
    GLES20.glClear(GLES20.GL_STENCIL_BUFFER_BIT);
    GLES20.glStencilFunc(GLES20.GL_ALWAYS, 1, 0xff); // 總是通過
    GLES20.glStencilOp(GLES20.GL_KEEP, GLES20.GL_KEEP, GLES20.GL_REPLACE);
    mShape.drawRanctangle(mMVPMatrix);
    GLES20.glStencilFunc(GLES20.GL_EQUAL, 1, 0xff); // 只有模板緩衝區中的模板值為1的地方才被繪製
    GLES20.glStencilOp(GLES20.GL_KEEP, GLES20.GL_KEEP, GLES20.GL_KEEP);
    mShape.drawTrangle(mMVPMatrix);
    GLES20.glDisable(GLES20.GL_STENCIL_TEST);
}

產生的的效果如圖所示
模板測試

程式碼

深度測試

深度就是畫素點在距離攝象機的距離,對應的也有一個深度緩衝區儲存著每個畫素點(繪製在螢幕上的)的深度值,深度值越大,表示離攝像機越遠。深度緩衝區中儲存著渲染表面上每個畫素與視點最近物體的距離值。

如果沒有深度緩衝區,當要繪製多個物體時,必須先繪製遠處的物體再繪製近處的物體,因為如果先繪製近處的物體,那麼後繪製的遠處的物體會覆蓋掉近處的物體。而有了深度緩衝區後,繪製的順序就不重要了,因為深度緩衝區代表了物體離視點的距離

深度測試的預設做法:對於每個新的輸入片元,將其與視點的距離和深度緩衝區中的值進行比較,如果輸入的片元的深度值小於深度緩衝區中的深度值,則表示它離視點更近,則輸入片元的深度將代替深度緩衝區中的值,其顏色值將代替顏色緩衝區中的顏色值。否則,表示當前要繪製的片元在已繪製的部分物體後面,則無需繪製該圖形,即丟棄。

啟用深度測試glEnable(GL_DEPTH_TEST)

  • glClear(GL_DEPTH_BUFFER_BIT) 清空 Depth Buffer (賦值為1.0),通常清空 Depth Buffer 和 Color Buffer 同時進行。
  • glClearDepthf(float depth) 指定清空 Depth Buffer 是使用的值,預設為 1.0,通常無需改變這個值。

混合

混合就是把某一畫素位置原來的顏色和將要畫上去的顏色通過某種方式混在一起。 即某個片元的顏色(源片元)和顏色緩衝區中的畫素顏色(目標片元中的畫素顏色)進行混合。

Cfinal=fsourceCsourceopfdestinationCdestination
其中, fsource Csource 分表表示輸入的片元的比例因子和顏色, fdestination Cdestination 分表表示顏色緩衝區中的對應位置的畫素比例因子和顏色

使用函式glBlendFunc和glBlendFuncSeparate設定混合因子

public static native void glBlendFunc(
        int sfactor, // 源片元的混合因子
        int dfactor  // 目標畫素的混合因子
    );
public static native void glBlendFuncSeparate(
        int srcRGB,  // 源片元的RGB顏色分量的混合因子
        int dstRGB,  // 目標畫素的RGB顏色分量混合因子
        int srcAlpha,  // 源片元的alpha值的混合因子
        int dstAlpha   // 目標畫素的alpha值的混合因子
    );

混合因子

混合係數列舉值 RGB混合因子 Alpha混合因子
GL_ZERO [0,0,0] 0
GL_ONE [1,1,1] 1
GL_SRC_COLOR [Rs,Gs,Bs] As
GL_ONE_MINUS_SRC_COLOR [1-Rs,1-Gs,1-Bs] 1-As
GL_DST_COLOR [Rd,Gd,Bd] Ad
GL_ONE_MINUS_DST_COLOR [1-Rd,1-Gd,1-Bd] 1-Ad
GL_SRC_ALPHA [As,As,As] As
GL_SRC_MINUS_SRC_ALPHA [1-As,1-As,1-As] 1-As
GL_DST_ALPHA [Ad,Ad,Ad] Ad
GL_SRC_MINUS_DST_ALPHA [1-Ad,1-Ad,1-Ad] 1-Ad
GL_SRC_ALPHA_STAURATE [f,f,f] f=min(As,1-Ad) 1

(Rs, Gs, Bs, As)是與輸入片段顏色相關的顏色分量,(Rd, Gd , Bd, Ad)是與輸入片段顏色相關的顏色分量, (Rc,Gc,Bc,Ac)是通過 glBlendColor 設定的顏色常量。 使用 GL_SRC_ALHPA_SATURATE時,計算出來的最小值只適用於源顏色。

public static native void glBlendColor(
        float red, float green, float blue, float alpha // 指定常量混合顏色的分量值
    );

混合因子取值含義

  • glBlendFunc(GL_ONE, GL_ZERO) 只取源顏色,這也是預設值。
  • glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) 典型的半透明遮擋效果,如果源色 alpha 為0,則取目標色,如果源色alpha為1,則取源色,最終繪製物體的顏色與物體的透明度有關。
  • glBlendFunc(GL_SRC_COLOR, GL_ONE_MINUS_SRC_COLOR) 實現濾鏡效果,透過有色眼鏡觀察事物的效果。

輸入片元和畫素顏色乘以各自的混合因子後,由函式glBlendEquation和glBlendEquationSeparate指定的運算來進行組合。預設情況下,混合後的顏色使用GL_FUNC_ADD運算進行加和。

public static native void glBlendEquation(
        int mode  // 對源片元和目標畫素的操作
    );
public static native void glBlendEquationSeparate(
        int modeRGB,  // RGB分量的混合操作
        int modeAlpha  // alpha分量的混合操作
    );

上面的操作可以除了GL_FUNC_ADD還可以使用GL_FUNC_SUBTRACT、GL_FUNC_REVERSE_SUBTRACT,GL_FUNC_SUBTRACT表示從輸入的片元值中減去顏色緩衝區中的畫素值,GL_FUNC_REVERSE_SUBTRACT表示從當前顏色緩衝區中的畫素值減去輸入片元的顏色。

在上面模板測試的程式碼的基礎上,修改onDrawFrame中增加混合部分的程式碼

if (!openStencilTest) {
    mShape.drawRanctangle(mMVPMatrix);
    GLES20.glEnable(GLES20.GL_BLEND);
    GLES20.glBlendFunc(GLES20.GL_SRC_COLOR, GLES20.GL_ONE_MINUS_SRC_COLOR);
    mShape.drawTrangle(mMVPMatrix);
    GLES20.glDisable(GLES20.GL_BLEND);
} else {
    GLES20.glEnable(GLES20.GL_STENCIL_TEST);
    GLES20.glClear(GLES20.GL_STENCIL_BUFFER_BIT);
    GLES20.glStencilFunc(GLES20.GL_ALWAYS, 1, 0xff);
    GLES20.glStencilOp(GLES20.GL_KEEP, GLES20.GL_KEEP, GLES20.GL_REPLACE);
    mShape.drawRanctangle(mMVPMatrix);
    // 開啟混合
    GLES20.glEnable(GLES20.GL_BLEND);
    // 使用的模式為兩個顏色混合
    GLES20.glBlendFunc(GLES20.GL_SRC_COLOR, GLES20.GL_ONE_MINUS_SRC_COLOR);
    GLES20.glStencilFunc(GLES20.GL_EQUAL, 1, 0xff);
    GLES20.glStencilOp(GLES20.GL_KEEP, GLES20.GL_KEEP, GLES20.GL_KEEP);
    mShape.drawTrangle(mMVPMatrix);
    GLES20.glDisable(GLES20.GL_STENCIL_TEST);
    GLES20.glDisable(GLES20.GL_BLEND);  
}

產生如下的效果
混合

程式碼