Code golfing a tiny demo using maths and a pinch of insanity
A matrix can be seen as a function, so mathematically writing p'=M \cdot p would be equivalent to the code with: Doing p'=M \cdot p is rotating the space p lies into, which means it gives the illusion the object is rotating clockwise . Though, in the expression , I can't help but be bothered by the redundancy of , so I would prefer to write instead. Since matrices are not commutative, this will instead do a counter-clockwise rotation of the object. The inlined rotation ends up being: To make the rotation clockwise, we can of course use , or we can transpose the matrix: . This is problematic though: we need to repeat the angle 4 times, which can be particularly troublesome if we want to create a macro and/or don't want an intermediate variable for the angle. But I got you covered: trigonometry has a shitton of identities, and we can express every according to a (and the other way around). For example, here is another formulation of the same expression: Now the angle appears only once, in a vectorized cosine call. GLSL has and functions, but it doesn't expose anything for \pi nor \tau constants. And of course, it doesn't have and implementation either. So it's obvious they want us to use \arccos(-1) for \pi and \arccos(0) for \pi/2 : To specify as a normalized value, we can use . On his Unofficial Shadertoy blog, Fabrice Neyret goes further and provide us with a very cute approximation , which is the one we will use: I checked for the best numbers in 2 digits , and I can confirm they are indeed the ones providing the best accuracy. On this last figure, the slight red/green on the outline of the circle represents the loss of precision. With 3 digits, and can respectively be used instead of and . This is good when we want a dynamic rotation angle (we will need that for the camera panning typically), but sometimes we just need a hardcoded value: for example in the of our combined noise function. is fine but we can do better. Through Inigo's demos I found the following: . It makes a rotation angle of about 37° (around 0.64 radians) in a very tiny form. Since 0.5 was pretty much arbitrary, we can just use this matrix as well. And we can make it even smaller (thank you jolle ): One last rotation tip from Fabrice's bag of tricks: rotating in 3D around an axis can be done with the help of GLSL swizzling: We will use this too. is the same , if we need to save one character and can't transpose the matrix. One last essential before going creative is the camera setup. We start with the 2D pixel coordinates which we are going to make resolution independent by transforming them into a traditional mathematical coordinates system: Since we know our demo will be rendered in landscape mode, dividing by is enough. We can also save one character using : To enter 3D space, we append a third component, giving us either a right or a left-handed Y-up coordinates system. This choice is not completely random. Indeed, it's easier/shorter to add a 3rd dimension at the end compared to interleaving a middle component. Compare the length of to (Z-up convention). In the former case, picking just a plane remains short and easy thanks to swizzling: instead of . To work in 3D, we need an origin point ( for ray origin) and a looking direction ( for ray direction). is picked arbitrarily for the eye position, while is usually calculated thanks to a helper: Which is then used like that, for example: I made a Shadertoy demo to experiment with different 3D coordinate spaces if you are interested in digging this further. All of this is perfectly fine because it is flexible, but it's also way too much unnecessary code for our needs, so we need to shrink it. One approach is to pick a simple origin and straight target point so that the matrix is as simple as possible. And then later on apply some transformations on the point. If we give and , we end up with an identity matrix, so we can ditch everything and just write: This can be shorten further: since the vector is normalized anyway, we can scale it at will, for example by a factor , saving us precious characters: And just like that, we are located at the origin , looking toward Z+, ready to render our scene. It's finally time to build our scene. We're going to start with our function previously defined, but we're going tweak it in various ways to craft a mountain height map function. Here is our first draft: We're exploiting one important correlation of the noise function: at every octave, the amplitude is halving while the frequency is doubling. So instead of having 2 running variables, we just have an amplitude getting halved every octave, and we divide our position by (which is the same as multiplying by a frequency that doubles itself). I actually like this way of writing the loop because we can stop the loop when the amplitude is meaningless ( acts as a precision stopper). Unfortunately, we'll have to change it to save one character: is too long for the iteration, we're going to double instead by using which saves one character. So instead the loop will be written the other way around: . It's not exactly equivalent, but it's good enough (and we can still tweak the values if necessary). We're going to inline the constants and rotate, and use one more cool trick: can be shortened: we just need another . Luckily we have , so we can simply write . Similarly, if we needed we could have written (it works also like that: to shorten ). We can also get rid of the braces of the loop by using the in its local scope. In the end, this is our function: To render this in 3D, we are going to do some ray-marching. The main technique used in most Shadertoy demos is ray-marching. I will assume familiarity with the technique, but if that's not the case, An introduction to Raymarching (YouTube) by kishimisu and Painting with Math: A Gentle Study of Raymarching by Maxime Heckel were good resources for me. In short: we start from a position in space called the ray origin and we project it toward a ray direction . At every iteration we check the distance to the closest solid in our scene, and step toward that distance, hoping to converge closer and closer to the object boundary. We end up with this main loop template: This works fine for solids expressed with 3D distance fields , that is functions that for a given point give the distance to the object. We will use it for our mountain, with one subtlety: the noise height map of the mountain is not exactly a distance (it is only the distance to what's below our current point ): Because of this, we can't step by the distance directly, or we're likely to go through mountains during the stepping ( ). A common workaround here is to step a certain percentage of that distance to play it safe. Technically we should figure out the theorical proper shrink factor , but we're going to take a shortcut today and just arbitrarily cut. Using trial and error I ended up with 20% of the distance. After a few simplifications, we end up with the following (complete) code: We start at so I dropped the variable entirely. Also, to avoid the division by 0 in in , is moved right at the beginning (we could also initialize to a value slightly different than 0). You may be curious about the power at the end; this is just a combination of luminance perception with gamma 2.2 (sRGB) transfer function. It only works well for grayscale; for more information, see my previous article on blending . Compared to the mountain, the clouds and fog will need a 3 dimensional noise. Well, we don't need to be very original here; we simply extend the 2D noise to 3D: The base frequency is lowered to to make it smoother, and the goes from 2 to 3 dimensions. Notice how the rotation is only done on the y-axis, the one pointing up): don't worry, it's good enough for our purpose. We also add a phase (meaning we are offsetting the sinusoid) of ( is the time in seconds, slowed down by the multiply) to slowly morph it over time. The base frequency and time scale being identical is a happy "coincidence" to be factored out later (I actually forgot about it until jolle reminded me of it). You also most definitely noticed isn't explicitly initialized: while only true WebGL, it guarantees zero initialization so we're saving a few characters here. For volumetric material (clouds and fog), the loop is a bit different: instead of calculating the distance to the solid for our current point , we do compute the density of our target "object". Funny enough, it can be thought as a 3D SDF but with the sign flipped: positive inside (because the density increases as we go deeper) and negative outside (there is no density, we're not in it). For simplicity, we're going to rewrite the function like this: Compared to the solid ray-marching loop, the volumetric one doesn't bail out when it reaches the target. Instead, it slowly steps into it, damping the light as the density increases: The core idea is that the volumetric material emit some radiance but also absorbs the atmospheric light. The deeper we get, the smaller the transmittance gets, til it converges to 0 and stops all light. All the threshold you see are chosen by tweaking them through trial and error, not any particular logic. It is also highly dependent on the total number of iterations. Steps get larger and larger as the distance increases; this is because we don't need as much precision per "slice", but we still want to reach a long distance. We want to be positioned below the clouds, so we're going to need a simple sign flip in the function. The fog will take the place at the bottom, except upside down (the sharpness will give a mountain-hug feeling) and at a different position. becomes: Having a single ray-marching loop combining the two methods (solid and volumetric) can be challenging. In theory, we should stop the marching when we hit a solid, bail out of the loop, do some fancy normal calculations along with light position. We can't afford any of that, so we're going to start doing art from now on. We start from the volumetric ray-marching loop, and add the distance to the mountain: If gets small enough, we can assume we hit a solid: In volumetric, the attenuation is calculated with the Beer-Lambert law. For solid, we're simply going to make it fairly high: This has the effect of making the mountain like a very dense gas. We're also going to disable the light emission from the solid (it will be handled differently down the line): The transmittance is not going to be changed when we hit a solid as we just want to accumulate light onto it: Finally, we have to combine the volumetric stepping ( ) with the solid stepping ( ) by choosing the safest step length, that is the minimum: We end up with the following: We can notice the mountain from negative space and the discrete presence of the fog, but it's definitely way too dark. So the first thing we're going to do is boost the radiance, as well as the absorption for the contrast: This will make the light actually overshoot, so we also have to replace the current gamma 2.2 correction with a cheap and simple tone mapping hack : . Halving the color is yet another tweak that is not obtained on anything but trial and error. There might be clever ways to reach the same result, but I leave that up to the reader: The clouds and fog are much better but the mountain is still trying to act cool. So we're going to tweak it in the loop: This boosts the overall emission. While at it, since the horizon is also sadly dark, we want to blast some light into it: When the density is null (meaning we're outside clouds and fog), an additional light is added, proportional to how far we are from any solid (the sky gets the most boost basically). The mountain looks fine but I wanted a more eerie atmosphere, so I changed the attenuation: Now instead of being a hard value, the attenuation is correlated with the proximity to the solid (when getting close to it). This has nothing to with any physics formula or anything, it's more of an implementation trick which relies on the ray-marching algorithm. The effect it creates is those crack-like polygon edges on the mountain. To add more to the effect, the emission boost is tweaked into: This makes the bottom of the mountain darker quadratically: only the tip of the mountain would have the glowing cracks. We've been working in grayscale so far, which is a usually a sound approach to visual art in general. But we can afford a few more characters to move the scene to a decent piece of art from the 21st century. Adding the color just requires very tiny changes. First, the emission boost is going to target only the red component of the color: And similarly, the overall addition of light into the horizon/atmosphere is going to get a redish/orange tint: We're almost done. For the last tweak, we're going to add a cyclic panning rotation of the camera, and adjust the moving speed: I'm currently satisfied with the "seed" of the scene, but otherwise it would have been possible to nudge the noise in different ways. For example, remember the can be replaced with in either or both volumetric and mountain related noises. Similarly, the offsetting could be changed into for a different morphing effect. And of course the rotations can be swapped (either by changing into or transposing the values). At this point, our code went through early stages of code golfing, but it still needs some work to reach perfection. Stripped out of its comments, it looks like this: The first thing we're going to do is notice that both the mountain, clouds, and fog use the exact same loop. Factoring them out and inlining the whole thing in the main function is the obvious move: Next, we are going to do the following changes: Onto the next pass of tricks: I'm also reordering a bit some instructions for clarity 🙃 The last touch is going to be nasty: we're going to reorder the instructions such that the 2nd loop is located at the very beginning of the 1st one: "Why?!" you may ask. Before answering this question, let's see why it still works: the first iteration ends up being executed with , where most calculations just cancel themselves out, leading to one wasted iteration (out of 100). Visually, it makes zero difference. But thanks to this weird change, we end up with a bunch of instructions that we can pack into the last placeholder of the main loop, comma separated. This notably allows us to drop the of the main loop: And here we are. All we have to do now is remove all unnecessary spaces and line breaks to obtain the final version. I'll leave you here with this readable version. I'm definitely breaking the magic of that artwork by explaining everything in detail here. But it should be replaced with an appreciation for how much concepts, math, and art can be packed in so little space. Maybe this is possible because they fundamentally overlap? Nevertheless, writing such a piece was extremely refreshing and liberating. As a developer, we're so used to navigate through mountains of abstractions, dealing with interoperability issues, and pissing glue code like robots. Here, even though GLSL is a very crude language, I can't stop but being in awe by how much beauty we can produce with a standalone shader. It's just... Pure code and math, and I just love it.