個(gè)人網(wǎng)頁可以做什么內(nèi)容seo網(wǎng)站優(yōu)化方案書
事物的本質(zhì)是事物本身所固有的、深藏于?現(xiàn)象背后并決定或支配現(xiàn)象的方面?。
還記得我們上一篇繪制的三角形嗎,我們確實(shí)能夠順利用OpenGL ES
繪制出圖形了,這是一個(gè)好的開始,但這還遠(yuǎn)遠(yuǎn)不夠。我們定義的坐標(biāo)是正三角形,但是繪制出來三角形卻拉升了(橫屏顯示會(huì)壓縮)。
為了方便大家,我們將OpenGL ES坐標(biāo)系圖再次貼出。OpenGL ES的坐標(biāo)系是一個(gè)正方形,他的四個(gè)頂點(diǎn)分別對(duì)應(yīng)GLSurfaceView
的四個(gè)頂點(diǎn),這個(gè)是定死的我們無法改變,那要怎么才能讓我的三角形變成等邊三角形呢?既然坐標(biāo)系為正方形,那么我們讓GLSurfaceView也為正方形是否可行呢?
1. 方式一:設(shè)置GLSurfaceView寬高相等
注意這里有個(gè)誤區(qū)就是OpenGL ES坐標(biāo)頂點(diǎn)
對(duì)應(yīng)的是GLSurfaceView
的四個(gè)頂點(diǎn),而不是屏幕的四個(gè)頂點(diǎn)。所以好多文章說變形拉升什么的,甚至是官方文檔都和手機(jī)屏幕扯上關(guān)系。我現(xiàn)在可以明確的告訴大家的是,這和手機(jī)的屏幕一點(diǎn)關(guān)系也沒有。
那為什么又要說和屏幕有關(guān)系,其本質(zhì)是將GLSurfaceView
的寬高使用了match_parent
導(dǎo)致GLSurfaceView
大小和屏幕相同而已,但是變形拉升只和GLSurfaceView
的大小有關(guān),GLSurfaceView
如果不是一個(gè)正方形,那么畫出的圖形就會(huì)變形。
既然我們知道了上述緣由后,最簡(jiǎn)單的方式就有了,就是設(shè)置GLSurfaceView的寬高相等即可:
<com.android.xz.opengldemo.view.TriangleGLSurfaceViewandroid:layout_width="400dp"android:layout_height="400dp"/>
運(yùn)行看看效果是否可行:
不出我們所料,果然是行得通的!!!
但是世界的運(yùn)行往往不是我們?nèi)藶槟芸刂频?#xff0c;GLSurfaceView
的寬高往往不是正方形,他要和應(yīng)用相結(jié)合,他可能是游戲全屏界面,也可能是某個(gè)顯示視頻的預(yù)覽界面,亦或是嵌到某個(gè)犄角旮旯充當(dāng)不重要的視圖,這個(gè)時(shí)候我們就引入了下面的方式。
2. 方式二:修改頂點(diǎn)坐標(biāo)數(shù)據(jù)
當(dāng)GLSurfaceView
的寬高不一致的時(shí),我們?cè)撊绾问呛?#xff1f;??就比如我們現(xiàn)在GLSurfaceView
是全屏的。
我們來分析下,GLSurfaceView
目前全屏后,視圖高被拉升了,原本三角形的top頂點(diǎn)
到底邊的垂直距離是0.866,也就是說我們按照GLSurfaceView
拉升比縮放這個(gè)距離是不是也是可行的?
// 三角形三個(gè)點(diǎn)的坐標(biāo),逆時(shí)針繪制static float triangleCoords[] = { // 坐標(biāo)逆時(shí)針順序0.0f, 0.616f, 0.0f, // top-0.5f, -0.25f, 0.0f, // bottom left0.5f, -0.25f, 0.0f // bottom right};
好了開始動(dòng)手干,我們?cè)?code>Triangle類中surfaceChanged
方法中重新計(jì)算縮放后Y的坐標(biāo)點(diǎn),如下:
public void surfaceChanged(int width, int height) {// 設(shè)置OpenGL ES畫布大小GLES20.glViewport(0, 0, width, height);float radio = (float) width / height;triangleCoords = new float[]{ // 坐標(biāo)逆時(shí)針順序0.0f, 0.616f * radio, 0.0f, // top-0.5f, -0.25f * radio, 0.0f, // bottom left0.5f, -0.25f * radio, 0.0f // bottom right};// 初始化形狀坐標(biāo)的頂點(diǎn)字節(jié)緩沖區(qū)ByteBuffer bb = ByteBuffer.allocateDirect(// (number of coordinate values * 4 bytes per float)triangleCoords.length * 4);// use the device hardware's native byte orderbb.order(ByteOrder.nativeOrder());// create a floating point buffer from the ByteBuffervertexBuffer = bb.asFloatBuffer();// add the coordinates to the FloatBuffervertexBuffer.put(triangleCoords);// set the buffer to read the first coordinatevertexBuffer.position(0);
}
查看效果:
其實(shí)在沒有看到效果時(shí),我已經(jīng)猜到最后肯定會(huì)繪制出正三角形,當(dāng)然看了效果我們也就踏實(shí)了。
OpenGL ES繪圖變形,其本質(zhì)無非就是要解決View的寬高比和OpenGL ES正方形坐標(biāo)系的一個(gè)變換;如果View是正方形無需變換,長(zhǎng)方形該縮放縮放。
目前我們是繪制了一個(gè)三角形,頂點(diǎn)只有三個(gè),修改頂點(diǎn)坐標(biāo)倒還不是那么復(fù)雜。如果我們要繪制更加復(fù)雜的圖像,頂點(diǎn)有幾十個(gè)上百個(gè),我們又該如何一個(gè)一個(gè)修改頂點(diǎn)的縮放比呢?聰明的你可能又想到了,我用for循環(huán)遍歷修改啊那樣就會(huì)方便很多,那我只能說你這是小聰明,OpenGL ES為我們提供了一個(gè)大聰明的方式:矩陣變換
3. 方式三:矩陣變換
我們知道OpenGL ES的世界里是三維空間,我們要對(duì)三維空間中的點(diǎn)進(jìn)行縮放、平移、旋轉(zhuǎn)實(shí)際在數(shù)學(xué)中有一個(gè)好的方式就是用矩陣來計(jì)算。而矩陣的知識(shí)是大學(xué)線性代數(shù)中的,這個(gè)基礎(chǔ)需要讀者自己去補(bǔ)。
空間中點(diǎn)縮放變換,建議看下這篇文章:【深度好文】3D坐標(biāo)系下的點(diǎn)的轉(zhuǎn)換矩陣(平移、縮放、旋轉(zhuǎn)、錯(cuò)切)
3.1 自定義矩陣縮放方法
接下來我們使用矩陣相乘變換點(diǎn)的坐標(biāo),定義縮放方法如下:
public static void scale(float[] coords, int stride, float sx, float sy, float sz) {float[] scaleM = {sx, 0, 0,0, sy, 0,0, 0, sz};for (int i = 0; i < coords.length; i += stride) {float x = coords[i];float y = coords[i + 1];float z = coords[i + 2];coords[i] = scaleM[0] * x;coords[i + 1] = scaleM[4] * y;coords[i + 2] = scaleM[8] * z;}
}
修改surfaceChanged
縮放坐標(biāo)代碼
public void surfaceChanged(int width, int height) {// 設(shè)置OpenGL ES畫布大小GLES20.glViewport(0, 0, width, height);scale(triangleCoords, 3, 1, (float) width / height, 1);// 初始化形狀坐標(biāo)的頂點(diǎn)字節(jié)緩沖區(qū)ByteBuffer bb = ByteBuffer.allocateDirect(// (number of coordinate values * 4 bytes per float)triangleCoords.length * 4);// use the device hardware's native byte orderbb.order(ByteOrder.nativeOrder());// create a floating point buffer from the ByteBuffervertexBuffer = bb.asFloatBuffer();// add the coordinates to the FloatBuffervertexBuffer.put(triangleCoords);// set the buffer to read the first coordinatevertexBuffer.position(0);
}
運(yùn)行查看效果,也可得到正三角形。
3.2 使用GLSL縮放
上面方式固然可行,但是大量頂點(diǎn)計(jì)算都在CPU端了,如何使用GPU程序去并行計(jì)算?
OpenGL ES也提供了矩陣相乘的方式,在三維圖形學(xué)中,一般使用的是4階矩陣。在DirectX中使用的是行向量,如[xyzw],所以與矩陣相乘時(shí),向量在前矩陣在后。OpenGL中使用的是列向量,如[xyzx]T,所以與矩陣相乘時(shí),矩陣在前,向量在后,我們最終通過“變換矩陣”來得到我們想要的向量
。
修改頂點(diǎn)著色器代碼并定義變換矩陣如下:
// 頂點(diǎn)著色器代碼
private final String vertexShaderCode =// 傳入變換矩陣"uniform mat4 uMVPMatrix;" +"attribute vec4 vPosition;" +"void main() {" +// 變換矩陣與頂點(diǎn)坐標(biāo)相乘等到新的坐標(biāo)" gl_Position = uMVPMatrix * vPosition;" +"}";/*** Shader程序中矩陣屬性的句柄*/
private int vPMatrixHandle;// 最終變化矩陣
private final float[] mMVPMatrix = new float[16];
記得在surfaceCreated
中獲取矩陣屬性句柄
public void surfaceCreated() {...// 獲取繪制矩陣句柄vPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");
}
在surfaceChanged
中設(shè)置縮放矩陣
public void surfaceChanged(int width, int height) {// 設(shè)置OpenGL ES畫布大小GLES20.glViewport(0, 0, width, height);float radio = (float) width / height;float[] scaleMatrix = new float[]{1, 0, 0, 0,0, radio, 0, 0,0, 0, 1, 0,0, 0, 0, 1};// 將縮放矩陣拷貝到變換矩陣中System.arraycopy(scaleMatrix, 0, mMVPMatrix, 0, scaleMatrix.length);}
在draw
方法中將縮放矩陣傳給OpenGL ES程序
public void draw() {...// 將縮放矩陣傳遞給著色器程序GLES20.glUniformMatrix4fv(vPMatrixHandle, 1, false, mMVPMatrix, 0);// 畫三角形GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, vertexCount);// 禁用頂點(diǎn)陣列GLES20.glDisableVertexAttribArray(positionHandle);
}
運(yùn)行查看效果,也可得到正三角形。
本文到現(xiàn)在已經(jīng)講了很多種方式可以正確繪制正三角形,但是到現(xiàn)在還沒有到正真要講的內(nèi)容。我們發(fā)現(xiàn)將縮放矩陣傳入著色器程序貌似已經(jīng)完成了最終的目的,如果我們僅僅只是為了把三角形畫正那么到這里應(yīng)該就結(jié)束了。世界的運(yùn)行往往出乎我們的意料,現(xiàn)在可能要求你縮放,但是未來可能還會(huì)平移、二維旋轉(zhuǎn)、三維旋轉(zhuǎn)、鏡像等等操作,如果要我們?nèi)ザx各種矩陣,那簡(jiǎn)直是災(zāi)難,于是下面的方式就應(yīng)運(yùn)而生了。
4. 相機(jī)和投影
- 投影:OpenGL 中主要有兩種投影模式,分別是正交投影和透視投影
- 相機(jī):相機(jī)視圖顧名思義就相當(dāng)于站在相機(jī)的角度觀察某個(gè)物體,相機(jī)會(huì)看到投影到近平面的物體
4.1 投影
OpenGL提供了兩種投影變換矩陣如下
透視投影
學(xué)過素描的應(yīng)該都知道透視圖的概念,符合人眼習(xí)慣,呈現(xiàn)近大遠(yuǎn)小的效果。
/*** @param m 生成的投影矩陣,float[4*4]* @param mOffset 填充時(shí)候起始的偏移量* @param left 近平面left邊的x坐標(biāo)* @param right 近平面right邊的x坐標(biāo)* @param bottom 近平面bottom邊的y坐標(biāo)* @param top 近平面top邊的y坐標(biāo)* @param near 近平面距離攝像機(jī)的距離* @param far 遠(yuǎn)平面距離攝像機(jī)的距離*/
public static void frustumM(float[] m, int mOffset,float left, float right, float bottom, float top,float near, float far) {
}
正交投影
該投影方式圖像大小不會(huì)隨著距離變化而變化
/*** @param m 生成的投影矩陣,float[4*4]* @param mOffset 填充時(shí)候起始的偏移量* @param left 近平面left邊的x坐標(biāo)* @param right 近平面right邊的x坐標(biāo)* @param bottom 近平面bottom邊的y坐標(biāo)* @param top 近平面top邊的y坐標(biāo)* @param near 近平面距離攝像機(jī)的距離* @param far 遠(yuǎn)平面距離攝像機(jī)的距離*/
public static void orthoM(float[] m, int mOffset,float left, float right, float bottom, float top,float near, float far) {
}
- 不管是正交投影還是透視投影,最終都是將視景體內(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),讓圖像保持居中。
4.2 相機(jī)
相機(jī)位置設(shè)置
/**** @param rm 生成的攝像機(jī)矩陣,float[16]* @param rmOffset 填充時(shí)候的起始偏移量* @param eyeX 攝像機(jī)x坐標(biāo)* @param eyeY 攝像機(jī)y坐標(biāo)* @param eyeZ 攝像機(jī)z坐標(biāo)* @param centerX 觀察目標(biāo)點(diǎn)的x坐標(biāo)* @param centerY 觀察目標(biāo)點(diǎn)的y坐標(biāo)* @param centerZ 觀察目標(biāo)點(diǎn)的z坐標(biāo)* @param upX 攝像機(jī)up向量在x上的分量* @param upY 攝像機(jī)up向量在y上的分量* @param upZ 攝像機(jī)up向量在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) {
}
- eyeX,eyeY,eyeZ:攝像機(jī)坐標(biāo)。
- centerX,centerY,centerZ:觀察點(diǎn)坐標(biāo),和攝像機(jī)坐標(biāo)一起決定了攝像機(jī)的觀察方向,即向量(centerX - eyeX, centerY - eyeY, centerZ - eyeZ)。觀察方向不朝向視景體是無法看到的。
- upX,upY,upZ:攝像機(jī)up向量。相對(duì)于人眼觀察物體中,人頭的朝向,頭的朝向影響了最后的成像。同樣以圖來說明:
當(dāng)up向量為Y的正方向時(shí),正如我們頭頂對(duì)著天花板,所以觀察到的物體是正的,投影在近平面的樣子就是正的,如右圖
當(dāng)up向量為X正方向時(shí),正如我們向右90度歪著腦袋去看這個(gè)三角形,看到的三角形就會(huì)是向左旋轉(zhuǎn)了90度的三角形
再比如up向量如果為Z軸正方向,就相當(dāng)于仰著頭去看這個(gè)三角形,但是因?yàn)槲覀兊膗p向量和觀察方向平行了,所以我們什么也看不到,就比如仰著頭去看你身前的物體時(shí),你什么也看不到。
所以在設(shè)置up向量時(shí),一般總是設(shè)置為(0,1,0),這是大多數(shù)觀察時(shí)頭朝上的方向。注意:up向量的大小無關(guān)緊要,有意義的只有方向。
4.3 near、far的取值范圍規(guī)定
- 正交投影時(shí),攝像機(jī)可位于視景體中間,此時(shí)near < 0,far > 0,近平面位于視點(diǎn)后面(Z軸正方向),遠(yuǎn)平面位于視點(diǎn)前面(Z軸負(fù)方向)
- 正交投影時(shí),視景體也可位于視點(diǎn)后面(Z軸正方向),此時(shí)near < 0, far < 0
- 正交投影時(shí),far 和 near沒有規(guī)定的大小關(guān)系,既可以far > near 也可以 far < near,只要物體在視景體內(nèi)都可以被觀察到。
- 透視投影時(shí),far>near>0;我們不考慮其他情況,我們默認(rèn)就在Z軸上看物體
當(dāng)centerZ - eyeZ>0時(shí):近平面nearZ坐標(biāo)=eyeZ+near,遠(yuǎn)平面farZ坐標(biāo)=eyeZ+far;
當(dāng)centerZ - eyeZ<0時(shí):近平面nearZ坐標(biāo)=eyeZ-near,遠(yuǎn)平面farZ坐標(biāo)=eyeZ-far;
我們要保證物體Z坐標(biāo)在nearZ和farZ之間就能看到,也就是物體在視景里就能看到。
4.4 構(gòu)造模型矩陣
根據(jù)上面的理論知識(shí),我們不用再手動(dòng)構(gòu)造一個(gè)縮放矩陣了,我們定義如下三個(gè)矩陣并進(jìn)行變換
public class Triangle {...// vPMatrix是“模型視圖投影矩陣”的縮寫// 最終變化矩陣private final float[] mMVPMatrix = new float[16];// 投影矩陣private final float[] mProjectionMatrix = new float[16];// 相機(jī)矩陣private final float[] mViewMatrix = new float[16];public void surfaceChanged(int width, int height) {// 設(shè)置OpenGL ES畫布大小GLES20.glViewport(0, 0, width, height);float ratio;if (width > height) {ratio = (float) width / height;// 橫屏使用// 透視投影,特點(diǎn):物體離視點(diǎn)越遠(yuǎn),呈現(xiàn)出來的越小。離視點(diǎn)越近,呈現(xiàn)出來的越大// 該投影矩陣應(yīng)用于對(duì)象坐標(biāo)Matrix.frustumM(mProjectionMatrix, 0, -ratio, ratio, -1, 1, 3, 7);} else {ratio = (float) height / width;// 豎屏使用// 透視投影,特點(diǎn):物體離視點(diǎn)越遠(yuǎn),呈現(xiàn)出來的越小。離視點(diǎn)越近,呈現(xiàn)出來的越大// 該投影矩陣應(yīng)用于對(duì)象坐標(biāo)Matrix.frustumM(mProjectionMatrix, 0, -1, 1, -ratio, ratio, 3, 7);}Matrix.setLookAtM(mViewMatrix, 0,0, 0, 3f,0f, 0f, 0f,0f, 1.0f, 0.0f);// Calculate the projection and view transformationMatrix.multiplyMM(mMVPMatrix, 0, mProjectionMatrix, 0, mViewMatrix, 0);}
}
說明:我們使用frustumM
做透視矩陣時(shí),我們要把近平面寬高和屏幕寬高比例對(duì)應(yīng),我們以較短的一邊作為1,按比例拉升即可。而far>near>0遵循此原則即可。
設(shè)置相機(jī)setLookAtM
參數(shù),我們只需要注意eyeZ的取值,根據(jù)上面的說明正確取值。上面方法取值為3那么近平面的nearZ坐標(biāo)=eyeZ-near=0和物體z坐標(biāo)重合,看到的物體比例正好不會(huì)變大也不會(huì)縮小。
最后我們用multiplyMM
方法將投影矩陣和相機(jī)矩陣轉(zhuǎn)換為最終的矩陣,然后傳給著色器程序即可。
運(yùn)行程序也可得到正三角形。
相機(jī)和投影概念實(shí)際上是為3D模型準(zhǔn)備的,現(xiàn)在我們把他用在2D圖形上,著實(shí)有中降維打擊,大炮打蒼蠅的感覺。但是我們不得不了解這個(gè)強(qiáng)大的工具,為將來遇到的3D場(chǎng)景變換做準(zhǔn)備。
最后
我們都知道獨(dú)孤九劍,劍法的最高境界是無招。上述介紹的幾種方式都可謂是劍招,當(dāng)我們了解了事物運(yùn)行的本質(zhì)后,這幾種方式皆可為我所用,在適當(dāng)?shù)膱?chǎng)景下選擇合適的方式,甚至可以創(chuàng)造招式。
《黑客帝國(guó)》中尼奧復(fù)活后了解了虛擬世界的本質(zhì),整個(gè)世界的運(yùn)行不過就是一串串?dāng)?shù)字。原來難以翻越的高山,現(xiàn)在也只是眼下的風(fēng)景。還記的我們第一篇章嗎Android OpenGLES2.0開發(fā)(一):艱難的開始,現(xiàn)在的我覺得腳下有路、心中有光,也期待未來會(huì)更美好。
參考:
- https://juejin.cn/post/6844903614838751240
- https://cloud.tencent.com/developer/article/1015587
- https://www.nxrte.com/jishu/13722.html