From Gossip@caterpillar

Computer Graphics: Z Buffer法

 

 


最大最小法 僅適用於曲面函式可以用顯函式y = f(x, z)來表示時,如果曲面函式非顯函式形式,則無法使用最大最小法來處理深度問題。

Z Buffer有些類似畫家演算法,都是以近景遮蓋遠景的方法來處理深度問題,所不同的是Z Buffer使用的是裁剪(culling)的方法,並以像素為處理的對象,Z Buffer將繪圖畫布內所有的座標當作一個深度緩衝區陣列zbuf[]的索引,每一個zbuf[]的元素記錄一個像素繪製時的Z深度資訊,可以使用它來 處理隱函式的圖形繪製。

假設畫布大小為600X400(X, Y),則zbuf[]的大小必須設定為600*400=240000(X*Y),一開始時所有zbuf[]元素的值設定為一個極小值,也就是所有的像素都 表示空間中一個極深的位置,開始繪製之後,必須在zbuf[]中記錄每一個像素的z值。

zbuf[]是個一維陣列,所以我們必須計算座標的索引值,如果以列為主的話,則(x, y)對應的zbuf[]元素值為:zbuf[x + y * 畫布高度];當然您也可以使用二維陣列zbuf[][]來直接對應。

如果後來要繪製的點之z值大於zbuf[]中記錄的值,表示此點在之前所繪點的前面,於是繪製此點來覆蓋之前所繪的點,並更新zbuf[] 中的z值為目前點的z值;如果後來要繪製的點之z值小於zbuf[]中記錄的值,表示此點在之前所繪點的後面,於是不用繪製此點,當然也不用更新zbuf []中的z值。

Z Buffer的深度處理方式無論從哪一個點開始繪製,都不會影響處理的結果;Z buffer的缺點就是使用大量的記憶體作為緩衝區,而由於它是以像素為處理的單位,所以需耗用相當大量的運算資源。

下面這個程式並不是一個很好的示範,因為我們並不是每個像素都考慮到,但可以讓您瞭解Z Buffer的演算法,如果要考慮所有的像素,這個程式要畫的好,最好加上陰影的效果,繪製的圖形之參數式如下,其中 a 表示圓的粗細:
x = (1+a*cosθ) * sinφ
y = a * sinθ
z = (1+a*cosθ)*cosφ
0 < a < 1, 0 <= θ <= 2π, 0 <= φ <= 2π


  • ZBufferMethod.java
package onlyfun.caterpillar;

import java.awt.Color;
import java.awt.Graphics;
import javax.swing.JApplet;

public class ZBufferMethodDemo extends JApplet {
private int orgX;
private int orgY;
private double[] zbuf;

public void init() {
super.init();
setBackground(Color.black);
orgX = (int)getSize().width /2;
orgY = (int) (getSize().height / 2);
zbuf =
new double[getSize().width * getSize().height];
}

public void paint(Graphics g) {
g.setColor(Color.yellow);

// 從斜角繪製
// 繞 x 軸轉 30 度
double angleX = Math.toRadians(30);

double a = 0.3;
double k = 200.0;

for(int l = 0; l < zbuf.length; l++)
zbuf[l] = -10000.0;

// 由於是單色,調整一下 j 與 i 可以看的明顯一些
for(double j = 0; j < 360; j+=0.2) {
for(double i = 0; i < 360; i+=0.1) {
double x = (1 + a*Math.cos(Math.toRadians(i))) *
Math.sin(Math.toRadians(j));
double y = a * Math.sin(Math.toRadians(i));
double z = (1 + a*Math.cos(Math.toRadians(i))) *
Math.cos(Math.toRadians(j));

// 立體旋轉,從斜角繪製,調整繪圖中心至視窗中心
double pointX = orgX + k * x;
double pointY = orgY - k *
(y*Math.cos(angleX) - z*Math.sin(angleX));

// Z buffer處理
int index = (int) (pointX +
pointY * getSize().width);
if(z > zbuf[index]) {
g.drawLine((int)pointX, (int)pointY,
(int)pointX, (int)pointY);
zbuf[index] = z;
}
}
}
}
}