1. 程式人生 > >Android開發筆記(一百五十四)OpenGL的畫筆工具GL10

Android開發筆記(一百五十四)OpenGL的畫筆工具GL10

上一篇文章介紹了OpenGL繪製三維圖形的流程,其實沒有傳說中的那麼玄乎,只要放平常心把它當作一個普通控制元件就好了,接下來繼續介紹OpenGL具體的繪圖操作,這項工作得靠三維圖形的畫筆GL10來完成了。

GL10作為三維空間的畫筆,它所描繪的三維物體卻要顯示在二維平面上,顯而易見這不是一個簡單的夥計。為了理順物體從三維空間到二維平面的變換關係,有必要搞清楚OpenGL關於三維空間的幾個基本概念。下面就概括介紹一下GL10編碼的三類常見方法:

一、顏色的取值範圍

Android中的三原色,不管是紅色還是綠色還是藍色,取值範圍都是0到255,對應的十六進位制數值則為00到FF,顏色數值越小表示亮度越弱,數值越大表示亮度越強。但在OpenGL之中,顏色的取值範圍卻是0.0到1.0,其中0.0對應Android標準的0,1.0對應Android標準的255,同理,OpenGL值為0.5的顏色對應Android標準的128。
GL10與顏色有關的方法主要有兩個,說明如下:
glClearColor : 設定背景顏色。以下程式碼表示給三維空間設定白色背景:
        // 設定白色背景。四個引數依次為透明度alpha、紅色red、綠色green、藍色blue
        gl.glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
glColor4f : 設定畫筆顏色。以下程式碼表示把畫筆顏色設定為橙色:
        // 設定畫筆顏色為橙色
        gl.glColor4f(0.0f, 1.0f, 1.0f, 0.0f);


二、三維座標系

三維空間用來表達立體形狀,需要三個方向的座標,分別為水平方向的x軸和y軸,以及垂直方向的z軸。如下圖的三維座標系所示,三維空間有個M點,該點在x軸上的投影為P點,在y軸上的投影為Q點,在z軸上的投影為R點,因此M點的座標位置就是(P, Q, R)。

既然三維空間中的每個點都存在x、y、z三個方向的座標值,那麼與物體位置有關的方法均需提供x、y、z三方向的數值。比如物體的旋轉方法glRotatef、平移方法glTranslatef、縮放方法glScalef,要分別指定物體在三個座標軸上的旋轉方向、平移距離、縮放倍率。具體的方法呼叫例子如下所示:
        // 沿著y軸的負方向旋轉90度
        gl.glRotatef(90, 0, -1, 0);
        // 沿x軸方向移動1個單位
        gl.glTranslatef(1, 0, 0);
        // x,y,z三方向各縮放0.1倍
        gl.glScalef(0.1f, 0.1f, 0.1f);


三、座標矩陣變換

有了三維座標系,還要把三維物體投影到二維平面上,才能在手機螢幕中繪製三維圖形。這個投影操作主要有三個步驟,下面分別展開敘述:

1、設定繪圖區域

前面說過OpenGL使用GLSurfaceView這個控制元件作為繪圖場所,於是允許繪製的區域範圍自然落在GLSurfaceView內部。設定繪圖區域的方法是glViewport,它指定了該區域左上角的平面座標,以及區域的寬度和高度。當然一般OpenGL的繪圖範圍與GLSurfaceView的大小重合,所以倘若GLSurfaceView控制元件的寬度為width,高度為height,則設定繪圖區域的方法呼叫示例如下:
        // 設定輸出螢幕大小
        gl.glViewport(0, 0, width, height);

2、調整鏡頭引數

框住了繪圖區域,還要把三維物體在二維平面上的投影一點一點描繪進去才行,這中間的座標變換計算由OpenGL內部自行完成,開發者無需關注具體的運算邏輯。好比日常生活中的拍照,使用者只管拿起手機咔嚓一下,根本不用關心攝像頭怎麼生成照片。使用者所關心的照片效果,不外乎景物是大還是小,是遠還是近;用專業一點的術語來講,景物的大小由鏡頭的焦距決定,景物的遠近由鏡頭的視距決定。
對於鏡頭的焦距而言,拍攝同樣尺寸的照片,廣角鏡頭看到的景物比標準鏡頭看到的景物更多,這意味著單個景物在廣角鏡頭中會比較小,從而照片面積不增大、容納的景物卻變多了。
對於鏡頭的視距而言,它表示鏡頭的視力好壞,即最近能看到多近的景物,最遠能看到多遠的景物。在日常生活當中,每個人的睫毛離自己的眼睛太近了,這麼近的東西能看得清楚嗎?所以必須規定一下,最近只能看清楚比如離眼睛十釐米的物體。很遙遠的景物自然也是看不清楚的,所以也要規定一下,比如最遠只能看到一公里之內的人影。這個能看清景物的最近距離和最遠距離,就構成了鏡頭的視距。
所以,鏡頭的焦距是橫向的,它反映了畫面的廣度;而鏡頭的視距是縱向的,它反映了畫面的深度。在OpenGL中,這些鏡頭引數的調節依賴於GL10的gluPerspective方法,具體的引數調整程式碼舉例如下:
        // 設定投影矩陣,對應gluPerspective(調整相機引數)、glFrustumf(調整透視投影)、glOrthof(調整正投影)
        gl.glMatrixMode(GL10.GL_PROJECTION);
        // 重置投影矩陣,即去掉所有的引數調整操作
        gl.glLoadIdentity();
        // 設定透檢視視窗大小。第二個引數是焦距的角度,第四個引數是能看清的最近距離,第五個引數是能看清的最遠距離
        GLU.gluPerspective(gl, 40, (float) width / height, 0.1f, 20.0f);

3、挪動觀測方位

調整好了鏡頭的拍照引數,要不要再擺個POSE,來個花式攝影?比如使用者躍上好幾級臺階,居高臨下拍攝;也可俯下身子,從下向上拍攝;還能把手機橫過來拍或者倒過來拍。要是怕攝影家累壞了,不妨叫擺拍的模特自己挪動身影,或者走進或者走遠,往左靠一點或者往右靠一點,還可以躺下來甚至倒立過來。
因此,不管是挪動相機的位置,還是挪動物體的位置,都會讓照片裡的景物發生變化。挪動相機的位置,依靠的是GL10的gluLookAt方法;至於挪動物體的位置,依靠的則是旋轉方法glRotatef、平移方法glTranslatef,以及縮放方法glScalef了。下面是OpenGL挪動相機位置的方法呼叫程式碼:
        // 選擇模型觀察矩陣,對應gluLookAt(人動)、glTranslatef/glScalef/glRotatef(物動)
        gl.glMatrixMode(GL10.GL_MODELVIEW);
        // 重置模型矩陣,即去掉所有的位置挪動操作
        gl.glLoadIdentity();
        // 設定鏡頭的方位。第二到第四個引數為相機的位置座標,第五到第七個引數為相機畫面中心點的座標,第八到第十個引數為朝上的座標方向,比如第八個引數為1表示x軸朝上,第九個引數為1表示y軸朝上,第十個引數為1表示z軸朝上
        GLU.gluLookAt(gl, 10.0f, 8.0f, 6.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f);

注意到前面調整相機引數和挪動相機位置這兩個動作,都事先呼叫了glMatrixMode與glLoadIdentity方法,這是什麼緣故呢?其實這兩個方法結合起來只不過是狀態重置操作,好比把手機恢復出廠設定,接下來重新進行狀態設定。glMatrixMode方法的引數指定了重置操作的型別,像GL10.GL_PROJECTION型別涵蓋了所有的鏡頭引數調整方法,包括gluPerspective(調整相機引數)、glFrustumf(調整透視投影)、glOrthof(調整正投影)三種方法,每次重置GL10.GL_PROJECTION型別,意味著之前的這三種引數設定統統失效。而GL10.GL_MODELVIEW型別涵蓋的是位置變換的相關方法,包括挪動相機的gluLookAt方法,以及挪動物體的glTranslatef/glScalef/glRotatef方法,每次重置GL10.GL_MODELVIEW型別,意味著之前的位置挪動統統失效。

現在瞭解了以上的三維繪圖的常見方法,接下來再看OpenGL的應用程式碼就會比較輕鬆了。先來看看一個最簡單的三維立方體是如何實現的,下面是OpenGL繪製立方體的程式碼例子片段:
public class GlCubeActivity extends Activity {
    private final static String TAG = "GlCubeActivity";
    private GLSurfaceView glsv_content;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_gl_cube);
        initVertexs(); // 初始化立方體的頂點集合,具體說明詳見下一篇文章
        glsv_content = (GLSurfaceView) findViewById(R.id.glsv_content);
        // 註冊渲染器
        glsv_content.setRenderer(new GLRender());
    }

    private class GLRender implements GLSurfaceView.Renderer {
        @Override
        public void onSurfaceCreated(GL10 gl, EGLConfig config) {
            // 背景:白色
            gl.glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
            // 啟動陰影平滑
            gl.glShadeModel(GL10.GL_SMOOTH);
        }

        @Override
        public void onSurfaceChanged(GL10 gl, int width, int height) {
            // 設定輸出螢幕大小
            gl.glViewport(0, 0, width, height);
            // 設定投影矩陣,對應gluPerspective(調整相機)、glFrustumf(調整透視投影)、glOrthof(調整正投影)
            gl.glMatrixMode(GL10.GL_PROJECTION);
            // 重置投影矩陣,即去掉所有的平移、縮放、旋轉操作
            gl.glLoadIdentity();
            // 設定透檢視視窗大小
            GLU.gluPerspective(gl, 40, (float) width / height, 0.1f, 20.0f);
            // 選擇模型觀察矩陣,對應gluLookAt(人動)、glTranslatef/glScalef/glRotatef(物動)
            gl.glMatrixMode(GL10.GL_MODELVIEW);
            // 重置模型矩陣
            gl.glLoadIdentity();
        }

        @Override
        public void onDrawFrame(GL10 gl) {
            // 清除螢幕和深度快取
            gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
            // 重置當前的模型觀察矩陣
            gl.glLoadIdentity();
            // 設定畫筆顏色
            gl.glColor4f(0.0f, 0.0f, 1.0f, 1.0f);
            // 設定鏡頭的方位
            GLU.gluLookAt(gl, 10.0f, 8.0f, 6.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f);
            // 旋轉圖形
            //gl.glRotatef(angle, 0, 0, -1);
            //gl.glRotatef(angle, 0, -1, 0);
            // 沿x軸方向移動1個單位
            //gl.glTranslatef(1, 0, 0);
            // x,y,z方向縮放0.1倍
            //gl.glScalef(0.1f, 0.1f, 0.1f);
            // 繪製一個立方體,具體說明詳見下一篇文章
            drawCube(gl);
        }
    }
}


點此檢視Android開發筆記的完整目錄


__________________________________________________________________________
本文現已同步釋出到微信公眾號“老歐說安卓”,開啟微信掃一掃下面的二維碼,或者直接搜尋公眾號“老歐說安卓”新增關注,更快更方便地閱讀技術乾貨。