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; } } } }}
最大最小法 仅适用于曲面函式可以用显函式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; } } } }}