探索体素图形:从基础到优化
在计算机图形的世界里,体素图形是一种相对新颖且充满潜力的技术。它为我们带来了全新的视角,能够创建出独特而逼真的场景。本文将深入探讨体素图形的相关知识,包括体素数据库的生成、射线投射技术、高度映射方法,以及如何对体素引擎进行优化等内容。
1. 体素图形基础
体素图形本质上是一种用于生成山脉、海底等地形数据的技术。“体素”即“体积像素”,体素图形就是渲染具有体积的物体,并将其分解为小立方体或体积元素的科学。对于游戏开发者来说,我们希望利用这种技术,基于某种数据集以简单的方式生成 3D 地形。
2. 生成体素数据库
体素数据包含高度数据和颜色数据。获取体素数据主要有两种基本来源:扫描数据和算法生成数据。
-
使用扫描地形地图
:我们的体素引擎将使用 2D 位图作为体素数据。每个像素的颜色不仅代表该点体素数据的颜色,还代表从 0 到 255 的高度。不过,使用 2D 图像作为颜色和高度信息时,需要注意调色板的选择,以确保颜色和所需高度之间存在一一对应关系。
-
分形和随机地形地图
:除了扫描图像,还可以通过各种算法来参数化生成数据。例如,使用分形算法(如等离子分形)或随机数填充 2D 地形数组。但使用随机数时,需要确保随机值平滑变化,否则渲染出的数据会看起来像噪声或“草”。
3. 射线投射
在计算机图形中,有两种主要方法用于生成场景:合成法和基于光线和能量物理交互的方法(如射线追踪和辐射度)。射线投射是射线追踪的简化版本,它从视点向外投射射线,检测射线与场景中物体的交点。这种技术在一些游戏(如《德军总部 3D》)中被用于生成逼真的显示效果。
射线投射的工作流程如下:
1. 从视点向外投射射线。
2. 检测射线与场景中物体的交点。
3. 根据交点信息生成 3D 显示。
4. 高度映射
高度映射是一种通过从视点投射射线,检测射线与地面或地形的交点,从而生成 3D 显示的技术。具体步骤如下:
1. 从视点投射一系列射线,沿单个方向扫描。
2. 旋转一定角度后再次扫描,直到覆盖整个视野。
3. 根据扫描结果绘制适当的显示。
为了实现高度映射,我们需要执行两个嵌套的 for 循环:
for (column=0; column<320; column++)
for (row=150; row<200; row++)
{
// 1. 从视点投射射线穿过当前列和行,并检测其与地形的交点
// 2. 记录相交体素的高度和颜色
// 3. 绘制体素条带
} // end for row
5. 射线旋转与畸变修正
在生成扫描射线时,会遇到一些问题,例如玩家视点的位置和角度。为了解决这些问题,我们可以使用查找表。通过将一个圆细分为 1920 个小弧段,并创建 SIN 和 COSINE 查找表,我们可以快速生成不同方向的射线。
然而,射线扫描的圆形路径会导致渲染后的视图出现球形畸变。为了消除这种畸变,我们需要将每条射线的长度乘以相对于中心角度的反余弦(正割)值。
6. 查找体素高度
对于视频显示的每一列,需要投射一系列射线穿过每一行,并找到它们与地形的交点。我们可以使用相似三角形的原理来计算射线与地形的交点。具体公式如下:
ray_length = distance * height / delta
delta = height - (SCREEN_HEIGHT - row)
7. 条带缩放
在绘制体素条带时,我们可以根据体素与玩家视点的距离对条带进行缩放。物体的缩放比例与它到视平面的距离成反比,即:
scale = k/object(z)
其中,k 是缩放因子,object(z) 表示物体到视平面的 Z 距离。
8. 实现简单体素显示演示
我们可以编写一个简单的体素显示演示程序,使用 320x200 的 PCX 文件作为高度和颜色数据。程序的主要函数是
Draw_Terrain()
,它负责将当前视图渲染到双缓冲区中。
以下是
VOXEL.C
程序的主要代码:
// VOXEL.C - Single texture based, ray casted voxel engine
// I N C L U D E S ///////////////////////////////////////////////////////////
#include <io.h>
#include <conio.h>
#include <stdio.h>
#include <stdlib.h>
#include <dos.h>
#include <bios.h>
#include <fcntl.h>
#include <memory.h>
#include <malloc.h>
#include <math.h>
#include <string.h>
#include "black3.h"
#include "black4.h"
#include "black5.h"
// D E F I N E S /////////////////////////////////////////////////////////////
#define WORLD_X_SIZE 320 // width of universe
#define WORLD_Y_SIZE 200 // height of universe
// constants used to represent angles for the ray casting a 60 degree field of view
#define ANGLE_0 0
#define ANGLE_1 5
#define ANGLE_2 10
#define ANGLE_4 20
#define ANGLE_5 25
#define ANGLE_6 30
#define ANGLE_15 80
#define ANGLE_30 160
#define ANGLE_45 240
#define ANGLE_60 320
#define ANGLE_90 480
#define ANGLE_135 720
#define ANGLE_180 960
#define ANGLE_225 1200
#define ANGLE_270 1440
#define ANGLE_315 1680
#define ANGLE_360 1920
// there are 1920 degrees in our circle or 360/1920 is the conversion factor
// from real degrees to our degrees
#define ANGULAR_INCREMENT ((float)0.1875)
// conversion constants from radians to degrees and viceversa
#define DEG_TO_RAD ((float)3.1415926/(float)180)
#define RAD_TO_DEG ((float)180/(float)3.1415926)
// G L O B A L S ////////////////////////////////////////////////////////////
pcx_picture image_pcx; // general pcx image
int play_x=1000, // the current world x of player
play_y=1000, // the current world y of player
play_z=150,
play_ang=ANGLE_90, // the current viewing angle of player
play_dist = 70,
mountain_scale=10; // scaling factor for mountains
float play_dir_x, // the direction the player is pointing in
play_dir_y,
play_dir_z,
cos_look[ANGLE_360], // cosine look up table
sin_look[ANGLE_360], // sin look up table
sphere_cancel[ANGLE_60]; // cancels fish eye distortion
// F U N C T I O N S ////////////////////////////////////////////////////////
void Line_VDB(int y1,int y2,int x,int color)
{
// draw a vertical line, note that a memset function can no longer be
// used since the pixel addresses are no longer contiguous in memory
// note that the end points of the line must be on the screen
unsigned char far *start_offset; // starting memory offset of line
int index, // loop index
temp; // used for temporary storage during swap
// make sure y2 > y1
if (y1>y2)
{
temp = y1;
y1 = y2;
y2 = temp;
} // end swap
// compute starting position
start_offset = double_buffer + ((y1<<8) + (y1<<6)) + x;
for (index=0; index<=y2-y1; index++)
{
// set the pixel
*start_offset = (unsigned char)color;
// move downward to next line
start_offset+=320;
} // end for index
} // end Line_VDB
//////////////////////////////////////////////////////////////////////////////
int Initialize(char *filename)
{
// this function builds all the look up tables for the terrain generator and
// loads in the terrain texture map
int ang; // looping variable
float rad_angle; // current angle in radians
// create sin and cos look up first
for (ang=0; ang<ANGLE_360; ang++)
{
// compute current angle in radians
rad_angle = (float)ang*ANGULAR_INCREMENT*DEG_TO_RAD;
// now compute the sin and cos
sin_look[ang] = sin(rad_angle);
cos_look[ang] = cos(rad_angle);
} // end for ang
// create inverse cosine viewing distortion filter
for (ang=0; ang<ANGLE_30; ang++)
{
// compute current angle in radians
rad_angle = (float)ang*ANGULAR_INCREMENT*DEG_TO_RAD;
// now compute the sin and cos
sphere_cancel[ang+ANGLE_30] = 1/cos(rad_angle);
sphere_cancel[ANGLE_30-ang] = 1/cos(rad_angle);
} // end for ang
// initialize the pcx structure
PCX_Init((pcx_picture_ptr)&image_pcx);
// load in the textures
return(PCX_Load(filename, (pcx_picture_ptr)&image_pcx,1));
} // end Initialize
/////////////////////////////////////////////////////////////////////////////
void Draw_Terrain(int play_x,
int play_y,
int play_z,
int play_ang,
int play_dist)
{
// this function draws the entire terrain based on the location and orientation
// of the player's viewpoint
int curr_ang, // current angle being processed
xr,yr, // location of ray in world coords
x_fine,y_fine, // the texture coordinates the ray hit
pixel_color, // the color of textel
ray, // looping variable
row, // the current video row being processed
row_inv, // the inverted row to make upward positive
scale, // the scale of the current strip
top, // top of strip
bottom; // bottom of strip
float ray_length; // the length of the ray after distortion compensation
// start the current angle off -30 degrees to the left of the player's
// current viewing direction
curr_ang = play_ang - ANGLE_30;
// test for underflow
if (curr_ang < 0)
curr_ang+=ANGLE_360;
// cast a series of rays for every column of the screen
for (ray=1; ray<320; ray++)
{
// for each column compute the pixels that should be displayed
// for each screen pixel, process from top to bottom
for (row = 100; row<150; row++)
{
// compute length of ray
row_inv = 200-row;
// use the current height and distance to compute length of ray.
ray_length = sphere_cancel[ray] * ((float)(play_dist*play_z)/
(float)(play_z-row_inv));
// rotate ray into position of sample
xr = (int)((float)play_x + ray_length * cos_look[curr_ang]);
yr = (int)((float)play_y - ray_length * sin_look[curr_ang]);
// compute texture coords
x_fine = xr % WORLD_X_SIZE;
y_fine = yr % WORLD_Y_SIZE;
// using texture index locate texture pixel in textures
pixel_color = image_pcx.buffer[x_fine + (y_fine*320)];
// draw the strip
scale = (int)mountain_scale*pixel_color/(int)(ray_length+1);
top = 50+row-scale;
bottom = top + scale;
Line_VDB(top,bottom,ray,pixel_color);
// Write_Pixel_DB(ray,50+row,pixel_color);
} // end for row
// move to next angle
if (++curr_ang >= ANGLE_360)
curr_ang=ANGLE_0;
} // end for ray
} // end Draw_Terrain
// M A I N //////////////////////////////////////////////////////////////////
void main(int argc, char **argv)
{
char buffer[80];
int done=0; // exit flag
float speed=0; // speed of player
// check to see if command line parms are correct
if (argc<=2)
{
// not enough parms
printf("\nUsage: voxopt.exe filename.pcx height");
printf("\nExample: voxopt voxterr3.pcx 10\n");
// return to DOS
exit(1);
} // end if
// set the graphics mode to mode 13h
Set_Graphics_Mode(GRAPHICS_MODE13);
// create a double buffer
Create_Double_Buffer(200);
// create look up tables and load textures
if (!Initialize(argv[1]))
{
printf("\nError loading file %s",argv[1]);
exit(1);
} // end if
// install keyboard driver
Keyboard_Install_Driver();
// set scale of mountains
mountain_scale = atoi(argv[2]);
// draw the first frame
Draw_Terrain(play_x,
play_y,
play_z,
play_ang,
play_dist);
Display_Double_Buffer(double_buffer,0);
// main event loop
while(!done)
{
// reset velocity
speed = 0;
// test if user is hitting keyboard
if (keys_active)
{
// what is user trying to do
// change viewing distance
if (keyboard_state[MAKE_F])
play_dist+=10;
if (keyboard_state[MAKE_C])
play_dist-=10;
// change viewing height
if (keyboard_state[MAKE_U])
play_z+=10;
if (keyboard_state[MAKE_D])
play_z-=10;
// change viewing position
if (keyboard_state[MAKE_RIGHT])
if ((play_ang+=ANGLE_5) >= ANGLE_360)
play_ang-=ANGLE_360;
if (keyboard_state[MAKE_LEFT])
if ((play_ang-=ANGLE_5) < 0)
play_ang+=ANGLE_360;
// move forward
if (keyboard_state[MAKE_UP])
speed=20;
// move backward
if (keyboard_state[MAKE_DOWN])
speed=-20;
// exit demo
if (keyboard_state[MAKE_ESC])
done=1;
// compute trajectory vector for this view angle
play_dir_x = cos_look[play_ang];
play_dir_y = -sin_look[play_ang];
play_dir_z = 0;
// translate viewpoint
play_x+=speed*play_dir_x;
play_y+=speed*play_dir_y;
play_z+=speed*play_dir_z;
// draw the terrain
Fill_Double_Buffer(0);
Draw_Terrain(play_x,
play_y,
play_z,
play_ang,
play_dist);
// draw tactical
sprintf(buffer,"Height = %d Distance = %d ",play_z,play_dist);
Print_String_DB(0,0,10,buffer,0);
sprintf(buffer,"Pos: X=%d, Y=%d, Z=%d ",play_x,play_y,play_z);
Print_String_DB(0,10,10,buffer,0);
Display_Double_Buffer(double_buffer,0);
} // end if
} // end while
// reset back to text mode
Set_Graphics_Mode(TEXT_MODE);
// remove the keyboard handler
Keyboard_Remove_Driver();
} // end main
运行该程序时,需要提供两个参数:320x200x256 PCX 文件的文件名和渲染的缩放因子。例如:
VOXEL VOXTERR4.PCX 10
控制操作如下表所示:
| 按键 | 描述 |
| ---- | ---- |
| [UP{ARROW] | 右转 |
| [DOWN{{ARROW] | 左转 |
| [RIGHT{ARROW] | 向前移动 |
| [LEFT{ARROW] | 向后移动 |
| [U] | 增加高度 |
| [D] | 降低高度 |
| [F] | 远离视平面 |
| [C] | 靠近视平面 |
| [ESC] | 退出演示 |
9. 基于图块的体素引擎
为了创建更大的世界,我们可以使用图块系统。通过将世界划分为多个图块,并为每个图块分配一个纹理,我们可以在有限的内存中创建出更大的世界。
以下是实现基于图块的体素引擎的步骤:
1. 定义图块地图,使用一个简单的 2D 数组表示。
2. 加载图块的位图纹理。
3. 在
Draw_Terrain()
函数中添加额外的代码,以处理图块的查找和渲染。
以下是
VOXTILE.C
程序的主要代码:
// VOXTILE.C - CELL based, ray casted voxel engine
// I N C L U D E S ///////////////////////////////////////////////////////////
#include <io.h>
#include <conio.h>
#include <stdio.h>
#include <stdlib.h>
#include <dos.h>
#include <bios.h>
#include <fcntl.h>
#include <memory.h>
#include <malloc.h>
#include <math.h>
#include <string.h>
#include "black3.h"
#include "black4.h"
#include "black5.h"
// D E F I N E S /////////////////////////////////////////////////////////////
#define NUM_X_CELLS 32 // number of cells in x direction
#define NUM_Y_CELLS 32 // number of cells in y direction
#define CELL_WIDTH 64 // width of a cell in pixels
#define CELL_HEIGHT 64 // height of a cell in pixels
#define WORLD_X_SIZE (NUM_X_CELLS*CELL_WIDTH) // width of universe in pixels
#define WORLD_Y_SIZE (NUM_Y_CELLS*CELL_HEIGHT) // height of universe in pixels
// constants used to represent angles for the ray casting a 60 degree field of
// view
#define ANGLE_0 0
#define ANGLE_1 5
#define ANGLE_2 10
#define ANGLE_4 20
#define ANGLE_5 25
#define ANGLE_6 30
#define ANGLE_15 80
#define ANGLE_30 160
#define ANGLE_45 240
#define ANGLE_60 320
#define ANGLE_90 480
#define ANGLE_135 720
#define ANGLE_180 960
#define ANGLE_225 1200
#define ANGLE_270 1440
#define ANGLE_315 1680
#define ANGLE_360 1920
// there are 1920 degrees in our circle or 360/1920 is the conversion factor
// from real degrees to our degrees
#define ANGULAR_INCREMENT ((float)0.1875)
// conversion constants from radians to degrees and viceversa
#define DEG_TO_RAD ((float)3.1415926/(float)180)
#define RAD_TO_DEG ((float)180/(float)3.1415926)
#define NUM_TEXTURES 4 // holds the number of textures in the system
// G L O B A L S ////////////////////////////////////////////////////////////
pcx_picture image_pcx; // general pcx image
sprite text_spr; // this sprite holds the bitmap textures
// to drape over each cell
int play_x=1000, // the current world x of player
play_y=1000, // the current world y of player
play_z=150, // the current world z of player, same as height
play_ang=ANGLE_90, // the current viewing angle of player
play_dist = 70,
mountain_scale=10; // scaling factor for mountains
float play_dir_x, // the direction the player is pointing in
play_dir_y,
play_dir_z,
cos_look[ANGLE_360], // cosine look up table
sin_look[ANGLE_360], // sin look up table
sphere_cancel[ANGLE_60]; // cancels fish eye distortion
// this map holds the terrain texture cells
// each cell is really a bitmap texture, so by using cells and tiling much
// larger worlds can be constructed
char terrain_texture[] =
{ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,
1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,
1,0,0,3,3,0,0,0,0,0,0,1,1,1,0,0,0,1,0,0,3,0,0,0,0,0,0,0,3,0,0,1,
1,0,0,3,3,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,3,0,0,0,0,0,0,0,3,3,0,1,
1,0,0,3,3,0,0,0,0,0,0,2,2,1,0,0,0,0,0,0,3,0,0,3,3,0,0,0,3,3,0,1,
1,0,0,0,3,0,0,2,0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,3,3,0,0,0,0,3,0,1,
1,0,0,0,0,0,0,2,0,2,2,2,0,0,3,0,3,3,3,0,0,0,0,3,3,0,0,0,0,0,0,1,
1,0,0,0,0,0,0,2,0,0,0,0,0,0,3,0,3,3,3,0,0,0,0,3,0,0,0,0,0,0,0,1,
1,0,0,2,0,0,0,2,0,0,0,0,0,0,3,0,3,3,0,0,0,0,0,0,0,2,0,0,2,0,0,1,
1,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,0,0,0,0,0,0,0,0,0,1,
1,0,0,2,0,0,1,1,1,0,0,2,0,2,0,0,1,0,0,0,2,2,2,0,0,0,0,0,0,0,0,1,
1,0,0,0,0,2,1,1,1,0,0,0,0,0,0,0,1,0,0,0,2,0,2,0,0,0,0,0,0,0,0,1,
1,0,0,0,0,0,0,1,3,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,
1,0,0,0,0,2,0,0,0,3,0,0,0,2,0,2,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,1,
1,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,
1,0,2,0,0,0,0,0,3,0,0,0,0,0,3,0,0,0,0,0,1,0,3,0,0,0,0,0,0,0,0,1,
1,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,2,0,0,1,0,3,0,0,0,2,0,0,0,0,1,
1,0,0,0,0,2,0,2,0,0,2,0,3,0,0,0,0,2,2,0,1,0,0,0,0,2,0,0,0,0,0,1,
1,0,2,0,0,2,0,0,0,0,2,0,0,0,3,0,0,2,0,0,0,0,3,3,0,2,2,2,0,0,0,1,
1,0,0,0,0,2,0,0,0,0,2,0,0,0,3,0,0,0,0,2,0,0,0,0,0,2,0,2,0,0,0,1,
1,0,0,0,0,2,0,0,0,0,0,3,0,0,0,0,0,0,2,2,0,0,0,3,0,0,2,2,0,0,0,1,
1,0,0,0,0,0,2,2,2,2,0,0,0,0,3,0,0,0,2,0,0,0,0,0,0,0,2,0,0,0,0,1,
1,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,1,
1,0,2,2,0,0,0,0,0,1,1,1,1,1,0,0,0,0,2,2,0,0,0,0,0,0,2,0,0,0,0,1,
1,0,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,1,
1,0,2,0,0,2,2,0,0,0,3,0,0,3,3,3,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,1,
1,0,0,2,0,2,2,0,0,0,3,0,0,3,3,3,0,0,0,0,0,0,3,3,3,0,0,0,0,0,0,1,
1,0,0,0,0,2,2,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0,3,3,3,0,0,0,0,0,0,1,
1,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,3,3,3,0,0,0,0,0,0,1,
1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1
};
// F U N C T I O N S ////////////////////////////////////////////////////////
void Line_VDB(int y1,int y2,int x,int color)
{
// draw a vertical line, note that a memset function can no longer be
// used since the pixel addresses are no longer contiguous in memory
// note that the end points of the line must be on the screen
unsigned char far *start_offset; // starting memory offset of line
int index, // loop index
temp; // used for temporary storage during swap
// make sure y2 > y1
if (y1>y2)
{
temp = y1;
y1 = y2;
y2 = temp;
} // end swap
// compute starting position
start_offset = double_buffer + ((y1<<8) + (y1<<6)) + x;
for (index=0; index<=y2-y1; index++)
{
// set the pixel
*start_offset = (unsigned char)color;
// move downward to next line
start_offset+=320;
} // end for index
} // end Line_VDB
//////////////////////////////////////////////////////////////////////////////
void Initialize(void)
{
// this function builds all the look up tables for the terrain generator
int ang, // looping variable
index; // looping variable
float rad_angle; // current angle in radians
// create sin and cos look up first
for (ang=0; ang<ANGLE_360; ang++)
{
// compute current angle in radians
rad_angle = (float)ang*ANGULAR_INCREMENT*DEG_TO_RAD;
// now compute the sin and cos
sin_look[ang] = sin(rad_angle);
cos_look[ang] = cos(rad_angle);
} // end for ang
// create inverse cosine viewing distortion filter
for (ang=0; ang<ANGLE_30; ang++)
{
// compute current angle in radians
rad_angle = (float)ang*ANGULAR_INCREMENT*DEG_TO_RAD;
// now compute the sin and cos
sphere_cancel[ang+ANGLE_30] = 1/cos(rad_angle);
sphere_cancel[ANGLE_30-ang] = 1/cos(rad_angle);
} // end for ang
// load the texture tiles into memory
// intialize the pcx structure
PCX_Init((pcx_picture_ptr)&image_pcx);
// load in the textures
PCX_Load("voxtext.pcx", (pcx_picture_ptr)&image_pcx,1);
// initialize the texture sprite
Sprite_Init((sprite_ptr)&text_spr,0,0,64,64,0,0,0,0,0,0);
// extract the bitmaps for the textures
for (index=0; index<NUM_TEXTURES; index++)
PCX_Get_Sprite((pcx_picture_ptr)&image_pcx,(sprite_ptr)&text_spr,index,index,0);
// done with this PCX file so delete memory associated with it
PCX_Delete((pcx_picture_ptr)&image_pcx);
} // end Initialize
/////////////////////////////////////////////////////////////////////////////
void Draw_Terrain(int play_x,
int play_y,
int play_z,
int play_ang,
int play_dist)
{
// this function draws the entire terrain based on the location and orientation
// of the player's viewpoint
int curr_ang, // current angle being processed
xr,yr, // location of ray in world coords
x_cell,y_cell, // the cell the ray hit
x_fine,y_fine, // the texture coordinates the ray hit
texture_index, // which texture
pixel_color, // the color of textel
ray, // looping variable
row, // the current video row being processed
row_inv, // the inverted row to make upward positive
scale, // the scale of the current strip
top, // top of strip
bottom; // bottom of strip
float ray_length; // the length of the ray after distortion compensation
// start the current angle off -30 degrees to the left of the player's
// current viewing direction
curr_ang = play_ang - ANGLE_30;
// test for underflow
if (curr_ang < 0)
curr_ang+=ANGLE_360;
// cast a series of rays for every column of the screen
for (ray=1; ray<320; ray++)
{
// for each column compute the pixels that should be displayed
// for each screen pixel, process from top to bottom
for (row = 100; row<150; row++)
{
// compute length of ray
row_inv = 200-row;
// use the current height and distance to compute length of ray.
ray_length = sphere_cancel[ray] * ((float)(play_dist*play_z)/
(float)(play_z-row_inv));
// rotate ray into position of sample
xr = (int)((float)play_x + ray_length * cos_look[curr_ang]);
yr = (int)((float)play_y - ray_length * sin_look[curr_ang]);
// compute cell of sampling tip
x_cell = xr >> 5; // / NUM_X_CELLS;
y_cell = yr >> 5; // / NUM_Y_CELLS;
// compute texture coords
x_fine = xr & 0x003F; // % CELL_WIDTH;
y_fine = yr & 0x003F; // % CELL_HEIGHT;
// locate texture index
texture_index = terrain_texture[x_cell + (y_cell<<5)];
// *NUM_X_CELLS];
// using texture index locate texture pixel in textures
pixel_color = text_spr.frames[texture_index][x_fine + (y_fine<<6)];
// *CELL_WIDTH];
// draw the strip
scale = (int)mountain_scale*pixel_color/(int)(ray_length+1);
top = 50+row-scale;
bottom = top + scale;
Line_VDB(top,bottom,ray,pixel_color);
} // end for row
// move to next angle
if (++curr_ang >= ANGLE_360)
curr_ang=ANGLE_0;
} // end for ray
} // end Draw_Terrain
// M A I N //////////////////////////////////////////////////////////////////
void main(int argc, char **argv)
{
char buffer[80]; // text output buffer
int done=0; // exit flag
float speed=0; // speed of player
// check to see if command line parms are correct
if (argc<=1)
{
// not enough parms
printf("\nUsage: voxtile.exe height");
printf("\nExample: voxtile 15\n");
// return to DOS
exit(1);
} // end if
// set scale of mountains
mountain_scale = atoi(argv[1]);
// set the graphics mode to mode 13h
Set_Graphics_Mode(GRAPHICS_MODE13);
// install keyboard driver
Keyboard_Install_Driver();
// build the look up tables and load textures
Initialize();
// create a double buffer
Create_Double_Buffer(200);
// display terrain manually first time
Draw_Terrain(play_x,
play_y,
play_z,
play_ang,
play_dist);
Display_Double_Buffer(double_buffer,0);
// main event loop
while(!done)
{
// reset velocity
speed = 0;
// test if user is hitting keyboard
if (keys_active)
{
// what is user trying to do
// change viewing distance
if (keyboard_state[MAKE_F])
play_dist+=10;
if (keyboard_state[MAKE_C])
play_dist-=10;
// change viewing height
if (keyboard_state[MAKE_U])
play_z+=10;
if (keyboard_state[MAKE_D])
play_z-=10;
// change viewing position
if (keyboard_state[MAKE_RIGHT])
if ((play_ang+=ANGLE_5) >= ANGLE_360)
play_ang-=ANGLE_360;
if (keyboard_state[MAKE_LEFT])
if ((play_ang-=ANGLE_5) < 0)
play_ang+=ANGLE_360;
// move forward
if (keyboard_state[MAKE_UP])
speed=20;
// move backward
if (keyboard_state[MAKE_DOWN])
speed=-20;
// exit demo
if (keyboard_state[MAKE_ESC])
done=1;
// compute trajectory vector for this view angle
play_dir_x = cos_look[play_ang];
play_dir_y = -sin_look[play_ang];
play_dir_z = 0;
// translate viewpoint
play_x+=speed*play_dir_x;
play_y+=speed*play_dir_y;
play_z+=speed*play_dir_z;
// draw the terrain
Fill_Double_Buffer(0);
Draw_Terrain(play_x,
play_y,
play_z,
play_ang,
play_dist);
// print out tactical
sprintf(buffer,"Height = %d Distance = %d ",play_z,play_dist);
Print_String_DB(0,0,10,buffer,0);
sprintf(buffer,"Pos: X=%d, Y=%d, Z=%d ",play_x,play_y,play_z);
Print_String_DB(0,10,10,buffer,0);
// show buffer
Display_Double_Buffer(double_buffer,0);
} // end if
} // end while
// reset back to text mode
Set_Graphics_Mode(TEXT_MODE);
// remove the keyboard handler
Keyboard_Remove_Driver();
} // end main
运行
VOXTILE.EXE
时,只需在命令行中提供高度缩放因子。例如:
VOXTILE 15
控制操作与
VOXEL.EXE
相同。
10. 优化体素引擎
为了提高体素引擎的性能,我们可以进行一些优化。主要的优化方法包括:
-
代码分析
:通过分析
Draw_Terrain()
函数的嵌套循环结构,我们发现内循环的执行次数远远多于外循环,因此应该重点优化内循环。
-
内联所有函数
:将
Line_VDB()
函数内联,避免创建和销毁 16,000 个栈帧,从而提高渲染速度。
-
减少绘制次数
:采用从前到后的顺序绘制地形,只绘制每个像素一次,避免重复绘制。
-
使用移位和逻辑运算
:将乘法和除法运算替换为移位和逻辑运算,提高计算速度。
-
使用查找表
:创建查找表来存储一些常用的计算结果,减少重复计算。
以下是优化后的
VOXOPT.C
程序的主要代码:
// VOXOPT.C - Single texture based, ray casted voxel engine
// I N C L U D E S ///////////////////////////////////////////////////////////
#include <io.h>
#include <conio.h>
#include <stdio.h>
#include <stdlib.h>
#include <dos.h>
#include <bios.h>
#include <fcntl.h>
#include <memory.h>
#include <malloc.h>
#include <math.h>
#include <string.h>
#include "black3.h"
#include "black4.h"
#include "black5.h"
// D E F I N E S /////////////////////////////////////////////////////////////
#define WORLD_X_SIZE 320 // width of universe
#define WORLD_Y_SIZE 200 // height of universe
// constants used to represent angles for the ray casting a 60 degree field of view
#define ANGLE_0 0
#define ANGLE_1 5
#define ANGLE_2 10
#define ANGLE_4 20
#define ANGLE_5 25
#define ANGLE_10 50
#define ANGLE_6 30
#define ANGLE_15 80
#define ANGLE_30 160
#define ANGLE_45 240
#define ANGLE_60 320
#define ANGLE_90 480
#define ANGLE_135 720
#define ANGLE_180 960
#define ANGLE_225 1200
#define ANGLE_270 1440
#define ANGLE_315 1680
#define ANGLE_360 1920
// there are 1920 degrees in our circle or 360/1920 is the conversion factor
// from real degrees to our degrees
#define ANGULAR_INCREMENT ((float)0.1875)
// conversion constants from radians to degrees and viceversa
#define DEG_TO_RAD ((float)3.1415926/(float)180)
#define RAD_TO_DEG ((float)180/(float)3.1415926)
// G L O B A L S ////////////////////////////////////////////////////////////
pcx_picture image_pcx; // general pcx image
int play_x=1000, // the current world x of player
play_y=1000, // the current world y of player
play_z=150,
play_ang=ANGLE_90, // the current viewing angle of player
play_dist = 70,
mountain_scale=10; // scaling factor for mountains
float play_dir_x, // the direction the player is pointing in
play_dir_y,
play_dir_z,
cos_look[ANGLE_360], // cosine look up table
sin_look[ANGLE_360], // sin look up table
sphere_cancel[ANGLE_60], // cancels fish eye distortion
ray_length[100]; // holds ray length look up
// F U N C T I O N S ////////////////////////////////////////////////////////
int Initialize(char *filename)
{
// this function builds all the look up tables for the terrain generator and
// loads in the terrain texture map
int ang, // looping variable
row,
row_inv;
float rad_angle; // current angle in radians
// create sin and cos look up first
for (ang=0; ang<ANGLE_360; ang++)
{
// compute current angle in radians
rad_angle = (float)ang*ANGULAR_INCREMENT*DEG_TO_RAD;
// now compute the sin and cos
sin_look[ang] = sin(rad_angle);
cos_look[ang] = cos(rad_angle);
} // end for ang
// create inverse cosine viewing distortion filter
for (ang=0; ang<ANGLE_30; ang++)
{
// compute current angle in radians
rad_angle = (float)ang*ANGULAR_INCREMENT*DEG_TO_RAD;
// now compute the sin and cos
sphere_cancel[ang+ANGLE_30] = 1/cos(rad_angle);
sphere_cancel[ANGLE_30-ang] = 1/cos(rad_angle);
} // end for ang
// create the pre-computed ray length array
for (row = 100; row<150; row++)
{
// compute length of ray
row_inv = 200-row;
// use the current height and distance to compute length of ray.
ray_length[row-100] = ((float)(play_dist*play_z)/(float)(play_z-row_inv));
} // end for row
// initialize the pcx structure
PCX_Init((pcx_picture_ptr)&image_pcx);
// load in the textures
return(PCX_Load(filename, (pcx_picture_ptr)&image_pcx,1));
} // end Initialize
/////////////////////////////////////////////////////////////////////////////
void Draw_Terrain(int play_x,
int play_y,
int play_z,
int play_ang,
int play_dist)
{
// this function draws the entire terrain based on the location and orientation
// of the player's viewpoint
int curr_ang, // current angle being processed
xr,yr, // location of ray in world coords
x_fine,y_fine, // the texture coordinates the ray hit
pixel_color, // the color of textel
ray, // looping variable
row, // the current video row being processed
scale, // the scale of the current strip
top, // top of strip
last_scale,
last_top,
diff,
index; // looping variable
float ray_length_final; // the length of the ray after distortion compensation
unsigned char far *start_offset; // used by inline line drawer
// start the current angle off -30 degrees to the left of the player's
// current viewing direction
curr_ang = play_ang - ANGLE_30;
// test for underflow
if (curr_ang < 0)
curr_ang+=ANGLE_360;
// cast a series of rays for every column of the screen
for (ray=1; ray<320; ray++)
{
// reset last top and scale
last_scale = 0;
last_top = 0;
// for each column compute the pixels that should be displayed
// for each screen pixel, process from top to bottom
for (row = 149; row>=100; row--)
{
// use the current height and distance to compute length of ray.
ray_length_final = sphere_cancel[ray] * ray_length[row-100];
// rotate ray into position of sample
xr = (int)((float)play_x + ray_length_final * cos_look[curr_ang]);
yr = (int)((float)play_y - ray_length_final * sin_look[curr_ang]);
// compute texture coords
x_fine = xr % WORLD_X_SIZE;
y_fine = yr % WORLD_Y_SIZE;
// using texture index locate texture pixel in textures
pixel_color = image_pcx.buffer[x_fine + (y_fine<<8) + (y_fine<<6)];
// draw the strip
scale = (int)mountain_scale*pixel_color/(int)(ray_length_final+1);
// test if we need to draw this segment
if (scale>=last_scale)
{
diff = scale-last_scale;
top = 50+row-scale;
// compute starting position
start_offset = double_buffer + ((top<<8) + (top<<6)) + ray;
for (index=0; index<=diff; index++)
{
// set the pixel
*start_offset = (unsigned char)pixel_color;
// move downward to next line
start_offset+=320;
} // end for index
} // end if scale
last_scale = scale;
} // end for row
// move to next angle
if (++curr_ang >= ANGLE_360)
curr_ang=ANGLE_0;
} // end for ray
} // end Draw_Terrain
// M A I N //////////////////////////////////////////////////////////////////
void main(int argc, char **argv)
{
char buffer[80];
int done=0; // exit flag
float speed=0; // speed of player
// check to see if command line parms are correct
if (argc<=2)
{
// not enough parms
printf("\nUsage: voxel.exe filename.pcx height");
printf("\nExample: voxel voxterr4.pcx 10\n");
// return to DOS
exit(1);
} // end if
// set the graphics mode to mode 13h
Set_Graphics_Mode(GRAPHICS_MODE13);
// create a double buffer
Create_Double_Buffer(200);
// create look up tables and load textures
if (!Initialize(argv[1]))
{
printf("\nError loading file %s",argv[1]);
exit(1);
} // end if
// install keyboard driver
Keyboard_Install_Driver();
// set scale of mountains
mountain_scale = atoi(argv[2]);
// draw the first frame
Draw_Terrain(play_x,
play_y,
play_z,
play_ang,
play_dist);
Display_Double_Buffer(double_buffer,0);
// main event loop
while(!done)
{
// reset velocity
speed = 0;
// test if user is hitting keyboard
if (keys_active)
{
// what is user trying to do
// change viewing distance
if (keyboard_state[MAKE_F])
play_dist+=10;
if (keyboard_state[MAKE_C])
play_dist-=10;
// change viewing height
if (keyboard_state[MAKE_U])
play_z+=10;
if (keyboard_state[MAKE_D])
play_z-=10;
// change viewing position
if (keyboard_state[MAKE_RIGHT])
if ((play_ang+=ANGLE_10) >= ANGLE_360)
play_ang-=ANGLE_360;
if (keyboard_state[MAKE_LEFT])
if ((play_ang-=ANGLE_10) < 0)
play_ang+=ANGLE_360;
// move forward
if (keyboard_state[MAKE_UP])
speed=20;
// move backward
if (keyboard_state[MAKE_DOWN])
speed=-20;
// exit demo
if (keyboard_state[MAKE_ESC])
done=1;
// compute trajectory vector for this view angle
play_dir_x = cos_look[play_ang];
play_dir_y = -sin_look[play_ang];
play_dir_z = 0;
// translate viewpoint
play_x+=speed*play_dir_x;
play_y+=speed*play_dir_y;
play_z+=speed*play_dir_z;
// draw the terrain
Fill_Double_Buffer(0);
Draw_Terrain(play_x,
play_y,
play_z,
play_ang,
play_dist);
// draw tactical
sprintf(buffer,"Height = %d Distance = %d ",play_z,play_dist);
Print_String_DB(0,0,10,buffer,0);
sprintf(buffer,"Pos: X=%d, Y=%d, Z=%d ",play_x,play_y,play_z);
Print_String_DB(0,10,10,buffer,0);
Display_Double_Buffer(double_buffer,0);
} // end if
} // end while
// reset back to text mode
Set_Graphics_Mode(TEXT_MODE);
// remove the keyboard handler
Keyboard_Remove_Driver();
} // end main
`VOXOPT.EXE` 的控制和命令行参数与 `VOXEL.EXE` 相同,但由于使用了查找表,不能更改高度或查看距离。优化内容如下:
1. 内联线绘制函数
2. 使用查找表存储预计算的射线长度
3. 尽可能使用移位进行乘法运算
4. 以从前到后的顺序绘制每个像素,且仅绘制一次
#### 11. 添加前景对象
若要向体素显示中添加对象,可使用 Z 缓冲区技术。即在体素显示从后向前渲染时,将希望与视平面保持一定距离的精灵对象在适当的时间进行渲染,使剩余的体素地形遮挡精灵的相应部分。具体步骤如下:
1. 确定对象在体素世界中的位置和深度信息。
2. 在渲染体素地形的过程中,根据对象的深度信息,在合适的时机渲染对象。
3. 确保后续渲染的体素地形能够正确遮挡对象被覆盖的部分。
#### 总结
本文深入探讨了体素图形这一新颖的计算机图形技术。从体素图形的基础概念出发,详细介绍了体素数据库的生成方式,包括使用扫描地形地图和算法生成数据。接着阐述了射线投射和高度映射技术,这是实现体素图形显示的核心方法。通过射线旋转和畸变修正,解决了视角和渲染畸变的问题。同时,介绍了查找体素高度和条带缩放的方法,以及如何实现简单的体素显示演示。
为了创建更大的世界,引入了基于图块的体素引擎,通过图块系统有效管理内存。最后,对体素引擎进行了优化,包括代码分析、内联函数、减少绘制次数、使用移位和逻辑运算以及查找表等方法,显著提高了引擎的性能。
以下是体素图形实现的主要流程 mermaid 图:
```mermaid
graph LR
A[体素图形基础] --> B[生成体素数据库]
B --> C[射线投射]
C --> D[高度映射]
D --> E[射线旋转与畸变修正]
E --> F[查找体素高度]
F --> G[条带缩放]
G --> H[实现简单体素显示演示]
H --> I[基于图块的体素引擎]
I --> J[优化体素引擎]
J --> K[添加前景对象]
体素图形技术在游戏开发、虚拟场景创建等领域具有广阔的应用前景。随着技术的不断发展和优化,相信体素图形将为我们带来更加逼真和精彩的视觉体验。希望本文能为对体素图形感兴趣的开发者提供有价值的参考,激发更多的创新和实践。
超级会员免费看

617

被折叠的 条评论
为什么被折叠?



