Hello programming addicts, game enthusiasts and fellow insane people
I've done a lot of playing around with procedural generation. Originally I started with fractals but because of the huge amounts of memory required to generate them and the fact you have to generate the whole thing in order to do anything with it, I never went very far. To be honest fractals are only good for small textures of landscapes (unless you've got a huge amount of memory to play with) and they would be useless for volumetric textures! I wanted something a little more usable.
I did, however, find a different method of generating procedural data called Perlin Noise, named after Ken Perlin who developed the technique back in the early 80's.
The process works in a similar way to fractals by calculating many different layers of random data and blending them together, but it works one position or set of co-ordinates at a time. It is slower then fractals but it's a lot more efficient since you can work out a small section of an image just as easily as a massive section. Plus there's no need to put the results through a series of filters, like fractals often require.
Ok, I'm first going to show you a demo program using the function I've created and then detail how to use the function yourself. (aren't I kind?)
One thing I neglected to remove from it was the multi-sampling, basically anti-aliasing, don't worry it's not important it's just a little fiddle to remove and this is only a demo, the multi-sampling isn't a part of the perlin function.
Anyway, what this program does it use the perlin noise function to generate a sphere-wrap texture to simulate landscapes and oceans, so when it's wrapped around a sphere you can't see the poles (the top and bottom caps). If you're feeling adventurous you could figure out how it does it. But basically it takes the co-ordinate of the texture and works out where on the surface of a sphere they would be in 3d space and then uses those co-ordinates to get a number from the perlin noise.
Think of it like grapping the colour of marble stone fro within the structure without having to carve you're way in.
It uses this like a height-map across the sphere to work out what areas are high and low, blah blah blah..
Once the texture is rendered it wraps it around a normal DBP sphere but made out of 32 rows and 64 columns. Just to show it if you hold the spacebar with the sphere is rotating you'll see it in wireframe.
One thing to remember also is if the program lots up for any reason or it's taking too long to draw the image press the spacebar and it should quit out.
WARNING: Before you run this program be aware that it uses a non-square image (1024/512) so older gfx cards may not like it. Also I'm running a 2ghz cpu here and it takes about 15-20 seconds to draw the image, so be patient if you're cpu is slower then mine.
Ok, just open the following code snippet, copy & paste it into DBP and run it:
Rem ***** Main Source File *****
set display mode 1024,768,32
hide mouse
sync on
sync rate 0
rem create arrays for perlin generator
dim s#(15,2)
dim r#(63,63,63)
rem prepare the perlin
prepare_perlin(1,0.5)
rem multisampling controller
multi = 1
milti = multi - 1
malt# = multi ^ 2
malt# = 1 / malt#
xsize = 1024
ysize = 512
rem get the base sizes
xsize# = xsize * multi
ysize# = ysize * multi
rem get the scalers
xscaler# = 360
yscaler# = 180
xscaler# = xscaler# / xsize#
yscaler# = yscaler# / ysize#
width# = 2
rem piece details
pxs = 1023
pys = 511
xof = 0
yof = 0
rem loop the y position
for x = 0 to pxs step 1
rem prepare screen for drawing
lock pixels
rem loop the x
for y = 0 to pys step 1
rem reset colours
g = 0
b = 0
rem get the scaled sizes
xp = (x + xof) * multi
yp = (y + yof) * multi
rem do the multisampling
for a = 0 to milti
rem get the baring around the sphere
ba# = xp + a
ba# = (ba# + 0.5) * xscaler#
xp# = cos(ba#)
zp# = sin(ba#)
for s = 0 to milti
rem get the pitch around the sphere
pa# = yp + s
pa# = ((pa# + 0.5) * yscaler#)
po# = sin(pa#) * width#
rem get the positions in the space
x# = (xp# * po#) + 5
y# = (cos(pa#) * width#) + 5
z# = (zp# * po#) + 5
rem get the perlin result for that part
h = int(perl(x#,y#,z#,8) * 255)
rem cap the value
if h => 255 then h = 255
if h <= 0 then h = 0
rem add appropriate colour
if h => 136
inc g,h
else
inc b,h
endif
rem failsafe quit
if spacekey() then end
next s
next a
rem scale down the colours
g = g * malt#
b = b * malt#
rem get the colour
ink rgb(0,g,b),0
rem put a dot there
dot x,y
next y
unlock pixels
sync
next x
get image 1,0,0,1024,512,1
make object sphere 1,1,32,64
texture object 1,1
backdrop on
color backdrop 0
do
turn object left 1,0.1
pitch object up 1,0.2
if spacekey()
set object wireframe 1,1
else
set object wireframe 1,0
endif
sync
loop
rem end
rem perlin function
function perl(x#,y#,z#,octaves)
rem make sure the pass value is zerod
h# = 0
rem shift octaves down to input works from 1 but system works from 0
dec octaves,1
rem make sue octaves are an ecceptable value
if octaves <= 0 then octaves = 0
if octaves => 15 then octaves = 15
rem loop the octaves
for oct = 0 to octaves
rem grab the frequency and amplitude for this
fre# = s#(oct,0)
amp# = s#(oct,1)
rem convert the co-ordinates into steps
x = int(x# * fre#)
y = int(y# * fre#)
z = int(z# * fre#)
rem get the inbetween co-ords
xb# = sine((x# * fre#) - flo(x))
yb# = sine((y# * fre#) - flo(y))
zb# = sine((z# * fre#) - flo(z))
xa# = 1 - xb#
ya# = 1 - yb#
za# = 1 - zb#
rem get the values for the 8 corners
v000# = vil(x,y,z) * xa# * ya# * za#
v100# = vil(x+1,y,z) * xb# * ya# * za#
v010# = vil(x,y+1,z) * xa# * yb# * za#
v001# = vil(x,y,z+1) * xa# * ya# * zb#
v101# = vil(x+1,y,z+1) * xb# * ya# * zb#
v110# = vil(x+1,y+1,z) * xb# * yb# * za#
v011# = vil(x,y+1,z+1) * xa# * yb# * zb#
v111# = vil(x+1,y+1,z+1) * xb# * yb# * zb#
rem add it on
inc h#,(v000# + v100# + v010# + v001# + v101# + v110# + v011# + v111#) * amp#
next oct
rem scale it down
h# = h# * s#(octaves,2)
endfunction h#
rem function to get the random value of a co-ordinate
function vil(x,y,z)
rem control edges
if x < 0 then x = x - (int((x / 64) - 1) * 64) else x = x - (int(x/64) * 64)
if y < 0 then y = y - (int((y / 64) - 1) * 64) else y = y - (int(y/64) * 64)
if z < 0 then z = z - (int((z / 64) - 1) * 64) else z = z - (int(z/64) * 64)
rem get the number
v# = r#(x,y,z)
endfunction v#
rem return an integer as a floating point
function flo(a)
b# = a
endfunction b#
rem function to turn a straight 0 - 1 into a sine curved 0 - 1
function sine(v#)
rem perform the change
v# = (1 - cos(v# * 180)) * 0.5
endfunction v#
rem function to prepare data for perlin noise
function prepare_perlin(seed,persistance#)
rem set the seed value
randomize seed
rem create seed data
for x = 0 to 63
for y = 0 to 63
for z = 0 to 63
z# = rnd(10000)
r#(x,y,z) = (z# * 0.0001)
next z
next y
next z
rem prepare octave data
for z = 0 to 15
rem work out the frequence of the octave
s#(z,0) = 2 ^ z
rem get the amplitude
s#(z,1) = persistance# ^ z
rem work out the maximum amplitude of
s#(z,2) = 0
for x = 0 to z
inc s#(z,2),s#(x,1)
next x
s#(z,2) = 1 / s#(z,2)
next z
endfunction
Right that's that, now to show you all the important stuff. One thing to remember is there are a number of functions here, so I'll explain all of them, where they're needed and what they're for, that way you can use this stuff in your programs.
First of all you need to include a couple of lines at the beginning of your program:
rem create arrays for perlin generator
dim s#(15,2)
dim r#(63,63,63)
Basically the first array is the octave data for the noise generator, think o the number of octaves as the number of layers in to the noise, I'll explain it later on.
The second one is the pre-calculated random data. Unfortunately I couldn't get the program to throw out the random numbers on the fly, for some reason the numbers weren't random, but this method is faster anyway and it doesn't up much memory, so don't worry.
Now I'll detail the first function you need:
rem function to prepare data for perlin noise
function prepare_perlin(seed,persistance#)
rem set the seed value
randomize seed
rem create seed data
for x = 0 to 63
for y = 0 to 63
for z = 0 to 63
z# = rnd(10000)
r#(x,y,z) = (z# * 0.0001)
next z
next y
next z
rem prepare octave data
for z = 0 to 15
rem work out the frequence of the octave
s#(z,0) = 2 ^ z
rem get the amplitude
s#(z,1) = persistance# ^ z
rem work out the maximum amplitude of
s#(z,2) = 0
for x = 0 to z
inc s#(z,2),s#(x,1)
next x
s#(z,2) = 1 / s#(z,2)
next z
endfunction
This function does what it says on the name, it prepares the arrays detailed above for the Perlin function. If you don't use this function then you won't get much from the Perlin function. You only need to use this function once, however if you want to re-seed the noise then use it again.
Anyway, you need to provide this function with 2 parametres, the first being the seed, you'll notice the use of the 'randomize' command early on.
The second perametre is the persistance of the noise, in basic terms think of it as the roughness of the noise. If you mapped the results of the noise across an image then a low persistance would result in a very smooth texture, while a high persistance would create something a little more random. Most people still with 0.5 or there-abouts, if you set the persistance higher then 1 or negative you'll get some wierd and most likely useless results.
Next I'll go through the 'other' functions, the ones used by the Perlin function itself:
rem function to get the random value of a co-ordinate
function vil(x,y,z)
rem control edges
if x < 0 then x = x - (int((x / 64) - 1) * 64) else x = x - (int(x/64) * 64)
if y < 0 then y = y - (int((y / 64) - 1) * 64) else y = y - (int(y/64) * 64)
if z < 0 then z = z - (int((z / 64) - 1) * 64) else z = z - (int(z/64) * 64)
rem get the number
v# = r#(x,y,z)
endfunction v#
rem return an integer as a floating point
function flo(a)
b# = a
endfunction b#
rem function to turn a straight 0 - 1 into a sine curved 0 - 1
function sine(v#)
rem perform the change
v# = (1 - cos(v# * 180)) * 0.5
endfunction v#
Don't worry about what these do, just include them with the Perlin function, otherwise it won't work.
Finally, the actual Perlin function itself, this is where all the work is done:
rem perlin function
function perl(x#,y#,z#,octaves)
rem make sure the pass value is zerod
h# = 0
rem shift octaves down to input works from 1 but system works from 0
dec octaves,1
rem make sue octaves are an ecceptable value
if octaves <= 0 then octaves = 0
if octaves => 15 then octaves = 15
rem loop the octaves
for oct = 0 to octaves
rem grab the frequency and amplitude for this
fre# = s#(oct,0)
amp# = s#(oct,1)
rem convert the co-ordinates into steps
x = int(x# * fre#)
y = int(y# * fre#)
z = int(z# * fre#)
rem get the inbetween co-ords
xb# = sine((x# * fre#) - flo(x))
yb# = sine((y# * fre#) - flo(y))
zb# = sine((z# * fre#) - flo(z))
xa# = 1 - xb#
ya# = 1 - yb#
za# = 1 - zb#
rem get the values for the 8 corners
v000# = vil(x,y,z) * xa# * ya# * za#
v100# = vil(x+1,y,z) * xb# * ya# * za#
v010# = vil(x,y+1,z) * xa# * yb# * za#
v001# = vil(x,y,z+1) * xa# * ya# * zb#
v101# = vil(x+1,y,z+1) * xb# * ya# * zb#
v110# = vil(x+1,y+1,z) * xb# * yb# * za#
v011# = vil(x,y+1,z+1) * xa# * yb# * zb#
v111# = vil(x+1,y+1,z+1) * xb# * yb# * zb#
rem add it on
inc h#,(v000# + v100# + v010# + v001# + v101# + v110# + v011# + v111#) * amp#
next oct
rem scale it down
h# = h# * s#(octaves,2)
endfunction h#
I know, it's not much code (I could
possibly enter this into the 20 liner section), anyway, to get this function to work there are 4 perametres:
x#,y# and z# are the 3d co-ordinates within the noise you want to get the value of. Just like sampling a normal texture in 2d this is like sampling a texture in 3d. Give it the co-ordinates and it returns a value for you.
The final perametre is 'octaves' (mentioned earlier), this tells the generator how many octaves or layers you want. In a way this controls how detailed the noise is, the higher the octaves the more layers are processed. When mapped as a texture this produces more fine detail, however it can easily go a hell of a lot further then you could draw. I've already tested the system at 16 octaves on the program above, and it could've (with several gb of ram) rendered the same image at bigger then 65536/32786 and it would still be giving highly detailed shore-lines! Increasing the octaves will slow the whole process done. These numbers count from 1, so you literally input the number of octaves you want.
Ok, before you go and try to use this function yourself, let me explain a few key things.
The most important thing you need to know about the function is it returns a floating point value between 0 and 1. The demo program above multiplies this by 255 and then turns it into an integer so it can be used for a colour. Rememeber this trying to manipulate the result.
This function works just like sampling a texture, so to get an image you need to sample a lot of points within the noise and map the results into an image. This is why the system is so slow (or can be), it needs to do a lot of processing to get the result for just one point.
I would advise to keeping your sample area quite small, this is because the co-ordinates used to sample do cover a lot of the noise. I don't know exactly how to explain it, but if you play with the function you'll get the hang of it.
It's also a good idea to keep all you're sample co-ordinates positive, the function will accept any 3d co-ordinates, but if you start going negative some wierd things may appear.
Also try to keep the sample area near to the origin (co-ordinates 0,0,0) this will help to make sure the results don't get degraded by the inaccuracies of floating point variables.
I can't think of anything else, and before I even high the post button I know this is a very long post! I dread to think what spelling mistakes I've missed!
Ok, to finish this is the result of several months worth of little bits of programming, one or two hours every now and then, several re-writes of the code, one or two migraines (
real migraines, not just bad headaches!) and a lot of playing around with the results this function can produce.
If anyone (possibly wishful thinking) can find a use for this, I'd appreciate a mention in the credits, and it'd probably be a good idea to mention the use of Ken Perlin's Noise as well.
Enjoy!
Don't look at me like that!
You're just jealous because the voices are talking to me!