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.
The planet starts as an icosphere:
I use a 3D Perlin noise field to offset points, producing terrain:
A little bit of random jitter stops it looking too clean:
Biomes are assigned based on distance from the center of the sphere:
The ocean starts out as a big blue icosphere:
I add two levels of specular highlights:
Then, I use a per-vertex noise value to jitter the edge of the highlights:
Finally, this noise also tweaks the baseline blue, to give it more texture:
Here's what it looks when we first combine the terrain and the ocean:
The atmosphere is a subtle effect: a translucent sphere that's slightly larger than the planet itself.
Some of the mountains poke above it – better bring your oxygen tanks!
The clouds are my favorite effect in this demo! They start as clusters of quads, jittered around random points:
Fragments outside of a circular region are discarded, giving more rounded silhouettes:
We apply transparency to feather the edges of each circle:
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.
Finally, each cloud quad draws a randomly selected subset of a Perlin noise field, producing a nice, fluffy effect:
Here's the full planet, with terrain, oceans, atmosphere, and clouds:
The stars are a Perlin noise field, blurred and level-adjusted to create a field of the desired density:
Nothing too fancy, but they do the job:
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
and the ergonomics improvements in Rust 2018).
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.
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
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.
I still don't understand how Rust modules work, despite having read the docs.
On an organizational level:
- Why do they need to be in subfolders sometimes?
mod.rsa special magic name, or just a convention?
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:
- Why do I need to import
- Why must I import
- Why didn't I need to declare
extern crate glium?
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
I wished there was a way to automatically handle the conversion to a
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.
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!