我們將以簡單的 長方形爲例, 並在最後討論當手機橫豎屏時,長方形顯示適配的問題。
在上一節中,我們講了如何對三角形進行上色。 它的關鍵點很簡單,就是通過在fragment shader裏定義一個uniform變量(unifrom是圖像管線中的全局變量,在圖形渲染中,其值不會被改變)。在圖形管線中,越是前面的shader越涉及到頂點的操作。 在光柵化之前,經過一些座標變換和燈光的顏色操作(T&L), 着色器通過生成新的頂點,完成對頂點屬性的賦值,這種賦值是通過頂點插值完成的。 如下圖所示,
假如在左邊的頂點定義爲紅色(rgb: 1, 0, 0),右邊頂點定義爲白色(rgb: 1, 1, 1)。 那麼, 左右頂點之間的頂點的顏色就會是一個線性插值滿足如下等時關係:
1
|
Color_point
= rightColor *x + (1-x)*leftColor; |
在光柵化之後,空間中的這些點被映射到屏幕上,fragment其實就相當於像素, 關於這些像素的位置、顏色信息其實都存在相應的buffer中,然後經過fragment shader和 blending,最終會將具有顏色的像素顯示在屏幕上。 如果,我們在fragment shader中定義了uniform變量,將其賦值給gl_FragColor輸出, 那麼,它將刷新每個fragment的顏色。如果,我們想讓每個fragment都具有自己不同的顏色,通過在vertex shader中定義每個頂點的顏色屬性,可以簡單的 實現這一目的。
修改simple_vertex_shader.glsl:
1
2
3
4
5
6
7
8
9
10
|
attribute
vec4 a_Position; attribute
vec4 a_Color; varying
vec4 v_Color; void main() { v_Color
= a_Color; gl_Position
= a_Position; } |
1
2
3
4
5
6
7
|
precision
mediump float ; varying
vec4 v_Color; void main() { gl_FragColor
= v_Color; } |
1
2
3
4
5
6
|
float []
tableVerticesWithTriangles = { //X,
Y, Z , R, G, B - 0 .5f,
- 0 .5f, 1 .0f, 0 .0f, 0 .0f,
0f,
0 .5f, 0 .0f, 1 .0f, 0 .0f, 0 .5f,
- 0 .5f, 0 .0f, 0 .0f, 1 .0f //
Triangle 1 }; |
通過上圖我們知道,shader中的頂點屬性與我們輸入的頂點數據之間的關係,vertex shader中定義的頂點屬性是相應的放在一個頂點數組中,每個屬性都分別指向GPU的一段內存。通過以下語句我,可以知道shader中的頂點屬性的這種指向關係。
在onSurfaceCreated最後添加:
1
2
3
4
5
6
7
8
9
10
|
aColorLocation
= glGetAttribLocation(program, A_COLOR); aPositionLocation
= glGetAttribLocation(program, A_POSITION); vertexData.position( 0 ); //
指向頂點位置數據起始的位置 glVertexAttribPointer(aPositionLocation,
NUM_POSITION_COMPONENT, GL_FLOAT, false ,
STRIDE, vertexData); glEnableVertexAttribArray(aPositionLocation); vertexData.position(NUM_POSITION_COMPONENT); //
指向頂點顏色數據起始的位置 glVertexAttribPointer(aColorLocation,
NUM_COLOR_COMPONENT, GL_FLOAT, false ,
STRIDE, vertexData); glEnableVertexAttribArray(aColorLocation); |
即通過vertexData的position 操作修改的數據的起始位置,即指向不同的內存段,而Stride參數表示一個頂點所有屬性佔用的內存。還需記住的一點是,我們輸入的頂點數據在內存中是連續存放的
接着,在MyGLRenderer.java類中定義如下私有變量:
1
2
3
4
5
6
7
|
private static final int NUM_POSITION_COMPONENT
= 2 ; private static final int NUM_COLOR_COMPONENT
= 3 ; private static final int STRIDE
= (NUM_POSITION_COMPONENT+NUM_COLOR_COMPONENT)*BYTES_PER_FLOAT; private static final String
A_COLOR = "a_Color" ; private int aColorLocation; |
最後,修改onDrawFrame:
1
2
3
4
5
6
|
public void onDrawFrame(GL10
gl) { glClear(GL_COLOR_BUFFER_BIT); //
glUniform4f(uColorLocation, 1.0f, 0.0f, 1.0f, 1.0f); glDrawArrays(GL_TRIANGLES, 0 , 3 ); } |
由上面的步驟,我們可以總結一下寫opengl的流程: 定義需求--》在shader中添加相應的屬性和算法--》在onSurfaceCreated裏添加獲取shader裏相應屬性的位置信息和數據--》在onDrawFrame裏完成相應的操作。
下面,我們將實現一個小例子: 這個小例子實現的是一箇中間發光的桌面。 我們將先實現畫一個長方形,然後,通過顏色漸變完成發光效果。由於glDrawArrays的圖形單元中,只接受點,線, 三角形等基本的圖形,因此如果我們想畫一個長方形,只需畫兩個三角形即可。
按照之前總結的流程, 首先修改shader。因爲我們還只是用到兩個頂點屬性,不用其它操作,因此無需更改。 然後, 更改頂點數據: 用兩個三角形畫一個長方形的順序如下:
即按照 1-2-3 3-4-2畫兩個三角形即可。 但是根據需要,我們需要在長方形的中間高亮。 考慮到之前的顏色漸變,我們應該畫如下的圖:
即按照0-1-2, 0-2-3, 0-3-4, 0-4-1, 然後爲每個頂點賦予不同的顏色, 0點的顏色最亮即可。 但是一次畫這麼多三角形需要重複定義每個頂點數據,很費勁。好在opengl爲我們提供了GL_TRIANGLE_FAN,這樣的基本畫法, 這樣我們只需按照0-1-2-3-4-1定義頂點數據即可。如下,
1
2
3
4
5
6
7
|
//
X, Y, R, G, B 0f,
0f, 1 .0f, 1 .0f, 1 .0f, - 0 .5f,
- 0 .75f, 0 .7f, 0 .7f, 0 .7f, 0 .5f,
- 0 .75f, 0 .7f, 0 .7f, 0 .7f, 0 .5f, 0 .75f, 0 .7f, 0 .7f, 0 .7f, - 0 .5f, 0 .75f, 0 .7f, 0 .7f, 0 .7f, - 0 .5f,
- 0 .75f, 0 .7f, 0 .7f, 0 .7f, |
1
|
glDrawArrays(GL_TRIANGLE_FAN, 0 , 6 ); |
可以發現: 豎屏有些拉長了, 橫屏還好一點。這是因爲我們定義的長方形 長寬比爲1.5:1,而我手機的 長寬比 1.7777:1, 我們已經知道我們所定義的點都會在X, Y軸爲(-1, 1)的範圍內, 這種設備歸一化座標不受屏幕分辨率的影響。但是glViewPort會考慮屏幕分辨率重新把點映射會屏幕座標,如果我們保持寬(X軸)是1的情況下, 那麼在豎屏狀態下,它的高(Y軸)就拉伸了1.77倍。 因此,我們需要對頂點進行操作,即使得Y軸座標能夠縮小1.77倍, 然後再經過設備歸一化,之後經過glViewPort就正常了。
我們可以利用一個被稱爲正交投影的矩陣實現這一步。 百度百科是如此定義正交投影的: 投影線垂直於投影面的投影屬於正交投影 ,也稱爲平行投影。即這種變換不會產生近大遠小的感覺,非常適合於2D的圖像變換。
到這裏,我們需要接觸一些矢量, 矩陣的概念。 OpenGL經常會與這兩個東西打交道。在解決手機橫豎屏適配問題之前,也是爲以後進入3D世界打下基礎。下面幾節,我會主要介紹這兩個概念以及一些關於座標系轉換及OpenGL最終如何成像的問題。
————————————————————————————————————
ARVR技術交流羣:129340649
歡迎加入!