OpenGL之三維

OpenGL之基礎(chǔ)
OpenGL之繪制簡單形狀
OpenGL之顏色
OpenGL之調(diào)整屏幕寬高比

從著色器到屏幕的坐標(biāo)變換

頂點(diǎn)著色器上的原始 gl_Position 坐標(biāo)變換為最終的屏幕坐標(biāo)流程


image-20211221143616767.png

剪裁空間

當(dāng)頂點(diǎn)著色器把一個值寫到 gl_Position 的時候班利,這個位置是在剪裁空間(clip space)中的酥艳。在剪裁空間中鸟妙,對于任何給定的位置调限,它的x茂蚓、y以及z分量都需要在那個位置的-w和w之間绣张。比如雅宾,如果一個位置的w是1唆迁,那么其x肉拓、y以及z分量都需要在-1和1之間后频。任何這個范圍外的事物在屏幕上都不可見的

透視除法

OpenGL 使用透視除法將剪裁空間中的位置坐標(biāo)變換為歸一化設(shè)備坐標(biāo),不管渲染區(qū)域的大小和形狀暖途,對于其中的每個可視坐標(biāo)卑惜,其x、y和z分量的取值都位于[-1,1]的范圍內(nèi)

為了在屏幕上創(chuàng)建三維的幻象驻售,OpenGL會把每個 gl_Position 的x露久、y和z分量都除以它的w分量。因此欺栗,當(dāng)w分量用來表示距離的時候毫痕,就使得較遠(yuǎn)處的物體被移動到距離渲染區(qū)域中心更近的地方征峦,這個中心的作用就像一個消失點(diǎn)


image-20211221150118548.png

為什么不把 z 解釋為距離,而是保留 z 分量作為深度緩沖區(qū)(depth buffer)消请,并增加 w 作為第四個分量栏笆,因為我們可以把投影的影響與實(shí)際的 z 坐標(biāo)解耦,以便我們可以在正交投影和透視投影之間切換

視口變換

OpenGL 把歸一化設(shè)備坐標(biāo)的 x 和 y 分量映射到屏幕上的一個區(qū)域內(nèi)臊泰,這個區(qū)域是操作系統(tǒng)預(yù)留出來用于顯示的蛉加,被稱為視口(viewport),這些變換后的坐標(biāo)被稱為窗口坐標(biāo)( window coordinate)

實(shí)現(xiàn)三維顯示

在頂點(diǎn)數(shù)據(jù)中加入 z 和 w 分量缸逃,近處的點(diǎn) w 值為 1针饥,越遠(yuǎn)的 w 的值大,這樣確實(shí)可以實(shí)現(xiàn)三維的顯示效果察滑,但不能改變桌子的角度或者縮小打厘、放大。要實(shí)現(xiàn)這些變換贺辰,不能使用硬編碼指定 w 的值户盯,而要用矩陣來生成這些值,用透視投影矩陣自動生成w的值

透視投影矩陣

透視投影矩陣需要和透視除法一起發(fā)揮作用饲化。投影矩陣不能自己做透視除法莽鸭,而透視除法需要 w 才能起作用,因此吃靠,透視投影矩陣最重要的任務(wù)就是為 w 產(chǎn)生正確的值

能實(shí)現(xiàn)這些的方法之一是利用 z 分量硫眨,把它作為物體與焦點(diǎn)(觀察點(diǎn))的距離并且把這個距離映射到w,這個距離越大巢块,w值越大礁阁,所得物體越小

示意圖

透視投影.jpg

可以看出,相比與正交投影矩陣族奢,透視投影矩陣是一個更加通用的投影矩陣姥闭,可以同時對寬高比和視野進(jìn)行調(diào)整,因此越走,使用透視投影矩陣棚品,就可以去掉正交投影矩陣了

創(chuàng)建透視投影矩陣

Matrix.perspectiveM()方法可以創(chuàng)建一個透視投影矩陣,其原型如下

public static void perspectiveM(float[] m, int offset,
      float fovy, float aspect, float zNear, float zFar) {

參數(shù)說明

參數(shù) 說明
m 最終生成的透視投影矩陣數(shù)組廊敌,長度至少有16個元素铜跑,才能存儲透視投影矩陣
offset 正交投影矩陣起始的偏移值
fovy y 方向的視野,以度為單位
aspect 屏幕的寬高比骡澈,它等于寬度/高度
zNear 到近處平面的距離锅纺,必須是正值。比如肋殴,如果此值被設(shè)為1伞广,那近處平面就位于一個z值為-1處
zFar 到遠(yuǎn)處平面的距離拣帽,必須是正值且大于到近處平面的距離

也可以自己實(shí)現(xiàn)透視矩陣,透視矩陣最終的數(shù)值如下


image-20211221154900531.png

a:如果我們想象一個相機(jī)拍攝的場景嚼锄,這個變量就代表那個相機(jī)的焦距减拭。焦距是由1/tan(視野/2) 計算得到的。這個視野必須小于180度区丑。比如拧粪,一個90度的視野,它的焦距會被設(shè)置為1/tan(90° /2)沧侥,也就是1/1或者1

其他的和Matrix.perspectiveM()方法參數(shù)含義一致

自己實(shí)現(xiàn)透視矩陣代碼

public class MatrixHelper {
    /**
     * @param matrix        矩陣
     * @param yFovInDegrees 視野角度
     * @param aspect        寬高比
     * @param n             近處到平面距離
     * @param f             遠(yuǎn)處到平面距離
     * @return
     */
    public static float[] perspectiveM(float[] matrix, float yFovInDegrees, float aspect, float n, float f) {
        // 計算焦距
        float angleInRadians = (float) (yFovInDegrees * Math.PI / 180.0);
        float a = (float) (1f / tan(angleInRadians / 2));

        // 輸出矩陣
        for (int i = 0; i < matrix.length; i++) {
            switch (i) {
                case 0:
                    matrix[i] = a / aspect;
                    break;
                case 5:
                    matrix[i] = a;
                    break;
                case 10:
                    matrix[i] = -((f + n) / (f - n));
                    break;
                case 11:
                    matrix[i] = -1f;
                    break;
                case 14:
                    matrix[i] = -((2 * f * n) / (f - n));
                    break;
                default:
                    matrix[i] = 0f;
                    break;
            }
        }
        return matrix;
    }
}

使用透視矩陣實(shí)現(xiàn)三維效果

// 用45度的視野創(chuàng)建一個透視投影可霎。這個視椎體從z值為-1的位置開始,在z值為-10的位置結(jié)束
Matrix.perspectiveM(projectionMatrix, 0, 45, (float) width / height, 1, 10);

使用上面的代碼替換之前的正交投影矩陣宴杀,運(yùn)行程序癣朗,會發(fā)現(xiàn)圖像不見了,因為沒有給頂點(diǎn)指定 z 的位置旺罢,默認(rèn)情況下它 z 在 0 的位置旷余。而這個視椎體是從z值為-1的位置開始的,除非把它移到那個距離內(nèi)扁达,否則我們無法看見圖像

無法創(chuàng)建包含 z 位置為 0 的四椎體正卧,因為 Matrix.perspectiveM 方法要求 zNear 和 zFar 都必須是正數(shù)

因此,在使用投影矩陣進(jìn)行投影之前跪解,需要使用一個平移矩陣把頂點(diǎn)移到可見的位置炉旷。通常把這個矩陣稱為模型矩陣(model matrix)

模型矩陣

把模型矩陣設(shè)為單位矩陣,再沿著z軸平移 -2叉讥,當(dāng)我們把頂點(diǎn)的坐標(biāo)與這個矩陣相乘的時候窘行,那些坐標(biāo)最終會沿著z軸負(fù)方向移動2個單位,同時图仓,如果只是平移的話抽高,看不到三維效果,因為每個頂點(diǎn)都平移了透绩,因此還需要旋轉(zhuǎn)一下

Matrix.setIdentityM(modelMatrix, 0);
// 沿著z軸平移 -2
Matrix.translateM(modelMatrix, 0, 0f, 0f, -3f);
// 沿著 x 軸旋轉(zhuǎn)45度
Matrix.rotateM(modelMatrix, 0, -45f, 1f, 0f, 0f);

可以把模型矩陣與投影矩陣相乘,得到一個最終矩陣壁熄,然后把這個矩陣傳遞給頂點(diǎn)著色器帚豪,這種方式可以在著色器中僅保留一個矩陣

// 設(shè)為單位矩陣
Matrix.setIdentityM(modelMatrix, 0);
// 沿著z軸平移 -2
Matrix.translateM(modelMatrix, 0, 0f, 0f, -3f);
// 沿著 x 軸旋轉(zhuǎn)45度
Matrix.rotateM(modelMatrix, 0, -45f, 1f, 0f, 0f);

// 將視圖投影矩陣和模型矩陣相乘
float[] temp = new float[16];
Matrix.multiplyMM(temp, 0, projectionMatrix, 0, modelMatrix, 0);
System.arraycopy(temp, 0, projectionMatrix, 0, temp.length);

不論什么時候把兩個矩陣相乘,都需要一個臨時變量來存儲其結(jié)果草丧。如果嘗試直接寫入這個結(jié)果狸臣,這個結(jié)果將是未定義的

修改渲染器

修改渲染器中的代碼,去掉正交投影矩陣昌执,加上模型矩陣和視圖投影矩陣的使用

public class MyRenderer implements GLSurfaceView.Renderer {
    private final FloatBuffer mVertexBuffer;
    private Context mContext;
    private int a_color;
    private int a_position;
    private int u_matrix;
    private float[] projectionMatrix = new float[16];
    private float[] modelMatrix = new float[16];

    public MyRenderer(Context context) {
        mContext = context;
        float[] vertex = new float[]{
                0f, 0f, 1f, 1f, 1f,
                -0.5f, -0.8f, 0f, 0f, 0f,
                0.5f, -0.8f, 0f, 0f, 0f,
                0.5f, 0.8f, 0f, 0f, 0f,
                -0.5f, 0.8f, 0f, 0f, 0f,
                -0.5f, -0.8f, 0f, 0f, 0f,

                -0.5f, 0f, 1f, 0f, 0f,
                0.5f, 0f, 1f, 0f, 0f,

                0f, -0.5f, 0f, 1f, 0f,
                0f, 0.5f, 0f, 0f, 1f
        };
        mVertexBuffer = ByteBuffer.allocateDirect(vertex.length * 4)
                .order(ByteOrder.nativeOrder())
                .asFloatBuffer()
                .put(vertex);
        mVertexBuffer.position(0);
    }

    @Override
    public void onSurfaceCreated(GL10 gl10, EGLConfig eglConfig) {
        glClearColor(1f, 1f, 1f, 1f);

        int vertexShader = ShaderHelper.compileVertexShader(mContext, R.raw.ratio3_vertex);
        int fragmentShader = ShaderHelper.compileFragmentShader(mContext, R.raw.ratio3_fragment);
        int program = ProgramHelper.getProgram(vertexShader, fragmentShader);
        glUseProgram(program);

        a_color = glGetAttribLocation(program, "a_Color");
        a_position = glGetAttribLocation(program, "a_Position");
        u_matrix = glGetUniformLocation(program, "u_Matrix");

        mVertexBuffer.position(0);
        glVertexAttribPointer(a_position, POSITION_COMPONENT_COUNT, GL_FLOAT, false, STRIDE, mVertexBuffer);
        glEnableVertexAttribArray(a_position);

        mVertexBuffer.position(POSITION_COMPONENT_COUNT);
        glVertexAttribPointer(a_color, COLOR_COMPONENT_COUNT, GL_FLOAT, false, STRIDE, mVertexBuffer);
        glEnableVertexAttribArray(a_color);
    }

    @Override
    public void onSurfaceChanged(GL10 gl10, int width, int height) {
        glViewport(0, 0, width, height);

        // 用45度的視野創(chuàng)建一個透視投影烛亦。這個視椎體從z值為-1的位置開始诈泼,在z值為-10的位置結(jié)束
        Matrix.perspectiveM(projectionMatrix, 0, 45, (float) width / height, 1, 10);

        // 設(shè)為單位矩陣
        Matrix.setIdentityM(modelMatrix, 0);
        // 沿著z軸平移 -2
        Matrix.translateM(modelMatrix, 0, 0f, 0f, -3f);
        // 沿著 x 軸旋轉(zhuǎn)45度
        Matrix.rotateM(modelMatrix, 0, -45f, 1f, 0f, 0f);

        // 將視圖投影矩陣和模型矩陣相乘
        float[] temp = new float[16];
        Matrix.multiplyMM(temp, 0, projectionMatrix, 0, modelMatrix, 0);
        System.arraycopy(temp, 0, projectionMatrix, 0, temp.length);
    }

    @Override
    public void onDrawFrame(GL10 gl10) {
        glClear(GL_COLOR_BUFFER_BIT);
        glUniformMatrix4fv(u_matrix, 1, false, projectionMatrix, 0);
        glDrawArrays(GL_TRIANGLE_FAN, 0, 6);

        glDrawArrays(GL_LINES, 6, 2);

        glDrawArrays(GL_POINTS, 8, 1);
        glDrawArrays(GL_POINTS, 9, 1);
    }
}

效果

image-20211221164736930.png
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市煤禽,隨后出現(xiàn)的幾起案子铐达,更是在濱河造成了極大的恐慌,老刑警劉巖檬果,帶你破解...
    沈念sama閱讀 221,820評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件瓮孙,死亡現(xiàn)場離奇詭異,居然都是意外死亡选脊,警方通過查閱死者的電腦和手機(jī)杭抠,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,648評論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來恳啥,“玉大人偏灿,你說我怎么就攤上這事《鄣模” “怎么了翁垂?”我有些...
    開封第一講書人閱讀 168,324評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長扁藕。 經(jīng)常有香客問我沮峡,道長,這世上最難降的妖魔是什么亿柑? 我笑而不...
    開封第一講書人閱讀 59,714評論 1 297
  • 正文 為了忘掉前任邢疙,我火速辦了婚禮,結(jié)果婚禮上望薄,老公的妹妹穿的比我還像新娘疟游。我一直安慰自己,他們只是感情好痕支,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,724評論 6 397
  • 文/花漫 我一把揭開白布颁虐。 她就那樣靜靜地躺著,像睡著了一般卧须。 火紅的嫁衣襯著肌膚如雪另绩。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,328評論 1 310
  • 那天花嘶,我揣著相機(jī)與錄音笋籽,去河邊找鬼。 笑死椭员,一個胖子當(dāng)著我的面吹牛车海,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播隘击,決...
    沈念sama閱讀 40,897評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼侍芝,長吁一口氣:“原來是場噩夢啊……” “哼研铆!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起州叠,我...
    開封第一講書人閱讀 39,804評論 0 276
  • 序言:老撾萬榮一對情侶失蹤棵红,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后留量,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體窄赋,經(jīng)...
    沈念sama閱讀 46,345評論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,431評論 3 340
  • 正文 我和宋清朗相戀三年楼熄,在試婚紗的時候發(fā)現(xiàn)自己被綠了忆绰。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,561評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡可岂,死狀恐怖错敢,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情缕粹,我是刑警寧澤稚茅,帶...
    沈念sama閱讀 36,238評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站平斩,受9級特大地震影響亚享,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜绘面,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,928評論 3 334
  • 文/蒙蒙 一欺税、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧揭璃,春花似錦晚凿、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,417評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至情组,卻和暖如春燥筷,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背院崇。 一陣腳步聲響...
    開封第一講書人閱讀 33,528評論 1 272
  • 我被黑心中介騙來泰國打工肆氓, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人亚脆。 一個月前我還...
    沈念sama閱讀 48,983評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像盲泛,于是被迫代替她去往敵國和親濒持。 傳聞我的和親對象是個殘疾皇子键耕,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,573評論 2 359

推薦閱讀更多精彩內(nèi)容