<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Ranmantaru Games &#187; article</title>
	<atom:link href="https://ranmantaru.com/blog/tag/article/feed/" rel="self" type="application/rss+xml" />
	<link>https://ranmantaru.com</link>
	<description></description>
	<lastBuildDate>Sun, 24 Apr 2016 12:50:43 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	
		<item>
		<title>How I do (not) debug my games</title>
		<link>https://ranmantaru.com/blog/2012/10/21/how-i-do-not-debug-my-games/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=how-i-do-not-debug-my-games</link>
		<comments>https://ranmantaru.com/blog/2012/10/21/how-i-do-not-debug-my-games/#comments</comments>
		<pubDate>Sun, 21 Oct 2012 15:33:22 +0000</pubDate>
		<dc:creator>e-dog</dc:creator>
				<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[article]]></category>
		<category><![CDATA[dev]]></category>
		<category><![CDATA[technobabble]]></category>

		<guid isPermaLink="false">http://ranmantaru.com/?p=308</guid>
		<description><![CDATA[I&#8217;m writing this because I thought it might be interesting to other programmers. I used this method for about ten years, so it&#8217;s pretty sound. I don&#8217;t use debugger. Well, I start it sometimes in (mostly futile) hope of getting a better exception stack trace. Or to look at the generated machine code in disassembly [...]]]></description>
			<content:encoded><![CDATA[<p>I&#8217;m writing this because I thought it might be interesting to other programmers. I used this method for about ten years, so it&#8217;s pretty sound.</p>
<p>I don&#8217;t use debugger. Well, I start it sometimes in (mostly futile) hope of getting a better exception stack trace. Or to look at the generated machine code in disassembly when optimizing. Otherwise, I find it mostly useless.</p>
<p>I don&#8217;t use &#8220;debug&#8221; build. I always build &#8220;release&#8221; with debug information. So I&#8217;m testing the same code people will use to play the game. Debug info is for getting stack info on exceptions and in some other cases.</p>
<p>When I encounter an error (crash, wrong logic, glitches or whatever) I follow these steps:</p>
<ul>
<li>Thinking</li>
<li>Reviewing</li>
<li>Logging</li>
<li>Debugging (or, rather, taking a break)</li>
</ul>
<p>Each step&#8217;s purpose is to get some new info about the problem. If I get new info, I go back to the first step &#8211; thinking.<br />
As you see, debugging is actually there, but I rarely get to it. Let&#8217;s look at the steps in more detail.</p>
<h2>Thinking</h2>
<p>Stop and think: what can cause this problem? Is it the result of the code I just wrote/changed? What did I do to cause it? Or was it there before? What subsystems is it limited to? When does it happen, in what conditions? And so on.<br />
Just thinking about the problem often gives some new insights about what can cause it. When you do small incremental changes (and I try to work that way when possible), the changed code is small and easy to grasp with you mind, and the problem is likely there.</p>
<h2>Reviewing</h2>
<p>Look at your code. Review it. Do you remember it correctly? What is actually written there? (Strange things can happen, especially if you use copy/paste/modify a lot).<br />
Look for things you didn&#8217;t think about. Look for things that differ from what you expect them to be. If you use third-party library/engine/whatever, look at the documentation for the things you use to make sure they do what you thing they do, and accept those arguments etc.</p>
<p>If you find something, go back to thinking.</p>
<h2>Logging</h2>
<p>The code is complex. It can produce emergent results (that&#8217;s why you use it in the game, right?) So you might need to look at some values it actually produces. That&#8217;s where logging comes in.</p>
<p>Logging is the code that writes some text message to some log. It can be a log file, console, stderr, socket, whatever. You must be able to view all log messages though, and searching and filtering them comes in handy too.</p>
<p>Why logging and not a debugger?<br />
It gives you exactly the info you ask for.<br />
It puts it in context, with other log messages. You can see <i>when</i> it happened in relation to other logged events. You can see how often it happened. You can see how the value changed over time.<br />
You can easily filter what values you log when you have multiple objects and are interested in only one of them. Or only values above a threshold. Or both.<br />
You can surround the code with log messages and find where the exception occurs even when you can&#8217;t get a stack trace (be sure to flush you log though).<br />
You can do all this in the release build.<br />
You can do it even when you code for console/mobile/set-top-box/toaster/whatever.<br />
It&#8217;s easy to turn off when you release a build. And it can be useful when the problem happens on some other computer, not on yours.</p>
<p>Some pitfalls:<br />
Make it easy to automatically flush the log after every message, it&#8217;s essential when diagnosing exceptions or freezing. Or flush it always (might be slow).<br />
Make sure you actually log what you want to log (reviewing helps here). If you write wrong value to the log, it can mislead you for a while.</p>
<h2>Visual aids</h2>
<p>That&#8217;s not exactly logging, but it helps a lot in games. You can write bounding box coordinates to your log, but actually seeing the box on the screen with other objects is much more useful and effective.<br />
Make it easy to add this form of &#8220;logging&#8221; to your code. Colored lines that fade out over time are useful, for example. You can add them anywhere in your code and see them on the screen for long enough (but not forever). You can make boxes, circles, spheres, arrows or whatever you want just writing a function that conveniently adds multiple lines based on the parameters you pass.<br />
Visualizing AI state, targets, ranges etc is extremely useful too.</p>
<h2>Debugging</h2>
<p>If everything else fails, you can try the debugger. But what info can you get from it that the previous steps didn&#8217;t uncover? I guess doing more thinking or taking a break is better.</p>
]]></content:encoded>
			<wfw:commentRss>https://ranmantaru.com/blog/2012/10/21/how-i-do-not-debug-my-games/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Soft object shadows with deferred rendering</title>
		<link>https://ranmantaru.com/blog/2012/04/17/soft-object-shadows-with-deferred-rendering/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=soft-object-shadows-with-deferred-rendering</link>
		<comments>https://ranmantaru.com/blog/2012/04/17/soft-object-shadows-with-deferred-rendering/#comments</comments>
		<pubDate>Tue, 17 Apr 2012 16:25:26 +0000</pubDate>
		<dc:creator>e-dog</dc:creator>
				<category><![CDATA[Arcane Worlds]]></category>
		<category><![CDATA[article]]></category>
		<category><![CDATA[code]]></category>
		<category><![CDATA[dev]]></category>
		<category><![CDATA[technobabble]]></category>

		<guid isPermaLink="false">http://ranmantaru.com/?p=236</guid>
		<description><![CDATA[Technobabble warning. In this article I assume you&#8217;re fairly familiar with deferred rendering/shading. If not, google it. There are numerous articles and presentations about what it is how it&#8217;s done in various games. That&#8217;s a lot more technobabble though. Deferred rendering in Arcane Worlds I use three ARGB8 textures for the G-buffer, so it&#8217;s 96 [...]]]></description>
			<content:encoded><![CDATA[<p>Technobabble warning. In this article I assume you&#8217;re fairly familiar with deferred rendering/shading. If not, google it. There are numerous articles and presentations about what it is how it&#8217;s done in various games. That&#8217;s a lot more technobabble though.</p>
<p><center>
<a href="https://ranmantaru.com/wordpress/wp-content/gallery/shots1/shot2012_04_15_02_25_27.png" title=""  >
	<img class="ngg-singlepic" src="https://ranmantaru.com/wordpress/wp-content/gallery/cache/44__512x288_shot2012_04_15_02_25_27.png" alt="Land and object shadows" title="Land and object shadows" />
</a>
</center></p>
<p><span id="more-236"></span></p>
<h2>Deferred rendering in Arcane Worlds</h2>
<p>I use three ARGB8 textures for the G-buffer, so it&#8217;s 96 bits per pixel. There&#8217;s also a shadow buffer and light buffer textures. All of them are reused later in the anti-aliasing post-effect, so deferred rendering has the same video memory requirements as before.</p>
<p>I use stencil when rendering geometry, so I can draw sky <b>after</b> the scene to save fillrate.</p>
<p>In the G-buffer I store:</p>
<ul>
<li>24-bit depth, packed into 3 channels.</li>
<li>World-space normal, simply stored in 3 channels.</li>
<li>Water factor (8 bit). Currently it&#8217;s either 0 or 1, but it&#8217;ll be used later to blend water based on its depth.</li>
<li>Gloss factor (8 bit). Currently only used for water foam, but it&#8217;ll be used later for glossy parts of objects and buildings.</li>
<li>Surface color (24-bit RGB). That&#8217;s surface albedo, not used for water.</li>
<li>Glow factor (8 bit). Not used for now, it&#8217;s reserved for lava and other glowing things (like monster eyes).</li>
</ul>
<p>So, world space pixel position can be reconstructed from depth, and we got normal and some other stuff.</p>
<h2>Soft shadows</h2>
<p>The huge sun in Arcane Worlds should cast very soft shadows that fade out quite fast with distance from the caster. This means the shadow density along the light ray is <b>not</b> monotonic, which is a problem with shadow maps. Also, I wanted sky shadows (ambient occlusion actually) because it becomes very important at night without direct sun/moon light.</p>
<p>So, after experimenting a bit with shadow maps, I decided to do screen-space shadows using deferred rendering.</p>
<p>With deferred rendering, you can apply any number of lights in screen space, as a post-processing effect. With world position and normal in each pixel, and knowing light properties (position, color etc) it&#8217;s easy to compute lighting and accumulate it in the light buffer. But the same can be done to compute shadows.</p>
<p>Knowing caster and light properties, one can compute the amount of shadowing for that particular light in the particular pixel, and accumulate it in the shadow buffer. The simplest object is sphere, it&#8217;s defined by four numbers: 3 for position and 1 for radius. Fits nicely in a single float4 shader register.</p>
<p>This means I need to add special &#8220;shadow-casting spheres&#8221; to each object I model, but I&#8217;m fine with that since I was ready to make special shadow-casting meshes for thin objects anyway. And on the plus side, far LODs can use less number of bigger spheres or no spheres at all. Filling object shape with spheres is an approximation of course, but with very blurry shadows it works fine even with a few spheres per object. The stones in the 0.04 have 3 spheres per object.</p>
<p>Computing sky shadow aka ambient occlusion from sphere caster is simple and intuitive, while being <a href="http://iquilezles.org/www/articles/sphereao/sphereao.htm" title="sphere ambient occlusion" target="_blank">mathematically accurate</a>.</p>
<p>Sun shadow is a bit more complex, but can be done with a variant of aperture lighting (see <a href="http://ranmantaru.com/blog/2012/02/25/lighting-in-arcane-worlds-technobabble/">previous technobabble article</a>). Looking from the surface point (the pixel being shadowed) you see two circles: the sun and the sphere caster. The amount of intersection of these circles defines the amount of shadowing. It can be computed approximately of course, since shadows are very blurry anyway.</p>
<p>The accumulated shadow buffer texture is used then in the full-screen lighting pass to modulate sky and sun light respectively.</p>
<h2>Implementation details</h2>
<p>I store shadows as light scaling factors, so 1 is no shadow at all and 0 is full shadow (no light). Sun and sky (AO) shadows are stored in separate 8-bit channels, and I have 2 more channels free for 2 more lights. That&#8217;s sky, two suns/moons and one lightning strike (or other effect light) total, all with shadows. Should be enough for this game.</p>
<p>The shadow accumulation is done just by multiplying those factors, both in the shader and as render target blending.</p>
<p>I started the implementation with a simple full-screen pass for each shadow sphere. When I got it working, I started optimizing the application of the shader.</p>
<p>The screen is divided into cells (16&#215;9 currently, but that&#8217;s easy to change) and each cell has a list of data to pass to the shader (float4 for each shadow sphere). When a shadow sphere is &#8220;rendered&#8221; its affected volume is projected to the screen and its data is added to all cells that the projected area covers. Then the data accumulated in cells is passed to the shader.</p>
<p>You can pass up to 10 float4 registers via interpolants in shader model 3, but I chose to pass 8 at most to simplify the implementation. In fact, I pass either 8, 4, 2 or 1 registers at once. First, I scan all cells and get data from those which have 8 or more entries in the list, storing the data and the cell coordinates in the dynamic vertex buffer. So after this pass, every cell has 7 or less entries in the list and I scan them again to pass data in packs of 4. Then it&#8217;s packs of 2 and finally 1 for odd number of entries in the list.</p>
<p>The technique is known and is usually used in deferred rendering to apply many lights which affect small screen area each. I&#8217;ll reuse it later to apply lights too, for spells, burning trees and the like.</p>
<p>To limit affected screen area, I have to limit the shadow range. For sky shadow, 4 times the radius is fine without any extra tricks, but the sun shadows are longer. I found that 8 times the radius range is acceptable if the end of the shadow is faded smoothly. I use a sphere to approximate affected volume, with center shifted away from the light by 3 times the radius of the shadow sphere, and its radius set to 5 times the shadow sphere radius. That way, I capture 8x radius shadow range away from the light and 2x radius around the caster for noticeable (in daylight) sky shadow.</p>
<h2>Code</h2>
<p>Here comes the shader code:</p>
<p><code><br />
#include&nbsp;"common.sh"</p>
<p>#define&nbsp;MAX_RK&nbsp;8<br />
#define&nbsp;FADE_RANGE&nbsp;4</p>
<p>#define&nbsp;MAX_RK2&nbsp;(MAX_RK*MAX_RK)<br />
#define&nbsp;FADE_RANGE2&nbsp;(FADE_RANGE*FADE_RANGE)</p>
<p>struct&nbsp;VsInput<br />
{<br />
&nbsp;&nbsp;float2&nbsp;dp&nbsp;&nbsp;:&nbsp;POSITION0;<br />
&nbsp;&nbsp;float2&nbsp;org&nbsp;:&nbsp;POSITION1;<br />
&nbsp;&nbsp;float4&nbsp;data[NUMVEC]&nbsp;:&nbsp;TEXCOORD0;<br />
};</p>
<p>struct&nbsp;VsOutput<br />
{<br />
&nbsp;&nbsp;float4&nbsp;pos&nbsp;&nbsp;:&nbsp;POSITION;<br />
&nbsp;&nbsp;float4&nbsp;data[NUMVEC]&nbsp;:&nbsp;TEXCOORD0;<br />
&nbsp;&nbsp;float2&nbsp;tc&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;:&nbsp;TEXCOORD9;<br />
&nbsp;&nbsp;float3&nbsp;wvec&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;:&nbsp;TEXCOORD10;<br />
};</p>
<p>float4&nbsp;postm&nbsp;:&nbsp;register(c15);<br />
float4&nbsp;texsz&nbsp;:&nbsp;register(c16);<br />
float4&nbsp;viewx&nbsp;:&nbsp;register(c17);<br />
float4&nbsp;viewy&nbsp;:&nbsp;register(c18);<br />
float4&nbsp;viewz&nbsp;:&nbsp;register(c19);</p>
<p>VsOutput&nbsp;vsh(VsInput&nbsp;I)<br />
{<br />
&nbsp;&nbsp;VsOutput&nbsp;O;<br />
&nbsp;&nbsp;float2&nbsp;pos=(I.org+I.dp)*postm.xy+postm.zw;<br />
&nbsp;&nbsp;O.pos=float4(pos,&nbsp;1,&nbsp;1);<br />
&nbsp;&nbsp;O.tc=pos.xy*texsz.xy+texsz.zw;<br />
&nbsp;&nbsp;O.wvec=pos.x*viewx.xyz+pos.y*viewy.xyz+viewz.xyz;<br />
&nbsp;&nbsp;O.data=I.data;<br />
&nbsp;&nbsp;return&nbsp;O;<br />
}</p>
<p>float3&nbsp;viewPos&nbsp;:&nbsp;register(c16);</p>
<p>float4&nbsp;psh(VsOutput&nbsp;I)&nbsp;:&nbsp;COLOR<br />
{<br />
&nbsp;&nbsp;UNPACK_GBUF(I.tc.xy)</p>
<p>&nbsp;&nbsp;float3&nbsp;eyeVec=I.wvec*depth;<br />
&nbsp;&nbsp;float3&nbsp;pos=eyeVec+viewPos;</p>
<p>&nbsp;&nbsp;float2&nbsp;shadow=1;</p>
<p>&nbsp;&nbsp;[unroll]<br />
&nbsp;&nbsp;for&nbsp;(int&nbsp;i=0;&nbsp;i&lt;NUMVEC;&nbsp;i++)<br />
&nbsp;&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;float4&nbsp;ball=I.data[i];</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;float3&nbsp;bpos=ball.xyz;<br />
&nbsp;&nbsp;&nbsp;&nbsp;float&nbsp;r=ball.w;<br />
&nbsp;&nbsp;&nbsp;&nbsp;float&nbsp;r2=r*r;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;float3&nbsp;dp=bpos-pos;<br />
&nbsp;&nbsp;&nbsp;&nbsp;float&nbsp;d2=dot(dp,&nbsp;dp);</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;float3&nbsp;dpn=dp*rsqrt(d2);</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;float&nbsp;dn=saturate(dot(dpn,&nbsp;norm));</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;sky&nbsp;/&nbsp;ambient<br />
&nbsp;&nbsp;&nbsp;&nbsp;float&nbsp;sin2=r2/d2;<br />
&nbsp;&nbsp;&nbsp;&nbsp;float&nbsp;sky=sin2*dn;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;sun<br />
&nbsp;&nbsp;&nbsp;&nbsp;float&nbsp;c=dot(dpn,&nbsp;sun_dir.xyz);<br />
&nbsp;&nbsp;&nbsp;&nbsp;float&nbsp;ca=sqrt(saturate(1-sin2)),&nbsp;cb=sun_dir.w;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;float&nbsp;cc=ca*cb,&nbsp;ss=sqrt((1-ca*ca)*(1-cb*cb));<br />
&nbsp;&nbsp;&nbsp;&nbsp;float&nbsp;maxc=cc+ss;<br />
&nbsp;&nbsp;&nbsp;&nbsp;float&nbsp;sun=smoothstep(cc-ss,&nbsp;maxc,&nbsp;c)*(1-ca)/(1-cb);</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;fade&nbsp;with&nbsp;distance<br />
&nbsp;&nbsp;&nbsp;&nbsp;float&nbsp;fk=saturate((r2*MAX_RK2-d2)/(r2*FADE_RANGE2));</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;shadow*=saturate(1-float2(sky,&nbsp;sun)*fk);<br />
&nbsp;&nbsp;}</p>
<p>&nbsp;&nbsp;return&nbsp;float4(shadow,&nbsp;0,&nbsp;0);<br />
}<br />
</code></p>
]]></content:encoded>
			<wfw:commentRss>https://ranmantaru.com/blog/2012/04/17/soft-object-shadows-with-deferred-rendering/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Lighting in Arcane Worlds (technobabble)</title>
		<link>https://ranmantaru.com/blog/2012/02/25/lighting-in-arcane-worlds-technobabble/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=lighting-in-arcane-worlds-technobabble</link>
		<comments>https://ranmantaru.com/blog/2012/02/25/lighting-in-arcane-worlds-technobabble/#comments</comments>
		<pubDate>Sat, 25 Feb 2012 15:58:10 +0000</pubDate>
		<dc:creator>e-dog</dc:creator>
				<category><![CDATA[Arcane Worlds]]></category>
		<category><![CDATA[article]]></category>
		<category><![CDATA[dev]]></category>
		<category><![CDATA[technobabble]]></category>

		<guid isPermaLink="false">http://ranmantaru.com/?p=174</guid>
		<description><![CDATA[I&#8217;ve been asked how lighting is done in Arcane Worlds, so I decided to write this article. It&#8217;s mostly technobabble, so I&#8217;m going to assume you have some coding/math skills to understand it. There are no pictures. You&#8217;ve been warned. The sky When I first started with the prototype I tried computing &#8220;real&#8221; sky with [...]]]></description>
			<content:encoded><![CDATA[<p>I&#8217;ve been asked how lighting is done in Arcane Worlds, so I decided to write this article.<br />
It&#8217;s mostly technobabble, so I&#8217;m going to assume you have some coding/math skills to understand it.<br />
There are no pictures. You&#8217;ve been warned.<br />
<span id="more-174"></span></p>
<h2>The sky</h2>
<p>When I first started with the prototype I tried computing &#8220;real&#8221; sky with multiple scattering. It had spectacular sunsets, but the day sky was rather bland and the night&#8230; um&#8230; was pitch black without moon or stars.<br />
It was really hard to tune to get the colors I wanted, due to obscure physical parameters and issues with HDR mapping. Tying it to land lighting had issues too. And the huge sun I wanted is far from small Earth-like sun expected in scattering computations.</p>
<p>So, I ditched it. Now I&#8217;m using an empirical sky model which is much easier to tune.<br />
This model has three parts: vertical gradient, sun-axis gradient and circumsolar region.</p>
<h3>Vertical gradient</h3>
<p>Vertical gradient is the most important one &#8211; it&#8217;s used as a multiplier for both other parts. It&#8217;s a zenith-horizon color gradient with exponential falloff, so it&#8217;s &#8220;thicker&#8221; at the horizon, simulating longer light path through the atmosphere near the horizon.<br />
Formula:<br />
<code>&nbsp;&nbsp;skyV0+skyVS*exp(-skyVG/(abs(mu)+0.001))</code><br />
where &#8220;mu&#8221; is the cosine of zenith-to-view angle, and coefficients are computed from zenith/horizon colors. Note: it&#8217;s mirrored using abs() at the horizon, and that 0.001 is to avoid division by zero.</p>
<h3>Sun-axis gradient</h3>
<p>Sun-axis gradient simulates (among other things) the anisotropic nature of <a href="http://en.wikipedia.org/wiki/Rayleigh_scattering" target="_blank">Rayleigh scattering</a> which makes sky darker at viewing directions orthogonal to sun direction. It&#8217;s the main gradient to tune when making a sunset sky.<br />
Formula is simple:<br />
<code>&nbsp;&nbsp;skyS0+(vs>0?skyS1:skyS2)*(vs*vs)</code><br />
where &#8220;vs&#8221; is the cosine of view-to-sun angle. As you see, it interpolates three colors: solar, orthogonal and anti-solar.</p>
<h3>Circumsolar region</h3>
<p>Circumsolar region simulates Mie scattering of sunlight, producing sun disk and corona. It&#8217;s added to the sun-axis gradient, and multiplied (together with it) by vertical gradient to produce the final sky+sun color.<br />
Formula:<br />
<code>&nbsp;&nbsp;skyCS*exp(-skyCG*acos(vsd))*hf<br />
&nbsp;&nbsp;vsd=min(vs+(1-cos(sun_size)), 1)</code><br />
where &#8220;vsd&#8221; is the cosine of view-to-sun angle, shifted by sun size and clamped to produce the sun disk, and &#8220;hf&#8221; is the &#8220;horizon factor&#8221; to cut off the sun disk and corona below the horizon. Also, I replaced acos() with an approximation, but it&#8217;s here for clarity.</p>
<h2>The horizon</h2>
<p>The &#8220;mu&#8221; term is actually shifted and scaled a bit to lower the horizon line, to make it look like a &#8220;small planet&#8221; with noticeably curved horizon. The area below the horizon line is darkened a bit so it looks like a fogged/reflecting planet surface.</p>
<h2>The fog</h2>
<p>The fog color is computed like sky, but without circumsolar region (hf=0). Then it&#8217;s darkened a bit (just like the &#8220;planet&#8221; in the background below the horizon) and applied using the simple scalar fog density term.</p>
<p>The fog density formula is unrealistic, crafted to produce 100% fog just before the visible land surface ends. It also fades slower vertically, so the land below you is less fogged with altitude than it could be.</p>
<p>The interesting effect: since the land closer to camera is visually closer to nadir (opposite of zenith), the fog color essentially changes with distance, producing a nice gradient. It could be mistaken for computed scattering, but it&#8217;s actually fake.</p>
<h2>Aperture lighting</h2>
<p>The huge sun I wanted should cast soft shadows (large penumbra). Also, I want multiple and dynamic light sources: moons, suns, lightning strikes. Shadow maps aren&#8217;t good at it. So, I used aperture lighting.</p>
<p>I&#8217;d refer you to <a href="http://developer.amd.com/media/gpu_assets/Oat-AmbientApetureLighting.pdf" target="_blank">this article</a> for details, but here&#8217;s the essential idea: for each point, approximate environment visibility with a circular &#8220;window&#8221; (aperture). The aperture depends on the geometry only, so it can be precomputed and then used with multiple lights. So, it&#8217;s a kind of directional ambient occlusion. The circular aperture can be specified with a vector and a scalar size, fitting it in 4 scalars, or even in 3 using tangent space. The range is low, so 8 bits per component is enough to store it.</p>
<p>It&#8217;s easy to make soft shadows with aperture lighting &#8211; just use a large light source. A small light will produce harder but inaccurate shadows (still can be good enough). Ambient occlusion and sky light are easy to make too.</p>
<p>In Arcane Worlds, the apertures are computed on GPU, sampling a lot of heightmap points (actually, that heightmap is land+water now, i.e. top surface). It&#8217;s done 20 times per second at most (game logic / physics step).</p>
<h2>Land lighting</h2>
<p>The land is lit by sun (see paper link above for details and formulas) and sky.</p>
<p>The sun color is constant in shader. There&#8217;s the usual normal-to-light dot product, except I interpolate the light direction between sun and aperture vectors, weighting by sun/aperture areas, so that small aperture receives light from aperture direction mostly.</p>
<p>The sky light is simple: compute sky color in the middle direction between surface normal and aperture vector (approximating diffuse lighting from environment), and scale it by aperture area.</p>
<p>The fog is applied after multiplying the light and surface colors.</p>
<h2>Water shader</h2>
<p>The foam is lit like land, but ignoring the normal and associated dot products, because the foam is strongly translucent and has no real surface normal.</p>
<p>The sky reflection is obvious. The aperture is used to occlude the sky (approximately), interpolating on its edge between sky reflection and the approximate land lighting in the reflected direction. So that blurred land reflections you can see in the demo are actually produced by apertures.</p>
<p>No Fresnel term is used. The fog is applied as usual.</p>
<h2>Some final notes</h2>
<p>I aim at painterly look with Arcane Worlds, so I choose what looks good instead of what is realistic or physically correct.</p>
<p>As you might have noticed, there are no real HDR computations or mappings, the sky is tuned in low dynamic range. But a decent gamma correction is still important. The formulas above are in linear color space, but the stored and final colors are packed to gamma 2.0. Why 2.0 and not the common 2.2 monitor gamma? Because it&#8217;s faster to compute and the difference is small anyway.</p>
<p>The sky is tuned using &#8220;sky.nut&#8221; Squirrel script. You can edit it and it should be reloaded automatically by the demo when you save it.</p>
]]></content:encoded>
			<wfw:commentRss>https://ranmantaru.com/blog/2012/02/25/lighting-in-arcane-worlds-technobabble/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Water erosion on heightmap terrain</title>
		<link>https://ranmantaru.com/blog/2011/10/08/water-erosion-on-heightmap-terrain/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=water-erosion-on-heightmap-terrain</link>
		<comments>https://ranmantaru.com/blog/2011/10/08/water-erosion-on-heightmap-terrain/#comments</comments>
		<pubDate>Sat, 08 Oct 2011 16:01:39 +0000</pubDate>
		<dc:creator>e-dog</dc:creator>
				<category><![CDATA[Arcane Worlds]]></category>
		<category><![CDATA[article]]></category>
		<category><![CDATA[code]]></category>
		<category><![CDATA[dev]]></category>
		<category><![CDATA[screenshots]]></category>

		<guid isPermaLink="false">http://ranmantaru.com/?p=77</guid>
		<description><![CDATA[I&#8217;m back from vacation, and I decided to write an article as a warm-up exercise. This article is for those who know what a heightmap is, and who are familiar with some basic heightmap generation. If you aren’t one, you can still enjoy the screenshots and skip most of the technobabble here Examples Here is [...]]]></description>
			<content:encoded><![CDATA[<p>I&#8217;m back from vacation, and I decided to write an article as a warm-up exercise.</p>
<p>This article is for those who know what a heightmap is, and who are familiar with some basic heightmap generation. If you aren’t one, you can still enjoy the screenshots and skip most of the technobabble here <img src='https://ranmantaru.com/wordpress/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' /> </p>
<h2>Examples</h2>
<p>Here is some fault-formed terrain:</p>
<table>
<tr>
<td>before erosion</td>
<td>after erosion</td>
</tr>
<tr>
<td>
<a href="https://ranmantaru.com/wordpress/wp-content/gallery/shots1/shot2011_10_06_15_57_03.jpg" title=""  >
	<img class="ngg-singlepic" src="https://ranmantaru.com/wordpress/wp-content/gallery/cache/19__256x_shot2011_10_06_15_57_03.jpg" alt="shot2011_10_06_15_57_03" title="shot2011_10_06_15_57_03" />
</a>
</td>
<td>
<a href="https://ranmantaru.com/wordpress/wp-content/gallery/shots1/shot2011_10_06_15_57_08.jpg" title=""  >
	<img class="ngg-singlepic" src="https://ranmantaru.com/wordpress/wp-content/gallery/cache/20__256x_shot2011_10_06_15_57_08.jpg" alt="shot2011_10_06_15_57_08" title="shot2011_10_06_15_57_08" />
</a>
</td>
</tr>
<tr>
<td>
<a href="https://ranmantaru.com/wordpress/wp-content/gallery/shots1/shot2011_10_06_15_56_26.jpg" title=""  >
	<img class="ngg-singlepic" src="https://ranmantaru.com/wordpress/wp-content/gallery/cache/17__256x_shot2011_10_06_15_56_26.jpg" alt="shot2011_10_06_15_56_26" title="shot2011_10_06_15_56_26" />
</a>
</td>
<td>
<a href="https://ranmantaru.com/wordpress/wp-content/gallery/shots1/shot2011_10_06_15_56_29.jpg" title=""  >
	<img class="ngg-singlepic" src="https://ranmantaru.com/wordpress/wp-content/gallery/cache/18__256x_shot2011_10_06_15_56_29.jpg" alt="shot2011_10_06_15_56_29" title="shot2011_10_06_15_56_29" />
</a>
</td>
</tr>
</table>
<p><span id="more-77"></span></p>
<table>
<tr>
<td>
<a href="https://ranmantaru.com/wordpress/wp-content/gallery/shots1/shot2011_10_05_17_06_26.jpg" title=""  >
	<img class="ngg-singlepic" src="https://ranmantaru.com/wordpress/wp-content/gallery/cache/7__256x_shot2011_10_05_17_06_26.jpg" alt="shot2011_10_05_17_06_26" title="shot2011_10_05_17_06_26" />
</a>
</td>
<td>
<a href="https://ranmantaru.com/wordpress/wp-content/gallery/shots1/shot2011_10_05_17_06_32.jpg" title=""  >
	<img class="ngg-singlepic" src="https://ranmantaru.com/wordpress/wp-content/gallery/cache/8__256x_shot2011_10_05_17_06_32.jpg" alt="shot2011_10_05_17_06_32" title="shot2011_10_05_17_06_32" />
</a>
</td>
</tr>
<tr>
<td>
<a href="https://ranmantaru.com/wordpress/wp-content/gallery/shots1/shot2011_10_05_17_11_28.jpg" title=""  >
	<img class="ngg-singlepic" src="https://ranmantaru.com/wordpress/wp-content/gallery/cache/9__256x_shot2011_10_05_17_11_28.jpg" alt="shot2011_10_05_17_11_28" title="shot2011_10_05_17_11_28" />
</a>
</td>
<td>
<a href="https://ranmantaru.com/wordpress/wp-content/gallery/shots1/shot2011_10_05_17_11_33.jpg" title=""  >
	<img class="ngg-singlepic" src="https://ranmantaru.com/wordpress/wp-content/gallery/cache/10__256x_shot2011_10_05_17_11_33.jpg" alt="shot2011_10_05_17_11_33" title="shot2011_10_05_17_11_33" />
</a>
</td>
</tr>
</table>
<p>And here is what my erosion method can do to a simple classic fractal-noise-generated terrain:</p>
<table>
<tr>
<td>before erosion</td>
<td>after erosion</td>
</tr>
<tr>
<td>
<a href="https://ranmantaru.com/wordpress/wp-content/gallery/shots1/shot2011_10_05_18_44_58.jpg" title=""  >
	<img class="ngg-singlepic" src="https://ranmantaru.com/wordpress/wp-content/gallery/cache/11__256x_shot2011_10_05_18_44_58.jpg" alt="shot2011_10_05_18_44_58" title="shot2011_10_05_18_44_58" />
</a>
</td>
<td>
<a href="https://ranmantaru.com/wordpress/wp-content/gallery/shots1/shot2011_10_05_18_45_15.jpg" title=""  >
	<img class="ngg-singlepic" src="https://ranmantaru.com/wordpress/wp-content/gallery/cache/12__256x_shot2011_10_05_18_45_15.jpg" alt="shot2011_10_05_18_45_15" title="shot2011_10_05_18_45_15" />
</a>
</td>
</tr>
<tr>
<td>
<a href="https://ranmantaru.com/wordpress/wp-content/gallery/shots1/shot2011_10_05_18_52_26.jpg" title=""  >
	<img class="ngg-singlepic" src="https://ranmantaru.com/wordpress/wp-content/gallery/cache/13__256x_shot2011_10_05_18_52_26.jpg" alt="shot2011_10_05_18_52_26" title="shot2011_10_05_18_52_26" />
</a>
</td>
<td>
<a href="https://ranmantaru.com/wordpress/wp-content/gallery/shots1/shot2011_10_05_18_52_29.jpg" title=""  >
	<img class="ngg-singlepic" src="https://ranmantaru.com/wordpress/wp-content/gallery/cache/14__256x_shot2011_10_05_18_52_29.jpg" alt="shot2011_10_05_18_52_29" title="shot2011_10_05_18_52_29" />
</a>
</td>
</tr>
</table>
<p>It also turns artificial forms into more natural looking ones:</p>
<table>
<tr>
<td>
<a href="https://ranmantaru.com/wordpress/wp-content/gallery/shots1/shot2011_10_05_19_03_09.jpg" title=""  >
	<img class="ngg-singlepic" src="https://ranmantaru.com/wordpress/wp-content/gallery/cache/15__256x_shot2011_10_05_19_03_09.jpg" alt="shot2011_10_05_19_03_09" title="shot2011_10_05_19_03_09" />
</a>
</td>
<td>
<a href="https://ranmantaru.com/wordpress/wp-content/gallery/shots1/shot2011_10_05_19_03_11.jpg" title=""  >
	<img class="ngg-singlepic" src="https://ranmantaru.com/wordpress/wp-content/gallery/cache/16__256x_shot2011_10_05_19_03_11.jpg" alt="shot2011_10_05_19_03_11" title="shot2011_10_05_19_03_11" />
</a>
</td>
</tr>
</table>
<h2>Applications</h2>
<p>As you see from the examples above, erosion can make generated terrain look much more interesting and natural. It looks even better when you add grass by slope and/or ground type.<br />
It&#8217;s very straightforward to integrate since it modifies existing heightmap. It can be applied several times too, both for increased effect or after other terrain modifiers.<br />
It&#8217;s useful for gameplay as well, since it makes lower areas more flat and thus easier to walk/navigate/build on.</p>
<p>It has some drawbacks of course.<br />
It&#8217;s a full-terrain method, not suitable for “infinite” generated worlds. It works on maps of limited size.<br />
It can be rather slow on large heightmaps. Especially if you want heavy erosion. So it&#8217;s probably better as offline method in your editor if your heightmap is large.</p>
<h2>Understanding water erosion</h2>
<p>The water starts somewhere on the terrain (as rain, water spring, whatever), then flows downhill, dissolving the soil, carrying it, and depositing it as sediment. Eventually it gets to a low point and evaporates.<br />
The trick here is understanding (and carefully implementing) the erosion/deposition process. This involves sediment carry capacity of the flow.<br />
At any time, the water flow can only carry a limited amount of dissolved soil. This amount depends on the surface slope, the speed of the flow and the amount of water.<br />
If less is carried than possible, erosion happens, removing soil from the terrain and adding it to the flow.<br />
If more is carried, deposition happens, dropping extra carried soil as sediment.<br />
As you can see, the process can switch fast between erosion and deposition as the flow accelerates, or drops down a steep slope, or comes to a flat area.<br />
The formula I use:<br />
<code>q=max(slope, minSlope)*v*w*Kq</code><br />
Where <em>slope</em> is just a tangent, <em>minSlope</em> and <em>Kq</em> are constant parameters, <em>v</em> is flow speed and <em>w</em> is amount of water in the flow.</p>
<h2>Droplets</h2>
<p>I use droplet model to simulate water erosion. That is, I pick a starting point on the terrain, start with zero velocity and carried soil, and proceed moving this point, changing its dynamic variables and the terrain in the process, until all its water evaporates or it flows to a pit it can&#8217;t get out of.<br />
Some tricky things here are:</p>
<ul>
<li>I use height gradient as the downhill direction. If the gradient is zero, I pick a random direction. I also add some inertia just for fun.</li>
<li>I then sample next height in the downhill direction. If it happens to be higher than the current one, we&#8217;re in the pit and should either drop sediment to fill it and flow on, or die trying. When the flow goes on after filling the pit, its speed is reset to zero.</li>
<li>When erosion happens, don&#8217;t remove more than the height difference (between current and next position). That is, don&#8217;t dig a pit in the place the water flows from, cut it flat at most. Digging a pit makes the process unstable, creating nasty spikes.</li>
<li>I deposit to heightmap using bilinear weights. This way it fills pits better while being somewhat smooth.</li>
<li>I erode the heightmap with a bell-like “brush”, to make smoother (yet somewhat wider) channels. That&#8217;s to make the final result look better too.</li>
<li>A single droplet effect on the terrain is hardly noticeable (unless you use some crazy parameters). Use at least a thousand droplets for testing. Their power is in numbers.</li>
<li>Erosion and deposition speed is affected by special parameters. So only some (not all) extra soil is dropped when carry capacity is exceeded, for example.</li>
<li>The flow speed is accelerated by height delta on each step, and some water is evaporated as well.</li>
</ul>
<h2>Droplet sources</h2>
<p>I use “rain” erosion in the examples, starting droplets randomly all over the terrain.<br />
However, other effects can be achieved with non-uniform droplet distribution, like creating streams from sources, or raining more/less in certain areas.</p>
<h2>Code sample</h2>
<p>Here is the code &#8220;as is&#8221;. It won&#8217;t compile since it uses some external stuff, but I hope it&#8217;s easy enough to figure it out.<br />
You can do with it whatever you want.</p>
<p><code><br />
void&nbsp;HeightmapData::genDropletErosion(unsigned&nbsp;iterations,&nbsp;ErosionParams&nbsp;&amp;params)<br />
{<br />
&nbsp;&nbsp;float&nbsp;Kq=params.Kq,&nbsp;Kw=params.Kw,&nbsp;Kr=params.Kr,&nbsp;Kd=params.Kd,&nbsp;Ki=params.Ki,<br />
&nbsp;&nbsp;&nbsp;&nbsp;minSlope=params.minSlope,&nbsp;Kg=params.g*2;</p>
<p>&nbsp;&nbsp;TempData&lt;Point2[HMAP_SIZE*HMAP_SIZE]&gt;&nbsp;erosion;</p>
<p>&nbsp;&nbsp;float&nbsp;flt=0;<br />
&nbsp;&nbsp;erosion.fillMem32(*(uint32_t*)&amp;flt);</p>
<p>&nbsp;&nbsp;static&nbsp;const&nbsp;unsigned&nbsp;MAX_PATH_LEN=HMAP_SIZE*4;</p>
<p>&nbsp;&nbsp;int64_t&nbsp;t0=get_ref_time();</p>
<p>&nbsp;&nbsp;#define&nbsp;DEPOSIT_AT(X,&nbsp;Z,&nbsp;W)&nbsp;\<br />
&nbsp;&nbsp;{&nbsp;\<br />
&nbsp;&nbsp;&nbsp;&nbsp;float&nbsp;delta=ds*(W);&nbsp;\<br />
&nbsp;&nbsp;&nbsp;&nbsp;erosion[HMAP_INDEX((X),&nbsp;(Z))].y+=delta;&nbsp;\<br />
&nbsp;&nbsp;&nbsp;&nbsp;hmap&nbsp;&nbsp;&nbsp;[HMAP_INDEX((X),&nbsp;(Z))]&nbsp;&nbsp;+=delta;&nbsp;\<br />
&nbsp;&nbsp;&nbsp;&nbsp;params.deposit(scolor,&nbsp;surface[HMAP_INDEX((X),&nbsp;(Z))],&nbsp;delta);&nbsp;\<br />
&nbsp;&nbsp;}</p>
<p>&nbsp;&nbsp;#if&nbsp;1<br />
&nbsp;&nbsp;&nbsp;&nbsp;#define&nbsp;DEPOSIT(H)&nbsp;\<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;DEPOSIT_AT(xi&nbsp;&nbsp;,&nbsp;zi&nbsp;&nbsp;,&nbsp;(1-xf)*(1-zf))&nbsp;\<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;DEPOSIT_AT(xi+1,&nbsp;zi&nbsp;&nbsp;,&nbsp;&nbsp;&nbsp;&nbsp;xf&nbsp;*(1-zf))&nbsp;\<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;DEPOSIT_AT(xi&nbsp;&nbsp;,&nbsp;zi+1,&nbsp;(1-xf)*&nbsp;&nbsp;&nbsp;zf&nbsp;)&nbsp;\<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;DEPOSIT_AT(xi+1,&nbsp;zi+1,&nbsp;&nbsp;&nbsp;&nbsp;xf&nbsp;*&nbsp;&nbsp;&nbsp;zf&nbsp;)&nbsp;\<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;(H)+=ds;<br />
&nbsp;&nbsp;#else<br />
&nbsp;&nbsp;&nbsp;&nbsp;#define&nbsp;DEPOSIT(H)&nbsp;\<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;DEPOSIT_AT(xi&nbsp;&nbsp;,&nbsp;zi&nbsp;&nbsp;,&nbsp;0.25f)&nbsp;\<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;DEPOSIT_AT(xi+1,&nbsp;zi&nbsp;&nbsp;,&nbsp;0.25f)&nbsp;\<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;DEPOSIT_AT(xi&nbsp;&nbsp;,&nbsp;zi+1,&nbsp;0.25f)&nbsp;\<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;DEPOSIT_AT(xi+1,&nbsp;zi+1,&nbsp;0.25f)&nbsp;\<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;(H)+=ds;<br />
&nbsp;&nbsp;#endif</p>
<p>&nbsp;&nbsp;uint64_t&nbsp;longPaths=0,&nbsp;randomDirs=0,&nbsp;sumLen=0;</p>
<p>&nbsp;&nbsp;for&nbsp;(unsigned&nbsp;iter=0;&nbsp;iter&lt;iterations;&nbsp;++iter)<br />
&nbsp;&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;((iter&amp;0x3FFF)==0&nbsp;&amp;&amp;&nbsp;iter!=0)&nbsp;show_splash("Calculating&nbsp;erosion",&nbsp;(iter+0.5f)/iterations);</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;int&nbsp;xi=game_rnd()&amp;(HMAP_SIZE-1);<br />
&nbsp;&nbsp;&nbsp;&nbsp;int&nbsp;zi=game_rnd()&amp;(HMAP_SIZE-1);</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;float&nbsp;xp=xi,&nbsp;zp=zi;<br />
&nbsp;&nbsp;&nbsp;&nbsp;float&nbsp;xf=&nbsp;0,&nbsp;zf=&nbsp;0;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;float&nbsp;h=HMAP(xi,&nbsp;zi);<br />
&nbsp;&nbsp;&nbsp;&nbsp;float&nbsp;s=0,&nbsp;v=0,&nbsp;w=1;<br />
&nbsp;&nbsp;&nbsp;&nbsp;vec4f&nbsp;scolor=zero4f();</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;float&nbsp;h00=h;<br />
&nbsp;&nbsp;&nbsp;&nbsp;float&nbsp;h10=HMAP(xi+1,&nbsp;zi&nbsp;&nbsp;);<br />
&nbsp;&nbsp;&nbsp;&nbsp;float&nbsp;h01=HMAP(xi&nbsp;&nbsp;,&nbsp;zi+1);<br />
&nbsp;&nbsp;&nbsp;&nbsp;float&nbsp;h11=HMAP(xi+1,&nbsp;zi+1);</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;float&nbsp;dx=0,&nbsp;dz=0;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;unsigned&nbsp;numMoves=0;<br />
&nbsp;&nbsp;&nbsp;&nbsp;for&nbsp;(;&nbsp;numMoves&lt;MAX_PATH_LEN;&nbsp;++numMoves)<br />
&nbsp;&nbsp;&nbsp;&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;calc&nbsp;gradient<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;float&nbsp;gx=h00+h01-h10-h11;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;float&nbsp;gz=h00+h10-h01-h11;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//==&nbsp;better&nbsp;interpolated&nbsp;gradient?</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;calc&nbsp;next&nbsp;pos<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;dx=(dx-gx)*Ki+gx;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;dz=(dz-gz)*Ki+gz;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;float&nbsp;dl=sqrtf(dx*dx+dz*dz);<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(dl&lt;=FLT_EPSILON)<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;pick&nbsp;random&nbsp;dir<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;float&nbsp;a=frnd()*TWOPI;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;dx=cosf(a);<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;dz=sinf(a);<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;++randomDirs;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;dx/=dl;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;dz/=dl;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;float&nbsp;nxp=xp+dx;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;float&nbsp;nzp=zp+dz;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;sample&nbsp;next&nbsp;height<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;int&nbsp;nxi=intfloorf(nxp);<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;int&nbsp;nzi=intfloorf(nzp);<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;float&nbsp;nxf=nxp-nxi;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;float&nbsp;nzf=nzp-nzi;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;float&nbsp;nh00=HMAP(nxi&nbsp;&nbsp;,&nbsp;nzi&nbsp;&nbsp;);<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;float&nbsp;nh10=HMAP(nxi+1,&nbsp;nzi&nbsp;&nbsp;);<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;float&nbsp;nh01=HMAP(nxi&nbsp;&nbsp;,&nbsp;nzi+1);<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;float&nbsp;nh11=HMAP(nxi+1,&nbsp;nzi+1);</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;float&nbsp;nh=(nh00*(1-nxf)+nh10*nxf)*(1-nzf)+(nh01*(1-nxf)+nh11*nxf)*nzf;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;if&nbsp;higher&nbsp;than&nbsp;current,&nbsp;try&nbsp;to&nbsp;deposit&nbsp;sediment&nbsp;up&nbsp;to&nbsp;neighbour&nbsp;height<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(nh&gt;=h)<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;float&nbsp;ds=(nh-h)+0.001f;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(ds&gt;=s)<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;deposit&nbsp;all&nbsp;sediment&nbsp;and&nbsp;stop<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ds=s;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;DEPOSIT(h)<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;s=0;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;break;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;DEPOSIT(h)<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;s-=ds;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;v=0;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;compute&nbsp;transport&nbsp;capacity<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;float&nbsp;dh=h-nh;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;float&nbsp;slope=dh;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//float&nbsp;slope=dh/sqrtf(dh*dh+1);</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;float&nbsp;q=maxval(slope,&nbsp;minSlope)*v*w*Kq;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;deposit/erode&nbsp;(don't&nbsp;erode&nbsp;more&nbsp;than&nbsp;dh)<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;float&nbsp;ds=s-q;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(ds&gt;=0)<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;deposit<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ds*=Kd;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//ds=minval(ds,&nbsp;1.0f);</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;DEPOSIT(dh)<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;s-=ds;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;erode<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ds*=-Kr;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ds=minval(ds,&nbsp;dh*0.99f);</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#define&nbsp;ERODE(X,&nbsp;Z,&nbsp;W)&nbsp;\<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{&nbsp;\<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;float&nbsp;delta=ds*(W);&nbsp;\<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;hmap&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[HMAP_INDEX((X),&nbsp;(Z))]-=delta;&nbsp;\<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Point2&nbsp;&amp;e=erosion[HMAP_INDEX((X),&nbsp;(Z))];&nbsp;\<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;float&nbsp;r=e.x,&nbsp;d=e.y;&nbsp;\<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(delta&lt;=d)&nbsp;d-=delta;&nbsp;\<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else&nbsp;{&nbsp;r+=delta-d;&nbsp;d=0;&nbsp;}&nbsp;\<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;e.x=r;&nbsp;e.y=d;&nbsp;\<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;scolor=params.erode(scolor,&nbsp;surface[HMAP_INDEX((X),&nbsp;(Z))],&nbsp;s,&nbsp;delta);&nbsp;\<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#if&nbsp;1<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;for&nbsp;(int&nbsp;z=zi-1;&nbsp;z&lt;=zi+2;&nbsp;++z)<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;float&nbsp;zo=z-zp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;float&nbsp;zo2=zo*zo;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;for&nbsp;(int&nbsp;x=xi-1;&nbsp;x&lt;=xi+2;&nbsp;++x)<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;float&nbsp;xo=x-xp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;float&nbsp;w=1-(xo*xo+zo2)*0.25f;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(w&lt;=0)&nbsp;continue;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;w*=0.1591549430918953f;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ERODE(x,&nbsp;z,&nbsp;w)<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#else<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ERODE(xi&nbsp;&nbsp;,&nbsp;zi&nbsp;&nbsp;,&nbsp;(1-xf)*(1-zf))<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ERODE(xi+1,&nbsp;zi&nbsp;&nbsp;,&nbsp;&nbsp;&nbsp;&nbsp;xf&nbsp;*(1-zf))<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ERODE(xi&nbsp;&nbsp;,&nbsp;zi+1,&nbsp;(1-xf)*&nbsp;&nbsp;&nbsp;zf&nbsp;)<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ERODE(xi+1,&nbsp;zi+1,&nbsp;&nbsp;&nbsp;&nbsp;xf&nbsp;*&nbsp;&nbsp;&nbsp;zf&nbsp;)<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#endif</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;dh-=ds;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#undef&nbsp;ERODE</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;s+=ds;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;move&nbsp;to&nbsp;the&nbsp;neighbour<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;v=sqrtf(v*v+Kg*dh);<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;w*=1-Kw;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;xp=nxp;&nbsp;zp=nzp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;xi=nxi;&nbsp;zi=nzi;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;xf=nxf;&nbsp;zf=nzf;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;h=nh;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;h00=nh00;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;h10=nh10;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;h01=nh01;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;h11=nh11;<br />
&nbsp;&nbsp;&nbsp;&nbsp;}</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(numMoves&gt;=MAX_PATH_LEN)<br />
&nbsp;&nbsp;&nbsp;&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;debug("droplet&nbsp;#%d&nbsp;path&nbsp;is&nbsp;too&nbsp;long!",&nbsp;iter);<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;++longPaths;<br />
&nbsp;&nbsp;&nbsp;&nbsp;}</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;sumLen+=numMoves;<br />
&nbsp;&nbsp;}</p>
<p>&nbsp;&nbsp;#undef&nbsp;DEPOSIT<br />
&nbsp;&nbsp;#undef&nbsp;DEPOSIT_AT</p>
<p>&nbsp;&nbsp;int64_t&nbsp;t1=get_ref_time();<br />
&nbsp;&nbsp;debug("computed&nbsp;%7d&nbsp;erosion&nbsp;droplets&nbsp;in&nbsp;%6u&nbsp;ms,&nbsp;%.0f&nbsp;droplets/s",<br />
&nbsp;&nbsp;&nbsp;&nbsp;iterations,&nbsp;get_time_msec(t1-t0),&nbsp;double(iterations)/get_time_sec(t1-t0));</p>
<p>&nbsp;&nbsp;debug("&nbsp;&nbsp;%.2f&nbsp;average&nbsp;path&nbsp;length,&nbsp;%I64u&nbsp;long&nbsp;paths&nbsp;cut,&nbsp;%I64u&nbsp;random&nbsp;directions&nbsp;picked",<br />
&nbsp;&nbsp;&nbsp;&nbsp;double(sumLen)/iterations,&nbsp;longPaths,&nbsp;randomDirs);<br />
}<br />
</code></p>
<h2>About parameters</h2>
<p>Here are the default parameters I use:<br />
<code><br />
ErosionParams()<br />
{<br />
&nbsp;&nbsp;Kq=10; Kw=0.001f; Kr=0.9f; Kd=0.02f; Ki=0.1f; minSlope=0.05f; g=20;<br />
}<br />
</code></p>
<p>Kq and minSlope are for soil carry capacity (see the formula above).<br />
Kw is water evaporation speed.<br />
Kr is erosion speed (how fast the soil is removed).<br />
Kd is deposition speed (how fast the extra sediment is dropped).<br />
Ki is direction inertia. Higher values make channel turns smoother.<br />
g is gravity that accelerates the flows.</p>
<h2>Other simulation methods</h2>
<p>Droplet model is not the only one. The same flow process can be simulated with iterations on the grid, for example. However I find droplets results more interesting.<br />
Also, there are methods to fake the erosion result. The fake isn&#8217;t as good as a &#8220;real&#8221; one, but it can be much faster. See for example “Realtime Procedural Terrain Generation” by Jacob Olsen.</p>
<h2>Other erosion types</h2>
<p>This article covers some water erosion only. There are other erosion types though. Rivers can do some interesting things to the terrain, for one. Coastal erosion is completely different, as well as glacial and thermal ones.<br />
Wind erosion can be done with droplet-like method, but is very different in nature too.</p>
]]></content:encoded>
			<wfw:commentRss>https://ranmantaru.com/blog/2011/10/08/water-erosion-on-heightmap-terrain/feed/</wfw:commentRss>
		<slash:comments>7</slash:comments>
		</item>
	</channel>
</rss>
