Dynamic Cubemaps

While watching John Beech work in MM Dreams, I noticed the background and had to try and replicate it. This effort resulted in a easy method of rendering painterly / cloudy looking backgrounds, with the added benefit of being very flexible while remaining cheap.

Inspiration

Media Molecule Dreams painterly background

A screenshot captured from an awesome MM Dreams stream
https://youtu.be/MumfFRy5IzI?t=1857

It occurred to me that they are just accumulating splats on the background, so I hopped into Krita and spammed to circles to see how it looked - I really liked the results and how simple it was to make this effect.

Effect testing in krita

Manually spamming circles in Krita

Approach

The main idea here is that you need to render to 6 different textures, one for each face of a cube. By leaving the previous cubemap's contents, but turning on alpha blending you get this nice incremental effect that looks like rain or brush strokes. Anything can be rendered to these textures, including fullscreen shader passes (e.g., shadertoy shaders). Care must be taken to make sure the corners/edges match or it will look pretty terrible.

Since my goal is to match the effect seen in Dreams, adding a few (10-100) randomly positioned splats per frame seemed like a good place to start. I tried GL_POINTS first, but couldn't get consistent UVs across cube seams.

point uv issues and box seams

Early attempt with clearly visible seams and weird UV flipping

This could probably be rectified with billboarded quads, but I had a wild idea about putting some volumetric data in each splat. In other words, I needed to be rendering boxes.

cubes

Consistent UVW across cubemap faces

If you look really close you can still see some seams, but it turns out those artifacts were created while rendering the cubemap to the screen and fixed by computing a more accurate direction vector.

Implementation

source (webgl2): dynamic-cubemaps.js

Conclusion

So there you have it, a simple way to render painterly backgrounds using ~100 cubes rendered 6 times per frame. The hardest part about this whole thing was setting up the framebuffers.

References