Mapgen4 river shader

 from Red Blob Games’s Blog
Blog post: 30 Sep 2025

Back in 2018 I had the idea to prerender short segments of river bends and confluences into a texture, and then use that texture to draw the rivers to the screen. I was trying to have draw exactly one triangle per Delaunay triangle, so that I could generate the geometry ahead of time and only change the texture coordinates. I planned to implement this[1] as a 2D table:

Debug visualization of river rendering texture

To simplify, I switched to using bezier curves:

Debug visualization of curved river rendering texture

I was planning to use the input and output width as the indices into the table, but ended up using only one width. What I didn’t realize at the time was that I no longer needed a 2D table; I could’ve used a 1D table. And even better, I could’ve drawn the curve in a shader, like I did in these shader experiments. But I was focused on shipping and not doing things the cleanest or most efficient way.

After I rewrote the main renderer, I decided to rewrite the river renderer too. I adapted one of the earlier shader experiments to make a river shader, and compared before/after:

Old and new river renderer

It’s a bit hard to tell at that zoom level, but if you open the two images in separate tabs you can flip back and forth to compare. Here’s a closer view:

Old and new rendering, close up

The rivers look sharper. But some of the flaws that were hidden before by the blurriness are now visible.

To fix those flaws, I wanted to use a shader to draw wide bezier curves inside a triangle. I didn’t have a good intuition for bezier curves with barycentric coordinates, so I had an LLM generate a quick & dirty tool to help me build my intuition. It’s like shadertoy but for one triangle[2]. This was an attempt at learning how to “vibe code”, and I have mixed feelings about it, but that’s a story for another time.

Playing with the toy made me see that there should be a way to do what I want, but it’s not from bezier curves like I originally thought. Instead, I can curve fit two degree-5 polynomials, one for each side of the river. Using the LLM-generated code helped me figure this out, but it wasn’t able to directly give me a solution. I’ll have to work out the details myself at some point.

I added “better river shader” to my list, but at a low priority, so I’m going to work on some other things next and come back to it.

Email me , or comment here: