Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android OpenGLES2.0(八)——紋理貼圖之顯示圖片

Android OpenGLES2.0(八)——紋理貼圖之顯示圖片

編輯:關於Android編程

前面幾篇博客,我們將了Android中利用OpenGL ES 2.0繪制各種形體,並在上一篇博客中專門講了GLSL語言。但是我們看到的基於OpenGL開發的應用和游戲,可不僅僅是那些規則形體和一些簡單的色彩構成,而是各種不規則的形體構成了現實世界或者卡通世界的人和事物,他們都是外面穿著漂亮“衣服”的。本篇博客就是來講解這些“衣服”的基礎的。這些衣服就是紋理貼圖

什麼是紋理貼圖

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

比如我們在利用OpenGL做游戲的時候,加載了一個人物模型進來了,這個人物模型上是沒有色彩的。我們需要給它繪上需要的色彩才行。但是這些色彩從哪裡來呢?我們不可能像之前處理球體那樣,根據頂點取生成需要的色彩,那樣對於我們給這個人物模型繪色的工作量實在太大了。這個時候我們就需要用到紋理貼圖的技術了——把一個紋理(對於2D貼圖,可以簡單的理解為圖片),按照所期望的方式顯示在諸多三角形組成的物體的表面。

紋理映射原理

啟用紋理映射後,如果想把一幅紋理映射到相應的幾何圖元,就必須告訴GPU如何進行紋理映射,也就是為圖元的頂點指定恰當的紋理坐標。紋理坐標用浮點數來表示,范圍一般從0.0到1.0,左上角坐標為(0.0,0.0),右上角坐標為(1.0,0.0),左下角坐標為(0.0,1.0),右下角坐標為(1.0,1.0),如下圖所示:
紋理圖 頂點圖vcfQzsnPo6jSsr7NysfSu7j2vtjQzqOpo6zQ6NKqvavOxsDt1/ix6ta4tqi1vdX9yLe1xLaltePJz6OsssXE3Mq5zsbA7dX9yLe1xM/Uyr6jrLfx1PLP1Mq+s/bAtLXEzsbA7bvhzt63qM/Uyr6jrLvy1d+z9s/W0P3XqqGit63XqqGitO3Ou7XIx+m/9qGjPGJyIC8+DQq9q9PSzby2pbXjsLTV1VYyVjFWNFYztKvI66Os0tTI/b3H0M7M9bT4t73Kvbvm1sajrNTyzsbA7df4serTprC01dVWMlYxVjRWM7SryOuho8jnufuwtNXVVjNWNFYxVjK0q8jro6y74bXDtb3Su7j20P3XqsHLMTgwtsi1xM7GwO2ho8jnufuwtNXVVjRWM1YyVjG0q8jro6zU8rvhtcO1vdK7uPbX89PSt63XqrXEzsbA7aGjPC9wPg0KPGgxIGlkPQ=="顯示圖片">顯示圖片

根據紋理映射原理,結合之前繪制正方形的經驗,我們可以根據以下步驟利用OpenGL ES顯示一張圖片:

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

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紋理取樣,根據紋理取樣器和紋理坐標,可以得到當前紋理取樣得到的像素顏色。

第二步,設置頂點坐標和紋理坐標

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

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

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

private final float[] sCoord={
            0.0f,0.0f,
            0.0f,1.0f,
            1.0f,0.0f,
            1.0f,1.0f,
    };

第三步,計算變換矩陣

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

@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即為我們所需要的變換矩陣。

第四步,顯示圖片

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

@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;
}

這樣我們就可以顯示出我們需要顯示的圖片,並且保證它完整的居中顯示而且不會變形了,如下圖:
豎屏顯示

橫屏顯示

源碼

所有的代碼全部在一個項目中,托管在Github上——Android OpenGLES 2.0系列博客的Demo

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved