<?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>TriangularPixels.com &#187; Shaders</title>
	<atom:link href="http://triangularpixels.net/games/category/shaders/feed/" rel="self" type="application/rss+xml" />
	<link>http://triangularpixels.net/games</link>
	<description>Blue Sky Games</description>
	<lastBuildDate>Tue, 28 Jul 2009 11:51:37 +0000</lastBuildDate>
	<generator>http://wordpress.org/?v=2.9.2</generator>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
			<item>
		<title>2D Ambient Shadows</title>
		<link>http://triangularpixels.net/games/games/2008/07/25/2d-ambient-shadows/</link>
		<comments>http://triangularpixels.net/games/games/2008/07/25/2d-ambient-shadows/#comments</comments>
		<pubDate>Fri, 25 Jul 2008 00:01:53 +0000</pubDate>
		<dc:creator>JC</dc:creator>
				<category><![CDATA[Development]]></category>
		<category><![CDATA[Games]]></category>
		<category><![CDATA[Graphics]]></category>
		<category><![CDATA[Shaders]]></category>

		<guid isPermaLink="false">http://triangularpixels.net/games/games/2008/07/25/2d-ambient-shadows/</guid>
		<description><![CDATA[For the last few days I've taken some time off from the AI work to mess around with some graphical effects, and in particular I've been experimenting with a 2d ambient shadows effect. This is inspired by the Screen Space Ambient Occulsion (SSAO) effect which has gotten popular lately, and is largely a translation and [...]]]></description>
			<content:encoded><![CDATA[<p>For the last few days I've taken some time off from the AI work to mess around with some graphical effects, and in particular I've been experimenting with a 2d ambient shadows effect. This is inspired by the Screen Space Ambient Occulsion (SSAO) effect which has gotten popular lately, and is largely a translation and adaptation of it into a 2d world.</p>
<p>To start with, here's my test scene (unrelated to the current platformer/ai work):</p>
<p><a href="http://www.triangularpixels.com/DreamCascade/Screenshots/AmbientShader_BaseColour.png"><img src="http://www.triangularpixels.com/DreamCascade/Screenshots/AmbientShader_BaseColour.png" width="200" height="150"></img></a><br />
(all screenshots a quater full size, click to view the full sized version)</p>
<p>That's a whole bunch of tightly packed parallax layers with some trees and letters interleaved between them. The parallax is quite subtle, so it's mostly lost in a static shot but it creates a nice 3d effect when scrolling.</p>
<p>The first step is to also generate a depth map from this. Since we're in 2d and we don't have a z-buffer, we can fake one with a simple shader to tint the sprites based on their depth.</p>
<p><a href="http://www.triangularpixels.com/DreamCascade/Screenshots/AmbientShader_Depth.png"><img src="http://www.triangularpixels.com/DreamCascade/Screenshots/AmbientShader_Depth.png" width="200" height="150"></img></a></p>
<p>(I've artificially tweeked the colour levels in the above to exagerate the layers, as otherwise you only really see white objects on a black background). It looks a bit jaggier than the base colour because we clamp the alpha values of the sprite textures to either be zero or one in the depth shader as otherwise the semi-transparent pixels introduce errors in the next step.</p>
<p>Next is the real magic, we apply the ambient shader. This accepts the previously generated depth texture as an input. For each fragment it looks up it's base depth, then samples a number of surrounding texels and finds their depth as well. Surrounding depths which are higher than our base depth (i.e. it's from a surface in front of us) darken our ambient shadow factor. We also apply a cutoff for this test so that depths which are really far in front get ignored as we decide that their shadow won't be cast onto our current pixel. Surrounding depths lower (i.e. behind) our base depth are ignored.</p>
<p>Surrounding texels are found using precalculated poisson disc offsets in a similar way to traditional growable blur. We also apply a constant offset to these samples so that the shadows appear dropped slightly down-left of the shadow casters.</p>
<p>This produces the raw ambient map:</p>
<p><a href="http://www.triangularpixels.com/DreamCascade/Screenshots/AmbientShader_RawAmbient.png"><img src="http://www.triangularpixels.com/DreamCascade/Screenshots/AmbientShader_RawAmbient.png" width="200" height="150"></img></a></p>
<p>You can see how the grass layers are much more clearly defined and that letters both cast shadows onto trees behind them and receive shadows from trees in front as well.</p>
<p>Since this is a little noisy, we apply a simple blur to the raw ambient map:</p>
<p><a href="http://www.triangularpixels.com/DreamCascade/Screenshots/AmbientShader_BlurredAmbient.png"><img src="http://www.triangularpixels.com/DreamCascade/Screenshots/AmbientShader_BlurredAmbient.png" width="200" height="150"></img></a></p>
<p>Then as a final stage we combine the blurred ambient map with the colour map (and any other layers, like a bloom map) to the framebuffer:</p>
<p><a href="http://www.triangularpixels.com/DreamCascade/Screenshots/AmbientShader_FinalComposite.png"><img src="http://www.triangularpixels.com/DreamCascade/Screenshots/AmbientShader_FinalComposite.png" width="200" height="150"></img></a></p>
<p>A pretty neat effect I think - it's certainly got a lot more depth than the basic colour version, and the shadows on moving objects really help them feel like they're part of the world.</p>
<p>And if anyone wants to play around with this, here's the GLSL shader to generate the raw ambient map:</p>
<pre class="c">&nbsp;
&nbsp;
uniform sampler2D depthMap;
&nbsp;
<span style="color: #993333;">const</span> <span style="color: #993333;">int</span> numSamples = <span style="color: #cc66cc;">16</span>;
<span style="color: #993333;">const</span> <span style="color: #993333;">float</span> divisor = <span style="color: #cc66cc;">1.0</span> / <span style="color: #993333;">float</span><span style="color: #66cc66;">&#40;</span>numSamples<span style="color: #66cc66;">&#41;</span>;
&nbsp;
vec2 samples<span style="color: #66cc66;">&#91;</span>numSamples<span style="color: #66cc66;">&#93;</span>;
&nbsp;
<span style="color: #993333;">void</span> main<span style="color: #66cc66;">&#40;</span><span style="color: #66cc66;">&#41;</span>
<span style="color: #66cc66;">&#123;</span>
	<span style="color: #808080; font-style: italic;">// Our generated poisson disc sample offsets</span>
	samples<span style="color: #66cc66;">&#91;</span><span style="color: #cc66cc;">0</span><span style="color: #66cc66;">&#93;</span> = vec2<span style="color: #66cc66;">&#40;</span><span style="color: #cc66cc;">0.007937789</span>, <span style="color: #cc66cc;">0.73124397</span><span style="color: #66cc66;">&#41;</span>;
	samples<span style="color: #66cc66;">&#91;</span><span style="color: #cc66cc;">1</span><span style="color: #66cc66;">&#93;</span> = vec2<span style="color: #66cc66;">&#40;</span><span style="color: #cc66cc;">-0.10177308</span>, <span style="color: #cc66cc;">-0.6509396</span><span style="color: #66cc66;">&#41;</span>;
	samples<span style="color: #66cc66;">&#91;</span><span style="color: #cc66cc;">2</span><span style="color: #66cc66;">&#93;</span> = vec2<span style="color: #66cc66;">&#40;</span><span style="color: #cc66cc;">-0.9906806</span>, <span style="color: #cc66cc;">-0.63400936</span><span style="color: #66cc66;">&#41;</span>;
	samples<span style="color: #66cc66;">&#91;</span><span style="color: #cc66cc;">3</span><span style="color: #66cc66;">&#93;</span> = vec2<span style="color: #66cc66;">&#40;</span><span style="color: #cc66cc;">-0.5583586</span>, <span style="color: #cc66cc;">-0.3614012</span><span style="color: #66cc66;">&#41;</span>;
	samples<span style="color: #66cc66;">&#91;</span><span style="color: #cc66cc;">4</span><span style="color: #66cc66;">&#93;</span> = vec2<span style="color: #66cc66;">&#40;</span><span style="color: #cc66cc;">0.7163085</span>, <span style="color: #cc66cc;">0.22836149</span><span style="color: #66cc66;">&#41;</span>;
	samples<span style="color: #66cc66;">&#91;</span><span style="color: #cc66cc;">5</span><span style="color: #66cc66;">&#93;</span> = vec2<span style="color: #66cc66;">&#40;</span><span style="color: #cc66cc;">-0.65210974</span>, <span style="color: #cc66cc;">0.37117887</span><span style="color: #66cc66;">&#41;</span>;
	samples<span style="color: #66cc66;">&#91;</span><span style="color: #cc66cc;">6</span><span style="color: #66cc66;">&#93;</span> = vec2<span style="color: #66cc66;">&#40;</span><span style="color: #cc66cc;">-0.12714535</span>, <span style="color: #cc66cc;">0.112056136</span><span style="color: #66cc66;">&#41;</span>;
	samples<span style="color: #66cc66;">&#91;</span><span style="color: #cc66cc;">7</span><span style="color: #66cc66;">&#93;</span> = vec2<span style="color: #66cc66;">&#40;</span><span style="color: #cc66cc;">0.48898065</span>, <span style="color: #cc66cc;">-0.66669613</span><span style="color: #66cc66;">&#41;</span>;
	samples<span style="color: #66cc66;">&#91;</span><span style="color: #cc66cc;">8</span><span style="color: #66cc66;">&#93;</span> = vec2<span style="color: #66cc66;">&#40;</span><span style="color: #cc66cc;">-0.9744036</span>, <span style="color: #cc66cc;">0.9155904</span><span style="color: #66cc66;">&#41;</span>;
	samples<span style="color: #66cc66;">&#91;</span><span style="color: #cc66cc;">9</span><span style="color: #66cc66;">&#93;</span> = vec2<span style="color: #66cc66;">&#40;</span><span style="color: #cc66cc;">0.9274436</span>, <span style="color: #cc66cc;">-0.9896486</span><span style="color: #66cc66;">&#41;</span>;
	samples<span style="color: #66cc66;">&#91;</span><span style="color: #cc66cc;">10</span><span style="color: #66cc66;">&#93;</span> = vec2<span style="color: #66cc66;">&#40;</span><span style="color: #cc66cc;">0.9782181</span>, <span style="color: #cc66cc;">0.90990245</span><span style="color: #66cc66;">&#41;</span>;
	samples<span style="color: #66cc66;">&#91;</span><span style="color: #cc66cc;">11</span><span style="color: #66cc66;">&#93;</span> = vec2<span style="color: #66cc66;">&#40;</span><span style="color: #cc66cc;">0.96427417</span>, <span style="color: #cc66cc;">-0.25506377</span><span style="color: #66cc66;">&#41;</span>;
	samples<span style="color: #66cc66;">&#91;</span><span style="color: #cc66cc;">12</span><span style="color: #66cc66;">&#93;</span> = vec2<span style="color: #66cc66;">&#40;</span><span style="color: #cc66cc;">-0.5021933</span>, <span style="color: #cc66cc;">-0.9712455</span><span style="color: #66cc66;">&#41;</span>;
	samples<span style="color: #66cc66;">&#91;</span><span style="color: #cc66cc;">13</span><span style="color: #66cc66;">&#93;</span> = vec2<span style="color: #66cc66;">&#40;</span><span style="color: #cc66cc;">0.3091557</span>, <span style="color: #cc66cc;">-0.17652994</span><span style="color: #66cc66;">&#41;</span>;
	samples<span style="color: #66cc66;">&#91;</span><span style="color: #cc66cc;">14</span><span style="color: #66cc66;">&#93;</span> = vec2<span style="color: #66cc66;">&#40;</span><span style="color: #cc66cc;">0.4665941</span>, <span style="color: #cc66cc;">0.96454906</span><span style="color: #66cc66;">&#41;</span>;
	samples<span style="color: #66cc66;">&#91;</span><span style="color: #cc66cc;">15</span><span style="color: #66cc66;">&#93;</span> = vec2<span style="color: #66cc66;">&#40;</span><span style="color: #cc66cc;">-0.461774</span>, <span style="color: #cc66cc;">0.9360856</span><span style="color: #66cc66;">&#41;</span>;
&nbsp;
	<span style="color: #808080; font-style: italic;">// Sample spread distance</span>
	<span style="color: #993333;">float</span> spread = <span style="color: #cc66cc;">0.007</span>;
&nbsp;
	<span style="color: #808080; font-style: italic;">// Offset to make shadows set slightly down and left from their caster</span>
	vec2 depthOffset = vec2<span style="color: #66cc66;">&#40;</span><span style="color: #cc66cc;">0.001</span>, <span style="color: #cc66cc;">0.003</span><span style="color: #66cc66;">&#41;</span>;
&nbsp;
	<span style="color: #808080; font-style: italic;">// Grab the base texture coord</span>
	vec2 baseCoord = gl_TexCoord<span style="color: #66cc66;">&#91;</span><span style="color: #cc66cc;">0</span><span style="color: #66cc66;">&#93;</span>.<span style="color: #202020;">xy</span>;
&nbsp;
	<span style="color: #993333;">float</span> baseDepth = texture2D<span style="color: #66cc66;">&#40;</span>depthMap, baseCoord<span style="color: #66cc66;">&#41;</span>.<span style="color: #202020;">r</span>;
&nbsp;
	<span style="color: #993333;">float</span> ambient = <span style="color: #cc66cc;">1.0</span>;
	<span style="color: #b1b100;">for</span> <span style="color: #66cc66;">&#40;</span><span style="color: #993333;">int</span> i=<span style="color: #cc66cc;">0</span>; i&amp;lt;numSamples; i++<span style="color: #66cc66;">&#41;</span>
	<span style="color: #66cc66;">&#123;</span>
		<span style="color: #993333;">float</span> offsetDepth = texture2D<span style="color: #66cc66;">&#40;</span>depthMap, baseCoord + depthOffset +
					<span style="color: #66cc66;">&#40;</span>samples<span style="color: #66cc66;">&#91;</span>i<span style="color: #66cc66;">&#93;</span> * spread<span style="color: #66cc66;">&#41;</span> <span style="color: #66cc66;">&#41;</span>.<span style="color: #202020;">r</span>;
		<span style="color: #993333;">float</span> diff = offsetDepth - baseDepth; <span style="color: #808080; font-style: italic;">// diff is +itive if offset depth</span>
							<span style="color: #808080; font-style: italic;">// is in front of us</span>
&nbsp;
		<span style="color: #993333;">float</span> cutoff = <span style="color: #cc66cc;">0.08</span>;	<span style="color: #808080; font-style: italic;">// limits how far objects can cast a shadow</span>
		<span style="color: #993333;">float</span> threshold = <span style="color: #cc66cc;">0.01</span>;	<span style="color: #808080; font-style: italic;">// must cross this threshold to cast a shdow</span>
&nbsp;
		<span style="color: #b1b100;">if</span> <span style="color: #66cc66;">&#40;</span>diff &amp;lt; cutoff &amp;&amp; diff &amp;gt; <span style="color: #cc66cc;">0.01</span><span style="color: #66cc66;">&#41;</span>
		<span style="color: #66cc66;">&#123;</span>
			diff = clamp<span style="color: #66cc66;">&#40;</span>diff, <span style="color: #cc66cc;">0.0</span>, cutoff<span style="color: #66cc66;">&#41;</span>;
			diff = cutoff - diff;
&nbsp;
			ambient -= diff;
		<span style="color: #66cc66;">&#125;</span>
	<span style="color: #66cc66;">&#125;</span>
&nbsp;
	gl_FragColor = vec4<span style="color: #66cc66;">&#40;</span>ambient, ambient, ambient, <span style="color: #cc66cc;">1.0</span><span style="color: #66cc66;">&#41;</span>;
<span style="color: #66cc66;">&#125;</span></pre>
<p>All of the other shaders are trivial, so I won't include those. And the above is probably pretty sub-optimal as it was written for clarity rather than speed but it seems to fly along at a nice smooth framerate regardless. <img src='http://triangularpixels.net/games/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' /> </p>
<p>If anyone experiments further with this I'd be interested in hearing about your results and comments. Ta.</p>
]]></content:encoded>
			<wfw:commentRss>http://triangularpixels.net/games/games/2008/07/25/2d-ambient-shadows/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
	</channel>
</rss>
