Designing Beautiful Worlds with Procedural Texture Generation

29. January 2023

If you don't wanna read the post you can check out an interactive playground here

Motivation

I wanted to experiment with rendering planets and stars to make cool pictures or to use them in a game. Creating visually appealing 3D renders of space can be difficult though, as there aren't many good textures around. NASA has released some color maps of real planets and moons, but many of them have very visible artifacts and other crucial maps such as normal maps and specular maps simply don't exist. That's why I decided to go for a procedural approach, meaning that instead of using premade textures, there is a program that calculates the textures dynamically. My goal was to render everything in real time and to make it look appealing (not necessarily realistic).

Starting with Basic Noise

The foundation of any procedural texture is randomness - in the form of a texture it is usually called noise. Let's try to create some noise by choosing a random grayscale value for each pixel in an image.

Random Noise

 

This looks pretty bad.. It is not natural at all. Let's try something else.

Perlin Noise

Perlin Noise is a widely-used noise function. It creates smooth looking noise, which is way better suited for many applications. However, in nature we won't find anything this uniformly smooth... let's keep looking.

Fractal Perlin Noise

The idea of fractal noise is to combine many noise layers with increasing frequency and decreasing amplitude. The low frequency noise defines the overall look while the high frequency noise adds a lot of detail. It has the best of both worlds - randomness and structure. We normalize all these noise functions to some range (usually [0,1] or [1,1]) to make it easier to work with.

Adding More Details: UV Noise

Another key concept is to displace the coordinates before plugging them into the noise function. Coordinates are stored in a UV map, where each pixel stores that pixel's coordinates (using two color channels). Displacing the coordinates is as easy as applying noise to the UV map. Generally we add some noise map with very low amplitude to the UV map, which results in small, local distortions. These distortions get amplified when plugged into a noise function, resulting in very fine details.

Regular 2D UV-Map (left), Distorted 2D UV-Map (right)
The Resulting Noise from these UV-Maps (Fractal Perlin)

We can also apply any mathematical function to the noise, for example abs() to create sharp ridges. With these simple tools we can easily create a variety of complex noise functions.

Different Noise Maps

If we use 3D noise functions we don't need to project a 2D texture onto a sphere which could lead to projection-artifacts. Plugging the (xyz) coordinates of each point of a sphere into our function, and coloring the pixels according to the output (gray), results in the following image (with some additional lighting & shading):

Our Noise Function Applied to the Surface of a Sphere

For animations we sometimes even need a 4D noise function!

Colors

Our results so far look pretty nice, but it's all gray. To add color we want to define some process that maps each grayscale value to some color value. One easy way to do that is through a gradient.

A Color Gradient

 

We define the gradient through two or more keys in (usually in the range [0,1]). The above gradient has two key-value-pairs: (0,blue) and (1,red). All values between two keys are linearly interpolated, which in this case results in purple at position (0.5). For a predefined gradient we just need to look up the keys and interpolate to get a color for a given value. For this project I limited the number of keys to four.

Plugging our previous noise into a gradient results in a colored planet:

Our Planet after Applying a Color Gradient to the Noise

Adding more Features: Craters, Oceans & Icy Poles

We have some nice looking rocky planets, but we might want to have some more features than just colored noise.

Atmospheres

To really sell the effect and to put it all together we can add an atmosphere. Calculating atmospheric scattering in real time is very expensive, so I opted for a simpler, cheaper approach. Basically, we have a semi-transparent hemisphere that always points in the direction of the light and gets less transparent on the edges to simulate thicker parts of the atmosphere. To achieve a more cinematic look we can even choose colors that wouldn't make any sense in an atmosphere (e.g. a golden atmosphere for mars). Again, the primary goal is to make it look good, not realistic.

Clouds can also be added easily by taking some swirly noise and making certain parts of it transparent.

An Earth-Like Planet with Clouds and Atmospheric Scattering

Gas Planets

We can create pretty nice looking rocky planets and moons, but what about gas planets? Our current setup isn't suitable for gas planets since most gas planets are uniform in color or have some very distinct horizontal bands. By plugging in some distorted UVs into a sine function we can create bands with turbulence.

Vertical Bands with "Turbulence"

By layering a bunch of these bands with different frequencies, amplitudes and phases, we get a very detailed map resembling a gas planet. Just like before, we color it using a gradient.

Uranus-Like Gas Planet
Neptune-Like Gas Planet

The "storm" in Neptune was created using Voronoi noise.

Ring Systems

Somethings seems to be missing... ah yes, rings! By now you should know the deal: Take the UVs, distort them, plug them into some noise function and voilà, rings.

2D-Map of Rings

We can use these values as the input for the alpha (transparency) channel.

Applying the Ring-Map as Transparency to a Plane around a Planet

Stars

Stars are arguably the easiest to recreate, since most stars are very uniform in texture and color. We can simply take some (distorted) Perlin noise and use it as an emission map for a sphere - that's it.

Stars with Different Scales of Noise and Color

Adding a Skybox

Thinking back to Voronoi noise, we can also use it to create a starry sky. We can add thousands of bright, possibly colored, dots in the background to get a nice space background.

Voronoi-Based Star Skybox

Finishing Touches: Post Processing

We can add a handful of post-processing image effects to spice up the final render and make it look more cinematic. The most notable effects are tonemapping/color grading, bloom and vignetting.

Here are some nice screenshots from various planets, moons and stars:

Various Planets, Moons and Stars

Conclusion

We can create some pretty complex space renders with fairly easy methods. Rendering these scenes in real time is possible, but not very efficient since some planets use hundreds of layers of noise. For static renders this is not a problem, but if we were to use some of these in a game we would ideally bake the maps to image textures.

Contact

This is my first ever blog post - I'd appreciate any form of feedback!

Member of the Polyring webring