目錄
零基礎(chǔ) OpenGL ES 學(xué)習(xí)路線(xiàn)推薦 : OpenGL ES 學(xué)習(xí)目錄 >> OpenGL ES 基礎(chǔ)
零基礎(chǔ) OpenGL ES 學(xué)習(xí)路線(xiàn)推薦 : OpenGL ES 學(xué)習(xí)目錄 >> OpenGL ES 特效
零基礎(chǔ) OpenGL ES 學(xué)習(xí)路線(xiàn)推薦 : OpenGL ES 學(xué)習(xí)目錄 >> OpenGL ES 轉(zhuǎn)場(chǎng)
零基礎(chǔ) OpenGL ES 學(xué)習(xí)路線(xiàn)推薦 : OpenGL ES 學(xué)習(xí)目錄 >> OpenGL ES 函數(shù)
零基礎(chǔ) OpenGL ES 學(xué)習(xí)路線(xiàn)推薦 : OpenGL ES 學(xué)習(xí)目錄 >> OpenGL ES GPUImage 使用
零基礎(chǔ) OpenGL ES 學(xué)習(xí)路線(xiàn)推薦 : OpenGL ES 學(xué)習(xí)目錄 >> OpenGL ES GLSL 編程
一.前言
在《OpenGL ES 名詞解釋一》中已經(jīng)講解了著色器渲染等相關(guān)知識(shí),本篇文章著重講解坐標(biāo)系和矩陣相關(guān)內(nèi)容;
二.坐標(biāo)系
1.屏幕坐標(biāo)系
屏幕坐標(biāo)系 的 左下點(diǎn)(0, 1),右下角(1,1) , 左上角(0, 0) , 右上角(1 , 0)

2.紋理坐標(biāo)系
紋理坐標(biāo)系 的 左下點(diǎn) (0, 0),右下角(1 , 0) , 左上角(0, 1 ), 右上角(1, 1)

3.頂點(diǎn)坐標(biāo)系
頂點(diǎn)坐標(biāo)系 的 左下點(diǎn)(-1, -1),右下角(1,-1) , 左上角(-1, 1) , 右上角(1 , 1)

4.圖像坐標(biāo)系
屏幕坐標(biāo)系 的 左下點(diǎn)(0, 1),右下角(1,1) , 左上角(0, 0) , 右上角(1 , 0)

很多人有一個(gè)誤解:認(rèn)為 OpenGL ES 紋理原點(diǎn)在左上角,因?yàn)槿绻L制時(shí)紋理坐標(biāo)設(shè)在左下角,繪制的圖像就是上下倒立;而紋理坐標(biāo)設(shè)制在左上角顯示正常;
原因:圖像默認(rèn)的原點(diǎn)在左上角,而 OpenGL ES 紋理讀取數(shù)據(jù)或者 FBO 讀取數(shù)據(jù)時(shí)都是以左下角開(kāi)始,所以圖像才會(huì)出現(xiàn)上下倒立的現(xiàn)象;
解決辦法:
- 方案一:繪制時(shí)將紋理坐標(biāo)上下鏡像
- 方案二:繪制時(shí)將頂點(diǎn)坐標(biāo)上下鏡像
- 方案三:繪制時(shí)將圖像上下鏡像后在填充到 OpenGL ES 紋理
關(guān)于方案三:將圖片上下顛倒可以使用 stb_image 完成
stbi_set_flip_vertically_on_load(true);//開(kāi)起上下鏡像
三.混合
假設(shè)一種不透明東西的顏色是 A,另一種透明的東西的顏色是 B ,那么透過(guò) B 去看 A ,看上去的顏色 C 就是 B 和 A 的混合顏色,可以用這個(gè)式子來(lái)近似,設(shè) 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 分量。看起來(lái)這個(gè)東西這么簡(jiǎn)單,可是用它實(shí)現(xiàn)的效果絕對(duì)不簡(jiǎn)單,應(yīng)用 alpha 混合技術(shù),可以實(shí)現(xiàn)出最眩目的火光、煙霧、陰影、動(dòng)態(tài)光源等等一切你可以想象的出來(lái)的半透明效果。

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

2.旋轉(zhuǎn)
旋轉(zhuǎn)過(guò)程涉及到弧度與角度的轉(zhuǎn)化:
弧度轉(zhuǎn)角度:角度 = 弧度 * (180.0f / PI)
角度轉(zhuǎn)弧度:弧度 = 角度 * (PI / 180.0f)

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

4.矩陣組合順序
矩陣組合順序 1:先平移,再旋轉(zhuǎn),最后縮放——— OK
矩陣組合順序 2:先平移,再縮放,最后旋轉(zhuǎn)——— ERROR
矩陣組合順序 3:先縮放,再旋轉(zhuǎn),最后平移——— ERROR
(除了第一種,其他組合順序都是錯(cuò)誤的)
矩陣組合順序可以參考 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;
}
至于矩陣組合順序?yàn)槭裁词窍绕揭?,再旋轉(zhuǎn),最后縮放,后面將專(zhuān)門(mén)留一篇文章做詳細(xì)講解!可以關(guān)注學(xué)習(xí)目錄《OpenGL ES 基礎(chǔ)》
五.投影矩陣
由觀(guān)察空間到裁剪空間在公式上左乘一個(gè)投影矩陣,投影矩陣的產(chǎn)生分為兩種:正交投影和透視投影;
不管是正交投影還是透視投影,最終都是將視景體內(nèi)的物體投影在近平面上,這也是 3D 坐標(biāo)轉(zhuǎn)換到 2D 坐標(biāo)的關(guān)鍵一步。
正投影就是沒(méi)有 3D 效果的投影方式,用于顯示 2D 效果;
透視投影就是有 3D 效果的投影方式,用于顯示 3D 效果.

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

透視投影也有響應(yīng)的函數(shù)產(chǎn)生投影矩陣:
Matrix.frustumM(float[] m, int offset, float left,
float right, float bottom, float top,
float near, float far);
3.總結(jié)
經(jīng)過(guò)上述的講解,我們要完成 4 個(gè)空間轉(zhuǎn)換,需要用到了 3 個(gè)轉(zhuǎn)換矩陣:
從局部空間轉(zhuǎn)換到世界空間,我們需要用到模型矩陣 ModeMatrix ,這個(gè)矩陣就是我們通常對(duì)物體進(jìn)行 translate 、rorate 換后產(chǎn)生的矩陣
從世界空間到觀(guān)察空間,我們需要用到觀(guān)察矩陣 ViewMatrix ,這個(gè)矩陣可以 setLookAt 方法幫我們生成
從觀(guān)察空間到裁剪空間,我們可以用到投影矩陣 ProjectMatrix,使用 ortho 、frustuM 還有 perspectiveM 方法產(chǎn)生投影矩陣
最后以上幾個(gè)坐標(biāo)依次左乘我們的定義的坐標(biāo) Position 就可以得到歸一化坐標(biāo)了
所以,總結(jié)出來(lái)的公式
//注意順序
gl_Position = ProjectMatrix * ViewMatrix * ModeMatrix * g_Position ;

六.幀緩沖區(qū)幀
緩沖區(qū)就是顯存,也被叫做幀緩存,它的作用是用來(lái)存儲(chǔ)顯卡芯片處理過(guò)或者即將提取的渲染數(shù)據(jù)。如同計(jì)算機(jī)的內(nèi)存一樣,顯存是用來(lái)存儲(chǔ)要處理的圖形信息的部件。
最終”存活”下來(lái)的像素需要被顯示到屏幕上,但是顯示屏幕之前,這些像素是會(huì)被先提交在幀緩沖區(qū)的。幀緩存區(qū)的每一存儲(chǔ)單元對(duì)應(yīng)屏幕上的一個(gè)像素,整個(gè)幀緩存區(qū)對(duì)應(yīng)一幀圖像。
在下一個(gè)刷新頻率到來(lái)時(shí),視頻控制器會(huì)把幀緩沖區(qū)內(nèi)的內(nèi)容映射到屏幕上。一般采用雙緩沖機(jī)制,存在兩個(gè)幀緩沖區(qū)。
七.VAO
VAO (頂點(diǎn)數(shù)組對(duì)象:Vertex Array Object)是指頂點(diǎn)數(shù)組對(duì)象,主要用于管理 VBO 或 EBO ,減少 glBindBuffer 、glEnableVertexAttribArray、 glVertexAttribPointer 這些調(diào)用操作,高效地實(shí)現(xiàn)在頂點(diǎn)數(shù)組配置之間切換。
OpenGL 2.0 有 VBO,沒(méi)有 VAO,VAO 是 OpenGL 3.0 才開(kāi)始支持的,并且在 OpenGL 3.0 中,強(qiáng)制要求綁定一個(gè) VAO 才能開(kāi)始繪制。
八.VBO
VBO(頂點(diǎn)緩沖區(qū)對(duì)象:Vertex Buffer Object)是指把頂點(diǎn)數(shù)據(jù)保存在顯存中,繪制時(shí)直接從顯存中取數(shù)據(jù),減少了數(shù)據(jù)傳輸?shù)拈_(kāi)銷(xiāo),因?yàn)轫旤c(diǎn)數(shù)據(jù)多了,就是坐標(biāo)的數(shù)據(jù)多了很多的很多組,切換的時(shí)候很麻煩,就出現(xiàn)了這個(gè) VAO,綁定對(duì)應(yīng)的頂點(diǎn)數(shù)據(jù)
OpenGL 2.0 有 VBO,沒(méi)有 VAO,VAO 是 OpenGL 3.0 才開(kāi)始支持的,并且在 OpenGL 3.0 中,強(qiáng)制要求綁定一個(gè) VAO 才能開(kāi)始繪制。
九.PBO
**PBO (Pixel Buffer Object)是 OpenGL ES 3.0 的概念(OpenGL 2.0 不支持 PBO ,3.0 支持 PBO),稱(chēng)為像素緩沖區(qū)對(duì)象,**主要被用于異步像素傳輸操作。PBO 僅用于執(zhí)行像素傳輸,不連接到紋理,且與 FBO (幀緩沖區(qū)對(duì)象)無(wú)關(guān)。PBO 設(shè)計(jì)的目的就是快速地向顯卡傳輸數(shù)據(jù),或者從顯卡讀取數(shù)據(jù),我們可以使用它更加高效的讀取屏幕數(shù)據(jù)。
- PBO 類(lèi)似于 VBO(頂點(diǎn)緩沖區(qū)對(duì)象),PBO 開(kāi)辟的也是 GPU 緩存,而存儲(chǔ)的是圖像數(shù)據(jù)。
- PBO 可以在 GPU 的緩存間快速傳遞像素?cái)?shù)據(jù),不影響 CPU 時(shí)鐘周期,除此之外,PBO 還支持異步傳輸。
- PBO 類(lèi)似于“以空間換時(shí)間”策略,在使用一個(gè) PBO 的情況下,性能無(wú)法有效地提升,通常需要多個(gè) PBO 交替配合使用。

十.FBO
FBO(Frame Buffer Object) 即幀緩沖對(duì)象。FBO 有什么作用呢?通常使用 OpenGL ES 經(jīng)過(guò)頂點(diǎn)著色器、片元著色器處理之后就通過(guò)使用 OpenGL ES 使用的窗口系統(tǒng)提供的幀緩沖區(qū),這樣繪制的結(jié)果是顯示到窗口(屏幕)上。

但是對(duì)于有些復(fù)雜的渲染處理,通過(guò)多個(gè)濾鏡處理,這時(shí)中間流程的渲染采樣的結(jié)果就不應(yīng)該直接輸出顯示屏幕,而應(yīng)該等所有處理完成之后再顯示到窗口上。這個(gè)時(shí)候 FBO 就派上用場(chǎng)了。

FBO 是一個(gè)容器,自身不能用于渲染,需要與一些可渲染的緩沖區(qū)綁定在一起,像紋理或者渲染緩沖區(qū)。,它僅且提供了 3 個(gè)附著(Attachment),分別是顏色附著、深度附著和模板附著。
十一.UBO
**UBO,Uniform Buffer Object 顧名思義,就是一個(gè)裝載 uniform 變量數(shù)據(jù)的緩沖區(qū)對(duì)象,**本質(zhì)上跟 OpenGL ES 的其他緩沖區(qū)對(duì)象沒(méi)有區(qū)別,創(chuàng)建方式也大致一致,都是顯存上一塊用于儲(chǔ)存特定數(shù)據(jù)的區(qū)域。
當(dāng)數(shù)據(jù)加載到 UBO ,那么這些數(shù)據(jù)將存儲(chǔ)在 UBO 上,而不再交給著色器程序,所以它們不會(huì)占用著色器程序自身的 uniform 存儲(chǔ)空間,UBO 是一種新的從內(nèi)存到顯存的數(shù)據(jù)傳遞方式,另外 UBO 一般需要與 uniform 塊配合使用。
本例將 MVP 變換矩陣設(shè)置為一個(gè) uniform 塊,即我們后面創(chuàng)建的 UBO 中將保存 3 個(gè)矩陣。
#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;
}
設(shè)置 uniform 塊的綁定點(diǎn)為 0 ,生成一個(gè) 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);
繪制的時(shí)候更新 Uniform Buffer 的數(shù)據(jù),更新三個(gè)矩陣的數(shù)據(jù),注意偏移量。
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
紋理緩沖區(qū)對(duì)象,即 TBO(Texture Buffer Object),是 OpenGL ES 3.2 引入的概念,因此在使用時(shí)首先要檢查 OpenGL ES 的版本,Android 方面需要保證 API >= 24 。
TBO 需要配合緩沖區(qū)紋理(Buffer Texture)一起使用,Buffer Texture 是一種一維紋理,其存儲(chǔ)數(shù)據(jù)來(lái)自紋理緩沖區(qū)對(duì)象(TBO),用于允許著色器訪(fǎng)問(wèn)由緩沖區(qū)對(duì)象管理的大型內(nèi)存表。
在 GLSL 中,只能使用 texelFetch 函數(shù)訪(fǎng)問(wèn)緩沖區(qū)紋理,緩沖區(qū)紋理的采樣器類(lèi)型為 samplerBuffer 。
生成一個(gè) TBO 的方式跟 VBO 類(lèi)似,只需要綁定到 GL_TEXTURE_BUFFER ,而生成緩沖區(qū)紋理的方式與普通的 2D 紋理一樣。
//生成一個(gè) 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;
}
//生成一個(gè) TBO ,并將一個(gè)大的數(shù)組上傳至 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;
使用紋理緩沖區(qū)的片段著色器,需要引入擴(kuò)展 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;
}
繪制時(shí)如何使用緩沖區(qū)紋理和 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);
十三.猜你喜歡
本文由博客 - 猿說(shuō)編程 猿說(shuō)編程 發(fā)布!