my website
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
dfsek.com/static_blog/noise_rivers.hamlet

74 lines
3.3 KiB

<div>
<h1 id="welcome-to-dfseks-website">Procedurally Generated Rivers Using OpenSimplex Noise
<p>
Procedurally generating rivers, especially purely with noise functions, is notoriously
difficult. Consider this commonly seen naive approach:
<pre>
<code class="javascript">
if(|simplex(x, z)| &gt; 0.2, 0, 1)
<img src="/static/blog/img/naive.png" alt="Naive solution to noise rivers.">
<br>
<sub>In this case, <code>simplex</code> is a 1-octave OpenSimplex2 noise function, domain-warped with another
1-octave OpenSimplex2 noise function with twice the frequency.</sub>
<br>
<p>
At first glance, this looks pretty good. However, there are several problems to this approach.
First, look at places where "forks" in the river form. The river becomes <i>incredibly</i> wide
in these areas, which is undesirable. The river also has inconsistent width in general. In some places it
is very wide, in others it pinches into very small areas.
<p>
Ideally, we could procedurally generate lines with a constant width, to allow finer control over how
wide the rivers are.
<h2>Procedurally generating constant-width lines</h2>
<p>
A simple way to generate constant-width lines from an arbitrary input function is to posterize it and
apply an edge-detection kernel. A simple edge-detection kernel I have found to work well is:
<pre>
<code class="yaml">
[
[-1, -1, -1],
[-1, 8, -1],
[-1, -1, -1]
]
<p>
To get a useful result from our Simplex function, first we posterize it to 2 values:
<pre>
<code class="javascript">
if(simplex(x, z) &gt; 0, -1, 1)
<img src="/static/blog/img/posterized.png" alt="Posterized noise function">
<p>
Then, we apply the kernel to it:
<pre>
<code class="javascript">
kernel(if(simplex(x, z) &gt; 0, -1, 1), 1)
<img src="/static/blog/img/kerneled.png" alt="Posterized noise function with kernel applied">
<br>
<sub>
<code>kernel</code> is simply a function that accepts a function to have the edge-detection kernel
applied, and the step at which to sample values for the kernel. E.G. 1 = sample every pixel, 4 =
sample every 4 pixels.
<br>
<p>
This gives us lines that are exactly 1 pixel wide in all locations. To get smooth lines of any width,
we simply sample the kernel at larger intervals.
<pre>
<code class="javascript">
kernel(if(simplex(x, z) &gt; 0, -1, 1), 4)
<img src="/static/blog/img/kerneled_scaled.png" alt="Posterized, scaled noise function with kernel applied">
<p>
Finally, we posterize again, and we have our result:
<pre>
<code class="javascript">
if(kernel(if(simplex(x, z) &gt; 0, -1, 1), 4) &gt; 0, 1, -1)
<img src="/static/blog/img/kerneled_scaled_posterized.png" alt="Posterized, scaled noise function with kernel applied, then posterized again">
<p>
This completed function can easily be inserted into a biome generation system to create good-looking
fully procedural noise-based rivers. If slight width variations are desired, a simple domain warp can
be applied to the function. This approach to generating rivers is very fast, and produces visually
appealing results.