Tuesday, 21 August 2018

Creative Uses for Not Quite Random Noise

This month we looked at creative uses for not quite random noise, including a brief insight into how Perlin noise works.

Noise is an important algorithm for digital artists, 3d designers and is used widely in visual effects.


This month there was no video recording of the talk, so this blog will be a little more narrative than usual.

The slides are at https://goo.gl/eYgqq6 and contain links to code and further references.


Motivation - When Random Isn't Useful

We started by looking at a very simple landscape.


We asked ourselves how we could generate such a landscape using our computers. An initial suggestion is to use random numbers for the height (altitude) of each part of the terrain. That's not an entirely bad suggestion.

We looked at an example of heights created by purely random numbers. The results didn't look very realistic.


We discussed what would need to be improved to make the terrain more like a natural landscape. The main idea was that the jump between successive values was too high, and a more realistic landscape would moderate the changes. In other words, the jump up or down between consecutive values wouldn't be too large.


This is a problem that something called noise solves. It is random, but no so random, because the values vary more smoothly. Noise values are not independent of the values before and after them, unlike purely random numbers which are totally independent of each other.

The results were much more realistic.


You can see the very simple p5js code here: https://www.openprocessing.org/sketch/501546


Why Noise Needs a Parameter

Th audience was asked to pick a random number. That was easy enough. The audience was then asked to pick a noise value.  That wasn't so easy. The reason is important.

To pick a noise value, we or. the computer, needs to know were we are in relation to neighbouring noise values. Without this context, we have no way of making sure the number we pick is not too different from the neighbouring ones.

One way to know this context is to use a parameter to describe how far long the sequence of noise values we are.


And that's why the noise() function provided by your favourite library takes a parameter. Noise values for similar parameters will be similar.

To encourage us to think about this, we considered what the noise parameter was that created these rings which have had noise added to them.


An initial thought might be that the radius of these circles has noise added using the radius as a parameter. This isn't correct, because the radius is the same all the way around the circles. What various around the circles? The angle. So the radius that is drawn is the basic circle radius plus noise(angle).


Two Dimensional Noise

Before thinking about two-dimensional noise, we looked at 2-dimensional randomness. The following shows a 2-dimensional array filled with random numbers, represented by colours from a grey scale.


As expected, the image is random, with no intentional pattern.

In contrast, 2-dimensional noise does have a discernible structure. The following is the same grid but with noise values deciding the colour of each pixel. The blue to white scale makes the image look like a photo of clouds.


Just as before, the noise values don't vary wildly, but change in a more moderated way as we follow them up or across the grid. It's easy to see that a 2-dimensional noise function takes 2 parameters so that it knows how far along and up the sequence of noise values it is.

The code for these clouds is at https://www.openprocessing.org/sketch/502021.


3D Landscape

Thinking back to the green landscape we started with, why don't we try again to recreate it more realistically. We don't actually need to implement a proper 3-dimensional model with perspective and so on. We can just have layers of 2-dimensional "fences" which have their height set using 2-dimensional noise.


We can start with a grid of points, which will be the points which will be raised (or lowered) according to the noise values at that position.


The results are impressive, given the simplicity of the idea.


That really does look like some kind of alien terrain, perhaps a scan of a planet's surface before landing...


Multiple Octaves of Noise

By stretching or squishing the parameters so they change slow for faster, we can control how lumpy or smooth the surface is. We saw this earlier in the talk with a simple 1-dimensional noise too.


It is common practise to combine different "octaves" of noise to have a result that has both a macro structure as well as finger-grained micro-structural detail.

You might be familiar with this idea from music or electronic signal processing.


Here's a comparison of a smooth landscape with one that has had finer-grained higher-frequency noise added to it.



Refining the Landscape

That landscape is simple and very effective. But we can do more with it.

Here is the landscape generated with 800 points. To avoid over saturation, each dot has been drawn with a high translucency. This allows detail to re-emerge even with large numbers of points being drawn - a broadly applicable technique.


The code for this sketch is at https://www.openprocessing.org/sketch/504970.

We can use the noise values, not just to determine the landscapes height, but also the colour. Here is an example showing more Earth-like greens and blues.


We can even cut-off the lower values to create what looks like a lake or a sea.


After seeing these examples, it's not a surprise that noise is used to generate landscapes in games and films.


Simple Textures

We looked at how noise can be used to generate textures - in fact, a common use for noise. Here is the simple starting point showing rings created by sin waves using the distance from the centre of the canvas as a parameter.


That's fairly regular as we'd expect. We can adjust the colour that's drawn using 2-dimensional noise that takes the (x,y) position as parameters. The result looks remarkably like wood or a sliced rock.


It doesn't take much to refine this further to make it look like polished wood - a slight change of colour palette and a vertical squish.


The code for this wood effect is at https://www.openprocessing.org/sketch/577875.

We also looked at an agate texture which is just the above but with a green-blue colour scale. We evens saw the use of noise to create a lumpy fog effect to be used in ray-tracing a 3-d scene.

Noise to create texture is a very popular technique, used directly by coders or indirectly by using modelling tools which provide textures based on noise.



Genuinely 3-Dimensional Noise

So ar we've only used 1-dimensional and 2-dimensinal noise, even when we're creating 3-dimensional scenes. The height values for the landscape are based on noise(x,y) for example.

It is possible to have 3-dimensional noise, based on 3 parameters.


The results can be used to create 3-dimensional structures. Some games use noise to create worlds which include caves.



Animated Noise

An interesting idea is to take 2-d slices of 3-d noise, and use that 3-rd parameter as time, or frame count. This way we can animate the 2-d slices.

We expect the resulting patterns to be smoothly changing because noise should vary smoothly along all its dimensions, inlacing any we consider time.

Here is an example of moving lava created by taking 2-d slices of 3-d noise and using a threshold to decide the colour.


That's rather effective! The simple code for this lava is at https://www.openprocessing.org/sketch/577863.


How Perlin Noise Actually Works

Many guides sadly don't give any insight into how this really useful not-so-random noise is actually created.

I wrote a blog explaining it on another blog http://makeyourownalgorithmicart.blogspot.com/2018/02/randomness-and-perlin-noise.html

Briefly, we discussed how even interpolating between random values wasn't good enough. That's because we have sharp bends (discontinuities) which we don't want for our smoother noise.


Even applying a smoothing function to this linear interpolation still has a problem.


The problem is that values that come from this process could remain high for too long a sequence, or alternatively, they could flip positive and negative too often. That is, the frequency is too free. Yo may be familiar with the term bandwidth-limited, which says that the frequency with which a signal is allowed to fluctuate is limited to a range. We want our noise to be band-width limited.

So a different approach is used. We start with a grid of regularly spaced points, but this time we place random unit vectors at each point. Their directions are totally random, and that's ok.


We then take a candidate point at x, and calculate the dot product of the vector from the previous grid point to x and the random unit vector at that grid point. Dot products, when normalised, only vary between -1 and +1 like a cosine wave. That gives us a nice smoothness (very smooth in fact because the first and second and derivatives of this value are also smooth). The dot product of the next unit vector and the vector from the next grid point to x is also calculated and the two are combined, even a simple addition is fine.

As x moves along the axis, the values that result from this process are smooth, based on an underlying pure randomness, and importantly are limited in frequency. You can see that between any two grid points, the values can only flip once or never. That gives us a tight bound on frequency.


You can find a Python notebook demonstrating this idea here:



Real libraries providing noise will combine several octaves of signals generated like this. And the extension to more than 2 dimensions still follows this basic idea.


OpenSimplex Noise

Ken Perlin, who invented the Perlin noise algorithm, updated it a few years later to address some weaknesses. In higher dimensions, the original algorithm started to display patterns that were too regular and not the desired lumpy noise.

Have a look at the following comparison. The top row is too "straight" in parts.


His updated algorithm was patented, so sadly is not open source friendly. Luckily, the open source community developed an open algorithm called OpenSimplex which you can find out more here:



I had previously used OpenSimplex to create 3-d textures for ray traced objects. This works better than mapping a 2-d pattern into 3-d spheres, which suffers from the well-known map projection distortion issues.



Moving Particles

Finally we looked at a rather different use of noise.

Following the path of one, or many, particles which move in a random direction at each step is a well known experiment. The results of can look interesting if we use the translucency technique to avoid over-saturation.


What if we changed the way these particles moved, so that they took a step in a direction determined by noise and not pure randomness?

The results are spectacular!


The image really looks like a pen and ink work. Amazing, given the simplicity of the underlying idea.

You can explore the code yourself at https://www.openprocessing.org/sketch/578607.

As a final extra idea, we can use an image like the following, and move particles based on the underlying colour/luminosity.


We can use the luminosity to determine the colour and the length of the stroke drawn at a point. the results is rather hairy!


You can explore the code here https://www.openprocessing.org/sketch/533299.


Feedback and Examples

Peter developed a couple of really nice animated sketches during the class using noise - take a look, they're really effective.



I was a little unsure if the audience would be bored by a talk which took a beginner-friendly approach and kept things fairly simple. I was pleased that quite a few people responded positively and - best of all - said they'd go and try using noise themselves!

Sunday, 22 July 2018

Creating 3D Printable Art with OpenJSCAD

This month we were lucky to have the multi-talented James Porter help us explore creating algorithmically designed for 3D printing.


The slides for James' tutorial are here: [pdf]. His event page is here: [link].

A video recording is here: [skillsmatter].


Why 3D Printing

The ever onward march of technology brings with it not just new innovations, but also makes them ever more affordable and practical.

Printing 3-dimensional physical objects has seen a growth in popularity and accessibility in recent years. Practical applications are plenty, but so are creative ones too.

Suddenly, imagining 3-dimensional objects and making them reality is no longer the realm of magical fantasy or science fiction.

Here is an example of James' experiments - an interesting landscape made of hexagonal rock forms.


In algorithmic art, we can use computers to create art forms that no human hand could ever create. Extending this to 3-dimensional forms is exciting!


OpenJSCAD

We're very fond of tools that work entirely in a browser, with no additional software needing to be installed. Not only does it make the tools independent of your computer brand, it also reduces technical complexity making them ideal for beginners. We love openprocessing, which is a p5js environment entirely on the web.

James introduced OpenJSCAD, which allows us to use code to create 3-dimensional forms, which can then be exported in a form suitable for 3D printers.


James then led us through the basic concepts for coding in OpenJSCAD, together with some interesting challenges for us to tackle. You can see them being discussed on the video.

Here I'll try my own introductory tutorial to create simple 3D forms.


Basic OpenJSCAD Ideas

OpenJSCAD will create 3-dimensional forms defined by computer program we write. That program is written in code which looks very much like javascript (tutorial).

OpenJSCAD expects to run a main() function, and expects it to return a single object. That single object can be a single sphere or cube, or a collection of shapes which together form an interesting form.


Let's try creating the simplest of objects. Try replacing the main() function with just the following simple code:

function main () {
  return cube({size: 10, center: true});
}

You can see the thing being returned by main() is a cube. The data inside the cube() function sets the size to 10, and locates the centre of the object at the centre of the virtual world. Let's have a look at the result:


You can use your pointer to drag the view around and confirm it is indeed a 3-dimensional object:


If we want to compose a more interesting form, we need to use several basic shapes. Remember that the main() function can only return one single thing, so that thing needs to be a group of other objects. That's called a union here, which reflects the mathematical term, a union of sets.

Here's how we use union:


Let's try joining a cube and a sphere together:

function main () {
  return union(

      cube({size: 20, center: true}),
      sphere({r: 12, center: true})

      );
}

The sphere() function needs its radius set, which we can see is set to 12 here. Here's the result:


If we kept adding more shapes, they'd all be overcrowding the same origin of this 3-d world. We need a way to move them around. We can do this by using a translate() function:


The translate() function takes an object and translates it by [x, y, z] along these axes. A translation of [1, 0, 0] would translate an object only along the x-axis by 1.

Let's try it:

function main () {
  return union(
      
      cube({size: 10, center: true}),
      translate([10, 0, 0], sphere({r: 6, center: true}))
      
      );
}

The result is a cube at the centre of the world, and a sphere which has been shifted along the x-axis by 10 units.


Let's chain a few of these shapes together. Here's some code which has a series of spheres and cubes all shifted along so that they form a nice line:

function main () {
  return union(
      
      translate([-30, 0, 0], sphere({r: 6, center: true})),
      translate([-20, 0, 0], cube({size: 10, center: true})),
      translate([-10, 0, 0], sphere({r: 6, center: true})),
      translate([0, 0, 0], cube({size: 10, center: true})),
      translate([10, 0, 0], sphere({r: 6, center: true})),
      translate([20, 0, 0], cube({size: 10, center: true})),
      translate([30, 0, 0], sphere({r: 6, center: true}))
      
      );
}

Here's the result:


Cool!

We can see how we could create quite sophisticated forms just by using a union of simple objects. I you look at the OpenJSCAD reference, you'll see that other simple shapes like a cylinder, torus and a polyhedron, as well as flat shapes like a circle and a rectangle.You can use any of these in a union, and you can translate any of them too.

Let's look at one more shape modifier - scale - which you can guess scales a shape. We use it in a similar way to translate where we pass it the object we want to scale.

The following code is the same as that above, but has the spheres scaled by 1.7.

function main () {
  return union(
      
      translate([-30, 0, 0], scale(1.7, sphere({r: 6, center: true}))),
      translate([-20, 0, 0], cube({size: 10, center: true})),
      translate([-10, 0, 0], scale(1.7, sphere({r: 6, center: true}))),
      translate([0, 0, 0], cube({size: 10, center: true})),
      translate([10, 0, 0], scale(1.7, sphere({r: 6, center: true}))),
      translate([20, 0, 0], cube({size: 10, center: true})),
      translate([30, 0, 0], scale(1.7, sphere({r: 6, center: true})))
      
      );
}

Here's the result.


We can see that only the spheres have been scaled, and that's because we only applied scale() to the spheres.

We have an interesting shape that we could now bring to life with a 3D printer. We can use the export options to save a file accepted by many printers. The STL format is more or less the standard for 3D printing.



More Interesting Ideas 

We can do more than just join objects together. One really interesting idea is to subtract one object from another, a bit like carving out one shape from another.

Let's go back to our example which had a simple union of a cube and a sphere. Let's change the union to a difference:

function main () {
    
  return difference(
      
      cube({size: 10, center: true}),
      translate([10, 0, 0], sphere({r: 6, center: true}))
      
      );
}

Comparing the results, we can see that the difference takes the first object and craves out the second object.


We can combine several objects into a union, and use that union to crave out a shape from another object. Have a look at this code:

function main () {
    
  return difference (
      
      cube({size: 10, center: true}),
      union (
          translate([10, 0, 0], sphere({r: 6, center: true})),
          translate([-10, 0, 0], sphere({r: 6, center: true})),
          translate([0, 10, 0], sphere({r: 6, center: true})),
          translate([0, -10, 0], sphere({r: 6, center: true})),
          translate([0, 0, 10], sphere({r: 6, center: true})),
          translate([0, 0, -10], sphere({r: 6, center: true}))
      )
      
      );
}

We can see that we've constructed a union of six spheres, and that union is taken from a cube.


You can explore more ways of combining and transforming objects on the OpenSCAD reference pages.


JavaScript Power

We also have the full power of javascript at our disposal. That means we can use loops, functions, and lasts to help construct sophisticated shapes more efficiently.

Let's use a loop to construct a circle of spheres.

function main () {
    
    // list of sheres
    var spheres = [];
    
    // loop
    for (let angle = 0; angle < 360; angle += 40) {
        let x = 10 * cos(angle);
        let y = 10 * sin(angle);
        
        spheres.push( translate([x, y, 0], sphere({r: 3, center: true})) );
    }
    
  return union(spheres);
}

You can see we create an empty list called spheres, and then using a loop, we add lots of spheres to it, each translated according to a bit of trigonometry to follow the edge of a larger circle. Finally the main() function returns the union of the list of spheres.


We can extend this idea into the z-axis, and vary the size of the spheres too.

function main () {
    
    // list of sheres
    var spheres = [];
    var z = 0;
    var distance = 10;
    var size = 1;
    
    // loop
    for (let angle = 0; angle < 720; angle += 10) {
        let x = (0.1 + distance) * cos(angle);
        let y = (0.1 + distance) * sin(angle);
        
        spheres.push( translate([x, y, z], sphere({r: 0.1+size, center: true})) );
        
        z += 0.2;
        distance *= 0.99;
        size *= 0.95;
    }
    
  return union (spheres);
}

The result is interesting, although not printable as it isn't a fully connected object.


A more printable composition can be made by carving out spheres from a block:

function main () {
    
    // list of sheres
    var spheres = [];
    
    // loop
    for (let angle = 0; angle < 360; angle += 40) {
        let x = 10 * cos(angle);
        let y = 10 * sin(angle);
        
        spheres.push( translate([x, y, 0], sphere({r: 3, center: true})) );
    }
    
    var block = translate([0, 0, -3], scale( [3, 3, 0.3], cube({size: 10, center: true}) ) );
    
  return difference(block, union(spheres));
}

The results are rather nice. In fact the union would make a nice physical object itself.


In the meetup itself, we were encouraged to explore using simple maths to calculate vertical blocks which form a landscape when places regularly on a grid.


James provides many more examples to explore on his GitHub repository.


Physical Objects

James also brought along examples of 3D printed forms for us to experience for real.


He did discuss some of the limitations of 3D printers that are priced to appeal to home enthusiasts.

  • there is a limit to the fine detail and intricacy that cheaper printers can reproduce
  • some objects bend and distort during the printing process
  • some shapes are either impossible, or barely constructible
  • the time taken to print objects is long, many minutes or hours even at medium quality settings




Conclusion

James succeeded in showing how easily and accessibly we can create objects ready for 3D printing, and not just any object but objects created algorithmically through code.

I was really pleased that the class was inspired by James, with some wanting to go on to try 3D printing themselves.


References