About

In the past few years, I've heard a lot about Rust.

As someone that hacks on computer graphics and low-level infrastructure libraries, it seems relevant to my interests. I decided to make a small demo – of a procedural planet generator – and see how it went.

Result

Breakdown

Planet

The planet starts as an icosphere:

Icosphere

I use a 3D Perlin noise field to offset points, producing terrain:

Terrain

A little bit of random jitter stops it looking too clean:

Jitter

Biomes are assigned based on distance from the center of the sphere:

Biomes

Oceans

The ocean starts out as a big blue icosphere:

Big blue

I add two levels of specular highlights:

Ocean specular highlights

Then, I use a per-vertex noise value to jitter the edge of the highlights:

Jittered specular highlights

Finally, this noise also tweaks the baseline blue, to give it more texture:

Textured ocean

Here's what it looks when we first combine the terrain and the ocean:

Day 2

Atmosphere

The atmosphere is a subtle effect: a translucent sphere that's slightly larger than the planet itself.

Atmosphere

Some of the mountains poke above it – better bring your oxygen tanks!

Clouds

The clouds are my favorite effect in this demo! They start as clusters of quads, jittered around random points:

Cloud quads

Fragments outside of a circular region are discarded, giving more rounded silhouettes:

Cloud circles

We apply transparency to feather the edges of each circle:

Cloud circles faded

This creates an interesting effect where clouds at the edge of the planet stack up, producing a white wall that's undesirable.

To fix this, I add a special-case to the fragment shader that causes the clouds to fade to transparency as they cross around to the back side of the sphere.

Cloud circles around

Finally, each cloud quad draws a randomly selected subset of a Perlin noise field, producing a nice, fluffy effect:

Fluffy clouds

Here's the full planet, with terrain, oceans, atmosphere, and clouds:

Planet

Stars

The stars are a Perlin noise field, blurred and level-adjusted to create a field of the desired density:

Stars

Nothing too fancy, but they do the job:

Full effect

Notes

Enough about the graphic effects – how was Rust?

It was a mixed experience, but I can see the potential. Notes are below, and reflect my own ignorance as much as limitations in the language and libraries

I also believe many of these issues are under active development (e.g. incremental compilation, the failure crate, and the ergonomics improvements in Rust 2018).

Live-coding

This hurdle was somewhat self-inflicted: I wanted to have a live-coding setup, where I could edit a source file and see the changes immediately. I implemented a custom system to do this, with shared libraries and unsafe calls to swap them in and out.

This worked fine, but the compiler was still quite slow (about 5 seconds for a debug build), so it didn't feel like a real-time development environment.

Graphics

For graphics, I used glium, since I'm comfortable with OpenGL and wanted to test out the Rust-flavored bindings. It worked okay, but was generally high-impedance, and I had trouble finding effective documentation.

There were a few things that I could never get working – in particular high-DPI rendering. The screenshots above were captured by only rendering the lower-left quarter of the window, then calling read_front_buffer; the function doesn't realize that my screen has a higher pixel density than usual, so if I rendered an entire window, it would only capture a quarter of it.

Error handling was another rough patch. When doing a series of OpenGL operations, I wanted to return an error if any of them failed. However, all of the different functions had their own error types, so I had to modify the top-level function signature to return a Box<Error>. The only way I knew this was educated guessing, because I'm coming from a C++ background and read the docs about trait objects.

Modules

I still don't understand how Rust modules work, despite having read the docs.

On an organizational level:

I basically moved files at random until the compiler stopped complaining at me.

Even within a single file, I find the logic confusing. Look at this block of imports:

Lifetimes and borrowing

No issues here, surprisingly enough.

The only pattern that got annoying was calling a function on an Option<T>, and having to type o.as_ref().map(|t| ...). I wished there was a way to automatically handle the conversion to a reference.

Libraries

It's very nice being able to drop crates into your project with a single line. I used a handful of external crates (noise, random number generation, image processing, etc), and found them to be high-quality.

I'm still a bit uncomfortable with the uncurated, flat namespace, which – in conjunction with a small standard library – puts the onus on the user to do all of the research to find which crates are de-facto ecosystem standards.

Closing thoughts

The code is here, and is very unpolished (lots of compiler warnings, etc).

At this point, I'll be doing more experiments in Rust, but am not ready to switch from my usual C++ / Qt stack (and didn't expect to be!).

As someone that writes bare-metal and embedded Linux software at work, I'm also excited for the future of embedded development in Rust!