在 OpenGL 投影矩陣 這篇文章中,講述了 OpenGL 座標系統中的投影矩陣,有兩種類型的投影矩陣,分別是正交投影和透視投影。html
這兩種投影實質上是兩種類型的裁剪空間,分別建立對應視景體對物體座標進行裁剪,位於裁剪空間內的纔會被映射到屏幕上,以下圖所示:(圖片來源:glumpy.github.io/modern-gl.h…)java
當定義裁剪空間視景體時,咱們都須要提供近平面和遠平面的距離,這裏的近和遠都是指相對於視點
的,視點也就是咱們這篇文章要講到的攝像機。git
在上面的圖片中,咱們能夠把投影矩陣的視景體的四條虛線邊當作是以攝像機爲起始點發出的射線。github
這樣一來,當起始點也就是攝像機位置發生改變時,它所發出的射線也會隨之改變,那麼視景體的形狀也就改變了,在其內部所觀察到的內容也會發生變化。微信
好比,假設此時攝像機位於彩色小球的上面,那麼整個視景體就是垂直立起來了的,所觀察到的小球也是朝上的那一面了。ide
因此,能夠看到相機的位置和朝向,決定了視景體在什麼位置和什麼朝向展開。函數
在 OpenGL 座標系統的轉換公式中也能夠印證這一點:post
它的計算順序是左乘,也就是說要先進行視圖矩陣的計算,而後再進行投影矩陣的計算,這樣一來咱們就要先肯定了相機的位置,而後再根據相機肯定投影矩陣。spa
在這裏,肯定相機的位置,並不只僅是定義相機在三維中的 座標,而是要肯定一個以相機位置爲原點的座標系。.net
在上面也提到,投影矩陣或者說視景體的一個展開,是以相機做爲參考的,那麼咱們確定還須要一個攝像機的觀察方向,這個方向就是視景體展開的方向。
如上圖的左二內容所示,攝像機在 Z 軸正方向向座標系的原點進行觀察,假設此時攝像機座標爲 ,而原點爲
,那麼觀察方向就是從
點向
點。而方向向量就是
,就是向量
,它的方向也就是圖二中的藍色箭頭所示,能夠看到 攝像機的方向向量和它的觀察方向正好是相反的。
一個三維的空間座標系是須要三個互相垂直的軸的,如今已經有了方向向量這一個了。這時能夠藉助一個輔助向量 上向量 ,把上向量與方向向量進行叉乘,
,就能夠獲得一個向量,同時垂直於上向量和方向向量,它就是右向量
,它的方向指向
軸正方向 。這裏要當心叉乘的順序,不然獲得的方向就是反的了。
如圖三因此,灰色的就是輔助上向量 ,而紅色箭頭所指方向就是
軸正方向。
再利用右向量和方向向量的叉乘,就能夠獲得指向攝像機 軸方向的向量,如最右圖的綠色箭頭所示。
這樣就構造了三個軸互相垂直的座標系,它就是攝像機的座標系。
從上面的內容能夠看到,只要知道了相機座標點,以及觀察的點,還有輔助的上向量,就能夠肯定攝像機的座標系了。
肯定攝像機以後,就是用它來生成咱們的觀察矩陣,把觀察矩陣用於在 OpenGL 渲染管線中進行處理。
和投影矩陣同樣,Android 也提供了對應函數 Matrix.setLookAtM
來生成 OpenGL 座標轉換中的觀察矩陣。
/** * Defines a viewing transformation in terms of an eye point, a center of * view, and an up vector. * * @param rm returns the result * @param rmOffset index into rm where the result matrix starts * @param eyeX eye point X * @param eyeY eye point Y * @param eyeZ eye point Z * @param centerX center of view X * @param centerY center of view Y * @param centerZ center of view Z * @param upX up vector X * @param upY up vector Y * @param upZ up vector Z */
public static void setLookAtM(float[] rm, int rmOffset, float eyeX, float eyeY, float eyeZ, float centerX, float centerY, float centerZ, float upX, float upY, float upZ) 複製代碼
其中,第一個參數就是要傳入的觀察矩陣,第二個參數是偏移量,這個通常是 0 ,以後就是相機位置、觀察點、輔助上向量。
接下來經過移動攝像機來觀察物體,從而加深對攝像機的理解。
用 OpenGL 來繪製一個立方體,並經過旋轉移動攝像機,讓攝像機繞 軸作圓形旋轉,從而能夠從不一樣方向來觀察物體,效果圖以下:
讓立方體稍微向 軸作一點傾斜,這樣最多就能夠觀察到三個面了。
具體代碼示例:
var num = 0
var RotateNum = 360 // 繞 Y 軸作圓形旋轉,把圓分紅 360 份
val radian = (2 * Math.PI / RotateNum).toFloat() // 每一份對應的弧度
override fun onSurfaceChanged(gl: GL10?, width: Int, height: Int) {
//... 省略代碼
// 經過 RxJava 的 interval 操做符,每隔 30 毫秒,移動相機觀察角度
Observable.interval(30, TimeUnit.MILLISECONDS)
.subscribe {
eyeX = eyeDistance * Math.sin((radian * num).toDouble()).toFloat()
eyeZ = eyeDistance * Math.cos((radian * num).toDouble()).toFloat()
num++
if (num > 360) {
num = 0
}
}
// 設置觀察矩陣
MatrixState.setCamera(eyeX, eyeY, eyeZ, lookX, lookY, lookZ, upX, upY, upZ)
// 讓物體稍微向 Z 軸負方向傾斜必定角度
MatrixState.rotate(-30f, 0f, 0f, 1f)
}
複製代碼
在 GLSurfaceView 的 onSurfaceChanged 方法裏面設定觀察矩陣,並經過 RxJava 的 interval 操做符每隔 30 毫秒改變相機位置的 座標和
座標,讓攝像機在
平面上繞
軸作圓周運動。
在 onDrawFrame 方法裏,每當座標改變了,就改變相機的位置。
override fun onDrawFrame(gl: GL10?) {
//... 省略代碼
MatrixState.setCamera(eyeX, eyeY, eyeZ, lookX, lookY, lookZ, upX, upY, upZ)
GLES20.glUniformMatrix4fv(uViewMatrixAttr, 1, false, MatrixState.getVMatrix(), 0)
//... 省略代碼
}
複製代碼
因爲是作圓周運動,圓的半徑是沒有變的,因此看到的物體大小是不變的,只是看到的內容不一樣。
咱們還能夠先後移動相機,這樣就至關於人走近或者離開物體,感受到物體大小發生變化(實際上並無)。
如上圖,物體仍是那個物體,可是從不一樣的遠近來觀察,所看到的大小就不同了。
override fun onSurfaceChanged(gl: GL10?, width: Int, height: Int) {
super.onSurfaceChanged(gl, width, height)
var isPlus = true
var distance = eyeZ
Observable.interval(100, TimeUnit.MILLISECONDS)
.subscribe {
distance = if (isPlus) distance + 0.1f else distance - 0.1f
if (distance < 2.0f) {
isPlus = true
}
if (distance > 5.0f) {
isPlus = false
}
eyeZ = distance updateCamera() } // 將物體調整一下,能夠看到三個面 MatrixState.rotate(-45f, 0f, 1f, 0f) MatrixState.rotate(45f, 1f, 0f, 0f) } 複製代碼
如上代碼所示,咱們改變了相機的 軸座標,讓它在
之間來回移動,這樣就達到了先後移動相機的效果。
最後,還能夠把兩種旋轉結合起來,即作圓周運動又先後移動相機,效果以下:
經過上面的例子,就應該對 OpenGL 中的相機有一個更加清晰的認識了。
具體代碼詳情,能夠參考個人 Github 項目:
最後,若是以爲文章不錯,歡迎關注微信公衆號:【紙上淺談】