OpenGL 學(xué)習(xí)系列---投影矩陣

原文鏈接:https://glumes.com/post/opengl/opengl-tutorial-projection-matrix/

OpenGL 坐標(biāo)系統(tǒng) 文章中,根據(jù)點(diǎn)的坐標(biāo)變換得出了如下的公式:

V_{clip}=M_{projection} \cdot M_{view} \cdot M_{model} \cdot V_{local}

這個(gè)公式每左乘一個(gè)矩陣,都代表了一種坐標(biāo)系的變換。

轉(zhuǎn)化為著色器腳本語言如下:

attribute vec4 a_Position;
uniform mat4 u_ModelMatrix;
uniform mat4 u_ProjectionMatrix;
uniform mat4 u_ViewMatrix;
void main()
{
    gl_Position  = u_ProjectionMatrix * u_ViewMatrix * u_ModelMatrix * a_Position;
}

本篇文章就主要是對(duì)投影矩陣來分析的。

OpenGL 在觀察空間轉(zhuǎn)換到裁剪空間時(shí),需要用到投影矩陣。而在著色器腳本中,也需要提供一個(gè)投影矩陣給對(duì)應(yīng)的 u_ProjectionMatrix變量。

首先要在程序里綁定到對(duì)應(yīng)的變量,然后再給變量賦值。

// 綁定到著色器腳本中的對(duì)應(yīng)變量
private static final String U_ProMatrix = "u_ProjectionMatrix";
private int uProMatrixLocation;
uProMatrixLocation = glGetUniformLocation(mProgram,U_ProMatrix);
// 給變量賦值,projectionMatrix 為投影矩陣
glUniformMatrix4fv(uProMatrixLocation,1,false,projectionMatrix,0)

正如前文講到的,投影矩陣會(huì)創(chuàng)建一個(gè)視景體對(duì)物體坐標(biāo)進(jìn)行裁剪,得到的裁剪坐標(biāo)再經(jīng)過透視除法之后,就會(huì)得到歸一化設(shè)備坐標(biāo)。歸一化設(shè)備坐標(biāo)再經(jīng)過視口轉(zhuǎn)換,最終將坐標(biāo)映射到了屏幕上。

OpenGL 提供了兩種投影方式:正交投影和透視投影。

正交投影矩陣

image

不管是正交投影還是透視投影,最終都是將視景體內(nèi)的物體投影在近平面上,這也是 3D 坐標(biāo)轉(zhuǎn)換到 2D 坐標(biāo)的關(guān)鍵一步。

而近平面上的坐標(biāo)接著也會(huì)轉(zhuǎn)換成歸一化設(shè)備坐標(biāo),再映射到屏幕視口上。

為了解決之前的圖像拉伸問題,就是要保證近平面的寬高比和視口的寬高比一致,而且是以較短的那一邊作為 1 的標(biāo)準(zhǔn),讓圖像保持居中。

OpenGL 提供了 Matrix.orthoM 函數(shù)來生成正交投影矩陣。

    /**
     * Computes an orthographic projection matrix.
     *
     * @param m returns the result 正交投影矩陣
     * @param mOffset 偏移量,默認(rèn)為 0 ,不偏移
     * @param left 左平面距離
     * @param right 右平面距離
     * @param bottom 下平面距離
     * @param top 上平面距離
     * @param near 近平面距離
     * @param far 遠(yuǎn)平面距離
     */
    public static void orthoM(float[] m, int mOffset,
        float left, float right, float bottom, float top,
        float near, float far)
image

需要注意的是,我們的左、上、右、下距離都是相對(duì)于近平面中心的。

近平面的坐標(biāo)原點(diǎn)位于中心,向右為 X 軸正方向,向上為 Y 軸正方向,所以我們的 left、bottom 要為負(fù)數(shù),而 right、top 要為正數(shù)。同時(shí),近平面和遠(yuǎn)平面的距離都是指相對(duì)于視點(diǎn)的距離,所以 near、far 要為正數(shù),而且 far > near

可以在 GLSurfaceView 的 surfaceChanged 里面來設(shè)定正交投影矩陣。

  @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {
        float aspectRatio = width > height ? (float) width / (float) height : (float) height / (float) width;
        if (width > height){
            Matrix.orthoM(projectionMatrix,0,-aspectRatio,aspectRatio,-1f,1f,0f,10f);
        }else {
            Matrix.orthoM(projectionMatrix,0,-1f,1f,-aspectRatio,aspectRatio,0f,10f);
        }
    }

這樣的話,就把近平面的寬高比設(shè)定與視口的寬高比一致了。

透視投影矩陣

OpenGL 提供了兩個(gè)函數(shù)來創(chuàng)建透視投影矩陣:frustumMperspectiveM。

frustumM

frustumM 函數(shù)創(chuàng)建的視景體是一個(gè)錐形。

image

它的視景體有點(diǎn)類似于正交投影,在參數(shù)理解上基本都相同的。

/**
     * Defines a projection matrix in terms of six clip planes.
     *
     * @param m the float array that holds the output perspective matrix
     * @param offset the offset into float array m where the perspective
     *        matrix data is written
     * @param left 
     * @param right
     * @param bottom
     * @param top
     * @param near
     * @param far
     */
    public static void frustumM(float[] m, int offset,
            float left, float right, float bottom, float top,
            float near, float far)

需要注意的是 near 和 far 變量的值必須要大于 0 。因?yàn)樗鼈兌际窍鄬?duì)于視點(diǎn)的距離,也就是照相機(jī)的距離。

當(dāng)用視圖矩陣確定了照相機(jī)的位置時(shí),要確保物體距離視點(diǎn)的位置在 near 和 far 的區(qū)間范圍內(nèi),否則就會(huì)看不到物體。

由于透視投影會(huì)產(chǎn)生近大遠(yuǎn)小的效果,當(dāng)照相機(jī)位置不變,改變 near 的值時(shí)也會(huì)改變物體大小,near 越小,則離視點(diǎn)越近,相當(dāng)于物體越遠(yuǎn),那么顯示的物體也就越小了。

當(dāng)然也可以 near 和 far 的距離不動(dòng),改變攝像機(jī)的位置來改變觀察到的物體大小。

perspectiveM

image

OpenGL 還提供了 perspectiveM 函數(shù)來創(chuàng)建投影矩陣,它的視景體和 frustumM 函數(shù)相同,但是構(gòu)造的參數(shù)有所不同。

 /**
     * Defines a projection matrix in terms of a field of view angle, an
     * aspect ratio, and z clip planes.
     *
     * @param m the float array that holds the perspective matrix
     * @param offset the offset into float array m where the perspective
     *        matrix data is written
     * @param fovy field of view in y direction, in degrees
     * @param aspect width to height aspect ratio of the viewport
     * @param zNear
     * @param zFar
     */
    public static void perspectiveM(float[] m, int offset,
          float fovy, float aspect, float zNear, float zFar)

視景體不再需要確定近平面左、上、右、下距離了。

通過視角來決定我們能看到的視野大小。視角就是圖中所示的那個(gè)夾角。另外的參數(shù)是視口的寬高比,還有近平面和遠(yuǎn)平面的距離,參數(shù)個(gè)數(shù)減少了。

image
image

上述圖片左邊是 90 視角,右邊是 45 度視角。顯然,視野角度越大,則看到的內(nèi)容更多,但是物體顯得更小,而視野角度越小,則看的內(nèi)容更少,但物體顯得更大。

frustumM不同的是,一旦確定了視角和寬高比,那么整個(gè)攝像機(jī)視野也就確定了,此時(shí)完整的錐形視野已經(jīng)形成了,也就是說物體的近大遠(yuǎn)小效果已經(jīng)完成了。這時(shí),近平面距離和遠(yuǎn)平面距離只是想要截取錐形視野中的那一部分了。不像在frustumM函數(shù)中,近、遠(yuǎn)平面的距離還能夠調(diào)整近大遠(yuǎn)小的效果。

一起交流學(xué)習(xí),答疑解惑,有問題,我們星球見~~~


圖形/圖像/音視頻交流

參考

  1. 《OpenGL ES 應(yīng)用開發(fā)實(shí)踐指南》
  2. 《OpenGL ES 3.x 游戲開發(fā)》

具體代碼詳情,可以參考我的 Github 項(xiàng)目:

https://github.com/glumes/AndroidOpenGLTutorial

OpenGL 系列文章:

  1. OpenGL 系列---基礎(chǔ)繪制流程

  2. OpenGL 學(xué)習(xí)系列---基本形狀的繪制

  3. OpenGL 學(xué)習(xí)系列---坐標(biāo)系統(tǒng)

掃描關(guān)注微信公眾號(hào)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容