在Android中使用OpenGL ES开发第(五)节:GLSL基础语法
一、前期基礎儲備
筆者之前的四篇文綜述了Android中使用OpenGL ES繪制基本圖形和實現了簡單的相機預覽,初次接觸OpenGL ES開發的讀者可能對其中新的概念比較迷惑,尤其是其中的頂點著色器(Vertex Shader)和片元著色器(Fragment Shader),我們知道,在OpenGL中頂點著色器是針對每個頂點執行一次,用于確定頂點的位置。片元著色器是針對每個片元,片元可以理解為每個像素,用于確定每個片元(像素)的顏色或者紋理。如下是在相機預覽中使用的兩個著色器的代碼段:
private final String vertexShaderCode =
"attribute vec4 vPosition;" +
"attribute vec2 inputTextureCoordinate;" +
"varying vec2 textureCoordinate;" +
"void main()" +
"{"+
"gl_Position = vPosition;"+
"textureCoordinate = inputTextureCoordinate;" +
"}";
private final String fragmentShaderCode =
"#extension GL_OES_EGL_image_external : require\n"+ //相機預覽時的片元指令
"precision mediump float;" + //相機預覽時的片元指令
"varying vec2 textureCoordinate;\n" +
"uniform samplerExternalOES s_texture;\n" + //相機預覽時的片元指令
"void main() {" +
" gl_FragColor = texture2D( s_texture, textureCoordinate );\n" +
"}";
兩段著色器中,使用的代碼不是Java代碼,語法也不是Java層面的,而是一種OpenGL特有的語法:GLSL語法
GLSL又叫OpenGL著色語言(OpenGL Shading Language),是用來在OpenGL中著色編程的語言,是一種面向過程的語言,基本的語法和C/C++基本相同,他們是在圖形卡的GPU (Graphic Processor Unit圖形處理單元)上執行的,代替了固定的渲染管線的一部分,使渲染管線中不同層次具有可編程性。比如:視圖轉換、投影轉換等。GLSL(GL Shading Language)的著色器代碼分成2個部分:Vertex Shader(頂點著色器)和Fragment Shader(片元著色器)。
在前面的文章中,我們基本上使用的都是非常簡單的著色器,基本上沒有使用過GLSL的內置函數,但是在后面我們完成其他的功能的時候應該就會用到這些內置函數了。比如說相機的各種濾鏡、各種靜態、動態貼紙,其實現少不了特定片元著色器的支持。
在筆者的文章《Android使用GPUImage實現濾鏡效果精煉詳解(一)》中通過一些示例代碼實現了調用GPUImage處理靜態圖片,為其設置各種濾鏡效果,在GitHub上找到GPUImage的開源庫打開之后,就可以發現,其實現各種濾鏡效果底層依靠的也是OpenGL,每種不同濾鏡效果,其片元著色器也是特定的,以黑白濾鏡的片元著色器為例:
public static final String GRAYSCALE_FRAGMENT_SHADER = "" +
"precision highp float;\n" +
"\n" +
"varying vec2 textureCoordinate;\n" +
"\n" +
"uniform sampler2D inputImageTexture;\n" +
"\n" +
"const highp vec3 W = vec3(0.2125, 0.7154, 0.0721);\n" +
"\n" +
"void main()\n" +
"{\n" +
" lowp vec4 textureColor = texture2D(inputImageTexture, textureCoordinate);\n" +
" float luminance = dot(textureColor.rgb, W);\n" +
"\n" +
" gl_FragColor = vec4(vec3(luminance), textureColor.a);\n" +
"}";
看起來,一頭霧水,實際上該代碼段是由OpenGL的特有語言GLSL構成的,GLSL的變量申明、GLSL語言的內置函數、GLSL的各種修飾符…只要掌握了GLSL語法,那么后期構造片元著色器會十分的便利,筆者是做相機開發的,工作中創建新濾鏡效果,離不開GPUImage,其原生的片元著色器一部分可以拿來直接使用,但是更多的都是要在其基礎上做修改的,使之符合自己的濾鏡需要,而在改造過程中,GLSL語法掌握就更為迫切了。
注:任何一種OpenGL程序本質上都可以被分為兩部分:CPU端運行的部分,采用C++、Java之類的語言編寫;以及GPU端運行的部分,使用GLSL語言編寫。
二、上代碼,具體實現
(1)基本數據類型
GLSL中的數據類型主要分為標量、向量、矩陣、采樣器、結構體、數組、空類型七種類型:
1)標量
標量表示的是只有大小沒有方向的量,在GLSL中標量只有bool、int和float三種。對于int,和C一樣,可以寫為十進制、八進制或者十六進制。對于標量的運算,我們最需要注意的是精度,防止溢出問題。
2)向量
向量我們可以看做是數組,在GLSL通常用于儲存顏色、坐標等數據,針對維數,可分為二維、三維和四位向量。針對存儲的標量類型,可以分為bool、int和float。共有vec2、vec3、vec4,ivec2、ivec3、ivec4、bvec2、bvec3和bvec4九種類型,數組代表維數、i表示int類型、b表示bool類型。需要注意的是,GLSL中的向量表示豎向量,所以與矩陣相乘進行變換時,矩陣在前,向量在后(與DirectX正好相反)。
作為顏色向量時,用rgba表示分量,就如同取數組的中具體數據的索引值。三維顏色向量就用rgb表示分量。比如對于顏色向量vec4 color,color[0]和color.r都表示color向量的第一個值,也就是紅色的分量。
作為位置向量時,用xyzw表示分量,xyz分別表示xyz坐標,w表示向量的模。三維坐標向量為xyz表示分量,二維向量為xy表示分量。
作為紋理向量時,用stpq表示分量,三維用stp表示分量,二維用st表示分量。
3)矩陣
在GLSL中矩陣擁有22、33、4*4三種類型的矩陣,分別用mat2、mat3、mat4表示。我們可以把矩陣看做是一個二維數組,也可以用二維數組下表的方式取里面具體位置的值。
4)采樣器
采樣器是專門用來對紋理進行采樣工作的,在GLSL中一般來說,一個采樣器變量表示一副或者一套紋理貼圖。所謂的紋理貼圖可以理解為我們看到的物體上的皮膚。紋理查找需要指定哪個紋理或者紋理單元將指定查找。
sampler1D // 訪問一個一維紋理
sampler2D // 訪問一個二維紋理
sampler3D // 訪問一個三維紋理
samplerCube // 訪問一個立方貼圖紋理
sampler1DShadow // 訪問一個帶對比的一維深度紋理
sampler2DShadow // 訪問一個帶對比的二維深度紋理
uniform sampler2D grass;
vcc2 coord = vec2(100, 100);
vec4 color = texture2D(grass, coord);
5)結構體
和C語言中的結構體相同,用struct來定義結構體,這是唯一的用戶定義類型。
struct light
{
vec3 position;
vec3 color;
};
light ceiling_light;
6)數組
數組知識也和C中相同,不同的是數組聲明時可以不指定大小,但是建議在不必要的情況下,還是指定大小的好。
// 創建一個10個元素的數組
vec4 points[10];
// 創建一個不指定大小的數組
vec4 points[];
points[2] = vec4(1.0); // points現在大小為3
points[7] = vec4(2.0); // points現在大小為8
7)空類型
空類型用void表示,僅用來聲明不返回任何值得函數。
數據聲明示例:
float a=1.5;
int b=2;
bool c=true;
vec2 d=vec2(1.1,2.2);
vec3 e=vec3(1.1,2.2,3.3)
vec4 f=vec4(vec3,1.1);
vec4 g=vec4(1.0); //相當于vec(1.0,1.0,1.0,1.0)
vec4 h=vec4(a,a,1.5,a);
mat2 i=mat2(1.1,1.5,2.2,4.4);
mat2 j=mat2(1.8); //相當于mat2(1.8,1.8,1.8,1.8)
mat3 k=mat3(e,e,1.2,1.4,1.6);
(2)運算符
GLSL中的運算符包括(越靠前,運算優先級越高):
索引:[ ]
前綴自加和自減:++,–-
一元非和邏輯非:~,!
加法和減法:+,-
等于和不等于:==,!=
邏輯異或:^^
三元運算符號,選擇:? :
成員選擇與混合:.
后綴自加和自減:++,–-
乘法和除法:*,/
關系運算符:>,<,=,>=,<=,<>
邏輯與:&&
邏輯或:||
賦值預算:=,+=,-=,*=,/=
(3)類型轉換
GLSL的類型轉換與C不同。在GLSL中類型不可以自動提升,比如float a=1;就是一種錯誤的寫法,必須嚴格的寫成float a=1.0,也不可以強制轉換,即float a=(float)1; 也是錯誤的寫法,但是可以用內置函數來進行轉換,如float a=float(1);還有float a=float(true);(true為1.0,false為0.0)等,值得注意的是,低精度的int不能轉換為高精度的float。
(4)限定符
觀察文首的兩段著色器,可以看到對于變量使用了不同的修飾符,以下是GLSL修飾符的主要類型:
attritude:一般用于各個頂點各不相同的量。如頂點顏色、坐標等。
uniform:一般用于對于3D物體中所有頂點都相同的量。比如光源位置,統一變換矩陣等。
varying:表示易變量,一般用于頂點著色器傳遞到片元著色器的量。
const:常量。 限定符與java限定符類似,放在變量類型之前,并且只能用于全局變量。在GLSL中,沒有默認限定符一說。
(5)流程控制
GLSL中的流程控制與C中基本相同,主要有條件判斷和各種循環:
if( )、if( )else、if( )else if( )else
while()和dowhile( )
for( ; ; )
break和continue
(6)函數修飾符
GLSL中也可以定義函數,定義函數的方式也與C語言基本相同。函數的返回值可以是GLSL中的除了采樣器的任意類型。對于GLSL中函數的參數,可以用參數用途修飾符來進行修飾,常用函數修飾符如下:
in:輸入參數,無修飾符時默認為此修飾符。
out:輸出參數。
inout:既可以作為輸入參數,又可以作為輸出參數。
(6)浮點精度
與頂點著色器不同的是,在片元著色器中使用浮點型時,必須指定浮點類型的精度,否則編譯會報錯。精度有三種,分別為:
lowp:低精度。8位。
mediump:中精度。10位。
highp:高精度。16位。
當然,也可以在片元著色器中設置默認精度,只需要在片元著色器最上面加上precision <精度> <類型>即可制定某種類型的默認精度。其他情況相同的話,精度越高,畫質越好,使用的資源也越多。
(7)著色器代碼結構
GLSL程序的結構和C語言差不多,main()方法表示入口函數,可以在其上定義函數和變量,在main中可以引用這些變量和函數。定義在函數體以外的叫做全局變量,定義在函數體內的叫做局部變量。與高級語言不同的是,變量和函數在使用前必須聲明,不能在使用的后面聲明變量或者函數。
三、GLSL內建變量和內置函數
在上面,我們了解了GLSL的基礎語法:數據類型、運算符、控制流,接下來,我們要了解另外一些關鍵的內容:GLSL為方便調用,內置了一些變量和函數,在著色器構造中,少不了這些知識的運用。在上面glsl提供了非常豐富的函數庫,供我們使用,這些功能都是非常有用且會經常用到的. 這些函數按功能區分大改可以分成7類:
(1)頂點著色器的內建變量
gl_Position:頂點坐標
gl_PointSize:點的大小,沒有賦值則為默認值1,通常設置繪圖為點繪制才有意義。
(2)片元著色器的內建變量
gl_FragCoord:當前片元相對窗口位置所處的坐標。
gl_FragFacing:bool型,表示是否為屬于光柵化生成此片元的對應圖元的正面。 輸出變量:
gl_FragColor:當前片元顏色
gl_FragData:vec4類型的數組。向其寫入的信息,供渲染管線的后繼過程使用。
GLSL提供了非常豐富的函數庫,供我們使用,這些功能都是非常有用而且經常會使用到的,這些函數按照功能區分大致可以分為7類:
(1)通用函數庫
(2)三角函數&角度
(3)指數函數
(4)幾何函數
(5)矩陣函數
(6)向量函數
(7)紋理查詢函數
紋理采樣函數中,3D在OpenGLES2.0并不是絕對支持。我們再次暫時不管3D紋理采樣函數。重點只對texture2D函數進行說明。texture2D擁有三個參數,第一個參數表示紋理采樣器。第二個參數表示紋理坐標,可以是二維、三維、或者四維。第三個參數加入后只能在片元著色器中調用,且只對采樣器為mipmap類型紋理時有效。
在看完以上的OpenGL內建變量和內置函數之后,在返回前面,查看黑白濾鏡的片元著色器,就會發現,各個變量的聲明和使用都可以看得懂了,GPUImage中每種不同類型的濾鏡實現都是采用了類似的方式,感興趣的讀者可以查看GPUImage的Android庫:android-gpuimage
四、延伸知識,JSON語法
(1)JSON簡介
? JSON指的是JavaScript對象表示法(JavaScript Object Notation)
? JSON是輕量級的文本交換格式,類似于XML,是純文本
? JSON獨立于語言,具有層級結構(值中存在值)
? JSON具有自我描述性,更易理解(人類可讀)
JSON使用JavaScript語法來描述數據對象,但是JSON仍然獨立于語言和平臺。
(2)相比于XML的不同之處
JSON比XML更小、更快、更易解析
沒有結束標簽
更短,讀寫速度更快
使用數組
不使用保留字
(3)JSON語法規則
JSON語法是JavaScript對象表示語法的子集
數據在 名稱/值 對中
數據由逗號隔開
花括號保存對象
方括號保存數組
(4)JSON 名稱/值 對
JSON 數據的書寫格式是:名稱/值對。
名稱/值對包括:字段名稱(在雙引號中),后面寫一個冒號,然后是值:
“firstName” : “John”
(5)JSON值
JSON 值可以是:
數字(整數或浮點數)
字符串(在雙引號中)
邏輯值(true 或 false)
數組(在方括號中)
對象(在花括號中)
Null
(6)JSON對象
JSON 對象在花括號中書寫:對象可以包含多個名稱/值對:
{ “firstName”:”John” , “lastName”:”Doe” }
(7)JSON 數組
JSON 數組在方括號中書寫:數組可包含多個對象:
{
“employees”: [
{ “firstName”:”John” , “lastName”:”Doe” },
{ “firstName”:”Anna” , “lastName”:”Smith” },
{ “firstName”:”Peter” , “lastName”:”Jones” }
]
}
相關語法可以搜索 GLSL 中文手冊 獲取更多的語法知識
轉載于:https://www.cnblogs.com/laughingQing/p/11058557.html
總結
以上是生活随笔為你收集整理的在Android中使用OpenGL ES开发第(五)节:GLSL基础语法的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 什么是软件测试架构师?
- 下一篇: 04 Websocket和Websock