該原創文章首發於微信公衆號:字節流動
OpenGL 座標系統
我們知道 OpenGL 座標系中每個頂點的 x,y,z 座標都應該在 -1.0 到 1.0 之間,超出這個座標範圍的頂點都將不可見。
將一個物體(圖像)渲染到屏幕上,通常經過將物體座標轉換爲標準化設備座標,然後再將標準化設備座標轉化爲屏幕座標的過程。
該過程通常涉及多個座標系統的變換,將所有頂點轉換爲片段之前,頂點需要處於不同的座標系統進行計算,對我們來說比較重要的有 5 個座標系統:
- 局部空間(Local Space,或者物體空間(Object Space))
- 世界空間(World Space)
- 觀察空間(View Space,
- 裁剪空間(Clip Space)
- 屏幕空間(Screen Space)
局部空間
局部空間 (Local Space) 是指對象所在的座標空間,座標原點由你自己指定,模型的所有頂點相對於你的對象來說都是局部的。
世界空間
在世界空間(World Space)主要實現對象的平移、縮放、旋轉變換,將它們放在我們指定的位置,這些變換是通過模型矩陣(Model Matrix)實現的。
在 C/C++ 中可以利用 GLM 構建模型矩陣:
glm::mat4 Model = glm::mat4(1.0f); //單位矩陣
Model = glm::scale(Model, glm::vec3(2.0f, 2.0f, 2.0f)); //縮放
Model = glm::rotate(Model, MATH_PI/2, glm::vec3(1.0f, 0.0f, 0.0f)); //沿 x 軸旋轉 90 度
Model = glm::translate(Model, glm::vec3(0.0f, 1.0f, 0.0f)); //沿 y 軸正方向平移一個單位
GLM 是 OpenGL Mathematics 的縮寫,它是一個只有頭文件的庫,也就是說我們只需包含對應的頭文件就行了,不用鏈接和編譯。GLM 可以在 Github 上下載,把頭文件的根目錄複製到你的includes文件夾,然後你就可以使用這個庫了。
觀察空間
觀察空間(View Space)也被稱爲 OpenGL 相機空間,即從攝像機的角度觀察到的空間,它將對象的世界空間的座標轉換爲觀察者視野前面的座標,這通常是由一系列的平移和旋轉的組合來平移和旋轉場景從而使得特定的對象被轉換到攝像機前面,這些組合在一起的轉換通常存儲在一個**觀察矩陣(View Matrix)**裏。
在 C/C++ 中可以利用 GLM 構建觀察矩陣:
// View matrix
glm::mat4 View = glm::lookAt(
glm::vec3(0, 0, 3), // Camera is at (0,0,1), in World Space 相機位置
glm::vec3(0, 0, 0), // and looks at the origin 觀察點座標
glm::vec3(0, 1, 0) // Head is up (set to 0,-1,0 to look upside-down) 相機 up 方向,即相機頭部朝向
);
裁剪空間
裁剪空間(Clip Space)是用來裁剪觀察對象的空間,在一個頂點着色器運行的最後,OpenGL 期望所有的座標都能落在一個給定的範圍內,且任何在這個範圍之外的點都應該被裁剪掉。**投影矩陣(Projection Matrix)**用來將頂點座標從觀察空間轉換到裁剪空間。
投影矩陣一般分爲兩種:正交投影(Orthographic Projection)和透視投影(Perspective Projection)。
正交投影
正交投影是一種平行投影,投影點與原頂點的連線相互平行,且物體不產生“近大遠小”的視覺效果。
在 C/C++ 中可以利用 GLM 構建正交投影矩陣:
glm::mat4 Projection = glm::ortho(-ratio, ratio, -1.0f, 1.0f, 0.0f, 100.0f); //ratio 一般表示視口的寬高比,width/height
前兩個參數指定了平截頭體的左右座標,第三和第四參數指定了平截頭體的底部和上部。通過這四個參數我們定義了近平面和遠平面的大小,然後第五和第六個參數則定義了近平面和遠平面的距離。這個指定的投影矩陣將處於這些 x,y,z 範圍之間的座標轉換到標準化設備座標系中。
透視投影
透視投影的投影線相交於一點,可以用來模擬真實世界“近大遠小”的視覺效果。
在 C/C++ 中可以利用 GLM 構建透視投影矩陣:
glm::mat4 Projection = glm::perspective(45.0f, ratio, 0.1f, 100.f); //ratio 一般表示視口的寬高比,width/height,
它的第一個參數定義了 fov 的值,它表示的是視野(Field of View),並且設置了觀察空間的大小。對於一個真實的觀察效果,它的值經常設置爲 45.0,但想要看到更多結果你可以設置一個更大的值。第二個參數設置了寬高比,由視口的高除以寬。第三和第四個參數設置了平截頭體的近和遠平面。我們經常設置近距離爲 0.1 而遠距離設爲 100.0 。所有在近平面和遠平面的頂點且處於平截頭體內的頂點都會被渲染。
最後整個座標系統的變換矩陣可以用一個矩陣表示 MVPMatrix = Projection * View * Model;
。
OpenGL 3D 變換實現
實現 OpenGL 3D 效果最簡單的方式是在頂點着色器中將頂點座標與 MVP 變換矩陣相乘:
#version 300 es
layout(location = 0) in vec4 a_position;
layout(location = 1) in vec2 a_texCoord;
uniform mat4 u_MVPMatrix;
out vec2 v_texCoord;
void main()
{
gl_Position = u_MVPMatrix * a_position; //頂點座標與 MVP 變換矩陣相乘
v_texCoord = a_texCoord;
}
在繪製之前構建變換矩陣:
/**
* @param angleX 繞X軸旋轉度數
* @param angleY 繞Y軸旋轉度數
* @param ratio 寬高比
* */
void CoordSystemSample::UpdateMVPMatrix(glm::mat4 &mvpMatrix, int angleX, int angleY, float ratio)
{
LOGCATE("CoordSystemSample::UpdateMVPMatrix angleX = %d, angleY = %d, ratio = %f", angleX, angleY, ratio);
angleX = angleX % 360;
angleY = angleY % 360;
//轉化爲弧度角
float radiansX = static_cast<float>(MATH_PI / 180.0f * angleX);
float radiansY = static_cast<float>(MATH_PI / 180.0f * angleY);
// Projection matrix
//glm::mat4 Projection = glm::ortho(-ratio, ratio, -1.0f, 1.0f, 0.1f, 100.0f);
//glm::mat4 Projection = glm::frustum(-ratio, ratio, -1.0f, 1.0f, 4.0f, 100.0f);
glm::mat4 Projection = glm::perspective(45.0f,ratio, 0.1f,100.f);
// View matrix
glm::mat4 View = glm::lookAt(
glm::vec3(0, 0, 4), // Camera is at (0,0,1), in World Space
glm::vec3(0, 0, 0), // and looks at the origin
glm::vec3(0, 1, 0) // Head is up (set to 0,-1,0 to look upside-down)
);
// Model matrix
glm::mat4 Model = glm::mat4(1.0f);
Model = glm::scale(Model, glm::vec3(1.0f, 1.0f, 1.0f));
Model = glm::rotate(Model, radiansX, glm::vec3(1.0f, 0.0f, 0.0f));
Model = glm::rotate(Model, radiansY, glm::vec3(0.0f, 1.0f, 0.0f));
Model = glm::translate(Model, glm::vec3(0.0f, 0.0f, 0.0f));
mvpMatrix = Projection * View * Model;
}
繪製時傳入變換矩陣:
void CoordSystemSample::Draw(int screenW, int screenH)
{
LOGCATE("CoordSystemSample::Draw()");
if(m_ProgramObj == GL_NONE || m_TextureId == GL_NONE) return;
// 旋轉角度變換,更新變換矩陣
UpdateMVPMatrix(m_MVPMatrix, m_AngleX, m_AngleY, (float)screenW / screenH);
//upload RGBA image data
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, m_TextureId);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, m_RenderImage.width, m_RenderImage.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, m_RenderImage.ppPlane[0]);
glBindTexture(GL_TEXTURE_2D, GL_NONE);
// Use the program object
glUseProgram (m_ProgramObj);
glBindVertexArray(m_VaoId);
// 將總變換矩陣傳入着色器程序
glUniformMatrix4fv(m_MVPMatLoc, 1, GL_FALSE, &m_MVPMatrix[0][0]);
// Bind the RGBA map
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, m_TextureId);
glUniform1i(m_SamplerLoc, 0);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, (const void *)0);
}
效果演示:
實現代碼路徑:
https://github.com/githubhaohao/NDK_OpenGLES_3_0