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

来源:http://www.cnblogs.com/younghao/archive/2016/02/01/5141290.html
-Advertisement-
Play Games

首先申明下,本文為筆者學習《OpenGL ES應用開發實踐指南(Android捲)》的筆記,涉及的代碼均出自原書,如有需要,請到原書指定源碼地址下載。 《OpenGL ES學習筆記(二)——平滑著色、自適應寬高及三維圖像生成》中闡述的平滑著色、自適應寬高是為了實現在移動端模擬真實場景採用的方法,並且...


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

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

  1. 紋理載入
  2. 紋理著色器
  3. 更新頂點數據類結構
  4. 著色器程式類
  5. 紋理繪製


一、紋理載入

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

clip_image001

      可見,兩者的差別在於繞橫軸翻轉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 resource
final 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 OpenGL
glBindTexture(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;
}

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

@Override
public 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()中繪製物體,繪製的方法就是通過調用前面著色器類和物體類(頂點數據)的方法來實現的。

@Override
public 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)紋理不能直接傳遞,需要被綁定到紋理單元,然後將紋理單元傳遞給著色器;


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • In the example below, we are using multiple search textboxes. As you type in the "Search name" textbox, only the name property is searched and matchin
  • As we type in the search textbox, all the columns in the table must be searched and only the matching rows should be displayed. Script.js : var app =
  • Here is what we want to do 1. The data should be sorted when the table column header is clicked 2. The user should be able to sort in both the directi
  • To sort the data in Angular 1. Use orderBy filter {{ orderBy_expression | orderBy : expression : reverse}} Example : ng-repeat="employee in employees
  • 好吧,這章不像上章那麼水了,總是炒剩飯也不好。 關於AJAX 所謂Ajax,全名Asynchronous JavaScript and XML。(也就非同步的JS和XML) 簡單點來講就是不刷新頁面來發送和獲取數據,然後更新頁面。 Ajax的優勢 無需插件支持 優秀的用戶體驗 提高web程式的性能 減
  • 前言 安全性,總是一個不可忽視的問題。許多人都承認這點,但是卻很少有人真的認真地對待它。所以我們列出了這個清單,讓你在將你的應用部署到生產環境來給千萬用戶使用之前,做一個安全檢查。 以下列出的安全項,大多都具有普適性,適用於除了Node.js外的各種語言和框架。但是,其中也包含一些用Node.js寫
  • 寫了一個slideDoor,reset.css就不放上來了,自行添加吧! 1 <!DOCTYPE html> 2 <html> 3 4 <head> 5 <meta charset="UTF-8"> 6 <title>slideDoor</title> 7 <link type="text/css"
  • 一,效果圖。 二,工程圖。 三,代碼。 RootViewController.m #import "RootViewController.h" @interface RootViewController () @end @implementation RootViewController - (id
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...