Planets is a Java based 3D program that procedurally generates low poly planets. It uses the OpenGL library LWJGL to render the planets, and is capable of generating different types of planets with different types of terrain. It uses Noise algorithms to procedurally generate them based on a seed.
The main reason I was motivated to create this project is that I was interested in learning how pseudo-random noise generation works. To create my planetes, I used 3 different noise generation. For the default planet shape, I experimented with a few different types of noise and control variables, the most notorious being Perlin Noise, first developed and published by Ken Perlin.
The main difficulty with this type of noise, however, is that I build my spheres using subdivided icosahedrons (icospheres). In comparison to "standard" UV mesh spheres, icospheres are built by subdividing the triangular faces of an icosahedron recursively into 4 new triangles. The benefit of icospheres is that all the faces of the resultant sphere are equally sized. However, the drawbacks are that they are significantly more difficult to apply textures to. Unlike a UV sphere, which is specifically designed by warping squares and triangles from a 2d surface, icospheres don't have the ability to be (easily) seamlessly textured.
Here is an example of a Perlin Noise-generated height map. Ideally, when making the terrain maps on the planets, I would plug in each position on the planet's surface's (x, y) coordinate into the noise function, and use the output as the height of the terrain at that point. However, because of the way icospheres are built, the (x, y) coordinates of the vertices are not evenly spaced out, and the noise function would not be able to generate a smooth height map. To solve this problem, I used a different noise function, called Simplex Noise, which is a modified version of Perlin Noise that is able to generate smooth noise on non-regular grids.
Another benefit of Simplex noise is that it's not dimensionally dependent. This allows me to use a 3D height map instead of a 2D one, which lets me use an (x, y, z) input to get a w (height) output. This represents the distance from a point on the sphere to it's focus. This solves the issue of wrapping a 2D plane onto a 3D surface (an illegal R2 → R3 operation), because I'm essentially wrapping a 3D surface around a 3D object (a legal R3 → R3 operation).
Each vertex of the icosphere is normalized to the value of the 3D heightmap. While this creates a seamless terrain over the icosphere, it also introduces a (stylistic) issue — the terrain looks too bumpy now. Because the Simplex noise function returns values in the range [-1, 1), the planets had trenches that were often deeper than the perceived "water level." Because of this, planets often didn't look realistic and looked very spikey.
To solve this completely, I decided to apply a noise-based filter. To do this, I modified my noise function to floor every output value to 0, to create an artificial sea level.
In the future, I would like to expand upon this sea level idea and create a more realistic water system. Currently, the water is just a flat plane that is rendered at the lowest point on the planet. I would like to create a more dynamic water system that can have different water levels in different valleys on the planet so I could have areas such as the Dead Sea, where the water level is much lower than sea level. I think that overall this would make a more realistic look and give the planet generation a more dynamic feel.
The next part of this project was to generate an atmosphere. My first attempt was to use the noise function to generate a color, and then offset the R, G, and B components based on values generated by the noise function. This was a step in the right direction, and produced an interesting looking result.
However, this was not the look I was going for, and wanted a more cloudy look. I did keep this functionality implemented as a toggle switch though, as I think it could be interesting on non-Earth-like planets.
For my next attempt, I used white everywhere and instead used the noise function to change the height of the vertices, and then blended them together. I thought that this would make a more cloudy look, but instead created a bumpy looking atmosphere.
For my final attempt, I decreased the amplitude of the height map to return values in the range [-0.2, 0.2). This caused for a significantly less bumpy and more realistic looking cloud shell. Then, instead of altering the color of the vertex, I altered the alpha value. Finally, I stopped interpolating the colors between vertices, giving the clouds a more low-poly look that I felt fit the planets better. I'm actually very happy with how the atmosphere turned out, and don't think that I could make it any better.
Overall, this project was a very interesting and fun way to learn how noise generation worked, and led to a very pleasing result. In the future, I would like to remake this project using a more optimized noise algorithm. To do this, instead of computing the height of each (x, y, z) component on the fly, I would like to compile a source of data in a unit-cube of pre-generated noise for each (x, y, z) point in the cube. Then, I would interpolate the height map for each (x, y, z) vertex on the icosphere from the pre-generated data. While this wouldn't make the algorithm more efficient by itself, it would let me pregenerate a large amount of data. By generating a cube with a radius much larger than that of each planet, I can efficiently store many iterations of the noise algorithm in a single cube, and then use offsets and scale factors to get a unique sample of noise for each generated planet. In the meantime, please enjoy some my favorite planets that I generated!
I built this quick code in Java over my 3D engine with LWJGL 3 and GLSL. Source code for this quick code is available on my GitHub.