Table of Contents
This section deals with making some more textures based on the principles illustrated in the previous chapters. Most, if not all of these textures are available in the distribution as built-in textures.
Let's start with a fireball texture first. First, we'll create a texture which consists of a radial gradient which goes from the center of the texture towards the edges.
public class Fireball extends AbstractTexture {
private Texture texture;
public Fireball() {
this(buildFireGradient(), 14, 6, 0.2);
}
protected static ColorGradient buildFireGradient() {
ColorGradient gradient = ColorGradient.buildFire();
gradient.add(RGBAColor.black());
gradient.add(RGBAColor.black());
gradient.add(RGBAColor.black());
gradient.add(RGBAColor.black());
return gradient;
}
public Fireball(double scale, int octaves, double size) {
this(buildFireGradient(), scale, octaves, size);
}
public Fireball(ColorGradient gradient, double scale, int octaves, double size) {
texture = new RadialGradient(gradient);
}
public void getColor(double u, double v, RGBAColor value) {
texture.getColor(u, v,value);
}
}
We added addition black colors on the end of the gradient since we don't want the fire ball to cover the entire texture, we want it positioned more in the center. This creates a fairly predictable texture.

Now we want to perturb the
(u,v)
points using a noise function so we create a flame
effect. We do this using the
UVTranslate
texture which takes a source texture and uses it to
generate the texture colors by calling it with
(u,v)
values that have been adjusted by noise. We make this
change in the constructor where we decorate the radial
gradient texture with a UV noise translation texture
public Fireball(ColorGradient gradient, double scale, int octaves, double size) {
texture = new RadialGradient(gradient);
texture = new UVNoiseTranslate(texture,14,5,0.2);
}
This gives us a rather pleasing result with a nice glowing fireball in the middle of our texture

Now let's extend the example to make the fireball look like an eclipsed sun by inserting a big dark circle in the middle. We'll make this a composite texture and create the black circle as an anonymous class. We'll then overlay the black circle texture over the fireball and use transparency to show the fire around the planet.
public class SunEclipse extends AbstractTexture {
Texture texture;
public SunEclipse() {
Texture planet = buildPlanet();
Texture fireball = new Fireball();
texture = new MergedTexture(fireball, planet);
}
public void getColor(double u, double v, RGBAColor value) {
texture.getColor(u, v, value);
}
private Texture buildPlanet() {
//get the distance from the center of the texture,
//if < 10, it is opaque black
//if > 10.5 it is transparent
//else we interpolate the transparency between 10-10.5 to give
//it a nice transition
return new AbstractTexture() {
public void getColor(double u, double v, RGBAColor value) {
u = u - 0.5;
v = v - 0.5;
double dist = Math.sqrt((u * u) + (v * v));
dist = dist * 50;
if (dist < 10) {
value.setColor(0, 0, 0, 1);
} else {
if (dist > 10.5) {
value.setColor(0, 0, 0, 0);
} else {
//in between
double fd = dist - (int) dist;
fd = fd * 2;
value.setColor(0, 0, 0, 1 - fd);
}
}
}
};
}
}

We can use color gradients with some transparent colors to generated faded background textures. We start with a marble texture and overlay it with a color gradient that has the same color across the gradient, but different alpha values.
Texture marble = new ComplexMarble();
ColorGradient gradient = new ColorGradient();
gradient.add(new RGBAColor(16,32,64,0.3));
gradient.add(new RGBAColor(16,32,64,1));
gradient.add(new RGBAColor(16,32,64,1));
Texture overlay = new VerticalGradient(gradient);
Texture texture = new MergedTexture(marble, overlay);
TextureViewer.show(texture);

We can use one texture to drive the channel signal for another texture. In this example, we use our image texture to set the alpha value for another texture which is a solid color. Because the alpha value of this texture is driven from the input signal, we get the shape of the image poking through on the final texture. This is overlaid onto a background image which shows through depending on the alpha value of the overlay.
public static void main(String[] args) {
//get out image texture
Texture image = new ImageTexture("c://camelback.jpg");
//build a channel signal to return the red component of the image
ChannelSignal signal = new TextureSignal(image,Channel.RED);
//create an alpha filter based on a black background and the channel signal
Texture overlay = new AlphaSignal(SolidTexture.blackBackground(), new InvertSignal(signal));
//select the background texture
Texture background = SolidTexture.whiteBackground();
//merge the overlay and the background
Texture merged = new MergedTexture( background,overlay);
//show the texture
TextureViewer.show( merged);
}

We can alter the makeup of this texture by using different textures with the alpha filter and also for the background texture.
// Image A Texture background = new Fireball(); // Image B Texture overlay = new AlphaSignal(SolidTexture.whiteBackground(), new InvertSignal(signal)); Texture background = new ColorFilter(new ComplexMarble(), new RGBAColor(64,128,255)); //Image C Texture background = new UVTranslate(new Flame(Flame.greenFlame),1.2,0);

Let's use our noise functions to create some cloud textures and apply it to our sunset texture we created earlier. We'll use two different sets of clouds and let them grow smaller as they appear further in the distance.
We start with two gradients that are used to color the
cloud textures. These two gradients are used to create
two new
HorizonalGradient
textures which are then run through a
NoisyTexture
texture. The
NoisyTexture
texture takes an input and sets the alpha value to a the
result of a noise function. We take our two cloud
textures and merge them into a single
clouds
texture. This texture is then laid over the top of our
SunsetTexture
we created earlier.
public class CloudTexture extends AbstractTexture {
ColorGradient redCloudGradient;
ColorGradient lightCloudGradient;
MergedTexture clouds;
NoisyTexture redCloud;
NoisyTexture lightCloud;
MergedTexture mixer;
public CloudTexture() {
redCloudGradient = new ColorGradient()
.add(new RGBAColor(255, 240, 230))
.add(new RGBAColor(255, 140, 64));
lightCloudGradient = new ColorGradient()
.add(new RGBAColor(255, 250, 240))
.add(new RGBAColor(255, 255, 245));
redCloud = new NoisyTexture(
new HorizontalGradient(redCloudGradient));
lightCloud = new NoisyTexture(
new HorizontalGradient(lightCloudGradient));
//merge the clouds into one texture
clouds = new MergedTexture(lightCloud, redCloud);
//lay the clouds over the sunset background
mixer = new MergedTexture(new SunsetTexture(), clouds);
}
public void getColor(double u, double v, RGBAColor value) {
mixer.getColor(u, v, value);
}
If we look at the texture now, we get some flat clouds over the top of a distant sunset.

What we want to do is try and speed up the rate of the noise the 'further away' the cloud texture is. In this case, the clouds are further away the closer we get to the bottom of the texture (as v approaches 1). Also, the further away the clouds are, the more dense they appear. This is because when we look at clouds above us, we are looking through a thing sliver of cloud. Further away, our line of sight cuts through the cloud layer at an angle making it appear denser. To resolve this, we change the scale and size values of the noise functions the nearer to the bottom of the texture we get.
public void getColor(double u, double v, RGBAColor value) {
redCloud.setScale(2 + (v * 14));
lightCloud.setScale(0.4 + (v * 18));
redCloud.setSize(0.3 + (v * 1.2));
lightCloud.setSize(0.3 + v);
mixer.getColor(u, v, value);
}

Not a thoroughly realistic sunset, but it might suffice for an image where you need some kind of background which will be out of focus in the background, especially if you are using a 3D renderer which will handle depth of field for you. This image was composed in a paint package and the background was manually blurred.

We have a number of textures which take a
ChannelSignal
and use it to interpolate a
ColorGradient
. Typically, these are noise based values, but we can
also use other sources. In this example, we will use a
Mandelbrot generator in a
ChannelSignal
to give us fractal values.
public class Mandelbrot extends AbstractTexture {
private ColorGradient gradient;
private MandelbrotSignal signal;
public Mandelbrot(ColorGradient gradient, double startX, double startY, double endX, double endY) {
signal = new MandelbrotSignal(startX,startY,endX,endY);
this.gradient = gradient;
}
public void getColor(double u, double v, RGBAColor value) {
double val = calculateSignalFromFilter(u, v, signal);
gradient.interpolate(val,value);
}
}

As part of the constructor, the
Mandelbrot
texture will take start and end parameters for the range
to generate. Unless you have a mandelbrot viewer,
picking values can be hit and miss so here are some
examples.
Texture texture = new Mandelbrot(0.35, 0.35, 0.3507, 0.3507);
Texture texture = new Mandelbrot(-1.487,-0.006950,-1.46944,0.007);
Texture texture = new Mandelbrot(-0.86429,-0.2578125,-0.5671,-0.01406);
Texture texture = new Mandelbrot(-0.75401,-0.23191,-0.68671,-0.1735);
Texture texture = new Mandelbrot(-0.745725,-0.215732,-0.728179,-0.200524);

This is another example of wrapping the texture logic in a Channel Signal that can be re-used in other textures. We can use the channel signal to drive the alpha value for merging two textures.
public static void main(String[] args) {
Texture texture = new AlphaSignal(new ComplexMarble(),new MandelbrotSignal(-0.745725,-0.215732,-0.728179,-0.200524));
MergedTexture text = new MergedTexture(new CloudTexture(), texture);
TextureViewer.show(text);
}

This texture involves using
NoiseSignal
textures to toggle between different textures to create
a camouflage effect. Let's start with 3 simple colors to
use in our texture and two
SmoothThreshold
textures switching between two solid color textures. The
SmoothThreshold
texture is the same as the
Threshold
texture except that when the texture switches, it does
so smoothly instead of toggling instantly between the
two.
SolidTexture green = new SolidTexture(new RGBAColor(119,139,111));
SolidTexture brown = new SolidTexture(new RGBAColor(119,110,100));
SolidTexture beige = new SolidTexture(new RGBAColor(166,150,116));
Texture cam1 = new SmoothThreshold(green,beige, new NoiseSignal(4,10,1,10),0.42);
Texture cam2 = new SmoothThreshold(beige,brown, new NoiseSignal(4,10,1,10),0.25);
Now let's take these two textures
cam1
and
cam2
and toggle between the two. Before we do that let's
apply a rotation and scale to the UV values before
calculating the texture. This lets us scale and rotate
the UV value per texture so we get skewed and rotated
results.
Texture rotatedCam1 = new UVRotate(new UVScale(cam1,1,4),20);
Texture rotatedCam2 = new UVRotate(new UVScale(cam2,1,6),34);
texture = new SmoothThreshold(rotatedCam1, rotatedCam2, new NoiseSignal(6,4,1));
texture = new Dirty(texture,RGBAColor.black(),0.32);
We take our rotated and scaled textures and create a new
Threshold
texture which will merge them based on another noise
signal.
Finally we add a
Dirty
texture to the final texture. This creates colored noise
(black in this case) and applies it to the source
texture with a given strength. In this case, we are
using it to just dirty up the final texture.
public class Camouflage extends AbstractTexture {
private Texture texture;
public Camouflage() {
SolidTexture green = new SolidTexture(new RGBAColor(119,139,111));
SolidTexture brown = new SolidTexture(new RGBAColor(119,110,100));
SolidTexture beige = new SolidTexture(new RGBAColor(166,150,116));
Texture cam1 = new SmoothThreshold(green,beige, new NoiseSignal(4,10,1,10),0.42);
Texture cam2 = new SmoothThreshold(beige,brown, new NoiseSignal(4,10,1,10),0.25);
Texture rotatedCam1 = new UVRotate(new UVScale(cam1,1,4),20);
Texture rotatedCam2 = new UVRotate(new UVScale(cam2,1,6),34);
texture = new SmoothThreshold(rotatedCam1, rotatedCam2, new NoiseSignal(6,4,1));
texture = new Dirty(texture,RGBAColor.black(),0.32);
}
public void getColor(double u, double v, RGBAColor value) {
texture. getColor(u*1.3, v*1.3,value);
}
}

You could create a huge image of this texture and use it in 3D modelling applications to texture map tanks and so on.
There are times when you might not need to create a brand new Signal or Texture class where you can simple use anonymous classes to implement the functionality. Doing so is easy with our single method interfaces.
//create anonymous signal
ChannelSignal signal = new ChannelSignal() {
public double getValue(double u, double v) {
return Math.sin(u * v * 10);
}
};
Texture texture = new GradientSignalTexture(signal,ColorGradient.buildFire());
TextureViewer.show( texture);

Let's try making a map out of our noise function and shade the landscape according to the height and add in a sea level at which point we just shade the texture blue.
We'll start with a
Map
texture class that takes a gradient, a sea color, and
the usual noise parameters for scale and octaves, and a
value indicating where the sea level is. To calculate
the map, we'll calculate a noise value based on the
(u,v)
values, and adjust it so the values fill the range 0..1
a little better. Since the value could go outside the
0..1 range, we will clamp it. Finally, we check the
height value and if it is above sea level, we use a map
color. If it is below sea level, we use the sea color.
We don't want to fade into the sea color since the edge
of the water on land is a fairly hard edge.
double height = noise.fbmNoise2(u * scale, v * scale, octaves);
height = height * 1.65;
height = height - 0.25;
height = GraphUtils.clamp(height);
if (height < seaLevel) {
value.setColor(seaColor);
} else {
value.setColor(gradient.interpolate(height));
}

If you want to generate a high resolution image outside
of the viewer, and save it to disk for printing or using
as a background, you can do so by going directly to the
TextureImage
class which is the foundation class for rendering
textures that the
TextureViewer
class uses. We simply create an instance and pass a
resolution into the constructor and call the
render
method. Once the image has rendered to its internal
canvas, we can then save the image, or in the case of
the viewer, render it in the window. Note that since the
renderer is multi-threaded by default, the
renderAndWait
method must be used so it will not execute
asynchonously. If we use the
render
method, then we will probably end up saving the image
when it is only 10% of the way through.
public class SaveFileDemo {
public static void main(String[] args) {
TextureImage textureImage = new TextureImage(240, 240);
Texture mand = new Mandelbrot(-0.86429,-0.2578125,-0.5671,-0.01406);
//we want 4X4 anti-aliasing
textureImage.setAntiAliased(true);
textureImage.renderAndWait(mand);
textureImage.saveAsPNG("c:/myImage.png");
}
}
It could be quite easy to go overboard with using
signals in textures and use them for every single
constant numerical value. We have a
ConstantSignal
signal class that just returns a single value that could
be used for default constant values. For example, we
could make the speed or octaves of a noise texture
dependent on the
u,v
values. As an example, we will create a
UVRotation
texture where the angle of rotation is dependent on a
ChannelSignal
used.
public class UVSignalRotateTexture extends AbstractTexture {
private ChannelSignal signal;
private Texture source;
public UVSignalRotateTexture(Texture source, ChannelSignal signal) {
this.signal = signal;
this.source = source;
}
public void getColor(double u, double v, RGBAColor value) {
//get the angle from the signal
double radAngle = calculateSignalFromFilter(u, v, signal);
//do the rotation
double ru = Math.cos(radAngle) * u - Math.sin(radAngle) * v;
double rv = Math.sin(radAngle) * u + Math.cos(radAngle) * v;
//get the final color with the newly rotated u,v values
calculateColorFromTexture(ru, rv, source,value);
}
}
We get a value from the signal and then use this to
drive the angle of rotation. Let's try it out using a
USignal
signal that returns the value 0 to 1 as it moves across
the texture.
public static void main(String[] args) {
Texture texture = new DirtyBrick();
ChannelSignal signal = new USignal();
texture = new UVSignalRotateTexture(texture, signal);
TextureViewer.show(texture);
}

Again, we gain the benefits of a pluggable system where
we can plug on signal or texture into another and use
that new value. We can plug this signal into a
NoisySignal
signal which will add random elements into it. We also
changed it from a
USignal
to a
VSignal
.
public static void main(String[] args) {
Texture texture = new DirtyBrick();
ChannelSignal signal = new VSignal();
signal = new NoisySignal(signal, 12, 8, 0.15);
texture = new UVSignalRotateTexture(texture, signal);
TextureViewer.show(texture);
}

We could make every single numerical parameter in a
texture a signal parameter, but it would be over the
top. However, it does indicate how useful the
ChannelSignal
architecture can be. We also have a class which lets you
provide
ChannelSignals
to make up the red,green, blue, and alpha channels. You
can pass in either 1 signal to be used for all channels,
or one for each channel.
Listing A:
public static void main(String[] args) {
ChannelSignal signal1 = new MandelbrotSignal(-1.487, -0.006950, -1.46944, 0.007);
ChannelSignal signal2 = new MandelbrotSignal(0.35, 0.35, 0.3507, 0.3507);
ChannelSignal signal3 = new MandelbrotSignal(-0.75401, -0.23191, -0.68671, -0.1735);
ChannelSignal signal4 = new SineWave(100, 10, 2);// new ConstantSignal(1);
Texture texture = new ChannelTexture(signal1, signal2, signal3, signal4);
TextureViewer.show(new Background(texture, RGBAColor.black()));
}
Listing B:
public static void main(String[] args) {
Texture image = new ImageTexture("c://camelback.jpg");
ChannelSignal signalRed = new AnimalStripe();
ChannelSignal signalGreen = new TextureSignal(image,ChannelSignal.Channel.GREEN);
ChannelSignal signalBlue = new TextureSignal(image,ChannelSignal.Channel.BLUE);
ChannelSignal signal4 = new ConstantSignal();
signal4 = new InvertSignal(new RadialSignal(10));
Texture texture = new ChannelTexture(signalRed, signalGreen, signalBlue, signal4);
TextureViewer.show(new Background(texture, RGBAColor.black()));
}
}
Listing C (variation on B):
public static void main(String[] args) {
Texture image = new ImageTexture("c://camelback.jpg");
ChannelSignal stripe = new AnimalStripe();
ChannelSignal imageRed = new TextureSignal(image,ChannelSignal.Channel.RED);
ChannelSignal defaultValue = new ConstantSignal(1);
ChannelSignal signalRed = new SignalThreshold(defaultValue,imageRed,stripe);
ChannelSignal signalGreen = new TextureSignal(image,ChannelSignal.Channel.GREEN);
ChannelSignal signalBlue = new TextureSignal(image,ChannelSignal.Channel.BLUE);
ChannelSignal signal4 = new ConstantSignal();
signal4 = new InvertSignal(new RadialSignal(0.9));
Texture texture = new ChannelTexture(signalRed, signalGreen, signalBlue, signal4);
TextureViewer.show(new Background(texture, RGBAColor.black()));
}
}

This section describes how to hook up textures and
signals using Spring. This way, we could modify our
textures without recompiling, and we can re-use some of
the items much easier. This demonstration describes the
content of the
application.xml
configuration part as well as the code required to fetch
the final texture bean.
To start with, we'll create a fire gradient using a
ColorGradient
. Not only do we have to create a bean for the gradient,
but also for the colors it contains.
<bean name="fireGradient" class="org.texturemaker.geom.ColorGradient">
<property name="colors">
<list>
<ref bean="blackColor"/>
<ref bean="blackColor"/>
<ref bean="redColor"/>
<ref bean="orangeColor"/>
<ref bean="yellowColor"/>
<ref bean="whiteColor"/>
</list>
</property>
</bean>
<bean class="org.texturemaker.geom.RGBAColor" name="whiteColor">
<property name="red" value="255"/>
<property name="green" value="255"/>
<property name="blue" value="255"/>
</bean>
<bean class="org.texturemaker.geom.RGBAColor" name="orangeColor">
<property name="red" value="255"/>
<property name="green" value="128"/>
<property name="blue" value="0"/>
</bean>
<bean class="org.texturemaker.geom.RGBAColor" name="yellowColor">
<property name="red" value="255"/>
<property name="green" value="255"/>
<property name="blue" value="0"/>
</bean>
<bean class="org.texturemaker.geom.RGBAColor" name="redColor">
<property name="red" value="255"/>
<property name="green" value="0"/>
<property name="blue" value="0"/>
</bean>
<bean class="org.texturemaker.geom.RGBAColor" name="blackColor">
<property name="red" value="0"/>
<property name="green" value="0"/>
<property name="blue" value="0"/>
</bean>
This creates the fire gradient that we need. We will
construct our fire using a chain of textures to modify
the basic horizontal fire gradient. We start with a
NoisySignal
that uses a
VSignal
signal as its source signal. This is used to disturb the
(u,v)
values used to calculate the fire color in the
ColorGradient
<bean name="vSignal" class="org.texturemaker.signals.VSignal"/>
<bean name="noisySignal" class="org.texturemaker.signals.NoisySignal">
<property name="source" ref="vSignal"/>
<property name="size" value="0.3"/>
<property name="octaves" value="15"/>
<property name="scale" value="15"/>
</bean>
<bean name="verticalFire" class="org.texturemaker.textures.signal.GradientSignalTexture">
<property name="gradient" ref="fireGradient"/>
<property name="signal" ref="noisySignal"/>
</bean>

This looks ok, but we don't have much intensity (white
parts) of the fire at the bottom, and it looks fairly
flat with no flames leaping upwards. We can use a
translation to move the fire upwards so we can see more
of the bottom, but while we are at it, we could
introduce some more noise to the fire. To do this, we
add another
NoisySignal
that is fed into our original noisy signal. This time,
we give it a negative size value so the fire shifts
upwards as the noise is introduced.
<bean name="noisySignal" class="org.texturemaker.signals.NoisySignal">
<property name="source" ref="offsetNoisySignal"/>
<property name="size" value="0.3"/>
<property name="octaves" value="15"/>
<property name="scale" value="15"/>
</bean>
<bean name="offsetNoisySignal" class="org.texturemaker.signals.NoisySignal">
<property name="source" ref="vSignal"/>
<property name="size" value="-0.3"/>
<property name="octaves" value="5"/>
<property name="scale" value="5"/>
</bean>

Now we have some more of a white heat at the bottom, but
it still looks rather flat. To remedy this, we can scale
the image so the
v
value changes much faster.
<bean name="scaleUV" class="org.texturemaker.textures.uv.UVScale">
<constructor-arg index="0" ref="verticalFire"/>
<constructor-arg index="1" value="2.5"/>
<constructor-arg index="2" value="1"/>
</bean>
This time, we also used the constructor arguments to set
the bean up. In order to view our texture, we need to
initialize the Spring environment, pass it the
application.xml
file and request and instance of the texture we want to
display.
public static void main(String[] args) {
Resource res = new ClassPathResource("applicationContext.xml");
XmlBeanFactory factory = new XmlBeanFactory(res);
Texture bean = (Texture) factory.getBean("scaleUV");
TextureViewer.show(bean);
}
It is really that simple. Using Spring to set up the
textures could be very useful for defining a library of
often used textures, that can just be imported into your
spring configuration. It can even let you define
commonly re-used textures such as
NoisySignal
instances to re-use for putting a subtle amount of noise
on a signal for example.
