OpenGL ES簡介: 簡述管線繪制流程

OpenGLES的術(shù)語很多,我想通過自己的語言講的簡單一些:(不知道效果怎樣,有意見的同學(xué)歡迎在評論區(qū)指教)

1、確定頂點(diǎn),紋理坐標(biāo):

任何的圖形: 點(diǎn)、線、幾何圖形、空間幾何圖形,都可以用幾個三角形來構(gòu)成。
三個頂點(diǎn)可以確定一個三角形,所以通過一系列頂點(diǎn)坐標(biāo)的組合,我們可以繪制出任何圖形。


image

紋理簡單來說就是一張圖片。
紋理坐標(biāo)就是圖片上的點(diǎn)的坐標(biāo)。
確定紋理坐標(biāo)是指:頂點(diǎn)對應(yīng)圖片資源上的點(diǎn)的坐標(biāo)。

2、頂點(diǎn)處理:

例如某個空間圖形旋轉(zhuǎn)90°,那旋轉(zhuǎn)前后該空間圖形的頂點(diǎn)的空間坐標(biāo)發(fā)生了變化,懶惰的程序員是不會重新列舉一遍頂點(diǎn)坐標(biāo)的,我們會通過一個矩陣和原來的頂點(diǎn)坐標(biāo)來計算新的頂點(diǎn)坐標(biāo)。這一步Opengl es 通過頂點(diǎn)著色器來實現(xiàn)。

3、繪制紋理:

你可以把圖片想象成塊布,紋理坐標(biāo)就是上面的特定位置加上圖釘??,最后把圖釘按到對應(yīng)的頂點(diǎn)坐標(biāo)上。
平面上的每一個點(diǎn)都會對應(yīng)圖片上某個點(diǎn),把對應(yīng)點(diǎn)的顏色繪制到平面上,整個空間圖形就有了色彩。
這一步Opengl es 通過片元著作色器實現(xiàn)。

記住流程,我們一步一步來調(diào)用具體api:

OpenGLES api

1、確定頂點(diǎn)坐標(biāo),紋理坐標(biāo):

2018080414041192.png

Opengl es 坐標(biāo)系中(0,0)指的是畫布的中心點(diǎn),對于GLSufaceView就是View的中心點(diǎn)。x=1指的是View最右的位置,y=1指的是view最上的位置。(1,1)就是右上角的點(diǎn)。
以繪制一個平面圖形為例,一個正方形由兩個三角形組成,所以坐標(biāo)為:

{
  -1, 1//三角形1號
  -1,-1
   1, 1

   1, 1//三角形2號
  -1,-1
   1,-1
}

OpenGL有個更簡便的寫法:

{
  -1, 1//三角形1號,1號點(diǎn)
  -1,-1//2號點(diǎn)
   1, 1 //3號點(diǎn)
   1,-1//三角形2號 ,4號點(diǎn)
}

方式1:太直觀了,6個頂點(diǎn)。
方式2:前三個頂點(diǎn)組成一個三角形,后面每一個頂點(diǎn)代表一個三角形。實際上方式2的頂點(diǎn)數(shù)組會由OpenGL轉(zhuǎn)換成方式1。
偶數(shù)點(diǎn)三角形的頂點(diǎn)為 [n-1 ,n-2 ,n],奇數(shù)點(diǎn)三角形的頂點(diǎn)為 [n-2 ,n-1 ,n]
方式2最后一個頂點(diǎn)是第四個,偶數(shù)點(diǎn),三角形坐標(biāo):(1,1),( -1,-1),(1,-1)和方式1一樣。

這里必須要注意的是:頂點(diǎn)數(shù)組的書寫順序是由要求的
先提個問:一個三角形有幾個面?1個?再想想。
兩個,正反面,OpenGL中使用頂點(diǎn)順序來標(biāo)識正反面。在沒有通過api進(jìn)行設(shè)置的情況下,OpenGL認(rèn)為逆時針的頂點(diǎn)排序為正面,順時針的頂點(diǎn)排序為反面。對于平面圖形來說,渲染到反面的圖像是看不見的。
所以注意,頂點(diǎn)的書寫順序為逆時針。

上圖可以明顯的看到頂點(diǎn)坐標(biāo)對應(yīng)的紋理坐標(biāo),以方法二為例:

{//頂點(diǎn)坐標(biāo)
  -1, 1//三角形1號,1號點(diǎn)
  -1,-1//2號點(diǎn)
   1, 1 //3號點(diǎn)
   1,-1//三角形2號 ,4號點(diǎn)
}

{//紋理坐標(biāo)
 0, 0//三角形1號,1號點(diǎn)
 0,1//2號點(diǎn)
 1, 0 //3號點(diǎn)
 1,1//三角形2號 ,4號點(diǎn)
} 

2、頂點(diǎn)處理:

上面說到OpenGL中這一步通過頂點(diǎn)著色器實現(xiàn)。那么什么是頂點(diǎn)著色器呢?
著色器是運(yùn)行在OpenGL里通過GLSL(OpenGL Shading Language)語言編寫的程序。
頂點(diǎn)著色器 Vertex Shader :則是一個用來操作頂點(diǎn)數(shù)據(jù)的著色器程序。

//這是一個最普通的頂點(diǎn)著色器
 String vStr =      "attribute vec4 vPosition;" +
                    "attribute vec2 vCoordinate;" +
                    "varying vec2 aCoordinate;" +
                    "uniform mat4 vMatrix;" +
                    "void main() { " +
                    "   gl_Position = vMatrix*vPosition;" +
                    "   aCoordinate=vCoordinate;" +
                    "}";

attribute vec4:attribute聲明的變量需要開發(fā)者從外部程序輸入一個坐標(biāo)數(shù)組,這個坐標(biāo)數(shù)組構(gòu)成一個區(qū)域,而OpenGL會把區(qū)域中的一個點(diǎn)用一個多維向量(vec4就是四維(x,y,z,w),vec2就是二維(x,y))賦值給該變量。OpenGL會反復(fù)調(diào)用程序,對區(qū)域內(nèi)的每一個點(diǎn)進(jìn)行計算
uniform:聲名一個值從程序外傳入的變量。
mat4:表示變量是個4x4浮點(diǎn)矩陣,同理mat3——3x3
varying:varying變量是頂點(diǎn)著色器和片元著色器之間做數(shù)據(jù)傳遞用的。
gl_Position :頂點(diǎn)著色器的輸出,是個vec4變量。其值決定了畫筆的位置。

既然著色器是另外一種語言,想在android中運(yùn)行另一種語言編寫的程序,其中必有一番曲折:
1、創(chuàng)建一個空的OpenGLES程序

int mProgram = GLES20.glCreateProgram();

返回值是個句柄,大部分OpenGL方法的返回值都是句柄。

2、編譯著色器
變成語言語言變成可運(yùn)行程序必然需要編譯啦。

//vStr是上面的頂點(diǎn)著色器程序代碼,以String形式輸入
int  vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vStr); 
------------------------------------------------------------------------------------
private int loadShader(int type, String str) {
      //1:創(chuàng)建Shader glCreateShader
     int id = GLES20.glCreateShader(type);
     //2:指定Shader源代碼 glShaderSource
     GLES20.glShaderSource(id, str);
     // 3:編譯Shader glCompileShader
     GLES20.glCompileShader(id);
     // 4:獲取shader狀態(tài) glGetShaderiv
     private int[] compile = new int[1];//入?yún)?     GLES20.glGetShaderiv(id, GLES20.GL_COMPILE_STATUS, compile, 0);
     // 5:如果出錯 就 獲取shader日志信息 glGetShaderInfoLog
    if (compile[0] == GLES20.GL_FALSE) {
         Log.e("ES20_ERROR", GLES20.glGetShaderInfoLog(id));
         GLES20.glDeleteShader(id);
         return -1;
     } else {
         return id;
     }
}

參數(shù)str指的是我們用GLSL語言書寫的著色器程序文本字符串。
GLES20.GL_VERTEX_SHADER:指的是當(dāng)前編譯的著色器類型——為頂點(diǎn)著色器。

3、將頂點(diǎn)著色器加入到程序

GLES20.glAttachShader(mProgram, vertexShader);

4、指定當(dāng)前使用的Program

GLES20.glLinkProgram(mProgram);

5、為著色器中的變量賦值

glPosition = GLES20.glGetAttribLocation(mProgram, "vPosition");//獲取句柄,獲取一次即可
glHCoordinate = GLES20.glGetAttribLocation(mProgram, "vCoordinate");
—————————————————————————————————————— 
GLES20.glEnableVertexAttribArray(glPosition);//激活,繪制開始時調(diào)用
GLES20.glVertexAttribPointer(glPosition, 2, GLES20.GL_FLOAT, false, 0, vertexBuffer); //輸入頂點(diǎn)坐標(biāo)數(shù)據(jù)
GLES20.glEnableVertexAttribArray(glHCoordinate);
GLES20.glVertexAttribPointer(glHCoordinate, 2, GLES20.GL_FLOAT, false, 0, textureBuffer);//輸入紋理坐標(biāo)數(shù)據(jù)
——————————————————————————————
GLES20.glDisableVertexAttribArray(glPosition);//繪制完成后調(diào)用
GLES20.glDisableVertexAttribArray(glHCoordinate);//繪制完成后調(diào)用

說一下 glVertexAttribPointer:
第一個參數(shù)為目標(biāo)參數(shù)句柄。
第二個參數(shù)2表示的是:坐標(biāo)的維數(shù),2就是2維(x,y)。
第三個參數(shù)GLES20.GL_FLOAT表示輸入數(shù)據(jù)的精度。
第四個參數(shù)定義是否希望數(shù)據(jù)被標(biāo)準(zhǔn)化(歸一化)。
第五個參數(shù)數(shù)是步長(Stride),指定在連續(xù)的頂點(diǎn)屬性之間的間隔。如果傳1取值方式為0123、1234、2345……
最后一個即頂點(diǎn)坐標(biāo)數(shù)組數(shù)據(jù),以 java.nio.Buffer形式輸入。還有另外一種輸入方式這里暫不討論

vertices={
  -1, 1//三角形1號,1號點(diǎn)
  -1,-1//2號點(diǎn)
   1, 1 //3號點(diǎn)
   1,-1//三角形2號 ,4號點(diǎn)
}


ByteBuffer vbb = ByteBuffer.allocateDirect(vertices.length * 4);
vbb.order(ByteOrder.nativeOrder());
FloatBuffer  vertexBuffer = vbb.asFloatBuffer();
vertexBuffer.put(vertices);
vertexBuffer.position(0);

3、繪制紋理:

繪制紋理OpenGL 通過片元著色器實現(xiàn),下面是一個簡單的片元著色器:

String fStr =
            " precision mediump float; " +
                    "uniform sampler2D vTexture; " +
                    "varying vec2 aCoordinate;  " +
                    "void main() {" +
                    "   gl_FragColor=texture2D(vTexture,aCoordinate);" +
                    "}";

sampler2D:一個紋理引用
aCoordinate:由頂點(diǎn)作色器傳過來的紋理坐標(biāo)
texture2D()函數(shù):從紋理上取紋理坐標(biāo)對應(yīng)點(diǎn)的顏色。
gl_FragColor:紋理著色器的輸出,也就是畫筆要畫的顏色
同樣片元著色器也需要經(jīng)過編譯綁定到OpenGL的program中

int fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fStr);
GLES20.glAttachShader(mProgram, fragmentShader);

需要賦值的參數(shù)只有一個uniform sampler2D vTexture

glTexture = GLES20.glGetUniformLocation(mProgram, "vTexture");//獲取句柄
---------------------------------------------------------------------------------------------------
int[] texture = new int[1];
if (bitmap != null && !bitmap.isRecycled()) {
     //生成紋理
    GLES20.glGenTextures(1, texture, 0);
    //激活0號紋理單元 
    GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
    //綁定紋理
    GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, texture[0]);
    //設(shè)置縮小過濾為使用紋理中坐標(biāo)最接近的一個像素的顏色作為需要繪制的像素顏色
    GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST);
    //設(shè)置放大過濾為使用紋理中坐標(biāo)最接近的若干個顏色,通過加權(quán)平均算法得到需要繪制的像素顏色
    GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
    //設(shè)置環(huán)繞方向S,截取紋理坐標(biāo)到[1/2n,1-1/2n]。將導(dǎo)致永遠(yuǎn)不會與border融合
    GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
    //設(shè)置環(huán)繞方向T,截取紋理坐標(biāo)到[1/2n,1-1/2n]。將導(dǎo)致永遠(yuǎn)不會與border融合
    GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
    //根據(jù)以上指定的參數(shù),生成一個2D紋理
    GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0);
    return texture[0];
}
-----------------------------------------------------------------------------------------------------
GLES20.glUniform1i(glTexture, 0);//輸入紋理,使用0號紋理單元
-----------------------------------------------------------------------------------------------------

這里說一下紋理單元,有點(diǎn)像一個類,它里面包含很多類型的紋理成員變量,比如說常用的GL_TEXTURE_2D,GL_TEXTURE_EXTERNAL_OES。

//打個比方,不是實際代碼
TextureUnit{
GL_TEXTURE_2D textureA;
GL_TEXTURE_EXTERNAL_OES textureB;
}

我們只有通過glActiveTexture激活了紋理單元后才能通過glBindTexture方法對其中的成員變量進(jìn)行賦值。

以上GLSL的相關(guān)流程就結(jié)束了,完成了OpenGL管線的配置,但是要開始實際的繪制,我們還要調(diào)用繪制的api

GLES20.glDrawElements(GLES20.GL_TRIANGLES, indices.length, GLES20.GL_UNSIGNED_SHORT, indexBuffer);//繪制調(diào)用方式1
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);//繪制調(diào)用方式2,參數(shù)4代表輸入頂點(diǎn)數(shù)組的長度

glDrawElements 需要額外輸入索引數(shù)組 (上面的indexBuffer),其內(nèi)容指的是頂點(diǎn)的繪制順序。

{//頂點(diǎn)坐標(biāo)
  -1, 1//三角形1號,1號點(diǎn)
  -1,-1//2號點(diǎn)
   1, 1 //3號點(diǎn)
   1,-1//三角形2號 ,4號點(diǎn)
}

{
  0,1,2,  
  1,3,2
 }

以上接口都需要在GLThread中調(diào)用,通過GLSufaceVIew可以簡單創(chuàng)建GLThread環(huán)境:
OpenGL簡介:簡單的使用GLSufaceView

我在學(xué)習(xí)android 的OpenGL時寫的小栗子
https://github.com/UniqueKenzhang/AllAboutVideo

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容