OpenGL ES學習筆記(二)——平滑著色、自適應寬高及三維圖像生成

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

首先申明下,本文為筆者學習《OpenGL ES應用開發實踐指南(Android捲)》的筆記,涉及的代碼均出自原書,如有需要,請到原書指定源碼地址下載。 《Android學習筆記——OpenGL ES的基本用法、繪製流程與著色器編譯》中實現了OpenGL ES的Android版HelloWorld,並...


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

      《Android學習筆記——OpenGL ES的基本用法、繪製流程與著色器編譯》中實現了OpenGL ES的Android版HelloWorld,並且闡明瞭OpenGL ES的繪製流程,以及編譯著色器的流程及註意事項。本文將從現實世界中圖形顯示的角度,說明OpenGL ES如何使得圖像在移動設備上顯示的更加真實。首先,物體有各種顏色的變化,在OpenGL ES中為了生成比較真實的圖像,對圖像進行平滑著色是一種常見的操作。其次,移動設備存在橫豎屏的切換,進行圖像顯示時,需要根據屏幕方向考慮屏幕的寬高比,使圖像不因屏幕切換而變形。最後,現實中的物體都是三維的,我們觀察物體都帶有一定的視角,因此需要在OpenGL ES實現三維圖像的顯示。本文主要包括以下內容:

  1. 平滑著色
  2. 自適應寬高
  3. 三維圖像生成


 

一、平滑著色

      平滑著色是通過在三角形的每個點上定義不同的顏色,在三角形的錶面混合這些顏色得到的。那麼,如何用三角形構成實際物體的錶面呢?如何混合定義在頂點出的不同顏色呢?

      首先引入三角形扇的概念。以一個中心頂點作為起始,使用相鄰的兩個頂點創建第一個三角形,接下來的每個頂點都會創建一個三角形,圍繞起始的中心點按扇形展開。為了使扇形閉合,只需在最後重覆第二個點。在OpenGL中通過GL_TRIANGLE_FAN指定數據代表三角形扇。

glDrawArrays(GL_TRIANGLE_FAN, 0, 6);

      上述代碼中,glDrawArrays的參數列表為:

// C function void glDrawArrays ( GLenum mode, GLint first, GLsizei count )

    public static native void glDrawArrays(
        int mode,
        int first,
        int count
    );

      可知,0代表第一頂點的位置,6表示6個頂點繪製一個三角形扇。

      接下來會把每個點上的顏色定義為一個頂點屬性,需要兩部分的工作:(1)頂點數據;(2)著色器。《Android學習筆記——OpenGL ES的基本用法、繪製流程與著色器編譯》中涉及到的頂點數據只有X/Y坐標,添加顏色屬性,則在頂點坐標後增加了R/G/B值。具體格式如下:

float[] tableVerticesWithTriangles = {   
            // Order of coordinates: X, Y, R, G, B
            
            // Triangle Fan
               0f,    0f,   1f,   1f,   1f,         
            -0.5f, -0.5f, 0.7f, 0.7f, 0.7f,            
             0.5f, -0.5f, 0.7f, 0.7f, 0.7f,
             0.5f,  0.5f, 0.7f, 0.7f, 0.7f,
            -0.5f,  0.5f, 0.7f, 0.7f, 0.7f,
            -0.5f, -0.5f, 0.7f, 0.7f, 0.7f,
};

      同樣的,相比於上一篇中涉及到的頂點著色器,增加顏色屬性。

attribute vec4 a_Position;  
attribute vec4 a_Color;

varying vec4 v_Color;

void main()                    
{                            
    v_Color = a_Color;
      
    gl_Position = a_Position;    
    gl_PointSize = 10.0;          
}

      這裡需要說明的是varying變數,varying變數是平滑的關鍵。以直線AB為例,如果頂點A的a_Color是紅色,頂點B的a_Color是綠色,那麼,從A到B,將會是紅色和綠色的混合。越接近頂點A,混合後的顏色顯得越紅;越接近頂點B,混合後的顏色就顯示越綠。至於混合的演算法,採用最基本的線性插值就可以完成。

      在三角形錶面混合時,與直線的線程插值相同,每個顏色在接近它的頂點處都是最強的,向其他頂點就會變暗,用比例確定每種顏色的相對權重,只是這裡使用的是面積的比例,而不是線性插值所使用的長度。

      回到AirHockeyRenderer中,首先在onSurfaceCreated體現顏色屬性。

aPositionLocation = glGetAttribLocation(program, A_POSITION);
aColorLocation = glGetAttribLocation(program, A_COLOR);

// Bind our data, specified by the variable vertexData, to the vertex
// attribute at location A_POSITION_LOCATION.
vertexData.position(0);
glVertexAttribPointer(aPositionLocation, POSITION_COMPONENT_COUNT, GL_FLOAT, 
     false, STRIDE, vertexData);

glEnableVertexAttribArray(aPositionLocation);     
        
// Bind our data, specified by the variable vertexData, to the vertex
// attribute at location A_COLOR_LOCATION.
vertexData.position(POSITION_COMPONENT_COUNT);        
glVertexAttribPointer(aColorLocation, COLOR_COMPONENT_COUNT, GL_FLOAT, 
      false, STRIDE, vertexData);        

glEnableVertexAttribArray(aColorLocation);

      流程與《Android學習筆記——OpenGL ES的基本用法、繪製流程與著色器編譯》中基本一致,只是添加了顏色屬性。aColorLocation為顏色屬性的位置,STRIDE為跨距,即tableVerticesWithTriangles數組中不僅包含頂點的坐標,還包含了顏色屬性,因此在取頂點坐標時,需要跨越顏色屬性。

      vertexData.position(POSITION_COMPONENT_COUNT)指定OpenGL讀取顏色屬性時,需要從第一個顏色屬性的位置開始,不是從第一個位置屬性。

      glVertexAttribPointer()關聯顏色數據和著色器中的attribute vec4 a_Color。glVertexAttribPointer的參數列表如下,其通過調用native方法glVertexAttribPointerBounds實現。

// C function void glVertexAttribPointer ( GLuint indx, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid *ptr )

    private static native void glVertexAttribPointerBounds(
        int indx,
        int size,
        int type,
        boolean normalized,
        int stride,
        java.nio.Buffer ptr,
        int remaining
    );

    public static void glVertexAttribPointer(
        int indx,
        int size,
        int type,
        boolean normalized,
        int stride,
        java.nio.Buffer ptr
    ) {
        glVertexAttribPointerBounds(
            indx,
            size,
            type,
            normalized,
            stride,
            ptr,
            ptr.remaining()
        );
    }

     在關聯好顏色屬性後,只需在AirHockeyRenderer的onDrawFrame繪製頂點數組即可,OpenGL會自動從頂點數據里讀入顏色屬性。

// Draw the table.        
glDrawArrays(GL_TRIANGLE_FAN, 0, 6);

// Draw the center dividing line.        
glDrawArrays(GL_LINES, 6, 2);

// Draw the first mallet.        
glDrawArrays(GL_POINTS, 8, 1);

// Draw the second mallet.
glDrawArrays(GL_POINTS, 9, 1);

     完成上述流程後,即可看到如下圖所示的效果。

device-2016-01-12-144016

     本節通過給頂點數據和頂點著色器增加顏色屬性,並且使用跨距讀入數據,最後通過varying在三角形平面上進行插值,使得兩點之間的顏色得以平滑過渡。

 

二、自適應寬高

      在Android開發中,橫豎屏切換時需要載入不同的佈局,採用OpenGL時,仍然存在屏幕大小、方向等的適配。OpenGL採用投影將真實世界映射到屏幕上,這種方式映射會使它在不同的屏幕尺寸或方向上看起來總是正確的。映射是通過矩陣變換來實現的,因此,這一節內容涉及到一些線性代數的基礎內容。

      首先需要瞭解歸一化坐標空間和虛擬坐標空間。之前使用的都是歸一化坐標空間,即把一切物體都映射到x軸和y軸的[-1,1]空間內,獨立於屏幕實際的尺寸和形狀。因此,在實際Android設備上,以1280*720解析度為例,歸一化坐標空間中的正方形會被壓扁。虛擬化坐標空間把較小的範圍固定在[-1,1]內,而按照屏幕尺寸調整較大的範圍。

      把虛擬坐標空間轉換會歸一化坐標空間的核心就是正交投影。正交投影矩陣與平移矩陣類似,會把左右、上下、遠近的事物映射到歸一化設備坐標[-1,1]範圍內。android.opengl包中的orthoM()方法可以生成一個正交投影矩陣,其參數列表為:

/**
     * Computes an orthographic projection matrix.
     *
     * @param m returns the result
     * @param mOffset
     * @param left
     * @param right
     * @param bottom
     * @param top
     * @param near
     * @param far
     */
    public static void orthoM(float[] m, int mOffset,
        float left, float right, float bottom, float top,
        float near, float far) {

    ……
}

      生成的正交投影矩陣的格式如下:

clip_image002

      要理解正交投影矩陣如何轉換虛擬坐標空間與歸一化坐標空間,最好的辦法是舉個例子。

clip_image001

      以1280*720解析度的橫屏模式為例,虛擬化坐標空間的x軸的範圍是[-1280/720,1280/720],即[-1.78,1.78],屏幕本身為歸一化坐標空間[-1,1],比如最右上角的點,在歸一化坐標空間中的坐標為[1,1],而在虛擬化坐標空間中的坐標為[1.78,1]。經過上述正交投影矩陣轉換之後,轉換回歸一矩陣。

clip_image002[6]

      將上述過程翻譯為代碼主要體現在三個地方:1)著色器;2)創建正交矩陣;3)傳遞矩陣給著色器。

uniform mat4 u_Matrix;

attribute vec4 a_Position;  
attribute vec4 a_Color;

varying vec4 v_Color;

void main()                    
{                            
    v_Color = a_Color;
            
    gl_Position = u_Matrix * a_Position;
    gl_PointSize = 10.0;          
}

      相比於之前的著色器,主要是設置gl_Position時,採用了u_Matrix與a_Position相乘,其中u_Matrix為上圖中左邊的正交投影矩陣,a_Position為右邊的虛擬化坐標空間坐標,相乘得到gl_Position為歸一化坐標空間坐標。

final float aspectRatio = width > height ? 
    (float) width / (float) height : 
    (float) height / (float) width;

if (width > height) {
    // Landscape
    orthoM(projectionMatrix, 0, -aspectRatio, aspectRatio, -1f, 1f, -1f, 1f);
} else {
    // Portrait or square
    orthoM(projectionMatrix, 0, -1f, 1f, -aspectRatio, aspectRatio, -1f, 1f);
}

      orthoM通過傳入不同的left-right,bottom-top參數生成針對橫豎屏的正交投影矩陣。

// Assign the matrix
glUniformMatrix4fv(uMatrixLocation, 1, false, projectionMatrix, 0);
// C function void glUniformMatrix4fv ( GLint location, GLsizei count, GLboolean transpose, const GLfloat *value )

    public static native void glUniformMatrix4fv(
        int location,
        int count,
        boolean transpose,
        float[] value,
        int offset
    );

      最後通過glUniformMatrix4fv方法將上述生成的正交投影矩陣傳遞給著色器。效果如下圖所示,可以看出,在橫豎屏模式下保持了同樣的形狀。

device-2016-01-12-175743  device-2016-01-12-175752

 

三、三維圖像生成

      在前一節中,為了使得物體能夠自適應屏幕的寬高比變化,使用了正交投影(Orthographic Projection;為了實現三維效果顯示,本節需要使用透視投影(Perspective Projection。如果對投影矩陣的推導過程有興趣的,可以參考《投影矩陣的推導(Deriving Projection Matrices)》,文中對正交投影以及透視投影的推導、使用做了詳細的介紹。

      OpenGL通過剪裁空間(Clip Space)的w分量做透視除法,得到三維效果。因此,從理論上講,只要更新頂點坐標tableVerticesWithTriangles數組中的w分量(同時設置z分量)為合適的值,OpenGL就能自動實現三維顯示。但實際操作中,一般不會硬編碼w分量的值,而是通過透視投影矩陣來生成。通用的透視投影矩陣如下:

clip_image002[1]

clip_image002[7]

      使用代碼創建透視投影矩陣:

public static void perspectiveM(float[] m, float yFovInDegrees, float aspect, float n, float f) {
    // 獲取視野角度,即公式中的(阿爾法)
    final float angleInRadians = (float) (yFovInDegrees * Math.PI / 180.0);
    // 計算焦距,即公式中的a    
    final float a = (float) (1.0 / Math.tan(angleInRadians / 2.0));
    // 生成矩陣    
    m[0] = a / aspect;
    m[1] = 0f;
    m[2] = 0f;
    m[3] = 0f;

    m[4] = 0f;
    m[5] = a;
    m[6] = 0f;
    m[7] = 0f;

    m[8] = 0f;
    m[9] = 0f;
    m[10] = -((f + n) / (f - n));
    m[11] = -1f;
        
    m[12] = 0f;
    m[13] = 0f;
    m[14] = -((2f * f * n) / (f - n));
    m[15] = 0f;        
}

      在onSurfaceChanged中調用該方法創建透視矩陣,這裡使用了45度的視野角度,並且距離近處平面距離為1,距離遠處平面距離為10,由於採用右手坐標系,所以視椎體從z值為-1的位置開始,在z值為-10的位置結束。

MatrixHelper.perspectiveM(projectionMatrix, 45, (float) width/ (float) height, 1f, 10f);

    因為沒有指定z的位置,預設情況下它處於0的位置,因此,還需將物體進行平移。平移採用模型矩陣,模型矩陣可以通過OpenGL內置函數生成。

setIdentityM(modelMatrix, 0);
translateM(modelMatrix, 0, 0f, 0f, -2.5f);

      同時使用模型矩陣與透視矩陣需要註意矩陣乘法的順序,直觀的理解,將物體在空間沿任意軸平移不會改變物體在相對視點所觀察到的形狀,而透視則會改變。因此,應該先將物體做平移變換,後做透視。

clip_image002[9]

      公式中將投影矩陣放在左邊,模型矩陣放在右邊,正好實現了先將頂點進行平移,後進行透視的目的。

final float[] temp = new float[16];
multiplyMM(temp, 0, projectionMatrix, 0, modelMatrix, 0);        
System.arraycopy(temp, 0, projectionMatrix, 0, temp.length);

      最後,還需要一步變化就可以看到真實的3D效果了,那就是旋轉變化。旋轉矩陣使用正弦和餘弦三角函數把旋轉角轉換成縮放因數,同樣,OpenGL提供了旋轉矩陣的實現方法:

/**
 * Rotates matrix m in place by angle a (in degrees)
 * around the axis (x, y, z).
 *
 * @param m source matrix
 * @param mOffset index into m where the matrix starts
 * @param a angle to rotate in degrees
 * @param x X axis component
 * @param y Y axis component
 * @param z Z axis component
 */
public static void rotateM(float[] m, int mOffset,
        float a, float x, float y, float z) {
    synchronized(sTemp) {
        setRotateM(sTemp, 0, a, x, y, z);
        multiplyMM(sTemp, 16, m, mOffset, sTemp, 0);
        System.arraycopy(sTemp, 16, m, mOffset, 16);
    }
}

      將m矩陣旋轉a度,可以分別針對x/y/z軸進行旋轉。這裡把物體繞x軸旋轉-60度。

rotateM(modelMatrix, 0, -60f, 1f, 0f, 0f);

      完成上述所有步驟之後,可以得到如下所示的效果圖。

image  image

 

總結:

(1)通過插值實現頂點間顏色的平滑過渡;

(2)通過正交投影實現橫豎屏切換時物體形狀的保持;

(3)通過透視投影、平移變化、旋轉變化實現物體的三維顯示。


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

-Advertisement-
Play Games
更多相關文章
  • 在C#和JAVA中無論是method還是variable都有嚴格的訪問級別控制,那麼在object-c中對訪問級別的使用非常稀少,原因可能是因為在method上沒有訪問級別的語法,單單控制變數沒有什麼力度吧。下麵我們來討論下實例變數的訪問級別。實例變數在類中聲明時預設是protected的不像一.....
  • http://www.kancloud.cn/digest/ios-mac-study/84557
  • 第十章 Android的消息機制 面試中經常會被問到的一個問題:handler是如何在子線程和主線程中進行消息的傳遞的,這個問題通過瞭解Android的消息機制可以得到一個準確的答案。 Android的消息機制主要就是指Handler的運行機制,Handler的運行需要底層的Messa...
  • Spinner(列表選項框) & AutoCompleteTextView(自動完成文本框)一、列表選項框核心屬性android:dropDownHorizontalOffset設置列表框的水平偏移距離android:dropDownVerticalOffset設置列表框的水平豎直距離android...
  • edgesForExtendedLayout:在IOS7以後 ViewController 開始使用全屏佈局的,而且是預設的行為通常涉及到佈局,就離不開這個屬性edgesForExtendedLayout,它是一個類型為UIExtendedEdge的屬性,指定邊緣要延伸的方向,它的預設值很自然地是U...
  • eMMC主要是針對手機和平板電腦等產品的內嵌式存儲器,由於其在封裝中集成了一個控制器,且提供標準介面並管理快閃記憶體等優勢,越來越受到Android手機廠商的青睞,以eMMC為存儲設備的android手機,其文件系統(system、data分區)一般採用ext4格式。如小米手機的線刷包:一.img解包 之...
  • GridView(網格視圖)講解一、GridView(網格視圖)這個是控制項也是比較多,和listView的很多地方都是一樣的,但是GridView可以顯示多列,而listView只能顯示一列,個人覺得這是最大的區別。常用屬性:android:columnWidth:設置列的寬度android:gra...
  • 註冊JPush賬號 JPush官網下載SDK下載地址:https://www.jpush.cn根據文檔進行集成文檔:http://docs.jpush.io/guideline/ios_guide/文檔介紹的已經很詳細了註意點:App Key需要登錄,添加自己的應用即可獲得,文檔中沒有給出跳轉的鏈接...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...