Table of Contents
From here on, we focus mainly on the texture class itself,
and assume that you are using the
TextureView
class or something similar to display the texture.
We'll start by looking at some simple gradient textures
based on the u or v values, and then start looking at
the
ColorGradient
class which provides us with some fundamental building
blocks.
A gradient is simply an interpolation from one point to another, or in this case, one color to another. As a simple example, let's make the red component of the resulting texture color equal to the u value.
public class UGradient extends AbstractTexture {
public void getColor(double u, double v, RGBAColor value) {
value.setColor(u,0,0,1);
}
}

Fairly simple, the red component of the resulting color increases as we go from left to right. This is because the U value increases in value from 0 to 1 as we go from left to right.
Let's extend this and set the green component to the v value of the input.
public class UVGradient extends AbstractTexture {
public void getColor(double u, double v, RGBAColor value) {
value.setColor(u,v,0,1);
}
}

Nothing we wouldn't expect, but we need something a
little more interesting. More complex gradients can be
achieved by using the u or v value to interpolate
through multiple points or colors. The
ColorGradient
class lets us easily perform this task.
public class SunsetTexture extends AbstractTexture {
private ColorGradient gradient;
public SunsetTexture() {
gradient = new ColorGradient();
gradient.add(new RGBAColor(16, 32, 64))
.add(new RGBAColor(32, 64, 128))
.add(new RGBAColor(128, 160, 255))
.add(new RGBAColor(192, 210, 255))
.add(new RGBAColor(250, 240, 192));
}
public void getColor(double u, double v, RGBAColor value) {
gradient.interpolate(v, value);
}
}
Our
ColorGradient
class takes a list of colors and when we are rendering
our texture, we use those colors to calculate the
resultant color based on the v value. This produces a
sunset like effect in our final texture.

Gradients are fairly simple components to use and are often building blocks for more complex textures.
On key element of procedural textures is the ability to introduce controlled randomness to the texture. This is so don't have to worry about individually placing elements (clouds, dirt etc) and also so we can render larger textures without worrying about repeating elements. However, we don't want to just use a random number generator, we want some pre-determination that will allow us to calculate the same result for the same inputs.
Also, if we have two points, for which we can always calculate the same value, as we move between the two points, we want our random value move between the two points correspondingly. To achieve this, we look at Ken Perlin, a graphics guru and inventor of Perlin Noise in 1985. He has a page describing Perlin Noise at http://www.noisemachine.com/talk1/ .
I've included his implementation of
ImprovedNoise.java
which can be found on the web. I'll try and give a brief
description of the technique of generating noise here,
but Ken Perlin's page has a much more technical
overview.
For any given point we can pass into the noise function we can construct a 'box' around the point where each corner of the box lies on the nearest integer values. If we consider one dimension, for the number 5.3, we would use the integer positions 5 and 6 as the nearest whole integer positions. In two dimensions, we would form a 2 dimensional rectangle on the integer boundaries and for 3 dimensions, it is a box with a corner on the surrounding integer points. The noise function takes the inputs and determines a value for each of those corners. Bear in mind that each time we call the function with the same values, we use the same corner values. We then interpolate the values across the faces of the box depending on the floating point part of the input values. However, straight interpolation can result in blocky looking noise, so the actual algorithm interpolates unit length gradients across the 'face' of the box. This results in much more rounded noise and also the levels tend towards zero at the lattice points.
Regardless, you don't need to fully understand how the
noise functions work to see what they do and how you can
use them in textures. Here is a simple noise texture
with the greyscale value set to the noise result for the
(u,v)
values of the texture.
public class GreyNoise extends AbstractTexture {
public void getColor(double u, double v, RGBAColor value) {
double noiseValue = noise.noise2(u*10,v*10);
value.setColor(noiseValue,noiseValue,noiseValue,1);
}
}

A single set of noise values can be uninteresting,
especially at lower frequencies. What we can do is sum
the noise at different frequencies, scale them with
different amplitudes, and add them to the result to
create a richer noise function. To do this, we can use
the
PerlinNoise.fbmNoise()
method which takes the input parameters and the number
of octaves to use. It then calculates the noise at
different frequencies and sums them together.
Noise = noise(i) + 1/2noise(2i) + 1/4noise(4i) ...
We can demonstrate this using multiple images for different noise coefficients and then merging them to create a final complex noise texture.

Another key basic texture is a marble type texture
that is calculated by summing up noise functions at
different frequencies and then passing the value
through a sine function. In the noise class, there
is a
noiseSine()
method that calculates this. The parameters for this
are speed, which determines how fast the sine wave
moves. Scale determines how much noise we apply to
it, and octaves determine how many frequencies we
use to sample the noise.

These images were generated using an anonymous
texture class around a
MarbleSignal
signal to get the marble value. Noise can also be a
great way to generate terrain landscapes, something
I've been using the JTexGen framework for.
public static void main(String[] args) {
Texture texture = new AbstractTexture() {
private ChannelSignal signal = new MarbleSignal(0,0,5,2,5,1);
public void getColor(double u, double v, RGBAColor value) {
double val = signal.getValue(u, v);
value.setColor(val,val,val,1);
}
};
TextureViewer.show(texture);
}
Now we have a number of textures to create, we need a
way to bring them all together, to take multiple
textures and combine them to form one single texture. To
do this, we have several composition textures that take
one or more textures as input and produce an output
based on the those textures and optionally based on the
(u,v)
input parameters. Usually, we need to consider the alpha
channel as it is used when compositing one texture over
another. We composite textures in such a way that the
visibility of the background texture is dependent on the
alpha value of the texture laid over it.
The simplest method of compositing is to use the
MergedTexture
texture. This texture takes two source textures, a
background and an overlay texture and merges them. It
does this by calculating the point for each texture at
point
(u,v)
and returns the color based on the source inputs and the
overlay's alpha value. If the overlay is anyway
transparent, we will see the background texture through
the overlay texture.
As an example of this, lets create a composite texture
that takes a
Marble
texture and merges it with a white background. In this
case, the
Marble
texture only produces the veins of the marble in a
specific color with varying alpha transparency. It is
supposed to be laid over another texture to provide a
background which is what this texture does.
public class MergeTest extends AbstractTexture {
private MergedTexture mixer;
public MergeTest() {
Marble marble = new Marble(RGBAColor.black());
SolidTexture background = new SolidTexture(RGBAColor.white());
mixer = new MergedTexture(background,marble);
}
public void getColor(double u, double v, RGBAColor value) {
mixer.getColor(u, v,value);
}
}
You can see the results below. The marble texture always returns a solid color, but the alpha value varies between 0 and 1 depending on the value returned from the marbling noise method. When the alpha value is less than 1, then it is mixed proportionally with the background texture.

When creating textures like the basic marble which is meant to be used in other textures, it is better to return a color with varying alpha rather than mixing the white and black in the marble texture. This is so that it can be placed over other textures and let the texture show through. This lets us create more re-usable textures.
Another alternative is to take one or more input sources
and return the average of the mixed results. We do this
by summing up the colors and then dividing by the number
of colors mixed into the result. We can do this with the
TextureMixer
class. A simpler version of this class is the
MixTexture
class which takes two textures and and optional mix
level (defaults to 0.5) and the result is based on :
Result = (SourceA * level) + (SourceB * (1-level))
The
MultiMergeTexture
can be used to compose multiple texture elements onto a
final texture which can then be composed onto a
background. These textures are merged in the order that
they are added. This next example takes a number of
marble textures and puts them into a
MultiMergeTexture
and composites the multiple marble textures into one
complex marble.
public static void main(String[] args) {
MultiMergeTexture mixer = new MultiMergeTexture();
mixer.getTextures().add(new UVRotate(new Marble(new RGBAColor(255,255,255,0.7),20.7,20,0.2,19,12,1),35));
mixer.getTextures().add(new UVRotate(new Marble(new RGBAColor(255,128,64,0.5),20.7,3,4,19,12,1),45));
mixer.getTextures().add(new Marble(new RGBAColor(200,200,200,0.6),1.5,5,7,4,10,1));
mixer.getTextures().add(new Marble(new RGBAColor(0,0,0,0.4),20,20,4,5,13,1.4));
mixer.getTextures().add(new UVRotate(new Marble(new RGBAColor(255,176,80,0.5),20.7,3,4,19,12,1),145));
mixer.getTextures().add(new Marble(new RGBAColor(255,176,80,0.5),10.5,10,4,5,20,1));
TextureViewer.show(new Background(mixer,new RGBAColor(122,80,67)));
}

Probably the easiest way of putting a texture onto a
background is to use the
Background
texture which takes a texture and overlays it on a solid
white background. You can use other colors in the
constructor, but white is the default.
Marble marble = new Marble(RGBAColor.black()); mixer = new Background(marble);
If you want to merge textures and layers using gradients, specifically by using gradients with the alpha channel, then the next section on filtering should help you create some more interesting effects.
The previous section looked at merging two textures
based on the calculated alpha value for one of them,
however, we can also use the
ChannelSignal
interface to merge textures together (as well as much
more). Channel signals are similar to textures in that
they take
(u,v)
input values, and return a result which is consistently
the same each time it is called for that
(u,v)
value. For signals however, we only return a single
double value instead of a color. That value can then be
processed by a texture that is able to use the value for
a number of things.
The
ChannelSignal
interface is as simple as the
Texture
interface we saw earlier. This interface is implemented
in an
AbstractChannelSignal
class.
public interface ChannelSignal {
public enum Channel {
RED, GREEN, BLUE, ALPHA;
}
double getValue(double u, double v);
}
Let's jump right in and create a simple channel signal. For our example, it will just return the u value that is passed in. That means that as we process the texture across the surface from left to right, the signal will change from 0 to 1.
public class ChannelTester extends AbstractChannelSignal {
public double getValue(double u, double v) {
return u;
}
}
We will test this with our
Threshold
texture which takes two source textures and a
ChannelSignal
and returns one texture if the signal is below a certain
threshold value, and returns another texture if the
signal is above a certain threshold value. The default
switch level is 0.5, which means that since our
ChannelSignal
just returns the
u
value, the output should switch from one texture to the
other around the middle of the surface texture.
public class ChannelMergeTest extends AbstractTexture {
private Texture texture;
public ChannelMergeTest() {
Texture marble = new ComplexMarble();
SolidTexture background = new SolidTexture(RGBAColor.blue());
texture = new Threshold(marble, background, new ChannelTester());
}
public void getColor(double u, double v, RGBAColor value) {
texture.getColor(u, v,value);
}
}
This isn't a very good example, but if we change the
source signal in the
ChannelTester
class to one of the built-in ones, e.g. the
NoiseSignal()
signal which returns a noise value for the given
(u,v)
values, we can create a slightly more interesting
texture.

Again, still not a very good texture, but it does offer us some possibilities. By using a Noisy signal with a threshold, we can let textures poke through other textures at random points.
Using Channel Signals has actually expanded the
ability to create re-usable components drastically.
For example, the
GradientSignalTexture
texture takes a
ColorGradient
and a
ChannelSignal
. For each
(u,v)
we calculate the output of the signal which results
in a double value. This value is then used to
interpolate the gradient to obtain the final texture
color. This texture has been used to rewrite a
number of the existing textures such as the
horizontal and vertical gradients (we use a
USignal
and
VSignal
to interpolate the gradient across the
(u,v)
values). We even used it to generate a
Mandelbrot
texture using a Mandelbrot
ChannelSignal
that returns the calculated fractal value for the
point on the texture.
We refactored a
MarbleSignal
signal from the original marble texture and we now
use it not only in the refactored
Marble
texture but in the
Flame
texture. The difference is that one takes a signal
and assigns it to the resulting color's alpha
channel while the other uses the value to
interpolate a gradient giving a colored marble
effect. We can easily re-use our signals for
different types of textures and they will probably
be used more in the future to create a more
pluggable system.
There are times when we want to modify the
(u,v)
inputs to reflect either scale, translation or rotation.
Scale multiplies the
(u,v)
value by a scalar value. Rotation rotates the values
around the point 0,0 and translation adds values to the
(u,v)
values. Each of these can useful effects on our
textures. Scaling can allow us to zoom in or out of the
texture, while rotation can give us a new angle on some
textures which by default look more vertical or
horizontal in nature. Translation can help us move the
texture around on the surface which can be used to move
more interesting parts of the texture into the center,
or also reduce the visibility of patterns based on noise
functions which are shared by two different textures.
Also, we can apply noise to the
(u,v)
inputs to disturb the values slightly.
Most of these textures are passive in that they obtain
the actual texture color value from a source texture we
pass in to the transformation texture using the
(u,v)
values once they have been transformed.
We can demonstrate the use of these textures using a
pattern based texture such as the
DirtyBrick
,
Checker
, or an image based texture.
The example below demonstrates the different types of transformations that can be performed.
// Rotation
Texture texture = new ImageTexture("c://camelback.jpg");
TextureViewer.show(new UVRotate(texture, 45));
// Scale
Texture texture = new ImageTexture("c://camelback.jpg");
TextureViewer.show(new UVScale(texture, 2,3));
// Translation
Texture texture = new ImageTexture("c://camelback.jpg");
TextureViewer.show(new UVTranslate(texture,0.2,0.5););

These transformations can be combined to produce more
sophisticated transformations.For example, the texture
is rotated around the
(0,0)
point, where you may want to transform around the center
of the texture. To do this, we first translate the
texture so the center is at
(0,0)
, then we rotate the texture and then translate the
texture back to the center. Like all transformations,
the ordering is important.
Texture texture = new ImageTexture("c://camelback.jpg");
texture = new UVTranslate(texture,-0.5,-0.5);
texture = new UVRotate(texture, 45);
texture = new UVScale(texture,2,2);
texture = new UVTranslate(texture,0.5,0.5);
TextureViewer.show(texture);
In this example, we also applied a scale to the rotated texture so we can see the whole image rotated in the middle of the texture.

Note that transformations and rotations could lead
to
(u,v)
parameters that are outside of the range 0 to 1. For
this reason, you need to use the
normalize(double value)
function in the
AbstractTexture
class. This method converts the out of range value
into an in-range value. For example,
normalize(-5.3)
returns 0.7 since -5.3 is 0.7 from the next lowest
integer value. This method can resolve your
(u,v)
values for textures where the size of the fractional
part is an issue (i.e. the
Checker
pattern).
In order to make things easier when using
transformations, we have a
UVTransformer
texture class which lets you use scale,rotation and
translation all in one texture. The transformations are
applied in the order of scale,rotation and then
translation. The transformations are automatically
applied to the texture around the center as opposed to
the top left corner where (0,0) is. This is similar to
the multi-transformation example shown above. Below is
an example of using this texture to create a series of
rounded boxes with a semi-transparent gradient fill.
Each box is moved to the right a little more and scaled
up in size as we go from left to right.
public static void main(String[] args) {
//make the color a gradient
Texture gradient = new HorizontalGradient(ColorGradient.buildFire());
//Put an alpha filter texture over it
gradient = new AlphaSignal(gradient, 0.75);
//make a rounded corner that uses our gradient as the color
RoundedCornerTexture box = new RoundedCornerTexture(gradient, 0.3);
//create the final merging texture
MultiMergeTexture mixer = new MultiMergeTexture();
for (int i = 0; i < 20; i++) {
//calcualte the scale and offset for this box
double offset = ((double) i / 22) - 0.5;
double scale = 14 - (i / 2);
//create the new transformer texture wrapped around the box
UVTransformer transformer = new UVTransformer(box, offset, 0, scale, scale, i * 3);
//add it to the final merged texture
mixer.getTextures().add(transformer);
}
//display it on a black background.
TextureViewer.show(new Background(mixer, RGBAColor.black()));
}
We use a lot of textures here to create an interesting
effect. We also see how we can use other simple textures
to modify existing ones. For example, we apply an
AlphaFilter
to the gradient so we don't have to build a new gradient
with a different alpha value. Also note that we re-use
the rounded corner box instance that we created to
re-display the box. We just apply a different
transformation to each instance. We can re-use the box,
and most textures, because there is no data contained in
the texture instances. They in essence act like
singletons or stateless beans.

We can transform the
(u,v)
points in other non-constant ways. One is to wrap the
texture in a
UVNoise
texture which takes the
(u,v)
input and generates noise from them to create 2 new
(u,v)
values.
Texture texture = new ImageTexture("c://camelback.jpg");
texture = new UVNoise(texture,1,5);
TextureViewer.show(texture);

Another way is to use the
UVNoiseTranslate
texture which takes the input
(u,v)
parameters and adds a little bit of noise to them so the
resultng
(u,v)
values are somewhere near where they were originally and
not some pseudo random values like the previous example.
// Less Noise
Texture texture = new ImageTexture("c://camelback.jpg");
texture = new UVNoiseTranslate(texture,4,4,0.3);
TextureViewer.show(texture);
// More Noise
Texture texture = new ImageTexture("c://camelback.jpg");
texture = new UVNoiseTranslate(texture,4,4,0.3);
TextureViewer.show(texture);

These two examples demonstrate the use of different
quantities of noise to offset the
(u,v)
values. One gives a slighly perturbed image while the
other is larger and looks like raindrops in a reflective
puddle type of image.
By default, when a texture is rendered, it is done so by spawning one or more threads. The number of threads is defaulted to the the number of processors available. This means that any signals or textures that you use to render a texture need to be thread safe because chances are that they are running in multiple threads on multiple processors. On the other hand, this will pretty much speed up the time taken to render the texture by the number of processors on the system.
As a general guide to creating thread safe textures and signals, make sure that they are immutable so that once you pass parameter values in to the signal/texture in the constructor, they cannot be changed by either the object itself or the calling code. The texture and signal objects should be stateless such that they carry no state from one call to the next. Any color objects passed to a texture are defensively copied so you can modify the object in calling code as it won't affect the texture holding a copy of it. All the textures and signals provided with the library to the best of my knowledge are thread safe with the caveat that they may use non-thread safe user defined textures or channels which are passed in to the signal or texture.