opengl es3.0學習篇八:紋理

學習內容來源and參考java

opengl es 3.0編程指南git

https://www.jianshu.com/p/4d8d35288a0fgithub

3D圖形渲染最基本的操做之一是對一個表面進行紋理,紋理能夠表現只從網格的幾何形狀中沒法獲得的附加細節。在opengl es3.0中的紋理有多種形式:2D紋理,2D紋理數組,3D紋理以及立方圖紋理。編程

2D紋理

2D紋理是一個圖像數據的二維數組。一個紋理單獨數據元素稱做「紋素」(Texel)。圖像中的每一個紋素根據基本格式和數據類型指定。若是用2D紋理渲染時,紋理座標用做圖像中的索引。2D紋理的紋理座標用一對2D座標(s,t)或者(u,v)來表示,這些座標用於查找一個紋理貼圖的規範化座標。數組

紋理座標以下所示:函數

紋理圖像的左下座標由(0.0,0.0)決定,右上角座標由(1.0,1.0)指定。在[0.0,1.0]以外的座標是容許的,在區間以外的紋理讀取行爲由紋理包裝模式決定。性能

立方圖紋理

立方圖是由6個單獨2D紋理面組成的紋理。對於立方圖紋理貼圖,通常使用環境貼圖特效,即在物體上的倒影經過使用一個表示環境的立方圖渲染。。一般,生成環境貼圖所用的立方圖經過在場景中央放置一個攝像機,從6個軸方向(+x,-x,+y,-y,+z,-z)捕捉場景圖像並將結果保存在立方體的每一個面上。學習

3D紋理

3D紋理能夠看作2D紋理多個切片的一個數組,用一個三元座標(s,t,r)訪問,r座標選擇3D紋理須要採樣的切片,(s,t)用來讀取每一個切片中的2D貼圖。動畫

2D紋理數組

2D紋理數組與3D紋理數組類似,可是用途不一樣,通常用來存儲2D圖像的動畫。數組的每一個切片標識紋理動畫的一幀。座標使用與3D紋理相同,都是使用(s,t,r)來表示,r座標選擇2D數組中須要採樣的切片,(s,t)用來選取切片。ui

紋理對象和紋理加載

紋理對象是一個容器對象,保存所須要渲染的紋理數據,如圖像數據,過濾模式以及包裝模式。紋理對象使用一個無符號整數表示,該整數位紋理對象的句柄,生成紋理對象的函數以下:

void glGenTextures(GLsizei n,GLuint *textures);//native 實現

 // C function void glGenTextures ( GLsizei n, GLuint *textures )  java層代碼
public static native void glGenTextures(
        int n,
        java.nio.IntBuffer textures
    );

Java代碼中n表示生成紋理對象數量,textures爲一個保存n個紋理對象id的無符號整數數組。建立時,glGenTextures方法生成的紋理對象是一個空的容器,用於加載紋理數據和參數。紋理對象在不使用時候能夠經過調用glDeleteTextures(...)方法來刪除。

一旦使用glGenTextures(...)來生成紋理對象id,應用程序就必須經過glBindTexture()綁定紋理對象進行操做。一旦綁定到一個特定的目標,紋理對象會一直綁定在此目標中直到刪掉爲止。在綁定完成後,須要去加載圖像了,用於加載立方圖紋理和2D的基本函數是glTexImage2D()。在opengl es3.0中可使用多種代替方法指定2D紋理,包括不可變紋理(glTexStorage2D)以及glTexSubImage2D的結合。爲了獲得最佳性能,推薦使用不可變紋理。在Android中,系統幫咱們封裝了GLUtils來方便咱們使用:

public static void texImage2D(int target, int level, Bitmap bitmap,
            int border);
//target爲將紋理對象綁定到GL_TEXTURE_2D,GL_TEXTURE_3D,GL_TEXTURE_2D_ARRAY,GL_TEXTURE_CUBE_MAP的標識
//level爲指定要加載mip的級別,第一個級別爲0,後續的遞增
//bitmap爲須要加載的圖像
//border在opengl es中忽略,傳0便可

上述方法使用以下:

private void generateTexture(Bitmap bitmap) {
        int[] size = new int[1];
        GLES30.glGenTextures(size.length, size, 0);
        if (size[0] == 0) {
            Log.w(TAG, "建立紋理失敗!");
            return;
        }
        int target = size[0];
        GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, target);
        GLUtils.texImage2D(target, 0, bitmap, 0);
		GLES30.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST);
        GLES30.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_NEAREST);
    }

上述的glTexParameteri(..)方法將縮小和放大過濾模式設置爲GL_NEAREST。這個對於紋理貼圖來講是必須的,由於咱們尚未爲紋理加載完整的mip貼圖鏈,所以,必須選擇非mip貼圖縮小過濾器。其餘的模式還有GL_LINEAR,提供雙線性非mip貼圖過濾。

紋理過濾和mip貼圖

紋理座標用於生成一個2d索引,用來從紋理貼圖中讀取,當縮小和放大的過濾器設置爲GL_NEAREST的時候,一個紋素將在提供的紋理座標位置上讀取,這個稱爲點採樣或者最近採樣。but,使用該方法採樣可能會形成嚴重的視覺僞像,由於三角形在屏幕空間中變得較小,在不一樣像素間的差值,紋理座標可能有很大的跳躍,從而形成從一個大的紋理塗重取得少許樣本,形成鋸齒僞像,而且可能形成巨大的性能損失。

上述的解決辦法能夠經過mip貼圖來解決僞像的問題。其思路是構建一個mip貼圖鏈,開始於原來指定的圖像,後續的每一個圖像在每一個維度上是前一個圖像的通常,一直持續到最後到達鏈底部的1x1紋理。mip貼圖級別能夠變成生成(上述的level參數),一個mip級別中的每一個像素一般根據上一級別中相同位置的4個像素的平均值計算。

紋理渲染時會發生兩種過濾狀況:縮小和放大。

  • 縮小的狀況發生在屏幕投影的多邊形小於紋理尺寸的時候。
  • 放大的狀況發生在屏幕投影的多邊形大於紋理尺寸的時候。

對於放大而言,mip貼圖是不起做用的,由於咱們老是從最大的可用級別進行採樣。對於縮小來講,可使用不一樣的採樣方式。 過濾模式使用glTexParameteri(...)進行設置:

public static native void glTexParameteri(
        int target,
        int pname,
        int param
    );
//紋理對象,GL_TEXTURE_2D,GL_TEXTURE_3D,GL_TEXTURE_2D_ARRAY,GL_TEXTURE_CUBE_MAP
//pname通常指定爲GL_TEXTURE_MIN_FILTER(縮小),GL_TEXTURE_MAG_FILTER(放大)
//param爲採用的過濾模式

過濾模式的採樣過程如圖所示:

GL_LINEAR

GL_LINEAR

GL_NEAREST

  • GL_LINEAR:從最靠近紋理座標中的紋理中得到一個雙線性樣本
  • GL_NEAREST:從最靠近紋理座標中的紋理中得到一個單點樣本

更加詳細介紹關於過濾模式的區別能夠查看這篇博客

自動mip貼圖生成

opengls es3.0中提供glGenerateMipmap()方法來自動生成mip貼圖。

public static native void glGenerateMipmap(
        int target
    );
//以前生成的紋理對象,GL_TEXTURE_2D,GL_TEXTURE_3D,GL_TEXTURE_2D_ARRAY,GL_TEXTURE_CUBE_MAP

紋理包裝模式

紋理包裝模式用於指定紋理座標超出[0.0,1.0]方位內所發生的行爲。使用glTexParameter[i|f]v來進行設置:

public static native void glTexParameterfv(
        int target,
        int pname,
        java.nio.FloatBuffer params
    );

    // C function void glTexParameteri ( GLenum target, GLenum pname, GLint param )

    public static native void glTexParameteri(
        int target,
        int pname,
        int param
    );

這些模式能夠爲s,t,r座標進行單獨設置,GL_TEXTURE_WRAP_S設置s座標的模式,GL_TEXTURE_WRAP_T設置t座標的模式,GL_TEXTURE_WRAP_R設置r座標的模式。

在opengl es中有三種模式可供使用:

  1. GL_REPEAT:對紋理的默認行爲,重複紋理圖像。
  2. GL_MIRRORED_REPEAT:和GL_REPEAT同樣,但每次重複圖片是鏡像放置的。
  3. GL_CLAMP_TO_EDGE:紋理座標會被約束在0到1之間,超出的部分會重複紋理座標的邊緣,產生一種邊緣被拉伸的效果。

代碼demo

GLES30.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
GLES30.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

着色器中使用紋理

這裏的demo不考慮圖像拉伸的問題,旨在理清展現的邏輯

簡單使用2D紋理展現一下效果,首先封裝一個展現紋理的關鍵方法:

代碼位於 https://github.com/JerryChan123/LearnOEL/tree/gl30 的[2D紋理普通貼圖&&2D紋理立方體貼圖]提交當中

private Bitmap mBitmap;

    public int loadTexture(Context context, int resId) {
        int[] textureObjIds = new int[1];
        GLES30.glGenTextures(1, textureObjIds, 0);
        if (textureObjIds[0] == 0) {
            return 0;
        }
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inScaled = false;
        if (mBitmap == null) {
            mBitmap = BitmapFactory.decodeResource(context.getResources(), resId, options);
            if (mBitmap == null) {
                GLES30.glDeleteTextures(1, textureObjIds, 0);
                return 0;
            }
        }
        GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, textureObjIds[0]);//bind
        GLES30.glTexParameteri(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_MIN_FILTER, GLES30.GL_NEAREST);
        GLES30.glTexParameteri(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_MAG_FILTER, GLES30.GL_LINEAR);
        GLUtils.texImage2D(GLES30.GL_TEXTURE_2D, 0, mBitmap, 0);
        GLES30.glGenerateMipmap(GLES30.GL_TEXTURE_2D);
        GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, 0);//unbind
        return textureObjIds[0];
    }

在SurfaceView的onDraw()方法中進行繪製:

...
 int texCoord = GLES30.glGetAttribLocation(mProgram, "texCoord");
 GLES30.glEnableVertexAttribArray(texCoord);
 GLES30.glVertexAttribPointer(texCoord, 2, GLES30.GL_FLOAT, false, 0, textBuffer);
 int textureId = loadTexture(mContext, R.drawable.aa);
 GLES30.glActiveTexture(GLES30.GL_TEXTURE0);
 GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, textureId);
 int uTextureUnitLocation = GLES30.glGetUniformLocation(mProgram, "s_texture");
 GLES30.glUniform1i(uTextureUnitLocation, 0);
...

繪製出來圖像以下所示:

附上次繪製立方體的紋理貼圖效果:

代碼地址:https://github.com/JerryChan123/LearnOEL/tree/gl30 提交信息爲[add the texture for cube]

相關文章
相關標籤/搜索