Opengl ES之矩陣變換(上)

来源:https://www.cnblogs.com/goFlyer/archive/2023/03/21/17239870.html
-Advertisement-
Play Games

華為HMS Core應用內支付服務(In-App Purchases,IAP)為應用提供便捷的應用內支付體驗和簡便的接入流程。該服務支持客戶端和服務端兩種開發形式,具體可以參考官方文檔。 往期文章:常見問題總結(2)中分享總結了有關無法拉起支付頁面的常見問題,本文將對近期開發者們較為關註的一些集成應 ...


前言

說到矩陣變換,我們第一時間想到的就是大學時代的線性代數這些複雜的東西,突然有了一種令人從入門到放棄的念頭,不慌,作為了一個應用層的CV工程師,
在實際應用中線性代數哪些複雜的計算根本不用我們自己去算,絕大部分情境下直接使用Matrix這個類或者glm這個庫即可。

關於矩陣與向量的相關知識,矩陣的加減乘除等規則,這裡就不展開細說,感興趣的同學自行查閱線性代數即可,不過這些規則忘記了也沒關係,反正有API可用。

我們知道在Opengl中有很多中坐標系,在Opengl中矩陣的一大作用就是將坐標從一個坐標系轉換到另一個坐標系下,同時還可以通過矩陣實現一些形變的效果,
今天我們就使用矩陣的方式搭配Opengl ES實現平移、縮放、旋轉等一些形變變換的效果。

通常來說在Opengl ES中的矩陣都是一個4X4的矩陣,也就是一個包含16個元素的一維數組。

下麵以Matrix這個類介紹一下矩陣變換的一些常用方法。下麵介紹的矩陣變換所參考的坐標系統都是一樣的,均是下圖這個:

單位矩陣

所謂的單位矩陣就是左上角到右下角對角線值均為1的矩陣,又成為單元矩陣。使用Matrix.setIdentityM方法可以將一個矩陣變為單位矩陣。

矩陣平移

矩陣平移所使用的方法是Matrix.translateM

需要註意的是在Opengl在頂點坐標的值是在-1到1之間,因此translateX的範圍可以為-2到2。為什麼呢?因為-1到1的距離是2,因此往最多可以往左移動2,同理,最多可以往右移動2。

矩陣旋轉

矩陣旋轉所使用的方法是Matrix.rotateM,其中第三個參數是表示選旋轉的角度,後面的三個參數xyz代表的是繞那個軸旋轉,繞那個軸旋轉就把那個軸的參數設置成1,其他軸設置成0即可。

矩陣縮放

矩陣縮放所使用的方法是Matrix.scaleM

組合矩陣的寫法

假如有以下形變步驟,先繞Z軸旋轉90度,再向X軸平移0.5,最後X軸縮放0.9倍,那麼最終這個形變矩陣該如何計算呢?是以下這個寫法嗎?

Matrix.rotateM(mvpMatrix, 0, 90, 0, 0, 1);
Matrix.translateM(mvpMatrix, 0, 0.5, 0, 0);
Matrix.scaleM(mvpMatrix, 0, 0.9, 1f, 0f);

不是的,組合矩陣的寫法有一個規則,這個規則大家一定要記住:

在組合矩陣時,先進行縮放操作,然後是旋轉,最後才是位移,但是寫法需要反正寫,也就是先寫translateM,然後rotateM,最後scaleM

如果不這樣寫會發生什麼呢?例如順著寫,先寫scaleM,然後是rotateM,最後寫translateM,測試時就會出現問題,旋轉超過180度之後再移動,就會出現移動方向相反的情況。

因此以上例子正確的寫法應該是這樣子的:

Matrix.translateM(mvpMatrix, 0, 0.5, 0, 0);
Matrix.rotateM(mvpMatrix, 0, 90, 0, 0, 1);
Matrix.scaleM(mvpMatrix, 0, 0.9, 1f, 0f);

show me code

在Opengl ES中可以使用mat4來表示一個4X4的矩陣,我們將總的變換矩陣在CPU中計算好之後以uniform的形式傳遞到著色器中去。
在頂點著色器中將矩陣與頂點坐標相乘的結果作為新的頂點輸出坐標即可完成矩陣變換。

以下是MatrixTransformOpengl.cpp的詳細代碼:

// 頂點著色器
static const char *ver = "#version 300 es\n"
                         "in vec4 aPosition;\n"
                         "in vec2 aTexCoord;\n"
                         "out vec2 TexCoord;\n"
                         "uniform mat4 mvpMatrix;\n"
                         "void main() {\n"
                         "  TexCoord = aTexCoord;\n"
                         "  gl_Position = mvpMatrix * aPosition;\n"
                         "}";

// 片元著色器
static const char *fragment = "#version 300 es\n"
                              "precision mediump float;\n"
                              "out vec4 FragColor;\n"
                              "in vec2 TexCoord;\n"
                              "uniform sampler2D ourTexture;\n"
                              "void main()\n"
                              "{\n"
                              "    FragColor = texture(ourTexture, TexCoord);\n"
                              "}";


// 使用繪製兩個三角形組成一個矩形的形式(三角形帶)
// 第一第二第三個點組成一個三角形,第二第三第四個點組成一個三角形
const static GLfloat VERTICES[] = {
        1.0f,-1.0f, // 右下
        1.0f,1.0f, // 右上
        -1.0f,-1.0f, // 左下
        -1.0f,1.0f // 左上
};

// 貼圖紋理坐標(參考手機屏幕坐標系統,原點在左上角)
//由於對一個OpenGL紋理來說,它沒有內在的方向性,因此我們可以使用不同的坐標把它定向到任何我們喜歡的方向上,然而大多數電腦圖像都有一個預設的方向,它們通常被規定為y軸向下,X軸向右
const static GLfloat TEXTURE_COORD[] = {
        1.0f,1.0f, // 右下
        1.0f,0.0f, // 右上
        0.0f,1.0f, // 左下
        0.0f,0.0f // 左上
};

MatrixTransformOpengl::MatrixTransformOpengl():BaseOpengl() {
    initGlProgram(ver,fragment);
    positionHandle = glGetAttribLocation(program,"aPosition");
    textureHandle = glGetAttribLocation(program,"aTexCoord");
    textureSampler = glGetUniformLocation(program,"ourTexture");
    matrixHandle = glGetUniformLocation(program,"mvpMatrix");
}

MatrixTransformOpengl::~MatrixTransformOpengl() noexcept {
    LOGD("MatrixTransformOpengl析構函數");
}

void MatrixTransformOpengl::setMvpMatrix(float *mvp) {
    for (int i = 0; i < 16; ++i) {
        mvpMatrix[i] = mvp[i];
    }
}

void MatrixTransformOpengl::setPixel(void *data, int width, int height, int length) {
    LOGD("texture setPixel");
    imageWidth = width;
    imageHeight = height;
    glGenTextures(1, &textureId);

    // 激活紋理,註意以下這個兩句是搭配的,glActiveTexture激活的是那個紋理,就設置的sampler2D是那個
    // 預設是0,如果不是0的話,需要在onDraw的時候重新激活一下?
//    glActiveTexture(GL_TEXTURE0);
//    glUniform1i(textureSampler, 0);

// 例如,一樣的
    glActiveTexture(GL_TEXTURE2);
    glUniform1i(textureSampler, 2);
    // 綁定紋理
    glBindTexture(GL_TEXTURE_2D, textureId);
    // 為當前綁定的紋理對象設置環繞、過濾方式
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
    // 生成mip貼圖
    glGenerateMipmap(GL_TEXTURE_2D);

    glBindTexture(GL_TEXTURE_2D, textureId);

    // 解綁定
    glBindTexture(GL_TEXTURE_2D, 0);
}

void MatrixTransformOpengl::onDraw() {

//    glViewport(0,0,imageWidth,imageHeight);

    glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);
    glUseProgram(program);

    // 激活紋理
    glActiveTexture(GL_TEXTURE2);
    glUniform1i(textureSampler, 2);

    // 綁定紋理
    glBindTexture(GL_TEXTURE_2D, textureId);

    // 設置矩陣
    glUniformMatrix4fv(matrixHandle, 1, GL_FALSE,mvpMatrix);

    /**
     * size 幾個數字表示一個點,顯示是兩個數字表示一個點
     * normalized 是否需要歸一化,不用,這裡已經歸一化了
     * stride 步長,連續頂點之間的間隔,如果頂點直接是連續的,也可填0
     */
    // 啟用頂點數據
    glEnableVertexAttribArray(positionHandle);
    glVertexAttribPointer(positionHandle,2,GL_FLOAT,GL_FALSE,0,VERTICES);

    // 紋理坐標
    glEnableVertexAttribArray(textureHandle);
    glVertexAttribPointer(textureHandle,2,GL_FLOAT,GL_FALSE,0,TEXTURE_COORD);

    // 4個頂點繪製兩個三角形組成矩形
    glDrawArrays(GL_TRIANGLE_STRIP,0,4);

    glUseProgram(0);

    // 禁用頂點
    glDisableVertexAttribArray(positionHandle);
    if(nullptr != eglHelper){
        eglHelper->swapBuffers();
    }

    glBindTexture(GL_TEXTURE_2D, 0);
}

java層的MatrixActivity.java實例代碼如下:

public class MatrixActivity extends BaseGlActivity {

    private MatrixTransformOpengl matrixTransformOpengl;
    // 遵守先縮放再旋轉最後平移的順序
    // 首先執行縮放,接著旋轉,最後才是平移。這就是矩陣乘法的工作方式。
    private final float[] mvpMatrix = new float[16];
    // 因為在Opengl在頂點坐標的值是在-1到1之間,因此translateX的範圍可以為-2到2。
    private float translateX = 0;
    private float scaleX = 1;
    private float rotationZ = 0;

    @Override
    public int getLayoutId() {
        return R.layout.activity_gl_matrix;
    }

    @Override
    public BaseOpengl createOpengl() {
        matrixTransformOpengl = new MatrixTransformOpengl();
        return matrixTransformOpengl;
    }

    @Override
    public Bitmap requestBitmap() {
        BitmapFactory.Options options = new BitmapFactory.Options();
        // 不縮放
        options.inScaled = false;
        Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_boy, options);

        // 設置一下矩陣
        Matrix.setIdentityM(mvpMatrix, 0);
        matrixTransformOpengl.setMvpMatrix(mvpMatrix);

        return bitmap;
    }

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        findViewById(R.id.bt_translate).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if (null != matrixTransformOpengl) {
                    translateX += 0.1;
                    if(translateX >=2 ){
                        translateX = 0f;
                    }
                    updateMatrix();
                }
            }
        });

        findViewById(R.id.bt_scale).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if (null != matrixTransformOpengl) {
                    scaleX += 0.1;
                    updateMatrix();
                }
            }
        });

        findViewById(R.id.bt_rotate).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if (null != matrixTransformOpengl) {
                    rotationZ += 10;
                    updateMatrix();
                }
            }
        });

        findViewById(R.id.bt_reset).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if (null != matrixTransformOpengl) {
                    translateX = 0;
                    scaleX = 1;
                    rotationZ = 0;
                    updateMatrix();
                }
            }
        });

    }

    private void updateMatrix() {
        Matrix.setIdentityM(mvpMatrix, 0);
        // 重點註釋
        // 在組合矩陣時,先進行縮放操作,然後是旋轉,最後才是位移,但是寫法需要反正寫,也就是先寫translateM,然後rotateM,最後scaleM
        // 如果不這樣寫會發生什麼呢?例如順這寫,先寫scaleM,然後是rotateM,最後寫translateM,測試時就會出現問題,旋轉超過180度之後再移動,就會出現移動方向相反的情況
        Matrix.translateM(mvpMatrix, 0, translateX, 0, 0);
        Matrix.rotateM(mvpMatrix, 0, rotationZ, 0, 0, 1);
        Matrix.scaleM(mvpMatrix, 0, scaleX, 1f, 0f);
        matrixTransformOpengl.setMvpMatrix(mvpMatrix);
        myGLSurfaceView.requestRender();
    }
}

運行結果

系列教程源碼

https://github.com/feiflyer/NDK_OpenglES_Tutorial

後續demo如果有完善可能會更新。

Opengl ES系列入門介紹

Opengl ES之EGL環境搭建
Opengl ES之著色器
Opengl ES之三角形繪製
Opengl ES之四邊形繪製
Opengl ES之紋理貼圖
Opengl ES之VBO和VAO
Opengl ES之EBO
Opengl ES之FBO
Opengl ES之PBO
Opengl ES之YUV數據渲染
YUV轉RGB的一些理論知識
Opengl ES之RGB轉NV21
Opengl ES之踩坑記
Opengl ES之矩陣變換(上)
Opengl ES之矩陣變換(下)
Opengl ES之水印貼圖

關註我,一起進步,人生不止coding!!!
微信掃碼關註


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

-Advertisement-
Play Games
更多相關文章
  • Linux 中的 Bash 腳本支持對變數的操作,下麵鹹魚將介紹 Linux Bash Shell 中關於變數的 5 個易錯點 因為編程習慣,這類現象往往發生在大多數使用過其他流行編程語言的程式員身上 變數賦值 對於許多編程語言(例如 Python),變數賦值的時候在等號兩邊添加空格是一個好的習慣 ...
  • MySQL基礎:函數 函數是指一段可以直接被另一段程式調用的程式或代碼。 字元串函數 MySQL中內置了很多字元串函數,常用的幾個如下: | 函數 | 功能 | | : : | : : | | CONCAT(S1,S2,...Sn) | 字元串拼接,將S1,S2,...Sn拼接成一個字元串 | | ...
  • SQL:DML、DQL、DCL DML:Data Manipulation Language(數據操作語言) DML用來對資料庫中的數據記錄進行增刪改操作。 DML-添加數據 給指定欄位添加數據(一條數據) INSERT INTO 表名(欄位名1,欄位名2,...) VALUES(值1,值2,... ...
  • 摘要:本文就針對因USING子句的書寫方式可能導致MERGE INTO語句的執行不下推的場景,對USING子句的SQL語句進行改寫一遍,整個SQL語句可以下推。 本文分享自華為雲社區《GaussDB(DWS)運維 -- values子句做MERGE數據源導致SQL執行不下推的改寫方案》,作者: 譡里 ...
  • 什麼是主數據? 主數據是一組用於提供有關業務數據(如位置、客戶、產品、資產等)情境的標識符。它是企業或單位內運行業務必不可少的核心數據。否則,將無法統一比較系統之間的數據。但是,並非所有主數據都是一樣的。被指定為主數據的數據類型可能因行業而異。即使在同一行業的不同業務實體中,主數據的示例也可能是離散 ...
  • 眾所周知Redis有以下幾種常見的數據類型 String(字元串)、List(列表)、Set(集合)、Hash(哈希)、Sorted set(有序集合)、Stream(流)、Geo(地理空間索引)、Bitmap(點陣圖)、HyperLogLog(基數統計)等。 我們最常用的就是String(字元串)... ...
  • 資料庫優化是一個綜合工程,不僅僅是需要DBA參與,更重要的是研發設計人員針對PG資料庫的特點來進行相關的優化設計。不過對於DBA來說,一旦接到上線和運維任務,基本上都是木已成舟,軟體設計方面留下的坑已經挖好,DBA的作為已經十分有限了。不過既然要乾運維,那麼少不了就要參與優化。PG的優化工作該如何開... ...
  • SparseArray家族 SparseArray基於鍵值對存儲數據,key為int,value為object,簡單使用如下: //聲明 SparseArray<String> sparseArray= new SparseArray<>(); //增加元素,append方式 sparseArray ...
一周排行
    -Advertisement-
    Play Games
  • 1. 說明 /* Performs operations on System.String instances that contain file or directory path information. These operations are performed in a cross-pla ...
  • 視頻地址:【WebApi+Vue3從0到1搭建《許可權管理系統》系列視頻:搭建JWT系統鑒權-嗶哩嗶哩】 https://b23.tv/R6cOcDO qq群:801913255 一、在appsettings.json中設置鑒權屬性 /*jwt鑒權*/ "JwtSetting": { "Issuer" ...
  • 引言 集成測試可在包含應用支持基礎結構(如資料庫、文件系統和網路)的級別上確保應用組件功能正常。 ASP.NET Core 通過將單元測試框架與測試 Web 主機和記憶體中測試伺服器結合使用來支持集成測試。 簡介 集成測試與單元測試相比,能夠在更廣泛的級別上評估應用的組件,確認多個組件一起工作以生成預 ...
  • 在.NET Emit編程中,我們探討了運算操作指令的重要性和應用。這些指令包括各種數學運算、位操作和比較操作,能夠在動態生成的代碼中實現對數據的處理和操作。通過這些指令,開發人員可以靈活地進行算術運算、邏輯運算和比較操作,從而實現各種複雜的演算法和邏輯......本篇之後,將進入第七部分:實戰項目 ...
  • 前言 多表頭表格是一個常見的業務需求,然而WPF中卻沒有預設實現這個功能,得益於WPF強大的控制項模板設計,我們可以通過修改控制項模板的方式自己實現它。 一、需求分析 下圖為一個典型的統計表格,統計1-12月的數據。 此時我們有一個需求,需要將月份按季度劃分,以便能夠直觀地看到季度統計數據,以下為該需求 ...
  • 如何將 ASP.NET Core MVC 項目的視圖分離到另一個項目 在當下這個年代 SPA 已是主流,人們早已忘記了 MVC 以及 Razor 的故事。但是在某些場景下 SSR 還是有意想不到效果。比如某些靜態頁面,比如追求首屏載入速度的時候。最近在項目中回歸傳統效果還是不錯。 有的時候我們希望將 ...
  • System.AggregateException: 發生一個或多個錯誤。 > Microsoft.WebTools.Shared.Exceptions.WebToolsException: 生成失敗。檢查輸出視窗瞭解更多詳細信息。 內部異常堆棧跟蹤的結尾 > (內部異常 #0) Microsoft ...
  • 引言 在上一章節我們實戰了在Asp.Net Core中的項目實戰,這一章節講解一下如何測試Asp.Net Core的中間件。 TestServer 還記得我們在集成測試中提供的TestServer嗎? TestServer 是由 Microsoft.AspNetCore.TestHost 包提供的。 ...
  • 在發現結果為真的WHEN子句時,CASE表達式的真假值判斷會終止,剩餘的WHEN子句會被忽略: CASE WHEN col_1 IN ('a', 'b') THEN '第一' WHEN col_1 IN ('a') THEN '第二' ELSE '其他' END 註意: 統一各分支返回的數據類型. ...
  • 在C#編程世界中,語法的精妙之處往往體現在那些看似微小卻極具影響力的符號與結構之中。其中,“_ =” 這一組合突然出現還真不知道什麼意思。本文將深入剖析“_ =” 的含義、工作原理及其在實際編程中的廣泛應用,揭示其作為C#語法奇兵的重要角色。 一、下劃線 _:神秘的棄元符號 下劃線 _ 在C#中並非 ...