前言
之前我們繪制的圖形都是只有一個顏色,而本章節我們繪制一個正方形,并且給圖形上漸變色,讓簡單的圖形變得絢麗些。
原理
在基礎概念的課程中,我們講解了渲染管道的流程,現在我們再回顧一下。
之前的章節我們都了解使用過了uniform、attribute這兩個限定符,現在我們再了解下varying這個限定符。
- attritude:一般用于各個頂點各不相同的量。如頂點位置、紋理坐標、法向量、顏色等等。
- uniform:一般用于對于物體中所有頂點或者所有的片段都相同的量。比如光源位置、統一變換矩陣、顏色等。
- varying:表示易變量,一般用于頂點著色器傳遞到片段著色器的量。
從上圖我們了解到,varying是從頂點著色器傳遞給片段著色器的變量數據,但這不夠嚴謹準確,接下來仔細分析下這個流程。
我們舉例的場景是這樣的:有一條線段,有2個頂點,頂點A是紅色,頂點B是藍色,他們做漸變色處理。相關代碼如下:
private static final String VERTEX_SHADER = "" +
"uniform mat4 u_Matrix;\n" +
"attribute vec4 a_Position;\n" +
// a_Color:從外部傳遞進來的每個頂點的顏色值
"attribute vec4 a_Color;\n" +
// v_Color:將每個頂點的顏色值傳遞給片段著色器
"varying vec4 v_Color;\n" +
"void main()\n" +
"{\n" +
" v_Color = a_Color;\n" +
" gl_Position = u_Matrix * a_Position;\n" +
"}";
private static final String FRAGMENT_SHADER = "" +
"precision mediump float;\n" +
// v_Color:從頂點著色器傳遞過來的顏色值
"varying vec4 v_Color;\n" +
"void main()\n" +
"{\n" +
" gl_FragColor = v_Color;\n" +
"}";
- 頂點著色器 : 每個頂點都執行一次,比如我們繪制一個線段,包含了2個頂點,那么就是執行2次頂點著色器。而我們這里傳遞給頂點著色器的數據包含了每個頂點的位置、顏色。
- 組裝圖元:將頂點連接,根據需求繪制頂點、線段、三角形,本次案例是線段。
- 光柵化圖元:關鍵! 在光柵化圖元的時候,將兩個頂點之間的線段分解成大量的小片段,varying數據在這個過程中計算生成,記錄在每個片段中(而不是從頂點著色器直接傳遞給片段著色器)。
- 片段著色器:每個片段都計算一次,假如是線段中間的片段,那么傳遞過來的varying值是紫色的。
所以,梳理下上面的流程:頂點的位置(attribute)、顏色(attribute) → 頂點著色器 → 光柵化:計算出每個片段的具體顏色值 → 片段著色器
代碼實現
了解了原理、GLSL代碼后,其實Java層的部分和之前差不多,應該部分讀者可以自己寫出來了。
這次課程的案例代碼是一個彩色的正方形,GLSL代碼如上,Java代碼如下。
private static final float[] POINT_DATA = {
-0.5f, -0.5f,
0.5f, -0.5f,
-0.5f, 0.5f,
0.5f, 0.5f,
};
private static final float[] COLOR_DATA = {
// 一個頂點有3個向量數據:r、g、b
1f, 0.5f, 0.5f,
1f, 0f, 1f,
0f, 1f, 1f,
1f, 1f, 0f,
};
/**
* 坐標占用的向量個數
*/
private static final int POSITION_COMPONENT_COUNT = 2;
/**
* 顏色占用的向量個數
*/
private static final int COLOR_COMPONENT_COUNT = 3;
@Override
public void onSurfaceCreated(GL10 glUnused, EGLConfig config) {
// 省略部分代碼
int aPositionLocation = getAttrib("a_Position");
int aColorLocation = getAttrib("a_Color");
mProjectionMatrixHelper = new ProjectionMatrixHelper(mProgram, "u_Matrix");
mVertexData.position(0);
GLES20.glVertexAttribPointer(aPositionLocation,
POSITION_COMPONENT_COUNT, GLES20.GL_FLOAT, false, 0, mVertexData);
GLES20.glEnableVertexAttribArray(aPositionLocation);
mColorData.position(0);
GLES20.glVertexAttribPointer(aColorLocation,
COLOR_COMPONENT_COUNT, GLES20.GL_FLOAT, false, 0, mColorData);
GLES20.glEnableVertexAttribArray(aColorLocation);
}
@Override
public void onDrawFrame(GL10 glUnused) {
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, POINT_DATA.length / POSITION_COMPONENT_COUNT);
}
效果如下:
數據傳遞格式優化
上面我們在描述頂點的信息時候,用了2個數組去分別描述位置、顏色兩個信息。那么我們就得去確保兩個數組中頂點的位置和顏色是否一一對應,一定錯亂就會得不到想要的效果。除了2個數組存兩種信息的方式外,我們還可以選擇一個數組存兩種信息的方式。可以采用“頂點1位置+頂點1顏色+頂點2位置+頂點2顏色......”這樣的方式去存儲。代碼如下:
private static final float[] POINT_DATA = {
// 一個頂點有5個向量數據:x、y、r、g、b
-0.5f, -0.5f, 1f, 0.5f, 0.5f,
0.5f, -0.5f, 1f, 0f, 1f,
-0.5f, 0.5f, 0f, 1f, 1f,
0.5f, 0.5f, 1f, 1f, 0f,
};
這樣我們每個頂點的顏色和位置從代碼層面上的清晰度就比較明確了。
而在將這些數據傳遞給GLSL中,則會稍微復雜點,我們需要引入跨距“Stride”這個概念。
/**
* 坐標占用的向量個數
*/
private static final int POSITION_COMPONENT_COUNT = 2;
/**
* 顏色占用的向量個數
*/
private static final int COLOR_COMPONENT_COUNT = 3;
/**
* 數據數組中每個頂點起始數據的間距:數組中每個頂點相關屬性占的Byte值
*/
private static final int STRIDE =
(POSITION_COMPONENT_COUNT + COLOR_COMPONENT_COUNT) * BYTES_PER_FLOAT;
@Override
public void onSurfaceCreated(GL10 glUnused, EGLConfig config) {
// 省略部分代碼......
mVertexData.position(0);
GLES20.glVertexAttribPointer(aPositionLocation,
POSITION_COMPONENT_COUNT, GLES20.GL_FLOAT, false, STRIDE, mVertexData);
GLES20.glEnableVertexAttribArray(aPositionLocation);
// 將數組的初始讀取位置右移2位,所以數組讀取的順序是r1, g1, b1, x2, y2, r2, g2, b2...
mVertexData.position(POSITION_COMPONENT_COUNT);
// COLOR_COMPONENT_COUNT:從數組中每次讀取3個向量
// STRIDE:每次讀取間隔是 (2個位置 + 3個顏色值) * Float占的Byte位
GLES20.glVertexAttribPointer(aColorLocation,
COLOR_COMPONENT_COUNT, GLES20.GL_FLOAT, false, STRIDE, mVertexData);
GLES20.glEnableVertexAttribArray(aColorLocation);
}
現在我們結合上面方法內第二段代碼中,實現顏色數據傳遞的內容,重新再講解下glVertexAttribPointer這個方法的參數要求。
- 頂點信息索引,位置、紋理坐標、法向量、顏色等等 - aColorLocation,頂點顏色索引
- 每個頂點屬性需要關聯的分量個數(必須為1、2、3或者4。初始值為4。) - |COLOR_COMPONENT_COUNT,顏色RGB需要3個向量
- 數據類型 - GLES20.GL_FLOAT,浮點類型
- 指定當被訪問時,固定點數據值是否應該被歸一化(GL_TRUE)或者直接轉換為固定點值(GL_FALSE)(只有使用整數數據時)
- 指定連續頂點屬性之間的偏移量。如果為0,那么頂點屬性會被理解為:它們是緊密排列在一起的。初始值為0。- STRIDE:每次讀取間隔是 (2個位置 + 3個顏色值) * Float占的Byte位 = 5 * 4 = 20.
- 數據緩沖區 - mVertexData,頂點數據,包含了位置和顏色
場景應用
在頂點數據較多,且屬性數據不變的情況下,使用單個數組來存儲數據是個比較好的方案,而在部分數據有所變動,如頂點位置不變,顏色變化的情況下,用多個數組存儲數據會是比較好的方案。
參考
見Android OpenGL ES學習資料所列舉的博客、資料。
GitHub代碼工程
本系列課程所有相關代碼請參考我的GitHub項目GLStudio。
課程目錄
本系列課程目錄詳見 簡書 - Android OpenGL ES教程規劃