創建頂點着色器和片段着色器
頂點着色器代碼示例:
attribute vec4 a_Position;
void main()
{
gl_Position = a_Position;
}
gl_Position爲當前頂點的最終位置
片段着色器代碼示例:
precision mediump float;
uniform ver4 u_Color;
void main()
{
gl_FragColor = u_Color;
}
gl_FragColor爲當前片段最終輸出的顏色
編譯着色器
因爲有頂點着色器和片段着色器,所以需要編譯兩次
- 創建着色器對象,type值爲GL_VERTEX_SHADER和GL_FRAGMENT_SHADER,分別代表頂點着色器和片段着色器類型
final int shaderObjectId = glCreateShader(type);
- 加載着色器代碼,shaderCode是前面寫的頂點着色器和片段着色器代碼,分別將其轉換成String類型即可
glShaderSource(shaderObjectId, shaderCode);
- 編譯着色器
glCompileShader(shaderObjectId);
現在頂點着色器和片段着色器都編譯好了,我們之後只需要通過shaderObjectId來調用對應的着色器對象即可使用。
將着色器連接到OpenGL ES的program中
前面創建好了頂點着色器對象和片段着色器對象,使用時兩者需要一起工作,OpenGL ES就是通過program來將兩者鏈接到一起
鏈接program的過程和編譯着色器的過程很類似,如下:
- 創建program對象
final int programObjectId = glCreateProgram();
- 附上着色器,vertexShaderId和fragmentShaderId就是前面編譯生成的着色器對象的ID
glAttachShader(programObjectId, vertexShaderId);
glAttachShader(programObjectId, fragmentShaderId);
- 鏈接program
glLinkProgram(programObjectId);
- 告訴OpenGL ES使用此program程序
glUseProgram(programObjectId);
到這裏OpenGL ES繪製任何東西到屏幕上都是使用此program程序,這時你問了那我們是怎麼設置頂點座標和片段顏色的呢?
設置頂點座標和片段顏色
我們在頂點着色器和片段着色器中分別定義了一下兩個變量a_Position和u_Color,頂點着色器設置當前頂點的最終座標位置gl_Position = a_Position;片段着色器設置當前片段的最終顏色gl_FragColor = u_Color;當OpenGL ES把兩個着色器鏈接成一個程序時,會給所有的變量都分配一個位置,我們需要獲取這些位置來設置頂點座標和片段顏色。
- 首先獲取頂點着色器的a_Position位置
我們在Renderer類中定義兩個實例變量,一個是所需獲取的着色器中的變量名,一個是代表此變量在program中的位置
private static final String A_POSITION = "a_Position";
private int aPositionLocation;
然後在Renderer的onSurfaceCreated()方法中獲取位置
aPositionLocation = glGetAttribLocation(program, A_POSITION);
- 獲取片段着色器的u_Color位置
同樣在Renderer類中定義兩個實例變量
private static final String U_COLOR = "u_Color";
private int uColorLocation;
然後在Renderer的onSurfaceCreated()方法中獲取位置
uColorLocation = glGetUniformLocation(program, U_COLOR);
現在我們獲取到了所需屬性的位置,通過這個位置我們就可以設置OpenGL ES的頂點座標和片段顏色了
- 定義頂點數據緩衝區,我們先畫個三角形
這裏是每兩個數據代表一個頂點的(x,y)座標,屏幕座標系如下,屏幕中間爲原點,(-1, -1)是屏幕的左下角,(1, -1)是右下角,(0, 1)在中上位置
float[] triangleVertices = {
-1.0f, -1.0f,
1.0f, -1.0f,
0.0f, 1.0f
};
因爲OpenGL ES作爲本地系統庫直接運行中硬件上的,所以我們的頂點數據也希望不能被垃圾回收,因此我們使用jni方式,但是爲了方便我們還可以使用另一種方式NIO,
private final FloatBuffer vertexData;
vertexData = ByteBuffer
.allocateDirect(triangleVertices.length * 4)
.order(ByteOrder.nativeOrder())
.asFloatBuffer();
vertexData.put(triangleVertices);
現在頂點數組就會從虛擬機拷貝到本地內存中,不會受到垃圾回收,當然當進程結束時這塊內存會被釋放掉。
關聯頂點數據和頂點屬性
現在頂點數據和OpenGL ES中的頂點位置都有了,我們還需要將兩者關聯起來,也就是讓OpenGL ES找到頂點座標的最終位置。
我們在Renderer的onSurfaceCreated方法中加入以下代碼:
//讓vertexData內部的指針指向數據的開頭
vertexData.position(0);處,否則可能有bug
//aPositionLocation代表對應OpenGL ES中的屬性位置,2代表這個屬性每次讀取幾個數據,GL_FLOAT代表數據類型,false表示我們暫時忽略此參數,0表示跨度,這裏就連着讀取數據,vertexData就是告訴OpenGL ES去哪讀數據
glVertexAttribPointer(aPositionLocation, 2, GL_FLOAT, false, 0, vertexData);
注意這個2,它是表示每次從vertexData讀兩個數據表示一個頂點,但是前面a_Position的類型是vec4,也就說每個頂點需要四個分量,而我們只傳了2個數據,那麼剩下的兩個分量會使用默認值(0,1),所以最後頂點的座標是(-1,-1,0,1),(1,-1,0,1),(0,1,0,1),對於0我覺得比較好理解,就是Z軸爲0,但是最後的是1怎麼說呢,它是用來做透視除法的,以後再細說,現在知道默認爲1就行
現在OpenGL ES的program程序已經和頂點數據關聯起來了,現在還需要使能頂點數據,也就是讓OpenGL ES開始更新頂點數據
在glVertexAttribPointer後面加入下述代碼
glEnableVertexAttribArray(aPositionLocation);
設置片段着色器輸出的顏色,也就是三角形的顏色
後面四個參數代表RGBA,此例爲紅色
在onDrawFrame()中加入下述代碼
glUniform4f(uColorLocation, 1.0f, 0.0f, 0.0f, 0.0f);
最後繪製出來三角形
在onDrawFrame()尾部加入下述代碼
//GL_TRIANGLES代表繪製三角形,GL_POINTS代表繪製點,GL_LINES代表繪製直線,0表示從頂點數組的起始位置讀頂點,3表示讀3個頂點,因爲一個三角形有三個頂點
glDrawArrays(GL_TRIANGLES, 0, 3);
現在運行起來屏幕上就會顯示紅色三角形了