From Gossip@caterpillar

Computer Graphics: 雙緩衝區繪圖

 

 


如果我們要繪製的圖形很多時,在動畫製作時,如果直接畫在前景上,可能會發生圖形一個一個顯現出來的畫面,讓整個畫面不完整呈現。

這時候可使用雙緩衝區(double-buffer)的方法,要準備兩個繪圖物件,其中一個是前景區,也就是您所看到的畫面,另一個繪圖物件您看不到,在 畫面準備好之前,所有的圖形先繪製在您看不到的畫面,當所有的圖形都繪製完畢,再將所有的圖形繪製到前景,此時前景會整個被後繪圖區所覆蓋,完成一次畫面 的播放,下圖為簡單的示意圖:


實作雙緩衝區的方法依所使用的API而有所不同,在Java中可以用Image物件來完成,而在使用DirectDraw時,可以使用 Surface來建立,下面這個程式是使用Java來製作,這個程式是至今所有文件內容驗收,包括了頂點配置、索引陣列、動畫、雙緩衝區等等,您可以先觀 看結果:一個繞Y軸旋轉的空心立方塊

  • DoubleBuffer.java
package onlyfun.caterpillar.graphics.animation;

import javax.swing.JApplet;
import java.awt.*;

class Point3D {
public double x, y, z;

public Point3D() {
x = y = z = 0;
}

public Point3D(double x, double y, double z) {
this.x = x;
this.y = y;
this.z = z;
}
}

// 立方體類別
class Cubic {
public int NFc; // 平面數量
public int NVt; // 頂點的數量
public int Nvt; // 1個平面的頂點數
public Point3D[] Vt; // 頂點的座標
public int[][] Ord; // 頂點索引

public Cubic(int r) {
NFc = 6;
NVt = 8;
Nvt = 4;
Vt = new Point3D[NVt];
Ord = new int[NFc][Nvt];
double sq3 = Math.sqrt(3.0);
Vt[0] = new Point3D(r/sq3,r/sq3,r/sq3);
Vt[1] = new Point3D(r/sq3,r/sq3,-r/sq3);
Vt[2] = new Point3D(-r/sq3,r/sq3,-r/sq3);
Vt[3] = new Point3D(-r/sq3,r/sq3,r/sq3);

for(int i = 0; i < NVt/2; i++)
Vt[NVt/2+i] = new Point3D(
-Vt[i].x, -Vt[i].y, -Vt[i].z);

int[][] ord={{0,1,2,3},{0,3,5,6},{0,6,7,1},
{4,7,6,5},{4,2,1,7},{4,5,3,2}};

for(int i = 0; i < NFc; i++)
for(int j = 0; j < Nvt; j++)
Ord[i][j]=ord[i][j];
}
}

public class DoubleBuffer extends JApplet implements Runnable {
private Image OffScreen;
private Graphics drawOffScreen;
private int orgX, orgY;
private Cubic cubic;

public void init() {
setBackground(Color.black);

orgX = (int)(getSize().width / 2);
orgY = (int)(getSize().height / 2);

OffScreen = createImage(getSize().width,
getSize().height);
drawOffScreen = OffScreen.getGraphics();

cubic = new Cubic(100);
}

public void start() {
(new Thread(this)).start();
}

public void run() {
int[] px = new int[4],
py = new int[4];
Point3D[] tp = new Point3D[4];

drawOffScreen.setColor(Color.yellow);

// 旋轉以斜角繪製圖形
double sinAx = Math.sin(Math.toRadians(30));
double cosAx = Math.cos(Math.toRadians(30));

double[] cosAys = new double[360];
double[] sinAys = new double[360];

for(int i = 0; i < 360; i++) {
cosAys[i] = Math.cos(Math.toRadians(i));
sinAys[i] = Math.sin(Math.toRadians(i));
}

int angle = 330;

double sinAy = sinAys[angle];
double cosAy = cosAys[angle];

// 動畫迴圈
while(true) {
drawOffScreen.clearRect(0, 0,
getSize().width, getSize().height);
for(int i = 0; i < cubic.NFc; i++) {
for(int j = 0; j < cubic.Nvt; j++) {
// 利用索引array取出正確的頂點
tp[j] = cubic.Vt[cubic.Ord[i][j]];

// 旋轉以斜角繪製圖形
px[j] = (int) (tp[j].x*cosAy
+ tp[j].z*sinAy);
py[j] = (int) (tp[j].y*cosAx
- (-tp[j].x*sinAy
+ tp[j].z*cosAy) * sinAx);
px[j] = px[j] + orgX;
py[j] = -py[j] + orgY;
}

// 在緩衝區上縮圖
drawOffScreen.drawPolyline(px, py, 4);
}

// 暫停 20 毫秒
try {
Thread.sleep(20);
}
catch(InterruptedException e) {
}

repaint(); // 重繪畫面
// 繞 y 軸轉動
angle = (angle+1) % 360;
sinAy = sinAys[angle];
cosAy = cosAys[angle];
}
}

// 重新定義update()
public void update(Graphics g) {
paint(g);
}

public void paint(Graphics g) {
// 將緩衝區畫面繪到前景
g.drawImage(OffScreen, 0, 0, this);
}
}