學習 Android 平臺 OpenGL ES API,學習紋理繪制,能夠使用 OpenGL 顯示一張圖片

1 概念

一般說來,紋理是表示物體表面的一幅或幾幅二維圖形,也稱紋理貼圖(texture)。當把紋理按照特定的方式映射到物體表面上的時候,能使物體看上去更加真實。當前流行的圖形系統中,紋理繪制已經成為一種必不可少的渲染方法。在理解紋理映射時,可以將紋理看做應用在物體表面的像素顏色。在真實世界中,紋理表示一個對象的顏色、圖案以及觸覺特征。紋理只表示對象表面的彩色圖案,它不能改變對象的幾何形式。更進一步的說,它只是一種高強度的計算行為。
概括為一句就是:紋理貼圖就是把一個紋理(對于2D貼圖,可以簡單的理解為圖片),按照所期望的方式顯示在諸多三角形組成的物體的表面。

2 原理

首先介紹一下紋理映射時的坐標系,紋理映射的坐標系和頂點著色器的坐標系是不一樣的。

頂點坐標系:

頂點坐標系

紋理坐標系

紋理坐標用浮點數來表示,范圍一般從0.0到1.0,左上角坐標為(0.0,0.0),右上角坐標為(1.0,0.0),左下角坐標為(0.0,1.0),右下角坐標為(1.0,1.0),具體如下:


紋理坐標系

OpenGL ES繪制四邊形

將紋理映射到下邊的兩個三角形上(也就是一個矩形),需要將紋理坐標指定到正確的頂點上,才能使紋理正確的顯示,否則顯示出來的紋理會無法顯示,或者出現旋轉、翻轉、錯位等情況。

規定:圖形環繞方向必須一致

1、GL_TRIANGLES:
v1, v2, v3,
v3, v2, v4,

2、GL_TRIANGLE_STRIP:
偶數:n-1, n-2, n
奇數:n-2, n-1, n

3 顯示紋理圖片

我們可以根據以下步驟利用OpenGL ES顯示一張圖片:

3.1 修改著色器

首先,我們需要修改我們的著色器,將頂點著色器修改為:

attribute vec4 vPosition;
attribute vec2 vCoordinate;
uniform mat4 vMatrix;

varying vec2 aCoordinate;

void main(){
    gl_Position=vMatrix*vPosition;
    aCoordinate=vCoordinate;
}

可以看到,頂點著色器中增加了一個vec2變量,并將這個變量傳遞給了片元著色器,這個變量就是紋理坐標。接著我們修改片元著色器為:

precision mediump float;

uniform sampler2D vTexture;
varying vec2 aCoordinate;

void main(){
    gl_FragColor=texture2D(vTexture,aCoordinate);
}

片元著色器中,增加了一個sampler2D的變量,sampler2D我們在前一篇博客GLSL語言基礎中提到過,是GLSL的變量類型之一的取樣器。texture2D也有提到,它是GLSL的內置函數,用于2D紋理取樣,根據紋理取樣器和紋理坐標,可以得到當前紋理取樣得到的像素顏色。

3.2 設置頂點坐標和紋理坐標

根據紋理映射原理中的介紹,我們將頂點坐標設置為:

private final float[] sPos={
            -1.0f,1.0f,    //左上角 V1
            -1.0f,-1.0f,   //左下角 V2
            1.0f,1.0f,     //右上角 V3
            1.0f,-1.0f     //右下角 V4
    };

相應的,對照頂點坐標,我們可以設置紋理坐標為:

private final float[] sCoord={
            0.0f,0.0f,  //左上角 V1
            0.0f,1.0f,  //左下角 V2
            1.0f,0.0f,   //右上角 V3
            1.0f,1.0f,  //右下角 V4
    };

3.3 計算變換矩陣

按照上步設置頂點坐標和紋理坐標,大多數情況下我們得到的一定是一張拉升或者壓縮的圖片。為了讓圖片完整的顯示,且不被拉伸和壓縮,我們需要向繪制等腰直角三角形一樣,計算一個合適的變換矩陣,傳入頂點著色器,代碼如下:

@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
    GLES20.glViewport(0,0,width,height);

    int w=mBitmap.getWidth();
    int h=mBitmap.getHeight();
    float sWH=w/(float)h;
    float sWidthHeight=width/(float)height;
    if(width>height){
        if(sWH>sWidthHeight){
            Matrix.orthoM(mProjectMatrix, 0, -sWidthHeight*sWH,sWidthHeight*sWH, -1,1, 3, 7);
        }else{
            Matrix.orthoM(mProjectMatrix, 0, -sWidthHeight/sWH,sWidthHeight/sWH, -1,1, 3, 7);
        }
    }else{
        if(sWH>sWidthHeight){
            Matrix.orthoM(mProjectMatrix, 0, -1, 1, -1/sWidthHeight*sWH, 1/sWidthHeight*sWH,3, 7);
        }else{
            Matrix.orthoM(mProjectMatrix, 0, -1, 1, -sWH/sWidthHeight, sWH/sWidthHeight,3, 7);
        }
    }
    //設置相機位置
    Matrix.setLookAtM(mViewMatrix, 0, 0, 0, 7.0f, 0f, 0f, 0f, 0f, 1.0f, 0.0f);
    //計算變換矩陣
    Matrix.multiplyMM(mMVPMatrix,0,mProjectMatrix,0,mViewMatrix,0);
}

mMVPMatrix即為我們所需要的變換矩陣。

3.4 顯示圖片

然后我們需要做的,就和之前繪制正方形一樣容易了。和之前不同的是,在繪制之前,我們還需要將紋理和紋理坐標傳入著色器:

@Override
public void onDrawFrame(GL10 gl) {
    GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT|GLES20.GL_DEPTH_BUFFER_BIT);
    GLES20.glUseProgram(mProgram);
    onDrawSet();
    GLES20.glUniformMatrix4fv(glHMatrix,1,false,mMVPMatrix,0);
    GLES20.glEnableVertexAttribArray(glHPosition);
    GLES20.glEnableVertexAttribArray(glHCoordinate);
    GLES20.glUniform1i(glHTexture, 0);
    textureId=createTexture();
    //傳入頂點坐標
    GLES20.glVertexAttribPointer(glHPosition,2,GLES20.GL_FLOAT,false,0,bPos);
    //傳入紋理坐標
    GLES20.glVertexAttribPointer(glHCoordinate,2,GLES20.GL_FLOAT,false,0,bCoord);
    GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP,0,4);
}

public abstract void onDrawSet();
public abstract void onDrawCreatedSet(int mProgram);

private int createTexture(){
    int[] texture=new int[1];
    if(mBitmap!=null&&!mBitmap.isRecycled()){
        //生成紋理
        GLES20.glGenTextures(1,texture,0);
        //生成紋理
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D,texture[0]);
        //設置縮小過濾為使用紋理中坐標最接近的一個像素的顏色作為需要繪制的像素顏色
        GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER,GLES20.GL_NEAREST);
        //設置放大過濾為使用紋理中坐標最接近的若干個顏色,通過加權平均算法得到需要繪制的像素顏色
        GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,GLES20.GL_TEXTURE_MAG_FILTER,GLES20.GL_LINEAR);
        //設置環繞方向S,截取紋理坐標到[1/2n,1-1/2n]。將導致永遠不會與border融合
        GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S,GLES20.GL_CLAMP_TO_EDGE);
        //設置環繞方向T,截取紋理坐標到[1/2n,1-1/2n]。將導致永遠不會與border融合
        GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T,GLES20.GL_CLAMP_TO_EDGE);
        //根據以上指定的參數,生成一個2D紋理
        GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, mBitmap, 0);
        return texture[0];
    }
    return 0;
}

這樣我們就可以顯示出我們需要顯示的圖片,并且保證它完整的居中顯示而且不會變形了。
源碼地址:https://github.com/Xiaoben336/OpenGLES20Study

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容