在飞行模拟中或在一些需要实时产生地形的游戏中,用了Voxel的技术。Voxel这个词从Pixel(像素)中来,即“volumetric pixel”。如果Pixel代表了二维世界中的一各矩形,那voxel就是三维世界的一个立方块。如图一: (if i have time i'll make better pictures)

Voxel的data就是由x(宽度),z(深度,从屏幕往里)加上高度y组成的。
在生成地形时,光有这些粒粒还不行,要看上去象地形,还得Render它们。可用不同的颜色表示地形的高低和远近。有几种方法可以render它们,光线跟踪(ray tracing)就是其中一种。光线跟踪的基本方法是从视点投射一束光线(ray casting),直到这束光线被障碍物阻挡为止。然后向另一个方向再投射一束光线,依此类推。最后,光线中止的点的集合就构成了地形。在飞行模拟中多用这个方法,在如Quake/Doom类的游戏中也用了光线跟踪。
如图二,

把三维的ray casting拆成二维,就有了图三Y-Z,和图四X-Z:
 
我们用到的数学只是三角比例关系。(图五)

这个算法可以这样来表示: p=当前点 while (不是屏幕上的最后一点) {
从视点投射光线去p点。 穿过p点后,光线继续。 if ( 光线碰到障碍物) { 以障碍物的位置的不同,根据高低远近给于p点以不同的颜色。} p指向下一个屏幕像素。
}
飞行模拟中的地形模拟就是这么回事,简单不简单?(说“简单”)。如果你的回答是“复杂”,这里有一个对简单地图Render的 实例。
现实和理论总有不同,其结果能差好多好多倍,比如在理论上我是个好人,而在实际上,我是个....hum....很好很好的人。当光线跟踪被军方使用时,他们用了几台超级电脑系统。才能使画面保持实时的效果。在我们PC based的游戏系统上,要能维持20fps以上的连续画面,在换帧之间完成所有地形的光线跟踪,是很困难的。因而,在PC game里,我们有更好的光线跟踪近似算法,实际的效果比标准的光线跟踪算法快约一千倍。
标准的光线跟踪方法慢的原因是因为对屏幕上每个像素点都要投射一次光线,并做相应的运算。在典型的640X480的分辨率中共有307200个像素,需要投射307200条光线并做计算。这些计算花去了太多的时间。但如果我们仅仅投射少量的光线。比如说640条,其余的让它自动生成,就会快得多。
简化的方法是,先投射一条光线去屏幕最下面的像素。等光线碰到地形障碍物后,不是再从视点投射另一条,而是让光线自己顺着地形的起伏爬。所以对每一列屏幕像素,只需投射一条光线。在640x480的例子里,我们只要投射640条光线。(图六)

那么怎样才能让光线爬行并返回我们要的数值呢?这里有一些数学的技巧。经过这些数学的运算,可以得出这样的一个结论:(图七)
最后,我们有了一个优化的算法: for (像素列从0到639) { //由于在SIN,COS的运算会耗去50-80个CPU时钟数,所以事先 //算好每一个角度的正,余弦值,放在COS_LOOK,SIN_LOOK表中。 //用时差表就快多了。 dx=COS_LOOK[当前角度]; dy=SIN_LOOK[当前角度]; dz=斜度*(试点夹角-屏幕高度); 画点; 当前角度递减--; }
如果你是个“拿来主义者”,或对算法本身不感兴趣,下面的子程式可以整个抄去你的主程式。 这里是一个完整的Render地形的子程式。 这个子程式的原作者为David Lindauer,后经过许多人许多次修改,最后定型。
void Render_Terrain(int vp_x, int vp_y, int vp_z, int vp_ang_x, int vp_ang_y, int vp_ang_z, UCHAR *dest_buffer) { // this function renders the terrain
//at the given position and orientation
int xr,yr, curr_column, curr_step, //current step ray is at raycast_ang, //current angle of ray being cast dx,dy,dz, //general deltas for ray to move from //pt to pt curr_voxel_scale, //current scaling factor to draw each voxel line column_height, //height of the column intersected and being rendered curr_row, //number of rows processed in current column x_ray,y_ray,z_ray, // the position of the tip of the ray map_addr; //temp var used to hold the addr of data bytes
CHAR color, //color of pixel being rendered ? dest_column_ptr; //address screen pixel being rendered //convert needed vars to fixed point vp_x=(vp_x<<FIXP_SHIFT);
vp_y=(vp_y<<FIXP_SHIFT);
vp_z=(vp_z<<FIXP_SHIFT); //push down destination buffer to bottom of screen
dest_buffer += (SCREEN_WIDTH * (SCREEN_HEIGHT-1)); //compute starting angle
raycast_ang=vp_ang_y+ANGLE_30; //cast a ray for each column of the screen
for (curr_column=0; curr_column < SCREEN_WIDTH-1;curr_column++)
{
//seed starting point for cast
x_ray=vp_x;
y_ray=vp_y;
z_ray=vp_z; //compute deltas to project ray at, note the spherical cancelation factor
dx=COS_LOOK(raycast_ang)<<1;
dy=SIN_LOOK(raycast_ang)<<1; //dz is a bit complex, remember dz is the slope of the ray we are casting,
//therefore, we need to take into consideration the
//down angle, or x axis angle, the more we are looking down the
//larger the initial dz must be
dz=dslope*(vp_ang_x-SCREEN_HEIGHT); // reset current voxel scale
curr_voxel_scale=0; //reset row
curr_row=0; //get starting address of bottom of current video column
dest_column_ptr=dest_buffer; //enter into casting loop
for(curr_step=0;curr_step<MAX_STEPS;curr_step++)
{
//compute pixel in height map to process
//note that the ray is converted back to an int
//and it is clipped to stay positive and in range
xr=(x_ray>>FIXP_SHIFT);
yr=(y_ray >> FIXP_SHIFT);
xr=(xr&(HFIELD_WIDTH-1));
yr=(yr&(HFIELD_HEIGHT-1));
map_addr=(xr+(yr<<HFIELD_BIT_SHIFT)); //get current height in height map, note the conversion to
//fixed point and the added multiplication factor used to
//scale the mountains
column_height=(heigth_map_ptr[map_addr]<<(FIXP_SHIFT+TERRAIN_SCALE_X2)); //test if column height is greater than current voxel height
//for current step from initial projection point
if(column_height>z_ray)
{
//get the color for the voxel
color=color_map_ptr[map_addr]; //draw vertical column voxel
while(1)
{
//draw a pixel
*dest_column_ptr=color; //now we need to push the ray upward on z axis
//so increment the slope
dz+=dslope; //now translate the current z position of the ray by
//the current voxel scale per unit
z_ray+=curr_voxel_scale; //move up one video line
dest_column_ptr-=SCREEN_HEIGHT)
if(++curr_row>=SCREEN_HEIGHT)
{
//force exit of outer stepping loop
//cheezy, but better than GOTO!
curr_step=MAX_STEPS;
break;
}//endif //test if we can break out of the loop
if(z_ray>column_height)break;
}//end while }//endif //update the position of the ray
x_ray+=dx;
y_ray+=dy;
z_ray+=dz; //update the current voxel scale, remember each step out
//means the scale increases by the delta scale
curr_voxel_scale+=dslope; }//end for curr_step //advance video pointer ot bottom of next column dest_buffer++; //advance to next angle
raycast_ang--; }//end for curr_col }//end render_terrain
|