目錄
- 一.前言
- 二.坐標系
- 1.螢屏坐標系
- 2.紋理坐標系
- 3.頂點坐標系
- 4.影像坐標系
- 三.混合
- 四.變換矩陣
- 1.平移
- 2.旋轉
- 3.縮放
- 4.矩陣組合順序
- 五.投影矩陣
- 1.正交投影
- 2.透視投影
- 3.總結
- 六.幀緩沖區幀
- 七.VAO
- 八.VBO
- 九.PBO??
- 十.FBO
- 十一.UBO
- 十二.TBO
- 十三.猜你喜歡
零基礎 OpenGL ES 學習路線推薦 : OpenGL ES 學習目錄 >> OpenGL ES 基礎
零基礎 OpenGL ES 學習路線推薦 : OpenGL ES 學習目錄 >> OpenGL ES 特效
零基礎 OpenGL ES 學習路線推薦 : OpenGL ES 學習目錄 >> OpenGL ES 轉場
零基礎 OpenGL ES 學習路線推薦 : OpenGL ES 學習目錄 >> OpenGL ES 函式
零基礎 OpenGL ES 學習路線推薦 : OpenGL ES 學習目錄 >> OpenGL ES GPUImage 使用
零基礎 OpenGL ES 學習路線推薦 : OpenGL ES 學習目錄 >> OpenGL ES GLSL 編程
一.前言
在《OpenGL ES 名詞解釋一》中已經講解了著色器渲染等相關知識,本篇文章著重講解坐標系和矩陣相關內容;
二.坐標系
1.螢屏坐標系
螢屏坐標系 的 左下點(0, 1),右下角(1,1) , 左上角(0, 0) , 右上角(1 , 0)

2.紋理坐標系
紋理坐標系 的 左下點 (0, 0),右下角(1 , 0) , 左上角(0, 1 ), 右上角(1, 1)

3.頂點坐標系
頂點坐標系 的 左下點(-1, -1),右下角(1,-1) , 左上角(-1, 1) , 右上角(1 , 1)

4.影像坐標系
螢屏坐標系 的 左下點(0, 1),右下角(1,1) , 左上角(0, 0) , 右上角(1 , 0)

很多人有一個誤解:認為 OpenGL ES 紋理原點在左上角,因為如果繪制時紋理坐標設在左下角,繪制的影像就是上下倒立;而紋理坐標設制在左上角顯示正常;
原因:影像默認的原點在左上角,而 OpenGL ES 紋理讀取資料或者 FBO 讀取資料時都是以左下角開始,所以影像才會出現上下倒立的現象;
解決辦法:
- 方案一:繪制時將紋理坐標上下鏡像
- 方案二:繪制時將頂點坐標上下鏡像
- 方案三:繪制時將影像上下鏡像后在填充到 OpenGL ES 紋理
關于方案三:將圖片上下顛倒可以使用 stb_image 完成
stbi_set_flip_vertically_on_load(true);//開起上下鏡像
三.混合
假設一種不透明東西的顏色是 A,另一種透明的東西的顏色是 B ,那么透過 B 去看 A ,看上去的顏色 C 就是 B 和 A 的混合顏色,可以用這個式子來近似,設 B 物體的透明度為 alpha (取值為 0 – 1 ,0 為完全透明,1 為完全不透明)
R(C)=alpha*R(B)+(1-alpha)*R(A)
G(C)=alpha*G(B)+(1-alpha)*G(A)
B(C)=alpha*B(B)+(1-alpha)*B(A)
R(x)、G(x)、B(x)分別指顏色 x 的 RGB 分量,看起來這個東西這么簡單,可是用它實作的效果絕對不簡單,應用 alpha 混合技術,可以實作出最眩目的火光、煙霧、陰影、動態光源等等一切你可以想象的出來的半透明效果,

四.變換矩陣
1.平移
為向量(x,y,z)定義一個平移矩陣

2.旋轉
旋轉程序涉及到弧度與角度的轉化:
弧度轉角度:角度 = 弧度 * (180.0f / PI)
角度轉弧度:弧度 = 角度 * (PI / 180.0f)

3.縮放
為向量(x,y,z)定義一個縮放矩陣

4.矩陣組合順序
矩陣組合順序 1:先平移,再旋轉,最后縮放——— OK
矩陣組合順序 2:先平移,再縮放,最后旋轉——— ERROR
矩陣組合順序 3:先縮放,再旋轉,最后平移——— ERROR
(除了第一種,其他組合順序都是錯誤的)
矩陣組合順序可以參考 glm 官方 demo 案例:
#include <glm/vec3.hpp> // glm::vec3
#include <glm/vec4.hpp> // glm::vec4
#include <glm/mat4x4.hpp> // glm::mat4
#include <glm/ext/matrix_transform.hpp> // glm::translate, glm::rotate, glm::scale
#include <glm/ext/matrix_clip_space.hpp> // glm::perspective
#include <glm/ext/scalar_constants.hpp> // glm::pi
glm::mat4 camera(float Translate, glm::vec2 const& Rotate)
{
glm::mat4 Projection = glm::perspective(glm::pi<float>() * 0.25f, 4.0f / 3.0f, 0.1f, 100.f);
glm::mat4 View = glm::translate(glm::mat4(1.0f), glm::vec3(0.0f, 0.0f, -Translate));
View = glm::rotate(View, Rotate.y, glm::vec3(-1.0f, 0.0f, 0.0f));
View = glm::rotate(View, Rotate.x, glm::vec3(0.0f, 1.0f, 0.0f));
glm::mat4 Model = glm::scale(glm::mat4(1.0f), glm::vec3(0.5f));
return Projection * View * Model;
}
至于矩陣組合順序為什么是先平移,再旋轉,最后縮放,后面將專門留一篇文章做詳細講解!可以關注學習目錄《OpenGL ES 基礎》
五.投影矩陣
由觀察空間到裁剪空間在公式上左乘一個投影矩陣,投影矩陣的產生分為兩種:正交投影和透視投影;
不管是正交投影還是透視投影,最終都是將視景體內的物體投影在近平面上,這也是 3D 坐標轉換到 2D 坐標的關鍵一步,
正投影就是沒有 3D 效果的投影方式,用于顯示 2D 效果;
透視投影就是有 3D 效果的投影方式,用于顯示 3D 效果.

1.正交投影
正交投影產生的效果無論你離物體多遠多近,都不會產生近大遠小的效果,大致如下圖,視點作為觀察點,視椎體由前后左右上下 6 個面包裹而成,物體在視椎體內部,最后投影到 near 近平面,視椎體范圍之外將無法顯示到螢屏上來
正投影就是沒有 3D 效果的投影方式,用于顯示 2D 效果;
透視投影就是有 3D 效果的投影方式,用于顯示 3D 效果.
正交投影矩陣,由 Matrix.ortho 這個方法產生
void orthoM(float[] m, int mOffset,
float left, float right, float bottom, float top,
float near, float far)
可以把近平面看作螢屏,left、right、top、bottom 都是以近平面中心相對的距離,由于手機螢屏的長寬一般不相等,以短邊為基準 1 ,長邊取值為長/寬,所以如果一個豎屏的手機使用這個正交投影產生的矩陣應該是:
float ratio = (float)height / width;
Matrix.ortho(projectMatrix,0,-1, 1, -ratio, ratio, 1, 6);
2.透視投影
透視投影會產生近大遠小的效果,正投影就是沒有 3D 效果的投影方式,用于顯示 2D 效果;透視投影就是有 3D 效果的投影方式,用于顯示 3D 效果.產生的視椎體如下圖:

透視投影也有回應的函式產生投影矩陣:
Matrix.frustumM(float[] m, int offset, float left,
float right, float bottom, float top,
float near, float far);
3.總結
經過上述的講解,我們要完成 4 個空間轉換,需要用到了 3 個轉換矩陣:
從區域空間轉換到世界空間,我們需要用到模型矩陣 ModeMatrix ,這個矩陣就是我們通常對物體進行 translate 、rorate 換后產生的矩陣
從世界空間到觀察空間,我們需要用到觀察矩陣 ViewMatrix ,這個矩陣可以 setLookAt 方法幫我們生成
從觀察空間到裁剪空間,我們可以用到投影矩陣 ProjectMatrix,使用 ortho 、frustuM 還有 perspectiveM 方法產生投影矩陣
最后以上幾個坐標依次左乘我們的定義的坐標 Position 就可以得到歸一化坐標了
所以,總結出來的公式
//注意順序
gl_Position = ProjectMatrix * ViewMatrix * ModeMatrix * g_Position ;

六.幀緩沖區幀
緩沖區就是顯存,也被叫做幀快取,它的作用是用來存盤顯卡芯片處理過或者即將提取的渲染資料,如同計算機的記憶體一樣,顯存是用來存盤要處理的圖形資訊的部件,
最終”存活”下來的像素需要被顯示到螢屏上,但是顯示螢屏之前,這些像素是會被先提交在幀緩沖區的,幀快取區的每一存盤單元對應螢屏上的一個像素,整個幀快取區對應一幀影像,
在下一個重繪頻率到來時,視頻控制器會把幀緩沖區內的內容映射到螢屏上,一般采用雙緩沖機制,存在兩個幀緩沖區,
七.VAO
VAO (頂點陣列物件:Vertex Array Object)是指頂點陣列物件,主要用于管理 VBO 或 EBO ,減少 glBindBuffer 、glEnableVertexAttribArray、 glVertexAttribPointer 這些呼叫操作,高效地實作在頂點陣列配置之間切換,
OpenGL 2.0 有 VBO,沒有 VAO,VAO 是 OpenGL 3.0 才開始支持的,并且在 OpenGL 3.0 中,強制要求系結一個 VAO 才能開始繪制,
八.VBO
VBO(頂點緩沖區物件:Vertex Buffer Object)是指把頂點資料保存在顯存中,繪制時直接從顯存中取資料,減少了資料傳輸的開銷,因為頂點資料多了,就是坐標的資料多了很多的很多組,切換的時候很麻煩,就出現了這個 VAO,系結對應的頂點資料
OpenGL 2.0 有 VBO,沒有 VAO,VAO 是 OpenGL 3.0 才開始支持的,并且在 OpenGL 3.0 中,強制要求系結一個 VAO 才能開始繪制,
九.PBO
**PBO (Pixel Buffer Object)是 OpenGL ES 3.0 的概念(OpenGL 2.0 不支持 PBO ,3.0 支持 PBO),稱為像素緩沖區物件,**主要被用于異步像素傳輸操作,PBO 僅用于執行像素傳輸,不連接到紋理,且與 FBO (幀緩沖區物件)無關,PBO 設計的目的就是快速地向顯卡傳輸資料,或者從顯卡讀取資料,我們可以使用它更加高效的讀取螢屏資料,
- PBO 類似于 VBO(頂點緩沖區物件),PBO 開辟的也是 GPU 快取,而存盤的是影像資料,
- PBO 可以在 GPU 的快取間快速傳遞像素資料,不影響 CPU 時鐘周期,除此之外,PBO 還支持異步傳輸,
- PBO 類似于“以空間換時間”策略,在使用一個 PBO 的情況下,性能無法有效地提升,通常需要多個 PBO 交替配合使用,

十.FBO
FBO(Frame Buffer Object) 即幀緩沖物件,FBO 有什么作用呢?通常使用 OpenGL ES 經過頂點著色器、片元著色器處理之后就通過使用 OpenGL ES 使用的視窗系統提供的幀緩沖區,這樣繪制的結果是顯示到視窗(螢屏)上,

但是對于有些復雜的渲染處理,通過多個濾鏡處理,這時中間流程的渲染采樣的結果就不應該直接輸出顯示螢屏,而應該等所有處理完成之后再顯示到視窗上,這個時候 FBO 就派上用場了,

FBO 是一個容器,自身不能用于渲染,需要與一些可渲染的緩沖區系結在一起,像紋理或者渲染緩沖區,,它僅且提供了 3 個附著(Attachment),分別是顏色附著、深度附著和模板附著,
十一.UBO
**UBO,Uniform Buffer Object 顧名思義,就是一個裝載 uniform 變數資料的緩沖區物件,**本質上跟 OpenGL ES 的其他緩沖區物件沒有區別,創建方式也大致一致,都是顯存上一塊用于儲存特定資料的區域,
當資料加載到 UBO ,那么這些資料將存盤在 UBO 上,而不再交給著色器程式,所以它們不會占用著色器程式自身的 uniform 存盤空間,UBO 是一種新的從記憶體到顯存的資料傳遞方式,另外 UBO 一般需要與 uniform 塊配合使用,
本例將 MVP 變換矩陣設定為一個 uniform 塊,即我們后面創建的 UBO 中將保存 3 個矩陣,
#version 310 es
layout(location = 0) in vec4 a_position;
layout(location = 1) in vec2 a_texCoord;
layout (std140) uniform MVPMatrix
{
mat4 projection;
mat4 view;
mat4 model;
};
out vec2 v_texCoord;
void main()
{
gl_Position = projection * view * model * a_position;
v_texCoord = a_texCoord;
}
設定 uniform 塊的系結點為 0 ,生成一個 UBO ,
GLuint uniformBlockIndex = glGetUniformBlockIndex(m_ProgramObj, "MVPMatrix");
glUniformBlockBinding(m_ProgramObj, uniformBlockIndex, 0);
glGenBuffers(1, &m_UboId);
glBindBuffer(GL_UNIFORM_BUFFER, m_UboId);
glBufferData(GL_UNIFORM_BUFFER, 3 * sizeof(glm::mat4), nullptr, GL_STATIC_DRAW);
繪制的時候更新 Uniform Buffer 的資料,更新三個矩陣的資料,注意偏移量,
glBindBuffer(GL_UNIFORM_BUFFER, m_UboId);
glBufferSubData(GL_UNIFORM_BUFFER, 0, sizeof(glm::mat4), &m_ProjectionMatrix[0][0]);
glBufferSubData(GL_UNIFORM_BUFFER, sizeof(glm::mat4), sizeof(glm::mat4), &m_ViewMatrix[0][0]);
glBufferSubData(GL_UNIFORM_BUFFER, 2 *sizeof(glm::mat4), sizeof(glm::mat4), &m_ModelMatrix[0][0]);
glBindBuffer(GL_UNIFORM_BUFFER, 0);
十二.TBO
紋理緩沖區物件,即 TBO(Texture Buffer Object),是 OpenGL ES 3.2 引入的概念,因此在使用時首先要檢查 OpenGL ES 的版本,Android 方面需要保證 API >= 24 ,
TBO 需要配合緩沖區紋理(Buffer Texture)一起使用,Buffer Texture 是一種一維紋理,其存盤資料來自紋理緩沖區物件(TBO),用于允許著色器訪問由緩沖區物件管理的大型記憶體表,
在 GLSL 中,只能使用 texelFetch 函式訪問緩沖區紋理,緩沖區紋理的采樣器型別為 samplerBuffer ,
生成一個 TBO 的方式跟 VBO 類似,只需要系結到 GL_TEXTURE_BUFFER ,而生成緩沖區紋理的方式與普通的 2D 紋理一樣,
//生成一個 Buffer Texture
glGenTextures(1, &m_TboTexId);
float *bigData = https://www.cnblogs.com/shuopython/archive/2022/10/27/new float[BIG_DATA_SIZE];
for (int i = 0; i < BIG_DATA_SIZE; ++i) {
bigData[i] = i * 1.0f;
}
//生成一個 TBO ,并將一個大的陣列上傳至 TBO
glGenBuffers(1, &m_TboId);
glBindBuffer(GL_TEXTURE_BUFFER, m_TboId);
glBufferData(GL_TEXTURE_BUFFER, sizeof(float) * BIG_DATA_SIZE, bigData, GL_STATIC_DRAW);
delete [] bigData;
使用紋理緩沖區的片段著色器,需要引入擴展 texture buffer ,注意版本宣告為 #version 320 es
#version 320 es
#extension GL_EXT_texture_buffer : require
in mediump vec2 v_texCoord;
layout(location = 0) out mediump vec4 outColor;
uniform mediump samplerBuffer u_buffer_tex;
uniform mediump sampler2D u_2d_texture;
uniform mediump int u_BufferSize;
void main()
{
mediump int index = int((v_texCoord.x +v_texCoord.y) /2.0 * float(u_BufferSize - 1));
mediump float value = https://www.cnblogs.com/shuopython/archive/2022/10/27/texelFetch(u_buffer_tex, index).x;
mediump vec4 lightColor = vec4(vec3(vec2(value / float(u_BufferSize - 1)), 0.0), 1.0);
outColor = texture(u_2d_texture, v_texCoord) * lightColor;
}
繪制時如何使用緩沖區紋理和 TBO
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_BUFFER, m_TboTexId);
glTexBuffer(GL_TEXTURE_BUFFER, GL_R32F, m_TboId);
GLUtils::setInt(m_ProgramObj, "u_buffer_tex", 0);
十三.猜你喜歡
- OpenGL ES 簡介
- OpenGL ES 版本介紹
- OpenGL ES 2.0 和 3.0 區別
- OpenGL ES 名詞解釋(一)
- OpenGL ES 名詞解釋(二)
本文由博客 - 猿說編程 猿說編程 發布!
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/521815.html
標籤:其他
下一篇:Docker詳解
