OpenGL ES正交投影實現(xiàn)方法(三)
本文實例為大家分享了OpenGL ES正交投影展示的具體代碼,供大家參考,具體內(nèi)容如下
繪制正方形
在最開始繪制的六邊形里面好像看起來挺容易的,也沒有出現(xiàn)什么問題,接下來不妨忘記前面繪制六邊形的代碼,讓我們按照自己的理解來繪制一個簡單的正方形。
按照我的理解,要想在屏幕中間顯示一個正方形,效果如下圖所示
應(yīng)該創(chuàng)建的數(shù)據(jù)如下圖所示
即傳給渲染管線的頂點數(shù)據(jù)如下圖:
float[] vertexArray = new float[] { (float) -0.5, (float) -0.5, 0, (float) 0.5, (float) -0.5, 0, (float) -0.5, (float) 0.5, 0, (float) 0.5, (float) 0.5, 0 };
于是代碼大概是這樣子的,這里省略掉與主題無關(guān)的代碼,顏色用純色填充,因此在片元著色器中指定顏色,也省略掉一系列矩陣變換。頂點著色器中直接將頂點傳給渲染管線,片元著色器中給片元設(shè)置固定顏色紅色。
Rectangle.java
public class Rectangle { private FloatBuffer mVertexBuffer; private int mProgram; private int mPositionHandle; public Rectangle(float r) { initVetexData(r); } public void initVetexData(float r) { // 初始化頂點坐標(biāo) float[] vertexArray = new float[] { (float) -0.5, (float) -0.5, 0, (float) 0.5, (float) -0.5, 0, (float) -0.5, (float) 0.5, 0, (float) 0.5, (float) 0.5, 0 }; ByteBuffer buffer = ByteBuffer.allocateDirect(vertexArray.length * 4); buffer.order(ByteOrder.nativeOrder()); mVertexBuffer = buffer.asFloatBuffer(); mVertexBuffer.put(vertexArray); mVertexBuffer.position(0); int vertexShader = loaderShader(GLES20.GL_VERTEX_SHADER, vertexShaderCode); int fragmentShader = loaderShader(GLES20.GL_FRAGMENT_SHADER, fragmentShaderCode); mProgram = GLES20.glCreateProgram(); GLES20.glAttachShader(mProgram, vertexShader); GLES20.glAttachShader(mProgram, fragmentShader); GLES20.glLinkProgram(mProgram); mPositionHandle = GLES20.glGetAttribLocation(mProgram, "aPosition"); } public void draw() { GLES20.glUseProgram(mProgram); // 將頂點數(shù)據(jù)傳遞到管線,頂點著色器 GLES20.glVertexAttribPointer(mPositionHandle, 3, GLES20.GL_FLOAT, false, 0, mVertexBuffer); GLES20.glEnableVertexAttribArray(mPositionHandle); // 繪制圖元 GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4); } private int loaderShader(int type, String shaderCode) { int shader = GLES20.glCreateShader(type); GLES20.glShaderSource(shader, shaderCode); GLES20.glCompileShader(shader); return shader; } private String vertexShaderCode = "attribute vec3 aPosition;" + "void main(){" + "gl_Position = vec4(aPosition,1);" + "}"; private String fragmentShaderCode = "precision mediump float;" + "void main(){" + "gl_FragColor = vec4(1,0,0,0);" + "}"; }
RectangleView.java
public class RectangleView extends GLSurfaceView{ public RectangleView(Context context) { super(context); setEGLContextClientVersion(2); setRenderer(new MyRender()); } class MyRender implements GLSurfaceView.Renderer { private Rectangle rectangle; @Override public void onSurfaceCreated(GL10 gl, EGLConfig config) { GLES20.glClearColor(0.5f, 0.5f, 0.5f, 1); rectangle = new Rectangle(0.5f); GLES20.glEnable(GLES20.GL_DEPTH_TEST); } @Override public void onSurfaceChanged(GL10 gl, int width, int height) { GLES20.glViewport(0, 0, width, height); } @Override public void onDrawFrame(GL10 gl) { GLES20.glClear( GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT); rectangle.draw(); } } }
然后出來的效果是這樣子的,實際上屏幕上的坐標(biāo)并不是這樣子的,后面可以知道上面畫的這個樣子其實只是一個歸一化的設(shè)備坐標(biāo)。歸一化設(shè)備坐標(biāo)可以通過公式映射到實際的手機屏幕,后面會學(xué)到。
咦,實際效果好像和想象中的不太一樣呀。我的本意是顯示一個正方形,但實際上現(xiàn)實的卻是一個矩形了,y軸上被拉伸了,并且橫屏狀態(tài)下也是類似的情況。但比較巧的是,如果以屏幕中心做一個坐標(biāo)軸,就會發(fā)現(xiàn),這個矩形的四個頂點在這個坐標(biāo)軸x、y范圍為[-1,1]的中間。
實際上,要顯示的所有物體映射到手機屏幕上,都是要映射到x、y、z軸上的[-1,1]范圍內(nèi),這個范圍內(nèi)的坐標(biāo)稱為歸一化設(shè)備坐標(biāo),獨立于屏幕的實際尺寸和形狀。
因此按照這樣的規(guī)定,我們要創(chuàng)建一個正方形就非常困難了,因為要創(chuàng)建正方形就必須考慮手機的寬高比,傳入數(shù)據(jù)的時候就比較復(fù)雜了:不能僅僅站在要繪制物體的自身角度來看了。也就是說,上面的例子中要繪制一個正方形,傳入的頂點數(shù)據(jù)的y坐標(biāo)要按照比例進(jìn)行一點轉(zhuǎn)換,比如對16:9的屏幕,將上面?zhèn)魅氲捻旤c數(shù)據(jù)的y坐標(biāo)都乘以9/16即可。但同時會發(fā)現(xiàn)當(dāng)處于橫屏?xí)r,又要處理傳入的x坐標(biāo)的值,顯然這不是一個好的方案。
引入投影
實際上,對于一個物體來說它有它自身的坐標(biāo),這個空間稱為物體空間,也就是設(shè)計物體的時候采用的一個坐標(biāo)空間,物體的幾何中心在坐標(biāo)原點上,歸一化后坐標(biāo)范圍在[-1,1]之間,x和y軸分度是一致的。
將在這個空間的物體直接往手機屏幕的歸一化坐標(biāo)繪制時,由于屏幕的寬高比的問題,就會出現(xiàn)和預(yù)料結(jié)果不一樣。所以只需要對物體空間的坐標(biāo)做一個映射即可。
正交投影就是為了解決這個問題的,
public static void orthoM(float[] m, int mOffset, float left, float right, float bottom, float top, float near, float far)
正交投影背后的數(shù)學(xué)
orthoM函數(shù)產(chǎn)生的矩陣會把所有的左右之間、上下之間,遠(yuǎn)近之間的點映射到歸一化設(shè)備坐標(biāo)中。
各參數(shù)的含義如圖所示
正交投影是一種平行投影,投影線是平行的,其視景體是一個長方體,坐標(biāo)位于視景體中的物體才有效,視景體里面的物體投影到近平面上的部分最終會顯示到屏幕的視口中,關(guān)于視口后面會降到。
會產(chǎn)生下面的矩陣,z軸的負(fù)值會反轉(zhuǎn)z坐標(biāo),這是因為歸一化設(shè)備坐標(biāo)是左手系統(tǒng),而OpenGL ES中的坐標(biāo)系統(tǒng)都是右手系統(tǒng),這里還涉及到頂點坐標(biāo)的w分量,目前暫時用不到。
利用矩陣的就可以將物體空間[-1,1]之間的坐標(biāo)映射到屏幕歸一化設(shè)備坐標(biāo)的[-1,1]之間。歸一化屏幕坐標(biāo)是右手坐標(biāo)系統(tǒng),原點在屏幕正中心,向右為x軸正方向,向上為y軸正方向,z軸垂直屏幕向外。以豎屏為例,比如設(shè)置left=-1,right=1,bottom=-hight/width,top=hight/width,比如我的手機分辨率為1920*1080 =1.8 對上面的正方形點(0.5,0.5)坐標(biāo)而言經(jīng)過變化就成了(0.5,0.3)
在屏幕的歸一化設(shè)備坐標(biāo)中來看就是一個正方形了,因為y軸范圍顯然比x軸大,0.3對應(yīng)的實際長度和x軸的0.5長度是一樣的。
上面的代碼需要做如下修改,在onSurfaceChanged里面增加如下代碼
@Override public void onSurfaceChanged(GL10 gl, int width, int height) { GLES20.glViewport(0, 0, width, height); // 根據(jù)屏幕方向設(shè)置投影矩陣 float ratio= width > height ? (float)width / height : (float)height / width; if (width > height) { // 橫屏 Matrix.orthoM(mProjectionMatrix, 0, -ratio, ratio, -1, 1, 0, 5); } else { Matrix.orthoM(mProjectionMatrix, 0, -1, 1, -ratio, ratio, 0, 5); } }
接著在頂點著色器中對頂點乘以投影矩陣
private String vertexShaderCode = "uniform mat4 uProjectionMatrix;" // 增加這一行 + "attribute vec3 aPosition;" + "void main(){" + "gl_Position = uProjectionMatrix * vec4(aPosition,1);" // 不是直接賦值而是乘以投影矩陣 + "}";
最后增加獲取著色器中uProjectionMatrix以及傳入值的代碼部分即可。最終的效果不論橫屏還是豎屏,顯示的都是我們期望的正方形。
攝像機設(shè)置
需要補充的是,上面的參數(shù)near、far的含義指的是和視點的距離,視點貌似到目前還未接觸到,它指的是攝像機的位置,和實際生活中用相機看物體一樣,從不同的角度和位置拍攝同一個物體獲得的照片肯定是不一樣的,攝像機位置用setLookAtM函數(shù)指定。
public static void setLookAtM(float[] rm, // 生成的攝像機矩陣 int rmOffset, float eyeX, float eyeY, float eyeZ, // 攝像機的位置 float centerX, float centerY, float centerZ, // 觀察目標(biāo)點的位置 // 攝像機位置和觀察目標(biāo)點的位置確定了觀察方向 float upX, float upY,float upZ // up向量在x、y、z軸上的分量,我覺得一般應(yīng)該是和觀察方向垂直的 )
前面提到的確定的視景體就和上面函數(shù)指定的攝像機位置和觀察方向有關(guān)。攝像機默認(rèn)位置在(0,0,0)處,在上面的設(shè)置下,如果將改正方形沿z軸正方向平移1個單位,屏幕上就顯示不了,因為已經(jīng)跑到了設(shè)置的視景體外面了。
關(guān)于攝像機的參數(shù)和投影near和far參數(shù)的設(shè)置需要注意,肯定不是胡亂設(shè)置的!攝像機的位置、方向和投影矩陣定義的視景體最終確定了視景體的位置,如果設(shè)置不當(dāng)就會導(dǎo)致物體沒有顯示在屏幕上,因為物體的坐標(biāo)可能位于視景體外面。
視口
前面說過在視景體中的物體最終會投影到近平面上,最終顯示到視口上,正如前面在onSurfaceChanged設(shè)置的那樣。
public static native void glViewport( int x, int y, int width, int height );
視口中各參數(shù)的含義
視口用的屏幕坐標(biāo)系原點并不在屏幕左上角而是在左下角,x軸向右,y軸向上。其實還不是很準(zhǔn)確,準(zhǔn)確的說,視口的坐標(biāo)原點位于該View的左下角,因為GLSurfaceView并不總是占據(jù)整個屏幕的。
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Android開發(fā)TextView內(nèi)的文字實現(xiàn)自動換行
這篇文章主要為大家介紹了Android開發(fā)TextView內(nèi)的文字實現(xiàn)自動換行,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-06-06Android?Compose狀態(tài)改變動畫animateXxxAsState使用詳解
這篇文章主要為大家介紹了Android?Compose狀態(tài)改變動畫animateXxxAsState使用詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-11-11Android藍(lán)牙庫FastBle的基礎(chǔ)入門使用
這篇文章主要給大家介紹了關(guān)于Android藍(lán)牙庫FastBle的基礎(chǔ)入門使用的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2018-07-07