OpenGL ES學習筆記(三)——紋理,es學習筆記

來源:互聯網
上載者:User

OpenGL ES學習筆記(三)——紋理,es學習筆記

 

首先申明下,本文為筆者學習《OpenGL ES應用開發實踐指南(Android卷)》的筆記,涉及的代碼均出自原書,如有需要,請到原書指定源碼地址下載。

      《OpenGL ES學習筆記(二)——平滑著色、自適應寬高及三維映像產生》中闡述的平滑著色、自適應寬高是為了實現在移動端類比真實情境採用的方法,並且通過w分量增加了三維視角,在具體實現上採用了正交投影、透視投影的理論。本文將在此基礎上,構建更加精美的三維情境。立體效果本質上是點、直線和三角形的組合,紋理是將映像或者照片覆蓋到物體表面,形成精美的細節。在實現上具體分為兩步:1)將紋理圖片載入進OpenGL;2)OpenGL將其顯示到物體表面。(有點像把大象裝進冰箱分幾步~~~)不過,在實現過程中,涉及到著色器程式的管理,涉及到不同的紋理過濾模式,涉及到頂點資料新的類結構等問題,下面將一一對其闡述:

一、紋理載入

      將紋理覆蓋到物體表面,最終是通對齊座標來實現的。而OpenGL中二維紋理的座標與電腦映像的座標並不一致,因此,首先對比下兩者的不同。

      可見,兩者的差別在於繞橫軸翻轉180度。另外,OpenGL ES支援的紋理不必是正方形,但每個維度都必須是2的冪。

      載入紋理圖片的方法參數列表應該包括Android上下文(Context)和資源ID,傳回值應該是OpenGL紋理的ID,因此,該方法申明如下:

public static int loadTexture(Context context, int resourceId) {}

      首先,建立一個紋理對象,與普通OpenGL對象產生模式一樣。產生成功之後,申明紋理調用應該應用於這個紋理對象。其次,載入位元影像資料,OpenGL讀入位元影像資料並複製到前面綁定的紋理對象。

final int[] textureObjectIds = new int[1];glGenTextures(1, textureObjectIds, 0);if (textureObjectIds[0] == 0) {    if (LoggerConfig.ON) {        Log.w(TAG, "Could not generate a new OpenGL texture object.");    }    return 0;}
final BitmapFactory.Options options = new BitmapFactory.Options();options.inScaled = false;// Read in the resourcefinal Bitmap bitmap = BitmapFactory.decodeResource(    context.getResources(), resourceId, options);    if (bitmap == null) {        if (LoggerConfig.ON) {            Log.w(TAG, "Resource ID " + resourceId + " could not be decoded.");        }        glDeleteTextures(1, textureObjectIds, 0);        return 0;    } // Bind to the texture in OpenGLglBindTexture(GL_TEXTURE_2D, textureObjectIds[0]);

      這兩段代碼需要說明的並不多,其中options.inScaled = false表明OpenGL讀入映像的非壓縮形式的未經處理資料。OpenGL讀入位元影像資料需要注意一點:紋理過濾。OpenGL紋理過濾模式如下表:(--內容來自原書)

GL_NEAREST

最近鄰過濾

GL_NEAREST_MIPMAP_NEAREST

使用MIP貼圖的最近鄰過濾

GL_NEAREST_MIPMAP_LINEAR

使用MIP貼圖層級之間插值的最近鄰過濾

GL_LINEAR

雙線性過濾

GL_LINEAR_MIPMAP_NEAREST

使用MIP貼圖的雙線性過濾

GL_LINEAR_MIPMAP_LINEAR

三線性過濾(使用MIP貼圖層級之間插值的雙線性過濾)

      至於每種過濾具體的解釋及實現,請自行Google吧。這裡對於縮小情況,採用了GL_LINEAR_MIPMAP_LINEAR,對於放大情況,採用了GL_LINEAR。

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

      載入紋理的最後一步就是將bitmap複製到當前綁定的紋理對象:

texImage2D(GL_TEXTURE_2D, 0, bitmap, 0);

      綁定之後,仍然需要做一些後續操作,比如回收bitmap對象(bitmap記憶體佔用大戶),產生MIP貼圖,接觸紋理綁定,最後返回紋理對象ID。

glGenerateMipmap(GL_TEXTURE_2D);// Recycle the bitmap, since its data has been loaded into OpenGL.bitmap.recycle();// Unbind from the texture.glBindTexture(GL_TEXTURE_2D, 0);return textureObjectIds[0];

 

二、紋理著色器

      在繼續採用GLSL編寫著色器程式之前,先說明下之前遺漏的一個問題:

OpenGL著色語言(OpenGL Shading Language)是用來在OpenGL中著色編程的語言,也即開發人員寫的短小的自訂程式,他們是在圖形卡的GPU (Graphic Processor Unit圖形處理器)上執行的,代替了固定的渲染管線的一部分,使渲染管線中不同層次具有可程式化型。比如:視圖轉換、投影轉換等。

GLSL(GL Shading Language)的著色器代碼分成2個部分:Vertex Shader(頂點著色器)和Fragment(片斷著色器),有時還會有Geometry Shader(幾何著色器)。負責運行頂點著色的是頂點著色器。它可以得到當前OpenGL 中的狀態,GLSL內建變數進行傳遞。GLSL其使用C語言作為基礎高階著色語言,避免了使用組合語言或硬體規格語言的複雜性。

      這段內容來自百度百科,有一點需要重視:採用GLSL編寫的程式是在GPU中執行的,意味著著色器程式並不佔用CPU時間,這啟發我們在某些耗時的渲染程式(網路攝影機即時濾鏡)中可以採用GLSL實現,或許比NDK方式實現資料處理更為高效。後續筆者會在這方面實踐,這裡先說明紋理著色器程式。同樣,為了支援紋理,需對頂點著色器和片段著色器變更。

uniform mat4 u_Matrix;attribute vec4 a_Position;  attribute vec2 a_TextureCoordinates;varying vec2 v_TextureCoordinates;void main()                    {                                v_TextureCoordinates = a_TextureCoordinates;                gl_Position = u_Matrix * a_Position;    }
precision mediump float;                            uniform sampler2D u_TextureUnit;                                           varying vec2 v_TextureCoordinates;                                               void main()                            {                                      gl_FragColor = texture2D(u_TextureUnit, v_TextureCoordinates);                                   }

      上述頂點著色器中,變數a_TextureCoordinates的類型為vec2,因為紋理座標的兩個分量:S座標和T座標。片段著色器中,sampler2D類型的u_TextureUnit表示接收二維紋理資料的數組。

 

三、更新頂點資料類結構

      首先將不同類型的頂點資料分配到不同的類中,每個類代表一個物理對象的類型。在類的構造器中初始化VertexArray對象,VertexArray的實現與前述文章中描述的一致,採用FloatBuffer在本地代碼中儲存頂點矩陣資料,並建立通用方法將著色器的屬性與頂點資料關聯。

private final FloatBuffer floatBuffer;public VertexArray(float[] vertexData) {        floatBuffer = ByteBuffer            .allocateDirect(vertexData.length * BYTES_PER_FLOAT)            .order(ByteOrder.nativeOrder())            .asFloatBuffer()            .put(vertexData);}        public void setVertexAttribPointer(int dataOffset, int attributeLocation,        int componentCount, int stride) {                floatBuffer.position(dataOffset);                glVertexAttribPointer(attributeLocation, componentCount, GL_FLOAT,             false, stride, floatBuffer);        glEnableVertexAttribArray(attributeLocation);                floatBuffer.position(0);}
public Table() {    vertexArray = new VertexArray(VERTEX_DATA);}

      構造器中傳入的參數VERTEX_DATA就是頂點資料。

private static final float[] VERTEX_DATA = {        // Order of coordinates: X, Y, S, T        // Triangle Fan           0f,    0f, 0.5f, 0.5f,         -0.5f, -0.8f,   0f, 0.9f,           0.5f, -0.8f,   1f, 0.9f,          0.5f,  0.8f,   1f, 0.1f,         -0.5f,  0.8f,   0f, 0.1f,         -0.5f, -0.8f,   0f, 0.9f };

      在該組資料中,x=0,y=0對應紋理S=0.5,T=0.5,x=-0.5,y=-0.8對應紋理S=0,T=0.9,之所以有這種對應關係,看下前面講到的OpenGL紋理座標與電腦映像座標的對比就清楚啦。至於紋理部分的資料使用了0.1和0.9作為T座標,是為了避免把紋理壓扁,而對紋理進行了裁剪,截取了0.1到0.9的部分。

      初始化vertexArray之後,通過其setVertexAttribPointer()方法將頂點資料綁定到著色器程式上。

public void bindData(TextureShaderProgram textureProgram) {    vertexArray.setVertexAttribPointer(        0,         textureProgram.getPositionAttributeLocation(),         POSITION_COMPONENT_COUNT,        STRIDE);            vertexArray.setVertexAttribPointer(        POSITION_COMPONENT_COUNT,         textureProgram.getTextureCoordinatesAttributeLocation(),        TEXTURE_COORDINATES_COMPONENT_COUNT,         STRIDE);}

      這個方法為每個頂點調用了setVertexAttribPointer(),並從著色器程式擷取每個屬性的位置。通過getPositionAttributeLocation()把位置資料綁定到被引用的著色器屬性上,並通過getTextureCoordinatesAttributeLocation()把紋理座標資料繫結到被引用的著色器屬性。

      完成上述綁定以後,繪製只需要調用glDrawArrays()實現。

public void draw() {                                    glDrawArrays(GL_TRIANGLE_FAN, 0, 6);}

 

四、著色器程式類

      隨著紋理的使用,著色器程式變得更多,因此需要為著色器程式添加管理類。根據著色器分類,這裡分別建立紋理著色器類和顏色著色器類,且抽象它們的共同點,形成基類ShaderProgram,TextureShaderProgram和ColorShaderProgram分別繼承於此實現。ShaderProgram主要的功能就是根據Android上下文Context和著色器資源ID讀入著色器程式,其構造器參數列表如下:

protected ShaderProgram(Context context, int vertexShaderResourceId,        int fragmentShaderResourceId) {    ……}

      讀入著色器程式的實現應該在ShaderHelper類中,其步驟與之前所述相似,包括編譯、連結等步驟。

public static int buildProgram(String vertexShaderSource,        String fragmentShaderSource) {    int program;    // Compile the shaders.    int vertexShader = compileVertexShader(vertexShaderSource);    int fragmentShader = compileFragmentShader(fragmentShaderSource);    // Link them into a shader program.    program = linkProgram(vertexShader, fragmentShader);    if (LoggerConfig.ON) {        validateProgram(program);    }    return program;}

      compileVertexShader(編譯)和linkProgram(連結)的實現在之前的筆記中已詳細描述過。ShaderProgram的構造器調用上述buildProgram()方法即可。

program = ShaderHelper.buildProgram(    TextResourceReader.readTextFileFromResource(        context, vertexShaderResourceId),    TextResourceReader.readTextFileFromResource(        context, fragmentShaderResourceId));

      得到著色器程式之後,定義OpenGL後續的渲染使用該程式。

public void useProgram() {    // Set the current OpenGL shader program to this program.    glUseProgram(program);}

      著色器程式類TextureShaderProgram和ColorShaderProgram在構造器中調用父類的建構函式,並讀入紋理著色器中uniform和屬性的位置。

public TextureShaderProgram(Context context) {    super(context, R.raw.texture_vertex_shader,        R.raw.texture_fragment_shader);    // Retrieve uniform locations for the shader program.    uMatrixLocation = glGetUniformLocation(program, U_MATRIX);    uTextureUnitLocation = glGetUniformLocation(program, U_TEXTURE_UNIT);            // Retrieve attribute locations for the shader program.    aPositionLocation = glGetAttribLocation(program, A_POSITION);    aTextureCoordinatesLocation =         glGetAttribLocation(program, A_TEXTURE_COORDINATES);}

      接下來,傳遞矩陣給uniform,這在之前的筆記中描述過了。

// Pass the matrix into the shader program.glUniformMatrix4fv(uMatrixLocation, 1, false, matrix, 0);

      紋理的傳遞相對於矩陣的傳遞要複雜一些,因為紋理並不直接傳遞,而是採用紋理單元(Texture Unit)來儲存,因為一個GPU只能同時繪製數量有限的紋理,使用這些紋理單元表示正在被繪製的活動的紋理。

// Set the active texture unit to texture unit 0.glActiveTexture(GL_TEXTURE0);// Bind the texture to this unit.glBindTexture(GL_TEXTURE_2D, textureId);// Tell the texture uniform sampler to use this texture in the shader by// telling it to read from texture unit 0.glUniform1i(uTextureUnitLocation, 0);

      glActiveTexture(GL_TEXTURE0)表示把活動的紋理單元設定為紋理單元0,調用glBindTexture將textureId指向的紋理綁定到紋理單元0,最後,調用glUniform1i把選定的紋理單元傳遞給片段著色器中的u_TextureUnit(sampler2D)。

      顏色著色器類與紋理著色器類的實現基本類似,同樣在構造器中擷取uniform和屬性的位置,不過設定uniform值只需傳遞矩陣即可。

public void setUniforms(float[] matrix) {    // Pass the matrix into the shader program.    glUniformMatrix4fv(uMatrixLocation, 1, false, matrix, 0);}

 

五、紋理繪製

      通過前面的準備,頂點資料,著色器程式已經放到了不同的類中,因此,在渲染類中可以通過前面的實現進行紋理繪製了。AirHockeyRenderer類更新後的成員變數和建構函式如下:

private final Context context;private final float[] projectionMatrix = new float[16];private final float[] modelMatrix = new float[16];private Table table;private Mallet mallet;    private TextureShaderProgram textureProgram;private ColorShaderProgram colorProgram;        private int texture;public AirHockeyRenderer(Context context) {    this.context = context;}

      初始設定變數主要包括清理螢幕、初始化頂點數組和著色器程式,載入紋理等。

@Overridepublic void onSurfaceCreated(GL10 glUnused, EGLConfig config) {    glClearColor(0.0f, 0.0f, 0.0f, 0.0f);    table = new Table();    mallet = new Mallet();            textureProgram = new TextureShaderProgram(context);    colorProgram = new ColorShaderProgram(context);                    texture = TextureHelper.loadTexture(context, R.drawable.air_hockey_surface);}

      最後,在onDrawFrame()中繪製物體,繪製的方法就是通過調用前面著色器類和物體類(頂點資料)的方法來實現的。

@Overridepublic void onDrawFrame(GL10 glUnused) {    // Clear the rendering surface.    glClear(GL_COLOR_BUFFER_BIT);    // Draw the table.    textureProgram.useProgram();    textureProgram.setUniforms(projectionMatrix, texture);    table.bindData(textureProgram);    table.draw();    // Draw the mallets.    colorProgram.useProgram();    colorProgram.setUniforms(projectionMatrix);    mallet.bindData(colorProgram);    mallet.draw();}

 

總結一下,這篇筆記涉及到一下內容:

1)載入紋理並顯示到物體上;

2)重新組織程式,管理多個著色器和頂點資料之間的切換;

3)調整紋理以適應它們將要被繪製的形狀,既可以調整紋理座標,也可以通過展開或壓扁紋理本身來實現;

4)紋理不能直接傳遞,需要被綁定到紋理單元,然後將紋理單元傳遞給著色器;

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.