OpenGL ES 高級進階:座標系及矩陣變換

今天給你們講講OpenGL ES中的座標系和矩陣變換,OpenGL ES 中的座標系實際上有不少,在我以前的文章中,由於對應的效果對座標系的要求不高,所用的座標其實是跳過的一系列的座標變換,這點後面會給你們說,而矩陣變換就是將座標從一個座標系轉換到另外一個座標系下。android

咱們先來了解一下OpenGL ES中的虛擬攝像機,在OpenGL ES中,有一個虛擬的攝像機,咱們渲染出來的景像實際上就是這個虛擬攝像機所拍攝到的景像,這個虛擬攝像機的效果和咱們真實生活中的攝像機效果更相似,咱們能夠經過調整虛擬攝像機的位置、朝向等參數來獲得不一樣的觀察結果,進而獲得不一樣的渲染畫面,舉個形象一點的例子,例如咱們平時玩的3D遊戲,有至關一部分是用OpenGL ES渲染的,咱們控制角色移動靠近一個物體時,物體就會變大,就像咱們拿着一個攝像機朝一個物體走過去同樣,拍攝到的物體就會變大,角色轉身時,能看到不一樣的畫面,就想是咱們拿着一個攝像機朝不一樣的方向拍。git

不過這個攝像機最終在OpenGL ES中是以矩陣的形式呈現出來的,咱們先來看一張總體流程圖:github

這張圖給咱們展現了OpenGL ES 中的座標系及矩陣變換過程,具體過程是這樣:spa

  • 首先OpenGL ES有個世界座標系,咱們渲染的物體就是在世界座標系中,咱們的模型須要放到世界座標系中,那麼當咱們還沒放的時候,模型就和世界座標系沒有聯繫,它就還處於本身的座標系中,咱們叫作模型座標系、局部空間、局部座標系,也就是圖中的LOCAL SPACE
  • 當咱們把模型放到世界座標系中,模型就在世界座標系裏有了座標,也就是原來在LOCAL SPACE中的那些座標值,變成了世界座標系中的座標值,幫助咱們完成這個變換的就是模型矩陣,對應圖中的MODEL MATRIX,因而這樣咱們就把模型放到了世界座標系WORLD SPACE
  • 放到世界座標系後,是否是就肯定了咱們渲染出來看到的樣子?尚未,你們能夠想像一下,我把一個東西放在世界座標系的某個地方,我能夠從近處看觀察它,也能夠從遠處觀察它,還能夠從上下左右觀察它,甚至還能夠倒着觀察它,所以還須要肯定咱們觀察它的狀態。這裏實際上就是在肯定虛擬攝像機的擺放,從API的層面上看,咱們只須要設置Camera的位置、朝向的點座標、以及Camera的上方向向量就能將觀察狀態定下來,而這些設置最終會轉換成OpenGL ES中的視圖矩陣,對應圖中的VIEW MATRIX
  • 通過View Matrix的變換後,咱們觀察它的結果就肯定了,圖中是從距離它必定的距離、上往下觀察它,這時候的點座標就來到了視圖座標系下,對應圖中的VIEW SPACE
  • 這時候,咱們能看到什麼東西,基本已經肯定了,不過還有一步投影變換,這是什麼東西?你們想像一下,咱們看到同一個東西,是否是一般都是近大遠小?那麼如何實現近大遠小?就要靠投影變換,OpenGL ES提供正交投影和透視投影,正交投影沒有近大遠小的效果,無論在什麼距離上看,都同樣大,透視投影則有近大遠小的效果,也是符合咱們實際生活的一種效果,透視投影應用得比較多,可看下面這張經典圖:

  • 通過投影變換後,就會轉換到裁剪座標系CLIP SPACE,這一步不只作了投影,也作了裁剪,也就是裁剪出上圖中左圖的梯形區域和右圖中的矩陣區域,不在這個區域中的物體不會在渲染的圖面中看到。咱們玩遊戲的時候,你們可能會碰到這樣的狀況,就是人物走到一個物體的近處,若是很靠近這個物體,畫面可能會穿進這個物體中,這就是由於物體的一部分超出了近平面,被裁剪掉了。
  • 再下一步是到NDC(設備標準化座標)座標系(圖中省略了這一步直接到屏幕座標系了),正如其名,這一步的座標都是通過標準化的,在可視範圍內的座標值都是在0~1之間,你們會想咱們以前的教程,裏面用的座標是否是都是0~1的?咱們那種寫法實際上就是在用NDC座標直接來渲染,並無通過矩陣變換,所以功能比較簡單,還用不上矩陣變換。
  • 最後就到了咱們的屏幕座標系,這個座標系你們應該很是熟悉了,android中的各類view裏用的座標就是屏幕座標。

這有一個初學者可能會誤解的點,就是認爲OpenGL ES的座標範圍就是0~1,超出0或1就會超出屏幕就看不見,這種理解其實不許確,座標範圍是多少,取決於說的是什麼座標系,咱們在平時作的更可能是2D渲染,經常就是像我以前的教程裏寫的座標那樣,直接使用0~1的NDC座標系,不須要矩陣變換,但實際上OpenGL ES的世界座標系是沒有範圍的,是負無窮到正無窮,至於某些座標下的東西是否最後能渲染出來看到,這就取決於前面說的矩陣變換過程,例如將虛擬攝像機對準一個距離999999的物體,而且物體在裁剪區域內,也是能看到的,並非說座標必定要是0~1。3d

矩陣變換主要仍是用在3D渲染和一些特殊的2D效果上,例如一個偏轉變形的2D平面,若是直接設置NDC座標,出來的效果會有畸變,須要本身進行透視矯正,關於透視矯正,這裏先不展開說了。code

接下來咱們來看一下如何在OpenGL ES中使用矩陣變換,首先看模型矩陣,前面提到過,模型矩陣是把座標從模型的局部座標系轉換到世界座標系,這個變換不只是位置的變換,還能夠有旋轉和縮放,例如把一個物體縮小一點、旋轉一點後放到世界座標系中的某個位置上,所以模型矩陣實際上包含的平移、旋轉和縮放,它就等於平移矩陣、旋轉矩陣和縮放矩陣相乘:orm

val translateMatrix = getIdentity()
val rotateMatrix = getIdentity()
val scaleMatrix = getIdentity()
val modelMatrix = getIdentity()

// 模型矩陣計算
// Calculate the Model matrix
Matrix.translateM(translateMatrix, 0, translateX, translateY, translateZ)
Matrix.rotateM(rotateMatrix, 0, rotateX, 1f, 0f, 0f)
Matrix.rotateM(rotateMatrix, 0, rotateY, 0f, 1f, 0f)
Matrix.rotateM(rotateMatrix, 0, rotateZ, 0f, 0f, 1f)
Matrix.scaleM(scaleMatrix, 0, scaleX, scaleY, scaleZ)
Matrix.multiplyMM(modelMatrix, 0, rotateMatrix, 0, scaleMatrix, 0)
Matrix.multiplyMM(modelMatrix, 0, modelMatrix, 0, translateMatrix, 0)

複製代碼

視圖矩陣則是對應前面說的虛擬攝像機,它共由虛擬攝像機的位置、朝向的點座標、以及虛擬攝像機的上方向向量肯定,OpenGL ES提供了方法來獲得視圖矩陣,咱們只須要給它傳遞這些參數就好了:cdn

val viewMatrix = getIdentity()
// 視圖矩陣計算
// Calculate the View matrix
Matrix.setLookAtM(
    viewMatrix, 
    0, 
    cameraPositionX, cameraPositionY, cameraPositionZ, 
    lookAtX, lookAtY, lookAtZ, 
    cameraUpX, cameraUpY, cameraUpZ
)
複製代碼

接下來是投影矩陣,前面提到投影矩陣有正交投影和透視投影兩種,本文中使用透視投影,它也是由OpenGL ES提供的方法來獲得,所須要的參數爲近平面矩陣的上、下、左、右座標,近平面距離和遠平面矩離(這張圖中的Left、Right、Bottom、Top標在了遠平面上,實際在OpenGL ES中生成透視投影矩陣的方法參數中的left抄下tbottomup指的是近平面):blog

生成透視投影矩陣代碼以下:教程

val projectMatrix = getIdentity()
// 透視投影矩陣計算
// Calculate the Project matrix
Matrix.frustumM(
    projectMatrix,
    0,
    nearPlaneLeft, nearPlaneRight, nearPlaneBottom, nearPlaneTop, 
    nearPlane, 
    farPlane
)
複製代碼

如今,模型矩陣、視圖矩陣和投影矩陣都生成了,下面將這三個矩陣相乘獲得最終的變換矩陣(MVP):

val mvpMatrix = getIdentity()
// MVP矩陣計算
// Calculate the MVP matrix
Matrix.multiplyMM(mvpMatrix, 0, viewMatrix, 0, modelMatrix, 0)
Matrix.multiplyMM(mvpMatrix, 0, projectMatrix, 0, mvpMatrix, 0)
複製代碼

而後將MVP矩陣傳遞到Vertex Shader中與頂點相乘進行矩陣變換:

#version 300 es
precision mediump float;
layout(location = 0) in vec4 a_position;
layout(location = 1) in vec2 a_textureCoordinate;
layout(location = 2) uniform mat4 u_mvp;
out vec2 v_textureCoordinate;
void main() {
    v_textureCoordinate = a_textureCoordinate;
    gl_Position = u_mvp * a_position;
}"
複製代碼

我作一了個demo,能夠調節各類參數實時查看效果:

這個demo是渲染一個立方體,立方體每一個面上貼上一個花的紋理,上面這張圖是一個初始狀態,咱們來看一下初始的參數:

var translateX = 0f         
var translateY = 0f
var translateZ = 0f
var rotateX = 0f
var rotateY = 0f
var rotateZ = 0f
var scaleX = 1f
var scaleY = 1f
var scaleZ = 1f
var cameraPositionX = 0f
var cameraPositionY = 0f
var cameraPositionZ = 5f
var lookAtX = 0f
var lookAtY = 0f
var lookAtZ = 0f
var cameraUpX = 0f
var cameraUpY = 1f
var cameraUpZ = 0f
var nearPlaneLeft = -1f
var nearPlaneRight = 1f
var nearPlaneBottom = -glSurfaceViewHeight.toFloat() / glSurfaceViewWidth
var nearPlaneTop = glSurfaceViewHeight.toFloat() / glSurfaceViewWidth
var nearPlane = 2f
var farPlane = 100f
複製代碼

和模型矩陣Model Matrix相關的參數是translateroratescale,這裏初始時咱們不對模型進行變換。

和視圖矩陣VIEW MATRIX相關的參數是CameraPositionlookAtCameraUp,咱們把虛擬攝像機放在(0, 0, 5)這個位置,而且讓它對向(0, 0, 0),而且攝像機的上方向是(0, 1, 0),也就是把攝像正立着。

和投影矩陣PROJECT MATRIX相關的參數是近平面nearPlane上下左右和距離、以及遠平面farPlane距離,咱們將近平面左右設爲-1和1,而且上下根據GLSurfaceView和尺寸設置,這樣是爲了避免變形,近平面設置爲2,遠平面設置爲100。

咱們渲染的這個立方體頂點座標都是-1和1,也就是在原點那裏,因此上面的圖中咱們看到的效果就是從Z軸上的(0, 0, 5)這個位置正對看向這個立方體,所以只能看到正面。

下面我調節一些參數看看效果:

上面中咱們經過設置模型矩陣的參數將立方體變換到了(3, 4, -5)這個位置,由於咱們的攝像機沒動,仍是對着原點看,那麼至關於這個立方體在攝像機的右上方,所以攝像機能看到這個立方體的左面和下面。

再繼續看:

這個是把立方體旋轉了一下,沒什麼好解釋的。

再來看:

這個是旋轉加上了縮放,把x座標變小、y座標變大了,因此橫向的邊就短了,豎向的就長了。

再來:

這個是立方體旋轉加上再把虛擬攝像機看的點從(0, 0, 0) 變成了(0, 2, 0),所以效果就是立方體就在視野下方了。

再看:

這個是把虛擬攝像機的上方向從(0, 1, 0)變成了(1, 1, 0),也就是原本虛擬攝像機是正立着的,如今變換歪了45度,因此拍攝到的畫面也歪了45度。

好了,還有不少種狀況,你們能夠到demo裏玩玩,看看渲染出來的效果是否與本身的理解和預期一致。

這節的內容比較複雜,若是有疑問,歡迎給我留言討論哈。

代碼在我githubOpenGLESPro項目中,本文對應的是SampleMatrixTransform,項目連接:github.com/kenneycode/…

感謝閱讀!

相關文章
相關標籤/搜索