最大最小法 僅適用於曲面函式可以用顯函式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π
      
      y = a * sinθ
z = (1+a*cosθ)*cosφ
0 < a < 1, 0 <= θ <= 2π, 0 <= φ <= 2π
- Demo.java
package cc.openhome;
import java.awt.Color;
import java.awt.Graphics;
import javax.swing.JApplet;
import static java.lang.Math.*;
public class Demo extends JApplet {
    private int orgX;
    private int orgY;
    private double[] zbuf;
    public void init() {
        super.init();
        setBackground(Color.black);
        setSize(640, 480);
        orgX = getWidth() / 2;
        orgY = getHeight() / 2;
        zbuf = new double[getWidth() * getHeight()];
    }
    public void paint(Graphics g) {
        g.setColor(Color.yellow);
        // 從斜角繪製
        // 繞 x 軸轉 30 度
        double sinRotateX = sin(toRadians(30));
        double cosRotateX = cos(toRadians(30));
        double A = 0.3;
        double K = 200.0;
        for (int i = 0; i < zbuf.length; i++) {
            zbuf[i] = -10000.0;
        }
        // 由於是單色,調整一下 j 與 i 可以看的明顯一些
        for (double j = 0; j < 360; j += 0.2) {
            for (double i = 0; i < 360; i += 0.1) {
                double sinRadI = sin(toRadians(i));
                double cosRadI = cos(toRadians(i));
                double sinRadJ = sin(toRadians(j));
                double cosRadJ = cos(toRadians(j));
                double x = (1 + A * cosRadI) * sinRadJ;
                double y = A * sinRadI;
                double z = (1 + A * cosRadI) * cosRadJ;
                // 立體旋轉,從斜角繪製,調整繪圖中心至視窗中心
                double pointX = orgX + K * x;
                double pointY = orgY - K * (y * cosRotateX - z * sinRotateX);
                // Z buffer處理
                int index = (int) (pointX + pointY * getWidth());
                
                if (z > zbuf[index]) {
                    int px = (int) pointX;
                    int py = (int) pointY;
                    g.drawLine(px, py, px, py);
                    zbuf[index] = z;
                }
            }
        }
    }
}以下是使用HTML5 Canvas的方式(如果瀏覽器支援HTML5 Canvas,例如最新版的Firexfox、Chrome、IE9等,可以直接將下面的內容存為HTML或按下檔名連結,直接載入瀏覽器執行觀看結果:
<!DOCTYPE html>
<html>
    <head>
        <meta content="text/html; charset=Big5" http-equiv="content-type">
        <script type="text/javascript">
            window.onload = function() {
                function toRadians(angle) {
                    return angle * Math.PI / 180;
                }
                var sin = Math.sin;
                var cos = Math.cos;
                var sqrt = Math.sqrt;
                                
                var canvas1 = document.getElementById('canvas1');
                var context = canvas1.getContext('2d');
                
                var orgX = canvas1.width / 2;
                var orgY = canvas1.height / 2;
                
                var zbuf = new Array(canvas1.width * canvas1.height);
                                
                var sinRotateX = sin(toRadians(30));
                var cosRotateX = cos(toRadians(30));
                var A = 0.3;
                var K = 200.0;
                for(var i = 0; i < zbuf.length; i++) {
                    zbuf[i] = -10000.0;
                }
                context.beginPath();
                var j = 0;
                // 這個繪製很耗時間
                // 在瀏覽器上會因Busy loop造成停止回應
                // 因此使用setTimeout來作一毫秒的暫停
                // 讓瀏覽器不至收不到程式回應而要求停止
                setTimeout(function() {
                    if(j < 360) {
                        for(var i = 0; i < 360; i += 0.1) {
                            var sinRadI = sin(toRadians(i));
                            var cosRadI = cos(toRadians(i));
                            var sinRadJ = sin(toRadians(j));
                            var cosRadJ = cos(toRadians(j));
                            var x = (1 + A * cosRadI) * sinRadJ;
                            var y = A * sinRadI;
                            var z = (1 + A * cosRadI) * cosRadJ;
                            // 立體旋轉,從斜角繪製,調整繪圖中心至視窗中心
                            var pointX = orgX + K * x;
                            var pointY = orgY - K * 
                                  (y * cosRotateX - z * sinRotateX);
                            // Z buffer處理
                            var index = parseInt(
                                  pointX + pointY * canvas1.width);
                
                            if(z > zbuf[index]) {
                                var px = parseInt(pointX);
                                var py = parseInt(pointY);
                                context.moveTo(pointX, pointY);
                                context.lineTo(pointX + 1, pointY + 1);
                                zbuf[index] = z;
                            }
                        }
                        j += 0.2;
                        setTimeout(arguments.callee, 1);
                    }
                    else {
                        context.stroke();
                        context.closePath();
                    }
                }, 1);
            };
        </script>
    </head>
    <body>       
        <canvas id="canvas1" width="640" height="480"></canvas>
    </body>
</html>
在Firefox上的結果如下:


