目錄 一.前言 二.坐標系 1.屏幕坐標系 2.紋理坐標系 3.頂點坐標系 4.圖像坐標系 三.混合 四.變換矩陣 1.平移 2.旋轉 3.縮放 4.矩陣組合順序 五.投影矩陣 1.正交投影 2.透視投影 3.總結 六.幀緩衝區幀 七.VAO 八.VBO 九.PBO 十.FBO 十一.UBO 十二. ...
目錄
零基礎 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 = 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 = 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);
十三.猜你喜歡
本文由博客 - 猿說編程 猿說編程 發佈!