Basic Idea

To build realistic models of wood and marble we exploit 3d textures. With 3d textures we can generate a color volume that resembles the desired material and then draw polygons into this volume. Colors in the volume can then be mapped to the polygon. When drawing closed surfaces in this manner we are essentially carving an object out of the color volume. This has an advantage over regular 2d texture maps in that the associated texture distortion can be avoided. Also elaborate techniques can be used in construction of the 3d texture. So to create wood and marble we need to look into 3d textures.

Generating Textures

In making these textures we want to construct color volumes that look like the desired material. Natural objects have the property that they follow a general pattern but are subject to local variations. We thus cannot make them in a completely deterministic way as this will result in textures that are too regular and would look unrealistic. We thus need to build a certain degree of randomness into the 3d texture by making use of random or pseudo-random sequences to perturb our deterministicly generated patterns. In what follows, the noise functions that are implemented in this program are explained.

Noise functions:

The basic random noise function used to generate 3d textures in this program is gradient noise. This noise has properties that are desirable as it is a repeatable function with known range with statistical properties that are invariant to translation and rotation. It is also bandlimited with approximately constant power spectrum where it is non-zero. Thus we can control its aliasing.

Computing Gradient Noise

Computing gradient noise involves finding random unit vectors at the integer locations of a lattice. There are two main issues in doing this first is the storage of such a structure and second is ensuring a uniform distribution of vector directions over the lattice.

Storage

Ultimately we desire a function that allows us to specify a floating point location in 3 space and returns a random number. The randomness referred to here is associated with location. So if a certain point is picked it will consistently return the same number, but this number is "random" with respect to other point locations. It may seem we need to use a 3d data structure to store this but this has demanding memory requirements. Instead we can use the approach described by Ebert in his book Texturing and Modeling, where a 1d data structure is used.

In this approach we first construct a table of random index locations. These will be the indexes into a random vector table. We thus need a random permutation of integers that span the table:

perm = {random permutations of 1..SIZE_RAND_TABLE}

Where SIZE_RAND_TABLE is the size of the random number table. This random number table will contain random numbers in triples forming unit vectors:

randVects[1..SIZE_RAND_TABLE] = {collection of uniformly distributed unit vectors}

To find a vector at an arbitrary integer location of a 3d lattice we call:

rVect(ix,iy,iz) = randVects[ PERM(ix + PERM(iy+PERM(iz))) ];

PERM(ix) = perm[ ix mod SIZE_RAND_TABLE]

The term PERM(ix + PERM(iy+PERM(iz))) will create a pseudo-random index into the array, randVects, and the function will return a consistent random number for repeated calls to a given location. Thus rVect(ix,iy,iz) will give random vectors at integer locations. Also note that if the random number table's size is a power of two PERM can be more efficiently written as:

PERM(ix) = perm[ ix & (SIZE_RAND_TABLE-1)]

where & is the bitwise and operator

Calculating Random Vectors

To generate the random vectors we first choose a z components between -1 and 1, defining a plane intersecting the unit circle. Then we randomly choose an angle in the xy plane for the projection of the vector to face. Combining this angle and the z component gives us uniformly distributed vector directions in the lattice:

Computation of Gradient Noise

Finding the value at a particular location requires computing the dot product of the vectors at the surrounding lattice locations and the fractional displacement from that lattice locations. Thus for a given floating point location x ,y,z we can compute :

gLattice = DOT(randVect(ix,iy,iz), (fx,fy,fz))

where ix,iy,iz are the 8 integer locations surrounding x,y,z and fx,fy,fz is the fractional displacement from the respective lattice point. These 8 values are then combined using smoothed tri-linear interpolation. This is like tri-linear interpolation but the interpolates are operated on by a smoothing function. Thus an interpolet dx becomes:

dw = SMOOTHSTEP(dx),

SMOOTHSTEP(x) = x*x*(3-2*x)

Note that since gradient noise computes dot products with displacements from lattice locations the value of gradient noise is zero at the lattice locations:

Gradient Noise

Fractal Noise and Turbulence.

We could apply this function to our 3d textures and achieve randomness, however this noise function turns out to be too noisy to give a natural look to textures. In terms of the frequency content of gradient noise it has constant power at all its non-zero frequencies components thus the higher frequencies have the same influence as the lower resulting a very wiggly function. We need a function that is locally random but has an overall smoothness to it, a phenomenon common to many natural objects. Functions that fall into this criteria are those with the 1/f power spectrum. In particular we make use of "pink noise", or fractal noise. Since the power law here is 1/f, higher frequencies do not have as great an influence as lower frequencies. Thus the smother variations due to the lower ones prevail on the overall scale, but locally we have the high frequency variations

Building the Fractal noise:

Given the gradient noise function we can construct the smother fractal noise function that can be applied to our textures.

To build fractal noise we calculate:

where noise is our previously discussed, gradient noise function. In the spacial domain this function is summing spatially scaled versions of noise. These versions of noise are also weighted such that the very compressed ones(ie the ones with greatest wiggliness ) get attenuated greatly. Thus we are taking a linear combination of noise functions scaled according to the inverse of the extent of their wiggliness resulting in a overall smooth varying function which is locally wiggly. Analogously, in the frequency domain we are stretching and summing constant and and limited signals such that the wider we stretch them the more we scale them down:

If we add an absolute value to the sum we get the turbulence function:

By adding the absolute value we have increased the number of peaks (since the negative values become positive) and we have inserted derivate discontinuities on the previous zero crossings. This gives it a more clumpy look to it:

Also the sharpness of the derivative discontinuities gives the appearance of cracks. We exploit this in generating marble textures.

Generating 3d Textures:

Marble:

Once we have 3d noise functions set up generating a marble pattern is a direct application of turbulence:

Marble(x,y,z) = turbulence(x,y,z)*turbScale

where the constant, turbScale, controls the range of color values. Also in this implementation using OpenGL, values are clipped to lie between 0 and 1. This function can be applied to every location in the 3d texture.

The distinguishing feature of marble is the cracks. To gain better control of the cracks we eliminate the lower frequency noise terms in the turbulence some. In our implementations a lower bound of i=2 was used.

Marble(x,y,z) is successful because of the discontinuities of the turbulence function like cracks. We can to some degree control the sharpness of these cracks by using Marble(x,y,z) as the input to another function:

Marble2(x,y,z) = f(Marble(x,y,z) = f( turbulence(x,y,z)*turbScale ).

f(x )can be a spline such that the derivative near zero is greater then one and thus accentuating the cracks. In out implementation, f(x) was a hermite spline that started at 0 and went to 1, with a range from 0 to 1. By changing the derivate at 0 the sharpness of the cracks could be controlled. Also, values of turbulence greater then 1 where clipped to 1.

Wood:

The first step in building wood is to generate the basic shape. This can be done by building concentric color rings as follows:

Wood(x,y,z) =WoodColor( sin( x^2 + y ^2 )).

Where wood color is a color spline that varies between light and dark shades of brown. This function creates regular cylinders of constant color around the z-axis such that the density of rings increase further from the z axis. To make this more realistic we apply turbulence , move the center of the cylinders, create elliptical cylinders, and alter the frequency of the cylinders:

Wood(x,y,z) =WoodColor( sin([ (x-.2)^2 + (y-.2) ^2/2]*ringFreq + turbulence(x,y,z)*turbScale)).

Here ringFreq controls the frequncy of the spacing of cylnders/rings and turbScale controls the amount of turbulence.

Using this function however gives bands of equal proportion. To make the dark lines skinnier that the light ones we replace the sin with a periodic function built out of a sinusoid and 2 constant regions. It starts out at zero then rises a hump of a sinusoid for the full period, going back to zero for the rest of the cycle. The function segments are joined such that they have C2 continuity. This function has the effect of creating longer gaps, and more pointy peaks between the peaks of the sinusoid

This 3d texture can then be applied to a polygonal model.

Hosted by www.Geocities.ws

1