POVRay, from version 3.1, supports a volumetric density media. This can be used to represent arbitrary media distributions, the volumetric values can be mapped to variations in density (using a density_map) or colour (using a colour_map). While this was primarily introduced as a powerful way to specify user defined media effects, it can also be used for certain volume rendering applications. For a simple example, the following contains a number of Gaussian smeared points of different standard deviations. The POVRay file is here: example.pov, note that the density in this example just controls the emission in the media. While the density_map in this case is quite boring, different mappings are possible depending on the application. For more information on the media settings see the POVRay documentation.
A couple of simple examples adding a colour map are given below.
By adding an absorption "absorption <1,1,1>" and turning on media interaction for the light sources, the media will cast shadows.
The format of a df3 file is straightforward, it consists of a 6 byte header containing the 3 dimensions of the volume, this is followed by the voxel cells. Each voxel can be represented as a single byte (unsigned char), a 2 byte integer (unsigned short), or 4 byte integer (unsigned int). The 2 and 4 byte versions are written as big-endian (Intel chips are little endian, Mac PowerPC or G4/G5 are big-endian). POVRay works out which datatype is used by the size of the file. The voxels are ordered as x varying the fastest, then y, and finally z varying the slowest. The following gives the outline of how one might create a 3D volume and save it as a df3 file where each voxel is a single unsigned byte. Using floats for the volume is simply to make the maths easier if one is creating arbitrary functions. For many applications it is OK to use short ints or even char directly. Note the endian of the short ints in the header and the order the volume voxels are written.
int i,j,k;
int nx=256,ny=256,nz=256;
float v,themin=1e32,themax=-1e32
float ***df3; /* So we can do easier maths for interesting functions */
FILE *fptr;
/* Malloc the df3 volume - should really check the results of malloc() */
df3 = malloc(nx*sizeof(float **));
for (i=0;i<nx;i++)
df3[i] = malloc(ny*sizeof(float *));
for (i=0;i<nx;i++)
for (j=0;j<ny;j++)
df3[i][j] = malloc(nz*sizeof(float));
/* Zero the grid */
for (i=0;i<nx;i++)
for (j=0;j<ny;j++)
for (k=0;k<nz;k++)
df3[i][j][k] = 0;
-- Create data volume data here --
/* Calculate the bounds */
for (i=0;i<nx;i++) {
for (j=0;j<ny;j++) {
for (k=0;k<nz;k++) {
themax = MAX(themax,df3[i][j][k]);
themin = MIN(themin,df3[i][j][k]);
}
}
}
if (themin >= themax) { /* There is no variation */
themax = themin + 1;
themin -= 1;
}
/* Write it to a file */
if ((fptr = fopen("example.df3","w")) == NULL)
return(FALSE);
fputc(nx >> 8,fptr);
fputc(nx & 0xff,fptr);
fputc(ny >> 8,fptr);
fputc(ny & 0xff,fptr);
fputc(nz >> 8,fptr);
fputc(nz & 0xff,fptr);
for (k=0;k<nz;k++) {
for (j=0;j<ny;j++) {
for (i=0;i<nx;i++) {
v = 255 * (df3[i][j][k]-themin)/(themax-themin);
fputc((int)v,fptr);
}
}
}
fclose(fptr);
If 2 or 4 byte voxels are being saved from a little-endian machine (Intel) then the following macros may be helpful.
#define SWAP_2(x) ( (((x) & 0xff) << 8) | ((unsigned short)(x) >> 8) )
#define SWAP_4(x) ( ((x) << 24) | \
(((x) << 8) & 0x00ff0000) | \
(((x) >> 8) & 0x0000ff00) | \
((x) >> 24) )
#define FIX_SHORT(x) (*(unsigned short *)&(x) = SWAP_2(*(unsigned short *)&(x)))
#define FIX_INT(x) (*(unsigned int *)&(x) = SWAP_4(*(unsigned int *)&(x)))
So for example to write 2 bytes (unsigned short) then one have the writing loop as follows.
for (k=0;k<nvol;k++) {
for (j=0;j<nvol;j++) {
for (i=0;i<nvol;i++) {
v = 65535 * (df3[i][j][k]-themin)/(themax-themin);
FIX_SHORT(v);
fwrite(&v,2,1,fptr);
}
}
}
Further examples
MRI "brain on fire" rendering.
Cosmological simulations
|