This web page describes a way to use the WaveFilter Plugin2Creator to create textures with bumps, that can be used from within WaveFilter Shader and Volume (see note below). Newtek has just purchased the WaveFilter plugins and programs and is offering them for free for LW5.x users (select "WaveFilter Available For Free Download" from the "What's New" menu), and they are also included with LW6. So experimenting with, and using these textures won't cost a thing if you already own Lightwave. And it's possible to compile these textures remotely (Intel or Alpha versions) and automatically add them to WaveFilter without the use of a local C compiler.
(NOTE: Currently, the bumps are supported only by Shader. The plugin2 textures will work under Volume as well, but the bumping of the texture will be ignored so you might as well set the Bump Amplitude to 0 to avoid doing unnecessary calculations.)
This page will describe an example Plugin2 texture and a technique used to add matching bumps to that texture. These bump code examples are not the easiest way to write a bumpy texture, but I wanted to make sure the code could be written and compiled remotely with the Plugin2Creator application. In this way, users without a compiler on their own machine can still experiment with their own textures, or just make small modifications to an existing texture in order to customize it for special needs.
To get the files that will load into the LW-Plugin2Creator application (.p2c files), download the wfbumps-p2c.zip archive. If you have followed the directions in the FinishSetupOfPlugin2Creator-READ_ME.txt (whew!, what a name :-), you should be able to compile these files remotely and have them installed automatically for use in Shader or Volume.
If you just want to try these textures in WaveFilter Shader or Volume, without having to look at the coding details, you can download the necessary .p2 files from the wfbumpsi-p2.zip (Intel version) or the wfbumpsa-p2.zip (Alpha version) archive. If you want to use these textures in the WaveFilter plugins, you will need to read the installation instructions included in the archive.
Just copy the four .p2c files into the WaveFilter directory (mine are in d:\Newtek\WaveFilter, the same location as all the files from the Newtek download). Now run LW-Plugin2Creator and type in the name of that directory in the "Plugin 2 Folder" requester. If things are working correctly, you will be able to choose any one of the 4 textures (Crinkle, HalfFractalBump, Marble, and Wood) from the "Texture Name" pull-down menu.
Once the code for a texture appears in the "Texture Code" window, if you have setup the ftpx application correctly, pressing the "Remote Compile" button should result in the texture being uploaded to WaveFilter's remote computer, compiled into a .p2 file which is sent back to your computer. When the process is finished, you should see a message at the bottom of the application's window stating that the plugin2 texture has also been automatically installed. You should now be able to run WaveFilter Shader or Volume, and see the new texture in amongst the other built-in WaveFilter textures.
Don't forget, if you skip using the .p2c files and LW-Plugin2Creator, and go right to using the .p2 files, you will need to manually modify the WaveFilter.lic file as described by the Install.txt file included in the wfbumps-p2.zip archive.
There are 10 sample textures available for inspection in Plugin2Creator, just by pressing the buttons at the bottom of the screen. Most of these (with the exception of Sample 10), are very simple, but good examples of how easy it can be to use the noise and turbulence functions to create your own textures. These will make good examples for adding bumps after reading the next section of this page, "Just Add Bumps".
But for our first example, we'll look at the code used to create the HalfFractal built-in texture. Mike Reed (author of WaveFilter) was kind enough to provide me with code for some of the internal textures, and I chose this simple texture for the first example.
// Mike Reed's HalfFractal texture with bumps
Q[0] = Pos[0] * 4.0;
Q[1] = Pos[1] * 4.0;
Q[2] = Pos[2] * 4.0;
value = noise3(Q);
value = value * 2.0;
You'll notice this is only a few multiplies and a call to the Plugin2 built-in noise3 function (press the Help button to find out more about the other available functions and the variables we'll be using shortly). The important thing is to use these calculations to assign a number between 0 and 1 into the "value" variable. Many different textures can be created in similar ways, using other math operators and functions on the built-in noise and turbulence functions. A good source for many texture ideas can be found in Renderman shaders. An excellent textbook on the subject of procedural textures (and lots of other goodies) is "Texturing and Modeling: A Procedural Approach" by Ebert, Musgrave, Peachey, Perlin, and Worley.
Creating a texture to use with WaveFilter Shader and Volume can be fun, and it's always enjoyable to see your patterns appear for the first time using the WaveFilter preview and then in a Lightwave rendering. But this tutorial's main purpose is to demonstrate a technique for adding bumps to the textures you create using Plugin2Creator. So let's spend just a few minutes talking about the necessary mathematics (dont' worry, it's not difficult math), and then apply those concepts to the HalfFractal texture above so we can have bumps that correspond to the texture calculations.
To calculate how a texture should be "bumped", we need to know how the value of the texture changes across the surface of the object being textured. To do this, we will estimate the texture's derivative in 3 dimensions (also called the gradient) at each pixel location, immediately following the "value" calculation in our texture function.
A one dimensional derivative, f'(x) is mathematically defined as the limit of
f'(x) = ( f(x + delta) - f(x) ) / delta
as delta approaches 0.
In non-mathematical terms, all this means is: the derivative of a function can be found by taking the difference between two different calculations of the function. One of those, the f(x) term, is the calculation of the function at the current point (the "value" that we have already calculated), and the other, the f(x + delta) term, is the calculation of the same function at a new point just offset slightly from the current point (a small value of delta). Dividing the difference of those calculations by delta will approximate the derivative, for small values of delta.
But our textures are not going to be one dimensional, so for a 3D shader, we will have to take the derivative in 3 directions. We will need the following calculations:
(1) The function at the current point f(x,y,z)
(2) The function at the current point with a small delta added to the
x coord
(3) The function at the current point with a small delta added to the
y coord
(4) The function at the current point with a small delta added to the
z coord
In mathematical terms, here is the calculation needed for all 3 components of the gradient:
dx = (f(x+delta, y, z) - f(x, y, z)) / delta
dy = (f(x, y+delta, z) - f(x, y, z)) / delta
dz = (f(x, y, z+delta) - f(x, y, z)) / delta
If we can calculate those 3 values, we have the components of the gradient (dx, dy, dz) needed for modifying the surface normal. As mentioned earlier, our texture function already calculates f(x,y,z), which is assigned to the "value" variable, so that calculation is already taken care of. But we also need to calculate f(x+delta, y, z), f(x, y+delta, z), f(x, y, z+delta).
And that's pretty easy, we just have to repeat the original texture calculations 3 more times. The next code segment from the HalfFractalBump texture illustrates the repetition of the texture calculation for vx = f(x+delta, y, z); vy = f(x, y+delta, z); and vz = f(x,y, z+delta). Realize that UserVar5 is the WaveFilter variable that in this case allows the user to specify the value of "delta".
// Delta offset in the x direction
Qx[0] = Q[0] + UserVar5;
Qx[1] = Q[1];
Qx[2] = Q[2];
vx = noise3(Qx);
vx = vx * 2.0;
// Delta offset in the y direction
Qy[0] = Q[0];
Qy[1] = Q[1] + UserVar5;
Qy[2] = Q[2];
vy = noise3(Qy);
vy = vy * 2.0;
// Delta offset in the z direction
Qz[0] = Q[0];
Qz[1] = Q[1];
Qz[2] = Q[2] + UserVar5;
vz = noise3(Qz);
vz = vz * 2.0;
(NOTE: If you look at the source code in Plugin2Creator, you may notice that I have included the above statements and all the rest of the bump mapping statements in an if statement that is not shown here. The if statement only allows the bump mapping calculations to take place if you are not in the WaveFilter interface, because the Lightwave ShaderAccess structure, which contains the surface normal at the current pixel, is not a valid structure while using a plugin's interface. This is not a major problem, since WaveFilter's preview does not support the bumps anyway. The bump calculations are also skipped if Bump Amplitude is set to 0 by the user. This way the extra calculations needed for bumps won't be calculated, speeding up the calcuation of the entire texture when the user does not want bumps.)
Once these values have been calculated at offsets along each coordinate axis, we now have all the values we need to calculate the gradient that will be used to modify the surface normal at this point.
dx = (value - vx) / delta;
dy = (value - vy) / delta;
dz = (value - vz) / delta;
And since UserVar4 contains a user defined value that allows the user to increase/decrease the size of the bump (Bump Amplitude), now is the time to mutiply that value into the gradient components:
dx = dx * UserVar4;
dy = dy * UserVar4;
dz = dz * UserVar4;
(NOTE: If you look at the actual HalfFractalBump code I am distributing, you will find a slightly modified version of the 6 statements above. I have chosen not to divide by delta, and instead just let the Bump Amplitude parameter determine the magnitude of the bump. This was done for 2 reasons. The first reason is, I like Bump Amplitudes to be in the range of approximately -5 to 5. If you do the division by delta (which is usually about .01), the range of values for Bump Amplitude would be more in the range of -.05 to .05, and I just prefer the values in the initial range. The second reason is for speed, since we eliminate the need to do 3 floating point divides for every rendered pixel of our surface. But I wanted to present the proper technique here, and you can decide how you want to implement your own bumps.)
Only a few details remain to finish up the bump mapping portion of our texture. First, all of these calculations have probably taken place in Object Space, so the gradient vector is also in Object Space. Lightwave's wNorm variable, which is the surface normal at the current point, is expected to be in World Space. So, if the calculations were in fact done using Object coordinates, we need to transform out gradient vector into World coordinates first:
if (!inst->NoiseWorldCoordinatesFlag) {
t[0] = dx; t[1] = dy; t[2] = dz;
dx = t[0] * sa->oXfrm[0] + t[1] * sa->oXfrm[1] + t[2] *
sa->oXfrm[2];
dy = t[0] * sa->oXfrm[3] + t[1] * sa->oXfrm[4] + t[2] *
sa->oXfrm[5];
dz = t[0] * sa->oXfrm[6] + t[1] * sa->oXfrm[7] + t[2] *
sa->oXfrm[8];
}
Next we add the gradient vector to the current surface normal:
sa->wNorm[0] = sa->wNorm[0] + dx;
sa->wNorm[1] = sa->wNorm[1] + dy;
sa->wNorm[2] = sa->wNorm[2] + dz;
And finally we must re-normalize the surface normal before giving it back to Lightwave:
s = sqrt (sa->wNorm[0] * sa->wNorm[0] + sa->wNorm[1] *
sa->wNorm[1] + sa->wNorm[2] * sa->wNorm[2]);
sa->wNorm[0] = sa->wNorm[0] / s;
sa->wNorm[1] = sa->wNorm[1] / s;
sa->wNorm[2] = sa->wNorm[2] / s;
As the comments in all of the .p2c files state, these last sets of statements will be the same for almost every texture that you add bumps to. The code can be copied from any of the included .p2c files, and pasted into your own textures.
The above tutorial led you through the implementation of bumps in the HalfFractalBump texture. You should be able to use this sample code to add bumps to your own versions of the Sample Textures. In particular, Samples 2, 3 and 5 -9 would be fairly simple exercises in adding your own bumps.
The Crinkle texture is also a fairly simple texture to study if you are interested in another example. It uses a slightly different technique for determining the "delta" used in the calculation of the derivative. The user is not given a requester to specify the delta value in this texture, it is determined by using the approximate size of the pixel as reported to us from the Lightwave spotSize variable. This is usually a good approximation for "delta", and saves the user from having to supply the value for that parameter in the texture's interface.
The Marble and Wood textures were translations of the Renderman shaders found in "The Renderman Companion: A Programmer's Guide to Realistic Computer Graphics", by Steve Upstill. The WaveFilter Wood texture has some graininess code added at the end, but otherwise you should be able to compare the original Renderman shader language to the WaveFilter textures and easily see the similarities.
The Wood texture is the most complex of the textures included in the archive. Because of this, I did not put all the bump calculations at the end of the function, I mixed the bump calculations into the code at various locations to make it a little easier to compare the texture calculation to the bump calculations.
The "graininess" of this wood texture may cause the bumps to be a little extreme at times when using this texture. But don't be afraid to add your own modifications to the textures, and create the illusion you would like. For instance, if you would like to "flatten" the tops of the bumps, just test the "value" variable (UserVar7 is for the user defined Bump Amplitude in these examples):
if (value < .7) amplitude = UserVar7; // The user defined Bump
Amplitude variable
else amplitude = 0; // Don't bump at all
Or if you want to make the bumps higher as value gets bigger just multiply the amplitude by the value variable:
amplitude = UserVar7 * value;
Or the opposite, the bumps are larger for smaller texture values:
amplitude = UserVar7 * (1 - value);
Now use the value of the amplitude variable as the multiplier for the gradient vector's components before adding the gradient to wNorm. Changes such as these are easy to make, and will allow you to customize the texture and bumps for your own needs and desires.