OpenGL關(guān)鍵API

OpenGL 的基本形狀是三角形,無論是繪制形狀還是填充,都是對(duì)于圖形進(jìn)行操作

對(duì)于一個(gè)平面圖形,繪制的結(jié)果是有正反面的,

著色器語言(GLSL)主要包括兩部分:Vertex shader(定點(diǎn)著色器,負(fù)責(zé)定點(diǎn)位置與坐標(biāo)變換,即決定顯示哪個(gè)部分,以何種位置/姿態(tài)顯示),F(xiàn)ragment shader(片元著色器,負(fù)責(zé)紋理的填充與轉(zhuǎn)換,即決定顯示成什么樣子)

OpenGL ES的屏幕坐標(biāo)系
OpenGL ES是一個(gè)三維的圖形庫,但是三維的圖像要在二維平面顯示,就要經(jīng)過一定的投影變換,將三維的空間以一定的方式顯示在二維屏幕上。

image
image

上圖顯示的是OpenGL ES的屏幕坐標(biāo)系,無論是X還是Y軸,取值范圍都是[-1,1],也就是說,即便你的手機(jī)是16:9的屏幕,對(duì)于OpenGL ES來說也是一個(gè)正方形的繪制范圍(是不是很奇怪)

初始化OpenGL ES環(huán)境

OpenGL ES的使用,一般包括如下幾個(gè)步驟:

  1. EGL Context初始化
  2. OpenGL ES初始化
  3. OpenGL ES設(shè)置選項(xiàng)與繪制
  4. OpenGL ES資源釋放(可選)
  5. EGL資源釋放

Android平臺(tái)提供了一個(gè)GLSurfaceView,來幫助使用者完成第一步和第五步,由于釋放EGL資源時(shí)會(huì)自動(dòng)釋放之前申請(qǐng)的OpenGL ES資源,所以需要我們自己做的就只有2和3。

使用GLSurfaceView
我們?cè)谥鞑季种幸胍粋€(gè)GLSurfaceView,并讓他充滿整個(gè)布局,并在Activity中獲取他的實(shí)例

<android.opengl.GLSurfaceView
        android:id="@+id/surface_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
public class MainActivity extends AppCompatActivity {

    private GLSurfaceView glSurfaceView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        glSurfaceView= (GLSurfaceView) findViewById(R.id.surface_view);
    }
}

獲取實(shí)例以后,我們就可以對(duì)于這個(gè)GLSurfaceView進(jìn)行配置:

glSurfaceView.setEGLContextClientVersion(2);//設(shè)置EGL上下文的客戶端版本,因?yàn)槲覀兪褂玫氖荗penGL ES 2.0,所以設(shè)置為2

glSurfaceView.setRenderer(new GLRenderer());

glSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);//代表渲染模式,選項(xiàng)有兩種(RENDERMODE_WHEN_DIRTY,RENDERMODE_CONTINUOUSLY),一個(gè)是需要渲染(觸控事件,渲染請(qǐng)求)才渲染,一個(gè)是不斷渲染。
GLSurfaceView.Renderer接口

GLRenderer是本文中的關(guān)鍵類,實(shí)現(xiàn)了GLSurfaceView.Renderer這個(gè)接口,用來完成繪制操作。現(xiàn)在我們來看看這個(gè)類的定義:

public class GLRenderer implements GLSurfaceView.Renderer {
    //這個(gè)函數(shù)在Surface被創(chuàng)建的時(shí)候調(diào)用,每次我們將應(yīng)用切換到其他地方,再切換回來的時(shí)候都有可能被調(diào)用,在這個(gè)函數(shù)中,我們需要完成一些OpenGL ES相關(guān)變量的初始化
    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {

    }
    //每當(dāng)屏幕尺寸發(fā)生變化時(shí),這個(gè)函數(shù)會(huì)被調(diào)用(包括剛打開時(shí)以及橫屏、豎屏切換),width和height就是繪制區(qū)域的寬和高(上圖黑色區(qū)域)
    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {

    }
    //每一次繪制時(shí)這個(gè)函數(shù)都會(huì)被調(diào)用,之前設(shè)置了GLSurfaceView.RENDERMODE_CONTINUOUSLY,也就是說按照正常的速度,每秒這個(gè)函數(shù)會(huì)被調(diào)用60次,雖然我們還什么都沒做
    @Override
    public void onDrawFrame(GL10 gl) {

    }
}
private Context context;

public GLRenderer(Context context) {
    this.context = context;
}

glSurfaceView.setRenderer(new GLRenderer(this));
//用來讀取raw中的文本文件,并且以String的形式返回
public static String readRawTextFile(Context context, int resId) {
    InputStream inputStream = context.getResources().openRawResource(resId);
    try {
        BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
        StringBuilder sb = new StringBuilder();
        String line;
        while ((line = reader.readLine()) != null) {
            sb.append(line).append("\n");
        }
        reader.close();
        return sb.toString();
    } catch (IOException e) {
        e.printStackTrace();
    }
    return null;
}

我們?cè)趓aw文件夾中創(chuàng)建兩個(gè)文件,fragment_shader.glsl和vertex_shader.glsl,他們分別是片元著色器和頂點(diǎn)著色器的腳本,之前說的可編程管線,就是指OpenGL ES 2.0可以即時(shí)編譯這些腳本,來實(shí)現(xiàn)豐富的功能,兩個(gè)文件的內(nèi)容如下:

vertex_shader.glsl
attribute vec4 aPosition;
void main() {
  gl_Position = aPosition;
}
  • vec4是一個(gè)包含4個(gè)浮點(diǎn)數(shù)(float,我們約定,在OpenGL中提到的浮點(diǎn)數(shù)都是指float類型)的向量,
  • attribute表示變?cè)?用來在Java程序和OpenGL間傳遞經(jīng)常變化的數(shù)據(jù),
  • gl_Position 是OpenGL ES的內(nèi)建變量,表示頂點(diǎn)坐標(biāo)(xyzw,w是用來進(jìn)行投影變換的歸一化變量),我們會(huì)通過aPosition把要繪制的頂點(diǎn)坐標(biāo)傳遞給gl_Position
fragment_shader.glsl
precision mediump float;
void main() {
    gl_FragColor = vec4(0,0.5,0.5,1);
}
  • precision mediump float用來指定運(yùn)算的精度以提高效率(因?yàn)檫\(yùn)算量還是蠻大的),
  • gl_FragColor 也是一個(gè)內(nèi)建的變量,表示顏色,以rgba的方式排布,范圍是[0,1]的浮點(diǎn)數(shù)

先完成onSurfaceCreated的代碼,使用readRawTextFile把文件讀進(jìn)來,然后創(chuàng)建一個(gè)OpenGL ES程序

String vertexShader = ShaderUtils.readRawTextFile(context, R.raw.vertex_shader);
String fragmentShader= ShaderUtils.readRawTextFile(context, R.raw.fragment_shader);
programId=ShaderUtils.createProgram(vertexShader,fragmentShader);

讀取文件應(yīng)該好理解,創(chuàng)建程序就比較復(fù)雜了,具體的步驟是這樣的,我們先看創(chuàng)建程序之前要做的事情:

  1. 創(chuàng)建一個(gè)新的著色器對(duì)象
  2. 上傳和編譯著色器代碼,就是我們之前讀進(jìn)來的String
  3. 讀取編譯狀態(tài)(可選)
public static int createProgram(String vertexSource, String fragmentSource) {
    //我們先創(chuàng)建頂點(diǎn)著色器和片元著色器
    int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource);
    if (vertexShader == 0) {
        return 0;
    }
    int pixelShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource);
    if (pixelShader == 0) {
        return 0;
    }
    //然后用GLES20.glCreateProgram()創(chuàng)建程序,如果創(chuàng)建成功,會(huì)返回一個(gè)非零的值
    int program = GLES20.glCreateProgram();
    if (program != 0) {
        
        GLES20.glAttachShader(program, vertexShader);//把程序和著色器綁定起來
        checkGlError("glAttachShader");
        GLES20.glAttachShader(program, pixelShader);
        checkGlError("glAttachShader");
        //然后用GLES20.glLinkProgram(program)鏈接程序(編譯鏈接)
        GLES20.glLinkProgram(program);
        int[] linkStatus = new int[1];
        
        GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0);//和之前的類似,是用來獲取鏈接狀態(tài)的
        
        if (linkStatus[0] != GLES20.GL_TRUE) {
            Log.e(TAG, "Could not link program: ");
            Log.e(TAG, GLES20.glGetProgramInfoLog(program));
            GLES20.glDeleteProgram(program);
            program = 0;
        }
    }
    return program;
}
//shaderType用來指定著色器類型,取值有GLES20.GL_VERTEX_SHADER和GLES20.GL_FRAGMENT_SHADER
//source就是剛才讀入的代碼
public static int loadShader(int shaderType, String source) {

    //如果創(chuàng)建成功,那么shader會(huì)是一個(gè)非零的值
    int shader = GLES20.glCreateShader(shaderType);
    if (shader != 0) {
        GLES20.glShaderSource(shader, source);
        GLES20.glCompileShader(shader);
        int[] compiled = new int[1];
        //我們用GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0)來獲取編譯的狀態(tài)
        GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0);
        if (compiled[0] == 0) {
            Log.e(TAG, "Could not compile shader " + shaderType + ":");
            Log.e(TAG, GLES20.glGetShaderInfoLog(shader));
            //如果創(chuàng)建失敗,就刪除這個(gè)著色器
            GLES20.glDeleteShader(shader);
            shader = 0;
        }
    }
    return shader;
}

另外還有一個(gè)打印錯(cuò)誤日志的功能函數(shù):

public static void checkGlError(String label) {
    int error;
    while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) {
        Log.e(TAG, label + ": glError " + error);
        throw new RuntimeException(label + ": glError " + error);
    }
}

創(chuàng)建好了程序之后,我們獲取之前頂點(diǎn)著色器中,aPosition的引用,以便于傳送頂點(diǎn)數(shù)據(jù)

aPositionHandle= GLES20.glGetAttribLocation(programId,"aPosition");

完成向OpenGL的數(shù)據(jù)傳送OpenGL ES工作在native層(C、C++),如果要傳送數(shù)據(jù),我們需要使用特殊的方法把數(shù)據(jù)復(fù)制過去。
首先定義一個(gè)頂點(diǎn)數(shù)組,這是我們要繪制的三角形的三個(gè)頂點(diǎn)坐標(biāo)(逆時(shí)針),三個(gè)浮點(diǎn)數(shù)分別代表xyz,因?yàn)槭窃谄矫嫔侠L制,我們把z設(shè)置為0

private final float[] vertexData = {
        0f,0f,0f,
        1f,-1f,0f,
        1f,1f,0f
};

如果程序正常工作,那么我們的三角形應(yīng)該出現(xiàn)在這個(gè)區(qū)域(見下圖):

image
image

我們使用一個(gè)FloatBuffer將數(shù)據(jù)傳遞到本地內(nèi)存
我們?cè)陬惖臉?gòu)造函數(shù)中把頂點(diǎn)數(shù)據(jù)傳遞過去:

//ByteBuffer用來在本地內(nèi)存分配足夠的大小
vertexBuffer = ByteBuffer.allocateDirect(vertexData.length * 4)//申請(qǐng)一個(gè)內(nèi)存,大小是data*4個(gè)字節(jié),因?yàn)閐ata里是float,一個(gè)float是4個(gè)字節(jié)
        //設(shè)置存儲(chǔ)順序?yàn)閚ativeOrder(關(guān)于存儲(chǔ)順序的更多資料可以在維基百科上找到)
        .order(ByteOrder.nativeOrder())
        .asFloatBuffer()
        .put(vertexData);//把vertexData放進(jìn)去
vertexBuffer.position(0);//設(shè)定索引位

完成onDrawFrame
完成了上述工作以后,我們就可以畫個(gè)三角形,試試手

@Override
public void onDrawFrame(GL10 gl) {
    //清空顏色緩沖區(qū)和深度緩沖區(qū)
    GLES20.glClear( GLES20.GL_DEPTH_BUFFER_BIT |GLES20.GL_COLOR_BUFFER_BIT);
    GLES20.glUseProgram(programId);//指定使用剛才創(chuàng)建的那個(gè)程序
    
    //啟用頂點(diǎn)數(shù)組,aPositionHandle就是我們傳送數(shù)據(jù)的目標(biāo)位置
    GLES20.glEnableVertexAttribArray(aPositionHandle);
    
    GLES20.glVertexAttribPointer(aPositionHandle, 3, GLES20.GL_FLOAT, false,
            12, vertexBuffer);
    GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, 3);
}
  • GLES20.glVertexAttribPointer的原型是這樣的:
glVertexAttribPointer(
        int indx,
        int size,
        int type,
        boolean normalized,
        int stride,
        java.nio.Buffer ptr
)

stride表示步長(zhǎng),因?yàn)橐粋€(gè)頂點(diǎn)三個(gè)坐標(biāo),一個(gè)坐標(biāo)是float(4字節(jié)),所以步長(zhǎng)是12字節(jié)
(當(dāng)然,這個(gè)只在一個(gè)數(shù)組中同時(shí)包含多個(gè)屬性時(shí)才有作用,例如同時(shí)包含紋理坐標(biāo)和頂點(diǎn)坐標(biāo),在只有一種屬性時(shí)(例如現(xiàn)在),和傳遞0是相同效果)

  • 最后,我們用GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, 3);把三角形畫出來,glDrawArrays的原型如下
public static native void glDrawArrays(
    int mode,
    int first,
    int count
);
image
image

因?yàn)镺penGL會(huì)把整個(gè)屏幕(其實(shí)是整個(gè)可以繪制的區(qū)域,也就是前面黑色的區(qū)域)當(dāng)成輸出,所以我們畫出來的三角形出現(xiàn)了變形。那么橫屏的情況下是什么樣的呢? 來看一下:

image
image

解決變形問題

要解決變形,要先掌握Projection Matrix(投影矩陣)的概念,在OpenGL中,投影矩陣用來改變場(chǎng)景在屏幕上的顯示方式(近大遠(yuǎn)小,平行投影等等)。
因?yàn)槲覀兝L制的是二維平面,所以問題還是比較好解決的,假如我們的屏幕是16:9的(橫屏情況),那么只要讓OpenGL的繪制范圍也是16:9的就好了,如下圖所示:

image
image

可以看到,我們把OpenGL的繪制區(qū)域橫向拉長(zhǎng)了,拉長(zhǎng)的比例就是(16/9 約1.777)
更多關(guān)于透視和投影變換
可以參考http://blog.csdn.net/popy007/article/details/1797121

那么代碼要如何實(shí)現(xiàn)呢?答案是矩陣,進(jìn)行正交投影的操作,我們并不需要去推導(dǎo)正交矩陣如何求出來,Android 提供的Matrix類中包含這個(gè)方法。

我們先聲明一個(gè)長(zhǎng)度16的float數(shù)組,這是Matrix的標(biāo)準(zhǔn)尺寸

private final float[] projectionMatrix=new float[16];

注意,在OpenGL中,數(shù)組是row-major的,如下所示:

/**
 * Matrix math utilities. These methods operate on OpenGL ES format
 * matrices and vectors stored in float arrays.
 * <p>
 * Matrices are 4 x 4 column-vector matrices stored in column-major
 * order:
 * <pre>
 *  m[offset +  0] m[offset +  4] m[offset +  8] m[offset + 12]
 *  m[offset +  1] m[offset +  5] m[offset +  9] m[offset + 13]
 *  m[offset +  2] m[offset +  6] m[offset + 10] m[offset + 14]
 *  m[offset +  3] m[offset +  7] m[offset + 11] m[offset + 15]</pre>
 */

創(chuàng)建了projectionMatrix以后,我們還需要更新glsl中頂點(diǎn)著色器的代碼,以便把這個(gè)變換用的矩陣傳遞過去,如下所示:

attribute vec4 aPosition;
uniform mat4 uMatrix;
void main() {
  gl_Position = uMatrix*aPosition;
}

uniform 是GLSL中的常量類型,之前的attribute類型是用來在Java代碼和頂點(diǎn)著色器(Vertex Shader)傳遞變量用的,uniform則是給頂點(diǎn)著色器(Vertex Shader)和片元著色器(Fragment Shader)傳遞常量用的。

我們把uMatrix和aPosition做矩陣乘法,就得到了一個(gè)新的頂點(diǎn)位置。
類似的,我們也需要一個(gè)入口,以便給這個(gè)矩陣傳遞數(shù)據(jù)

uMatrixHandle=GLES20.glGetUniformLocation(programId,"uMatrix");
完成正交投影
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
    float ratio=width>height?(float)width/height:(float)height/width;
    if (width>height){
        Matrix.orthoM(projectionMatrix,0,-ratio,ratio,-1f,1f,-1f,1f);
    }else Matrix.orthoM(projectionMatrix,0,-1f,1f,-ratio,ratio,-1f,1f);
}

在onSurfaceChanged中,我們獲取了屏幕的寬和高,所以我們?cè)谶@里計(jì)算縮放的比例,需要注意的是橫屏和豎屏的時(shí)候是剛好相反的處理(一個(gè)改變x,一個(gè)改變y)

正交投影方法 : Matrix.orthoM() 方法設(shè)置正交投影;

public static void orthoM(float[] m, int mOffset,float left, float right, float bottom, float top,float near, float far)

左右(x)下上(y)近遠(yuǎn)(z)

更新onDrawFrame

@Override
public void onDrawFrame(GL10 gl) {
    GLES20.glClear( GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT);
    GLES20.glUseProgram(programId);
    GLES20.glUniformMatrix4fv(uMatrixHandle,1,false,projectionMatrix,0);
    GLES20.glEnableVertexAttribArray(aPositionHandle);
    GLES20.glVertexAttribPointer(aPositionHandle, 3, GLES20.GL_FLOAT, false,
            12, vertexBuffer);
    GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, 3);
}

看一下效果吧:

image
image

如果是豎屏的情況,那么應(yīng)該是這樣子的:

image
image

下面我們來顯示一張圖片,圖片可以看做一個(gè)矩形,所以我們先來畫一個(gè)矩形
之前提到OpenGL的基本形狀是三角形,一個(gè)矩形可以看成由4個(gè)三角形構(gòu)成,如果我們一個(gè)一個(gè)畫,那需要12個(gè)頂點(diǎn),36個(gè)坐標(biāo),效率不高,所以我們采用另外一種方式——頂點(diǎn)索引與glDrawElements配合使用。
什么是頂點(diǎn)索引呢?頂點(diǎn)索引就是給出頂點(diǎn)的下標(biāo)而不給出具體的頂點(diǎn)坐標(biāo),看代碼:

private final float[] vertexData = {
        0f,0f,0f,
        1f,1f,0f,
        -1f,1f,0f,
        -1f,-1f,0f,
        1f,-1f,0f
};

private final short[] indexData = {
        0,1,2,
        0,2,3,
        0,3,4,
        0,4,1
};

我們的繪制區(qū)域是(-1,-1)到(1,1)的平面區(qū)域,vertexData給出了5個(gè)頂點(diǎn),indexData給出了4個(gè)三角形的描述:

聲明一個(gè)ShortBuffer ,用來存放頂點(diǎn)的索引數(shù)據(jù)

private ShortBuffer indexBuffer;
indexBuffer = ByteBuffer.allocateDirect(indexData.length * 2)
        .order(ByteOrder.nativeOrder())
        .asShortBuffer()
        .put(indexData);
indexBuffer.position(0);

然后,使用GLES20.glDrawElements把三角形畫出來,注意如果我們之前的數(shù)組類型是byte,那么就應(yīng)該使用GLES20.GL_UNSIGNED_BYTE,總之類型要對(duì)齊

GLES20.glDrawElements(GLES20.GL_TRIANGLES,indexData.length,GLES20.GL_UNSIGNED_SHORT,indexBuffer);

創(chuàng)建一個(gè)紋理

紋理的創(chuàng)建比較復(fù)雜,我們創(chuàng)建一個(gè)新的工具類,并且加入如下代碼:

public class TextureHelper {
    private  static  final String TAG="TextureHelper";
    public static int loadTexture(Context context,int resourceId){

        final int[] textureObjectIds=new int[1];
        GLES20.glGenTextures(1,textureObjectIds,0);
        if (textureObjectIds[0]==0){
            Log.d(TAG,"生成紋理對(duì)象失敗");
            return 0;
        }

        BitmapFactory.Options options=new BitmapFactory.Options();
        options.inScaled=false;

        Bitmap bitmap=BitmapFactory.decodeResource(context.getResources(),resourceId,options);

        if (bitmap==null){
            Log.d(TAG,"加載位圖失敗");
            GLES20.glDeleteTextures(1,textureObjectIds,0);
            return 0;
        }

        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D,textureObjectIds[0]);

        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D,GLES20.GL_TEXTURE_MIN_FILTER,GLES20.GL_LINEAR_MIPMAP_LINEAR);
        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D,GLES20.GL_TEXTURE_MAG_FILTER,GLES20.GL_LINEAR);

        GLUtils.texImage2D(GLES20.GL_TEXTURE_2D,0,bitmap,0);

        bitmap.recycle();

        //為與target相關(guān)聯(lián)的紋理圖像生成一組完整的mipmap
        GLES20.glGenerateMipmap(GLES20.GL_TEXTURE_2D);

        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D,0);

        return textureObjectIds[0];
    }
}
  • GLES20.glGenTextures(1,textureObjectIds,0);生成一個(gè)紋理,放入textureObjectIds中,同樣地,如果生成成功,那么就會(huì)返回一個(gè)非零值

  • 然后我們將bitmap從raw中讀進(jìn)來,這個(gè)就不解釋了

  • GLES20.glBindTexture(GLES20.GL_TEXTURE_2D,textureObjectIds[0]);的作用是將我們剛生成的紋理和OpenGL的2D紋理綁定,告訴OpenGL這是一個(gè)2D的紋理(貼圖)

  • GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D,GLES20.GL_TEXTURE_MIN_FILTER,GLES20.GL_LINEAR_MIPMAP_LINEAR);
    GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D,GLES20.GL_TEXTURE_MAG_FILTER,GLES20.GL_LINE AR);
    這兩句話用來設(shè)置紋理過濾的方式,GL_TEXTURE_MIN_FILTER是指縮小時(shí)的過濾方式,GL_TEXTURE_MAG_FILTER則是放大的
    關(guān)于放大和縮小時(shí)的可用方式,參見下圖:

  • GLUtils.texImage2D(GLES20.GL_TEXTURE_2D,0,bitmap,0);
    然后將紋理加載到OpenGL中,并且及時(shí)回收bitmap

  • GLES20.glBindTexture(GLES20.GL_TEXTURE_2D,0);用于解除和紋理的綁定,等使用時(shí)再綁定

OpenGL紋理坐標(biāo)系

又是一個(gè)新的坐標(biāo)系,

image
image

S-T坐標(biāo)系(橙色)就是OpenGL紋理坐標(biāo)系,藍(lán)色的是OpenGL屏幕坐標(biāo)系,對(duì)比一下,明顯的區(qū)別有:

  • 原點(diǎn)的位置不一樣
  • 取值的范圍不一樣
  • Y軸的方向也剛好和T軸相反
  • 如果我們要將一張圖貼到整個(gè)顯示區(qū)域,即(-1,-1,0)-(1,1,0),那么對(duì)應(yīng)的關(guān)系就如圖中所示
vertex_shader.glsl

attribute vec4 aPosition;
attribute vec2 aTexCoord;
varying vec2 vTexCoord;
uniform mat4 uMatrix;
void main() {
    vTexCoord=aTexCoord;
    gl_Position = uMatrix*aPosition;
}
  • 首先,aTexCoord是一個(gè)二維向量,表示紋理的坐標(biāo),
  • varying這個(gè)變量是用來在vertex_shader和fragment_shader之間傳遞值用的,所以名稱要相同,我們把a(bǔ)TexCoord賦值給vTexCoord,

然后來看片元著色器的代碼

fragment_shader.glsl

precision mediump float;
varying vec2 vTexCoord;
uniform sampler2D sTexture;
void main() {
    //gl_FragColor = vec4(0,0.5,0.5,1);
    gl_FragColor = texture2D(sTexture,vTexCoord);
}
  • 在片元著色器中,我們聲明了一個(gè)uniform常量,類型是sampler2D,這個(gè)類型是指一個(gè)二維的紋理數(shù)據(jù)數(shù)組

  • 使用texture2D來處理被插值的紋理坐標(biāo)vTexCoord和紋理數(shù)據(jù)sTexture,得到的顏色值就是要顯示的顏色,交給gl_FragColor

更新Renderer類

首先,利用剛才寫的類來獲得一個(gè)紋理ID,這句話放在onSurfaceCreated里面:
textureId=TextureHelper.loadTexture(context,R.raw.demo_pic);
然后我們加入紋理坐標(biāo)數(shù)據(jù)(參見上圖的對(duì)應(yīng)關(guān)系)

private final float[] textureVertexData = {
        0.5f,0.5f,
        1f,0f,
        0f,0f,
        0f,1f,
        1f,1f
};

并把它復(fù)制到OpenGL的本地內(nèi)存中

textureVertexBuffer = ByteBuffer.allocateDirect(textureVertexData.length * 4)
        .order(ByteOrder.nativeOrder())
        .asFloatBuffer()
        .put(textureVertexData);
textureVertexBuffer.position(0);

類似的,我們要獲得剛才的變量、常量引用(handle):

uTextureSamplerHandle=GLES20.glGetUniformLocation(programId,"sTexture");
aTextureCoordHandle=GLES20.glGetAttribLocation(programId,"aTexCoord");

更新onDrawFrame
首先,把紋理坐標(biāo)用類似的方法傳遞過去:

GLES20.glEnableVertexAttribArray(aTextureCoordHandle);
GLES20.glVertexAttribPointer(aTextureCoordHandle,2,GLES20.GL_FLOAT,false,8,textureVertexBuffer);

然后,我們啟用一個(gè)紋理,并把它和剛才生成的紋理ID綁定,再把紋理數(shù)據(jù)引用傳過去,因?yàn)槲覀儐⒂玫氖荊L_TEXTURE0,所以在glUniform1i中第二個(gè)參數(shù)是0(大家可以都改成1試一下,在這里應(yīng)該是一樣的效果):

GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D,textureId);

GLES20.glUniform1i(uTextureSamplerHandle,0);

目前onDrawFrame的代碼如下:

@Override
public void onDrawFrame(GL10 gl) {
    GLES20.glClear( GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT);
    GLES20.glUseProgram(programId);
    GLES20.glUniformMatrix4fv(uMatrixHandle,1,false,projectionMatrix,0);
    GLES20.glEnableVertexAttribArray(aPositionHandle);
    GLES20.glVertexAttribPointer(aPositionHandle, 3, GLES20.GL_FLOAT, false,
            12, vertexBuffer);



    GLES20.glEnableVertexAttribArray(aTextureCoordHandle);
    GLES20.glVertexAttribPointer(aTextureCoordHandle,2,GLES20.GL_FLOAT,false,8,textureVertexBuffer);

    GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
    GLES20.glBindTexture(GLES20.GL_TEXTURE_2D,textureId);

    GLES20.glUniform1i(uTextureSamplerHandle,0);

    GLES20.glDrawElements(GLES20.GL_TRIANGLES,indexData.length,GLES20.GL_UNSIGNED_SHORT,indexBuffer);
}

運(yùn)行一下,顯示圖片

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,835評(píng)論 6 534
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,676評(píng)論 3 419
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,730評(píng)論 0 380
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,118評(píng)論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,873評(píng)論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,266評(píng)論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,330評(píng)論 3 443
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,482評(píng)論 0 289
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,036評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,846評(píng)論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,025評(píng)論 1 371
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,575評(píng)論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,279評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,684評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,953評(píng)論 1 289
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,751評(píng)論 3 394
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,016評(píng)論 2 375

推薦閱讀更多精彩內(nèi)容