你們好,下面和大學一塊兒學習如何使用頂點着色器vertex shader
來作頂點變換,在個人github上有一個項目OpenGLES2.0SamplesForAndroid
,我會不斷地編寫學習樣例,文章和代碼同步更新,歡迎關注,連接:github.com/kenneycode/…git
在以前的例子中,咱們都使用到了頂點着色器vertex shader
,但只是簡單地用了一下,把輸入的頂點座標又原樣地輸出了,沒有作任何操做,這篇文章給你們介紹如何在vertex shader
作頂點變換。github
咱們先看了解一下OpenGL的渲染管線(pipeline): 編程
這張圖展現了咱們調用OpenGL的drawXXX()
方法後執行的流程,咱們傳遞的頂點首先會通過頂點着色器
vertex shader
的處理,通常會在裏面作頂點變換相關的邏輯,而後進行圖元裝配,再通過幾何着色器
geometry shader
,這個着色器相對來講使用得少一些,可暫時先忽略,而後接下來就是光柵化,所謂光柵化就是把咱們要渲染的圖像打碎成屏幕上的像素,由於最終要顯示到屏幕上,就必須將圖形對應到像素上,光柵化完成後,咱們就有了要渲染的圖形對應的像素,此時像素尚未顏色,須要咱們填上顏色,這時就到達到了片斷着色器
fragment shader
,在
fragment shader
中咱們一般進行顏色的計算,肯定對應的像素顯示什麼顏色,
fragment shader
將在下篇文章中介紹。
在整個渲染管線中,vertex shader
、geometry shader
和fragment shader
這三部分是可編程分部,可編寫shader
代碼實現相應的功能,咱們目前重點關注vertex shader
和fragment shader
。bash
這裏特別注意一點,咱們的shader
代碼並非像普通程序那樣,一次性輸入全部頂點,而後再輸出,例如對於vertex shader
,咱們傳遞了3個頂點,並非3個頂點一塊兒執行一次vertex shader
,而是分別對這3個頂點執行一次,也就是執行了3次。對於fragment shader
也是相似的,並非執行一次爲全部的像素填充顏色,而是對每一個像素都執行一次。這個特色有時讓初學者感到困惑。編程語言
先來回顧一下咱們簡單的vertex shader
:post
precision mediump float;
attribute vec4 a_Position;
void main() {
gl_Position = a_Position;
}
複製代碼
第一行,咱們聲明瞭這個shader
使用的精度,mediump
表示使用中等精度,一些對精度要求很高的應用,能夠聲明高精度。學習
第二行,咱們聲明瞭一個attribute vec4 a_Position
變量,它代表這是一個attribute
類型的四維向量,什麼attribute
類型?上文提到若是咱們傳遞了3個頂點,就會對這3個頂點分別執行一次vertex shader
,在一次執行中,這個attribute
類型的變量所對應的就是這3個頂點中的某個頂點,與此相對的是uniform
變量,uniform
變量在全部vertex shader
的執行過程當中都是同一個值,在本文中咱們也會遇到。ui
這裏爲何一個頂點是四維向量?咱們不是隻傳了二維的x、y
座標嗎?在OpenGL中,頂點老是四維的,即x、y、z、w
,其中x、y、z
不傳的話默認是0,w
不傳的話默認是1,w
是用來作歸一化(標準化)的,後續文章會有介紹。spa
回看咱們以前《Android OpenGL ES 2.0 手把手教學(1)- Hello World!》例子,咱們有這樣一句:3d
// 指定a_Position所使用的頂點數據
// Specify the vertex data of a_Position
GLES20.glVertexAttribPointer(location, 2, GLES20.GL_FLOAT, false,0, buffer)
複製代碼
其中第2個參數就是指定了一個頂點有多少個成份,所以在vertex shader
中,vec4 a_Position
接受的只有x、y
,z
和w
保持默認值。
咱們繼續往下看,vertex shader
中包含一個main()
方法做爲入口,和不少編程語言相似。gl_Position
是vertex shader
的一個內置變量,表示vertex shader
的輸出,在咱們以前的例子,是直接將輸入的頂點原樣又輸出了,本文將對頂點作變換,先看個簡單的例子:
precision mediump float;
attribute vec4 a_Position;
void main() {
gl_Position = a_Position + vec4(0.3, 0.3, 0, 0);
}
複製代碼
咱們給頂點加上了一個偏移量,來看看效果:
能夠看到,三角形發生的偏移,接下來,咱們來完善一下,將平移、縮放和旋轉一塊兒寫到vertex shader
中,爲了計算上的方便,咱們使用平移矩陣、縮放矩陣和旋轉矩陣,這些矩陣的寫法是數學上的知識,並非OpenGL特有的,這裏就不展開講了,來看看加入變換矩陣後的vertex shader
:
precision mediump float;
attribute vec4 a_Position;
uniform vec2 u_Translate;
uniform float u_Scale;
uniform float u_Rotate;
void main() {
mat4 translateMatrix = mat4(1.0, 0.0, 0.0, 0.0,
0.0, 1.0, 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
u_Translate.x, u_Translate.y, 0.0, 1.0);
mat4 scaleMatrix = mat4(u_Scale, 0.0, 0.0, 0.0,
0.0, u_Scale, 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
0.0, 0.0, 0.0, 1.0);
mat4 rotateMatrix = mat4(cos(u_Rotate), sin(u_Rotate), 0.0, 0.0,
-sin(u_Rotate), cos(u_Rotate), 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
0.0, 0.0, 0.0, 1.0);
gl_Position = translateMatrix * rotateMatrix * scaleMatrix * a_Position;
}
複製代碼
咱們向vertex shader
中傳遞平移量u_Translate
、縮放量u_Scale
和旋轉量u_Rotate
(單位是弧度),前面演示過了平移,下面咱們利用這個vertex shader
來演示縮放,將u_Scale
設置爲0.5,平移和旋轉設置爲0:
// 獲取字段u_Offset在shader中的位置
// Get the location of translate in the shader
val uTranslateLocation = GLES20.glGetUniformLocation(programId, "u_Translate")
// 啓動對應位置的參數
// Enable the parameter of the location
GLES20.glEnableVertexAttribArray(uTranslateLocation)
// 指定u_Offset所使用的頂點數據
// Specify the vertex data of translate
GLES20.glUniform2f(uTranslateLocation, 0f, 0f)
// 獲取字段u_Offset在shader中的位置
// Get the location of u_Scale in the shader
val uScaleLocation = GLES20.glGetUniformLocation(programId, "u_Scale")
// 啓動對應位置的參數
// Enable the parameter of the location
GLES20.glEnableVertexAttribArray(uScaleLocation)
// 指定u_Scale所使用的頂點數據
// Specify the vertex data of u_Scale
GLES20.glUniform1f(uScaleLocation, 0.5f)
// 獲取字段u_Offset在shader中的位置
// Get the location of u_Rotate in the shader
val uRotateLocation = GLES20.glGetUniformLocation(programId, "u_Rotate")
// 啓動對應位置的參數
// Enable the parameter of the location
GLES20.glEnableVertexAttribArray(uRotateLocation)
// 指定u_Rotate所使用的頂點數據
// Specify the vertex data of u_Rotate
GLES20.glUniform1f(uRotateLocation, Math.toRadians(0.0).toFloat())
複製代碼
來看看效果:
咱們再來看看讓它旋轉90度,平移設爲0,縮放設爲1:
奇怪,旋轉是旋轉了,但爲何感受形狀變了呢?在第一篇文章中有提到過,咱們直接給gl_Position
傳遞座標的話,這時就至關因而傳了設備標準化座標,也就是x
和y
的取值範圍都是-1~1
,而寬比長要小,一樣的一個值在x
軸上就顯示小一些,好比(0, 0.5)
這個點,旋轉90度後變成(-0.5, 0)
,x
軸上的0.5
比y
軸上的0.5
要短些,能夠作些簡單的換算,讓它變得正常,咱們傳入GLSurfaceView
寬高比,來作換算:
precision mediump float;
attribute vec4 a_Position;
uniform vec2 u_Translate;
uniform float u_Scale;
uniform float u_Rotate;
uniform float u_Ratio;
void main() {
vec4 p = a_Position;
p.y = p.y / u_Ratio;
mat4 translateMatrix = mat4(1.0, 0.0, 0.0, 0.0,
0.0, 1.0, 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
u_Translate.x, u_Translate.y, 0.0, 1.0);
mat4 scaleMatrix = mat4(u_Scale, 0.0, 0.0, 0.0,
0.0, u_Scale, 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
0.0, 0.0, 0.0, 1.0);
mat4 rotateMatrix = mat4(cos(u_Rotate), sin(u_Rotate), 0.0, 0.0,
-sin(u_Rotate), cos(u_Rotate), 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
0.0, 0.0, 0.0, 1.0);
p = translateMatrix * rotateMatrix * scaleMatrix * p;
p.y = p.y * u_Ratio;
gl_Position = p;
}
複製代碼
// 獲取字段u_Ratio在shader中的位置
// Get the location of u_Rotate in the shader
val uRatioLocation = GLES20.glGetUniformLocation(programId, "u_Ratio")
// 啓動對應位置的參數
// Enable the parameter of the location
GLES20.glEnableVertexAttribArray(uRatioLocation)
// 指定u_Ratio所使用的頂點數據
// Specify the vertex data of u_Ratio
GLES20.glUniform1f(uRatioLocation, glSurfaceViewWidth * 1.0f / glSurfaceViewHeight)
複製代碼
如今再看看效果:
如今就正常了。
咱們來作一下組合,將平移設爲(0.3, 0.3)
,縮放設爲0.5
,旋轉設爲45
度,看看效果:
代碼在我github的OpenGLES2.0SamplesForAndroid
項目中,本文對應的是SampleVertexShader
,項目連接:github.com/kenneycode/…
感謝閱讀!