Android游戲開發:游戲框架的搭建(3)


5. 圖像模塊(Graphics)

  最后一個模塊是圖像操作模塊,用來繪制圖像到屏幕上。不過要想高性能的繪制圖像,就不得不了解一些基本的圖像編程知識。讓我們從繪制2D圖像開始,首先要了解的一個問題是:圖像究竟是如何繪制到屏幕的?答案相當復雜,我們不需要知道所有的細節。

光柵、像素和幀緩沖(Framebuffers)

  現在的顯示器都是基於光柵的,光柵是一個兩維度的格子組成,也就是像素格。光柵格子的長寬,我們一般用像素來表示。如果仔細觀察顯示器(或者用放大鏡),我們就可以發現顯示器上面有一個一個的格子,這就是像素格或者光柵格。每個像素的位置可以用坐標表示,於是引入了二維坐標系統,這也意味着坐標值是整數。顯示器源源不斷地收到從圖形處理器傳過來的圖像流,解碼每個像素的顏色(程序或者操作系統設定),然后繪制到屏幕上。每秒鍾顯示器會進行多次刷新,刷新頻率單位是Hz,比如LCD顯示器主流刷新率是85Hz。

  圖形處理器需要從一個特殊的存儲區域獲取像素信息以便顯示在顯示器上,這個區域就叫做視頻內存區,或者叫VRAM。這個區域一般稱作幀緩沖區(framebuffer)。因此一個完整的屏幕圖形叫做一個幀。對於每個顯示器柵格中的像素,在幀緩沖區都有一個對應的內存地址。當我們需要改變屏幕顯示內容時,我們只需要簡單地改變幀緩沖區中的內容即可。

  下圖是顯示器柵格和幀緩沖區的簡單示意圖:

垂直同步和雙緩沖

  普通的繪圖方法,當要繪制的對象太復雜,尤其是含有位圖時,這時的畫面會顯示的很慢,對於運動的畫面,會給人“卡”住了的感覺,有時候還會導致畫面閃爍。於是我們采用雙緩沖技術(采用兩個framebuffer)。 雙緩沖的原理可以這樣形象的理解:把電腦屏幕看作一塊黑板。首先我們在內存環境中建立一個“虛擬“的黑板,然后在這塊黑板上繪制復雜的圖形,等圖形全部繪制完畢的時候,再一次性的把內存中繪制好的圖形“拷貝”到另一塊黑板(屏幕)上。采取這種方法可以提高繪圖速度,極大的改善繪圖效果。下面是原理圖:

  要知道什么是垂直同步,必須要先明白顯示器的工作原理。顯示器上的所有圖像都是一線一線的掃描上去的,無論是隔行掃描還是逐行掃描,顯示器,都有2種同步參數——水平同步和垂直同步。水平同步信號決定了CRT畫出一條橫越屏幕線的時間,垂直同步信號決定了CRT從屏幕頂部畫到底部,再返回原始位置的時間,而恰恰是垂直同步代表着CRT顯示器的刷新率水平!

  關閉垂直同步:我們平時運行操作系統一般屏幕刷新率一般都是在85Hz上下,此時顯卡就會每按照85Hz的頻率時間來發送一個垂直同步信號,信號和信號的時間間隔是85的分辨率所寫一屏圖像時間。

  打開垂直同步:在游戲中,或許強勁的顯卡迅速的繪制完一屏的圖像,但是沒有垂直同步信號的到達,顯卡無法繪制下一屏,只有等85單位的信號到達,才可以繪制。這樣fps自然要受到操作系統刷新率運行值的制約。也就是說,當然打開后如果你的游戲畫面FPS數能達到或超過你顯示器的刷新率,這時你的游戲畫面FPS數被限制為你顯示器的刷新率。如果達不到會出現不同程度的跳幀現象,FPS與刷新率差距越大跳幀越嚴重。一般對於高性能的顯卡建議打卡,游戲畫面會更好!打開后能防止游戲畫面高速移動時畫面撕裂現象,比如實況足球等。

  關閉垂直同步,那么游戲中作完一屏畫面,顯卡和顯示器無需等待垂直同步信號,就可以開始下一屏圖像的繪制,自然可以完全發揮顯卡的實力。

  但是,不要忘記,正是因為垂直同步的存在,才能使得游戲進程和顯示器刷新率同步,使得畫面平滑,使得畫面穩定。取消了垂直同步信號,固然可以換來更快的速度,但是在圖像的連續性上,性能勢必打折扣。這也是關閉垂直同步后發現畫面不連續的理論原因!

圖像格式

  比較流行的兩個圖形格式是JPEG和 PNG。JPEG是有損壓縮格式,PNG是無損壓縮格式,因此PNG格式可以百分百重現原始的圖像。有損壓縮格式通常占用少的磁盤空間。我們采用何總壓縮格式取決於我們的磁盤空間。和音頻類似,當我們加載到內存中時,我們需要完全地解壓一個圖像。因此,即使你的壓縮圖像在磁盤上只有20K,在RAM中你依然需要width×height ×color depth的存儲空間。

圖像疊加

  假定有一個我們可以渲染的幀緩沖區(framebuffer),同時有幾個加載到RAM中的圖片,我們笑需要把RAM中的圖片逐次放入到幀緩沖區,比如一個背景圖片和一個前景圖片如圖所示:

  這個過程就叫做圖像的合成和疊加,我們需要把不同的圖片合成一個最終顯示的圖片。繪制圖片的此項很重要,因為上面的圖片總會覆蓋下面的圖片。

  上面圖像合成出現了問題:第二張圖片的白色背景覆蓋了第一張背景圖片。我們怎樣把第二張圖的白色背景消去呢?這就需要alpha混合(alpha blending)。alpha混合是一種把源點的顏色值和目標點的顏色值按照一定的算法進行運算,得到一種透明的效果。

  下面是最終合成圖像的RGB值,公式如下

  red = src.red * src.alpha + dst.red * (1 – src.alpha)
  blue = src.green * src.alpha + dst.green * (1 – src.alpha)
  green = src.blue * src.alpha + dst.blue * (1 – src.alpha)  

  src和dst分別是我們需要混合的源圖像和目標圖像(源圖像相當於人物,目標圖像相當於背景)。下面是一個例子。

  src = (1, 0.5, 0.5), src.alpha = 0.5, dst = (0, 1, 0)
  red = 1 * 0.5 + 0 * (1 – 0.5) = 0.5
  blue = 0.5 * 0.5 + 1 * (1 – 0.5) = 0.75
  red = 0.5 * 0.5 + 0 * (1 – 0.5) = 0.25

  效果如下圖所示

  上述公式用了兩次乘法,乘法消耗的時間多,為了提高運算速度,可以進行優化。如

    red = (src.red- dst.red) * src.alpha + dst.red

  Alpha是一個浮點數,我們可以轉換成整數運算,因為一種顏色最多占8Bit,所以Alpha值最多是256,於是我們把Alpha的值乘以256,然后運算的時候再除以256,就得到下面的公式:

    red = (src.red- dst.red) * src.alpha /256+ dst.red

  這里,Alpha是一個0到256的數值。

  具體到這個例子,我們只需要把源文件的白色像素的alpha值設為0即可。最終效果如下圖:

 

圖像模塊的接口代碼

  通過以上介紹,我們可以開始設計我們的圖像模塊的接口。問下需要實現如下功能:

  • 從磁盤加載圖片到內存中,為以后繪制到屏幕做准備。
  • 用特定顏色清除framebuffer
  • 用指定顏色在framebuffer指定位置繪制像素。
  • 在framebuffer上繪制線條和矩形。
  • 繪制上面內存中的圖片到framebuffer,能夠整個繪制和部分繪制,alpha混合繪制。
  • 得到framebuffer的長寬。

  這里用兩個接口來實現:Graphics和 Pixmap,下面是Graphics接口:

package com.badlogic.androidgames.framework;

public interface Graphics {

  public static enum PixmapFormat {

    ARGB8888, ARGB4444, RGB565

  }

  public Pixmap newPixmap(String fileName, PixmapFormat format);

  public void clear(int color);

  public void drawPixel(int x, int y, int color);

  public void drawLine(int x, int y, int x2, int y2, int color);

  public void drawRect(int x, int y, int width, int height, int color);

  public void drawPixmap(Pixmap pixmap, int x, int y, int srcX, int srcY, int srcWidth, int srcHeight);

  public void drawPixmap(Pixmap pixmap, int x, int y);

  public int getWidth();

  public int getHeight();

}

  枚舉 PixmapFormat保存了該游戲支持的像素的顏色值(包括透明度)。比如ARGB8888,A表示透明度,R表示紅色,G表示綠色,B表示藍色,他們非別用8位來表示,就是各有256種狀態。            接下來看下接口的方法:

  • Graphics.newPixmap()方法加載指定格式的圖片。
  • Graphics.clear()方法用特定顏色清除framebuffer。
  • Graphics.drawPixel()方法在framebuffer中指定位置繪制給定顏色的像素。
  • Graphics.drawLine()和 Graphics.drawRect()方法在framebuffer繪制線條和矩形。
  • Graphics.drawPixmap()方法繪制圖像的到framebuffer。(x, y) 坐標指定了framebuffer繪制的起始位置,參數 srcX和srcY指定了圖片被繪制部分的起始位置。srcWidth和srcHeight制定了繪制的寬度和高度。
  • Graphics.getWidth()和Graphics.getHeight()方法返回framebuffer的寬度和高度。
 
  接下來我們看一下Pixmap接口:
//Pixmap接口

package com.badlogic.androidgames.framework;

import com.badlogic.androidgames.framework.Graphics.PixmapFormat;

public interface Pixmap {

  public int getWidth();

  public int getHeight();

  public PixmapFormat getFormat();

  public void dispose();

}
  • Pixmap.getWidth()和Pixmap.getHeight()方法返回圖像的寬度和高度。
  • Pixmap.getFormat()返回圖片的格式。
  • Pixmap.dispose()方法。Pixmap實例使用內存資源和其他潛在的系統資源,如果我們不在需要它,我們需要回收資源,這也該方法的作用。

PS: 歡迎關注公眾號"Devin說",會不定期更新Java相關技術知識。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM