2011年10月23日 星期日

Android Game : OpenGL 如何寫一次應用自動對應支援多種解析度上?

OpenGL 用來開發 Android 的遊戲是很方便的, 許多功能可以直接硬體支援, 而不需要像 SurfaceView 一堆東西都硬做.



最近在研究如何將 SurfaceView 改成 GLSurfaceView 上, 發現直接使用是很方便的, 但在有些情況下, SurfaceView 表現不比 GLSurfaceView 差甚至在低端手機配備還能有滿速的情況, 但就如同前面所提的, 只要使用旋轉縮放就會開始 Lag 不順, 或是想要做一些變色效果都會令 FPS 數低下.

我們來談談另一個問題, 移植成功的遊戲, 放在 320x480 的機子上沒什麼問題, 但最近螢幕解析度有越來越高的趨勢, 尤其是 Galaxy Nexus ICS(Ice Cream Sandwich)已解高達 720p(1280x720), 那麼遊戲如果不能自動支援, 會使得玩家觀感變差, 在之前頗受注目的開發 Android 程式遇到大小尺寸不合問題,如何一次搞定全部機種畫面? 有提到相關的對應方式, 但套用到 OpenGL, 還要再做些修改.

使用 OpenGL 的 GLSurfaceView, 必須實作 OnSurfaceCreate/onSurfaceChanged/onDrawFrame, 看一下程式碼 :

OnSurfaceCreate :
gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_FASTEST);
gl.glClearColor(0, 255, 0, 1);
gl.glShadeModel(GL10.GL_FLAT);

gl.glDisable(GL10.GL_DEPTH_TEST);

gl.glDisable(GL10.GL_DITHER);
gl.glDisable(GL10.GL_LIGHTING);

gl.glEnable(GL10.GL_TEXTURE_2D);
gl.glEnable(GL10.GL_BLEND);
gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA);
gl.glEnableClientState(GL11.GL_VERTEX_ARRAY);
gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);

OnSurfaceChanged :
 gl.glViewport(0, 0, width, height);

 gl.glMatrixMode(GL10.GL_PROJECTION);
 gl.glLoadIdentity();
 gl.glOrthof(0.0f, width, 0.0f, 0.0f, height, 1.0f);
 gl.glEnable(GL10.GL_TEXTURE_2D);

 gl.glMatrixMode(GL10.GL_MODELVIEW);

OnDrawFrame 依照原本的繪製進行即可.

咦? 怎麼會有這種怪事?

下面這張圖, 顯示了三個方塊, 最左邊的是包含紅色邊線的方塊, 中間是無邊線的方塊, 最右邊也是無邊線的方塊. 等等, 最右邊的方塊不是上面有紅色邊線嗎?(中間跟最右的方塊只取得中間的像素)


這是因為在 AndroidManifest.xml 的 anyDensity="true" 設定上, 會傳給程式真正的寬高, 也不會自動進行縮放, 導致紋理貼圖在高於 320x480 的解析度和進行 Scaling, UV 貼圖參數對應有時會出現 1 個像素(Pixel) 的誤差, 這是 OpenGL 縮放(Scale)紋理貼圖的問題, 解決方式將 anyDensity="false" 以及 Bind 貼圖時指定參數即可, 這個方式有一些問題存在, 例如只能使用在 Landscape mode (橫向), 而且由於不是透過 OpenGL 縮放, 所以看起來會有點模糊, 即使 GL_NEARST 還是有點模糊.

Bind Texture 程式片段 :
            gl.glBindTexture(GL10.GL_TEXTURE_2D, textureId);

            gl.glTexParameterx(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_NEAREST);

            gl.glTexParameterx(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_NEAREST);       



            gl.glTexParameterx(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_S, GL10.GL_CLAMP_TO_EDGE);

            gl.glTexParameterx(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_T, GL10.GL_CLAMP_TO_EDGE);

   
            gl.glTexParameterx(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_T, GL10.GL_REPEAT);
           

            GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, mBitmap, 0);

會有點糊, 但結果正確 :


在某些復古遊戲做法上, 會使用拼圖方式組成背景地版圖, 如果是使用大張圖拼接, 比較看不出來; 但若為了提高圖塊使用率與控制記憶體用量, 使用小張圖拼接還是有必要, 這時候設定此種方式縮放來適配裝置, 是比較恰當的做法.


沒有留言:

張貼留言