<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:georss='http://www.georss.org/georss' xmlns:gd='http://schemas.google.com/g/2005' xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-1023441640234597436</id><updated>2012-01-31T00:25:38.144-05:00</updated><category term='deferred rendering'/><category term='Post Processing'/><category term='depth of field'/><category term='Scientific Visualization'/><category term='ray tracing'/><category term='Instant Radiosity'/><category term='smoke'/><category term='parallax mapping'/><category term='SSAO'/><category term='Water'/><category term='depth impostors'/><category term='Bloom'/><category term='XNA'/><category term='Global Illumination'/><category term='Atmospheric Scattering'/><category term='shadow mapping'/><category term='Volumetric Clouds'/><category term='General'/><category term='O3D'/><category term='refractions'/><category term='Non-Pinhole Impostors'/><category term='Eurographics'/><category term='SSGI'/><category term='Human Head'/><category term='Dual-Paraboloid'/><category term='managed'/><category term='PCF'/><category term='OpenGL'/><category term='Particle Effects'/><category term='normal mapping'/><category term='reflections'/><category term='cuda'/><category term='Volume Ray-Casting'/><category term='occlusion camera'/><category term='fog'/><category term='camera animation'/><category term='Software Rendering'/><category term='fluid dynamics'/><category term='Impostors'/><category term='Wii'/><category term='Prey 2'/><category term='Terrain'/><category term='Optix'/><category term='CG'/><category term='VSM'/><category term='SIGGRAPH'/><category term='C#'/><category term='GPGPU'/><category term='fire'/><category term='Volume Rendering'/><category term='DirectX'/><title type='text'>Graphics Runner</title><subtitle type='html'>Delusions of graphics programming and other ramblings</subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://graphicsrunner.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1023441640234597436/posts/default?max-results=100'/><link rel='alternate' type='text/html' href='http://graphicsrunner.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><author><name>Kyle Hayward</name><uri>http://www.blogger.com/profile/00654406875137720609</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://bp3.blogger.com/_VelpN_FHzhk/R-Lx4Kk_nkI/AAAAAAAAAN8/jn_4uDeKxNk/S220/me.jpg'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>37</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>100</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-1023441640234597436.post-1341399776888920908</id><published>2011-06-04T20:56:00.002-04:00</published><updated>2011-07-28T11:55:34.476-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Prey 2'/><title type='text'>New Prey 2 E3 Trailer</title><content type='html'>Bethesda released a new trailer for Prey 2. I'm not gonna lie, it looks awesome.&lt;br /&gt;&lt;br /&gt;&lt;iframe allowfullscreen="" frameborder="0" height="349" src="http://www.youtube.com/embed/5h2TkpFEsn8" width="560"&gt;&lt;/iframe&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1023441640234597436-1341399776888920908?l=graphicsrunner.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://graphicsrunner.blogspot.com/feeds/1341399776888920908/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=1023441640234597436&amp;postID=1341399776888920908' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1023441640234597436/posts/default/1341399776888920908'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1023441640234597436/posts/default/1341399776888920908'/><link rel='alternate' type='text/html' href='http://graphicsrunner.blogspot.com/2011/06/new-prey-2-e3-trailer.html' title='New Prey 2 E3 Trailer'/><author><name>Kyle Hayward</name><uri>http://www.blogger.com/profile/00654406875137720609</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://bp3.blogger.com/_VelpN_FHzhk/R-Lx4Kk_nkI/AAAAAAAAAN8/jn_4uDeKxNk/S220/me.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://img.youtube.com/vi/5h2TkpFEsn8/default.jpg' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1023441640234597436.post-4133393968987379162</id><published>2011-04-07T15:10:00.001-04:00</published><updated>2011-07-28T11:55:52.250-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Prey 2'/><title type='text'>Prey 2 Concept and Screenshot</title><content type='html'>Bethesda posted a screenshot and concept piece from Prey 2 today: &lt;a href="http://bethblog.com/index.php/2011/04/07/sneak-peek-at-prey-2/"&gt;http://bethblog.com/index.php/2011/04/07/sneak-peek-at-prey-2/&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://farm6.static.flickr.com/5069/5597986810_79201b61d8_b.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="209" src="http://farm6.static.flickr.com/5069/5597986810_79201b61d8_b.jpg" width="480" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://farm6.static.flickr.com/5187/5598199064_67d204a3d7_b.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="294" src="http://farm6.static.flickr.com/5187/5598199064_67d204a3d7_b.jpg" width="480" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1023441640234597436-4133393968987379162?l=graphicsrunner.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://graphicsrunner.blogspot.com/feeds/4133393968987379162/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=1023441640234597436&amp;postID=4133393968987379162' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1023441640234597436/posts/default/4133393968987379162'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1023441640234597436/posts/default/4133393968987379162'/><link rel='alternate' type='text/html' href='http://graphicsrunner.blogspot.com/2011/04/prey-2-concept-and-screenshot.html' title='Prey 2 Concept and Screenshot'/><author><name>Kyle Hayward</name><uri>http://www.blogger.com/profile/00654406875137720609</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://bp3.blogger.com/_VelpN_FHzhk/R-Lx4Kk_nkI/AAAAAAAAAN8/jn_4uDeKxNk/S220/me.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://farm6.static.flickr.com/5069/5597986810_79201b61d8_t.jpg' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1023441640234597436.post-9090660062173443464</id><published>2011-03-28T02:58:00.009-04:00</published><updated>2011-03-28T03:40:48.106-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='deferred rendering'/><category scheme='http://www.blogger.com/atom/ns#' term='Instant Radiosity'/><category scheme='http://www.blogger.com/atom/ns#' term='ray tracing'/><category scheme='http://www.blogger.com/atom/ns#' term='GPGPU'/><category scheme='http://www.blogger.com/atom/ns#' term='Global Illumination'/><category scheme='http://www.blogger.com/atom/ns#' term='Optix'/><category scheme='http://www.blogger.com/atom/ns#' term='DirectX'/><category scheme='http://www.blogger.com/atom/ns#' term='cuda'/><title type='text'>Instant Radiosity using Optix and Deferred Rendering</title><content type='html'>&lt;a href="http://lh4.ggpht.com/_VelpN_FHzhk/TZAsI9L0ykI/AAAAAAAAAsE/PmVDegnX9v8/s1600-h/cornell_gi_0%5B5%5D.png"&gt;&lt;img alt="cornell_gi_0" border="0" height="364" src="http://lh3.ggpht.com/_VelpN_FHzhk/TZAsJQeN0AI/AAAAAAAAAsI/_Ii4vUsKX2E/cornell_gi_0_thumb%5B3%5D.png?imgmax=800" style="background-image: none; border-bottom-width: 0px; border-left-width: 0px; border-right-width: 0px; border-top-width: 0px; display: inline; padding-left: 0px; padding-right: 0px; padding-top: 0px;" title="cornell_gi_0" width="484" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;This comes a little later than I wanted, I hadn’t factored in Crysis 2 taking up as much of my time as it did last week :-)&lt;br /&gt;&lt;br /&gt;I’ve been using Nvidia’s &lt;a href="http://www.nvidia.com/object/optix.html" target="_blank"&gt;Optix raytracing API&lt;/a&gt; for quite some time, and decided that a good introduction to Optix and what it can do for you would be using it in an Instant Radiosity demo. The demo is fairly large so I won’t cover all of it here, as that would be entirely too long of a post, but just the main parts.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;&lt;span style="color: #f79646;"&gt;Instant Radiosity&lt;/span&gt;&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;Instant Radiosity is a global illumination algorithm that approximates the diffuse radiance of a scene by placing many virtual point lights that act as indirect light. The algorithm is fairly simple: for each light in the scene you cast N photon rays into the scene. At each intersection the photon either bounces and another ray is cast or, through Russian Roulette, is killed of. At each of the intersections you create a Virtual Point Light (VPL) that has the same radiance value as the photon. Once you have these VPLs you render them as you would any other light source.&lt;br /&gt;&lt;br /&gt;One optimization that the demo makes is to divide the scene into a regular grid. For each grid voxel, we find all the VPLs in the voxel and merge them together to form a new VPL that represents the merged VPLs. Any voxels that don’t contain any VPLs are skipped. This dramatically reduces the number of VPLs that we need to render, and trades off indirect accuracy for speed. The following couple of shots demonstrate this idea. The image on the left shows the VPLs as calculated from our Optix program. The image on the right shows the merged VPLs.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://lh6.ggpht.com/_VelpN_FHzhk/TZAsKYHMlEI/AAAAAAAAAsM/4Cc-qohlk5U/s1600-h/scattered_vpl8.png"&gt;&lt;img alt="scattered_vpl" height="180" src="http://lh4.ggpht.com/_VelpN_FHzhk/TZAsK3CwgUI/AAAAAAAAAsQ/YMknXYoLpy8/scattered_vpl_thumb6.png?imgmax=800" style="display: inline;" title="scattered_vpl" width="260" /&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="http://lh4.ggpht.com/_VelpN_FHzhk/TZAsLjCd1ZI/AAAAAAAAAsU/qW1ztJ-Agt8/s1600-h/grid_vpl3.png"&gt;&lt;img alt="grid_vpl" border="0" height="180" src="http://lh4.ggpht.com/_VelpN_FHzhk/TZAsLzdsT1I/AAAAAAAAAsY/DjFv8_SA-YU/grid_vpl_thumb1.png?imgmax=800" style="background-image: none; border-bottom-width: 0px; border-left-width: 0px; border-right-width: 0px; border-top-width: 0px; display: inline; padding-left: 0px; padding-right: 0px; padding-top: 0px;" title="grid_vpl" width="260" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;&lt;span style="color: #f79646;"&gt;Optix&lt;/span&gt;&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;Optix is Nvidia’s ray tracing API that runs on Nvidia GPUs (on G80 and up). Giving an overview to optix could take many blog posts so I won’t go that in depth here. There are a couple of SIGGRAPH presentations that give a good overview:&lt;br /&gt;&lt;br /&gt;&lt;a href="http://nvidia.fullviewmedia.com/siggraph2010/04-dev-austin-robison.html" target="_blank"&gt;http://nvidia.fullviewmedia.com/siggraph2010/04-dev-austin-robison.html&lt;/a&gt;&lt;br /&gt;&lt;a href="http://graphics.cs.williams.edu/papers/OptiXSIGGRAPH10/" target="_blank"&gt;http://graphics.cs.williams.edu/papers/OptiXSIGGRAPH10/&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;To create an optix program you essentially need two things: a ray generation program and a material program ( essentially a shader ) that gets called when a ray intersects geometry. The ray generation program does exactly as it sounds, it generates rays. The program is called for each pixel of your program’s dimensions. Rays cast by your ray generation program will traverse the scene for intersections, once a ray intersects geometry it will call its material program. The material program is responsible for say shading in a classic ray tracer, or any other computation you want to perform. In our case we’ll use it to create our Virtual Point Lights. So lets get down to business.&lt;br /&gt;Here we have the ray generation program that will cast rays from a light. In the case of our cornell box room, we have an area light at the ceiling and we need to cast photons from this light.&lt;br /&gt;&lt;br /&gt;&lt;pre class="mycode"&gt;RT_PROGRAM &lt;span style="color: blue;"&gt;void &lt;/span&gt;instant_radiosity()&lt;br /&gt;{&lt;br /&gt;&lt;span style="color: green;"&gt;     //get our random seed&lt;br /&gt;&lt;/span&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: blue;"&gt;uint2 &lt;/span&gt;seed = seed_buffer[ launch_index ];&lt;br /&gt;&lt;br /&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: green;"&gt;//create a random photon direction&lt;br /&gt;&lt;/span&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: blue;"&gt;float2 &lt;/span&gt;raySeed = make_float2( ( (&lt;span style="color: blue;"&gt;float&lt;/span&gt;)launch_index.x + rnd( seed.x ) ) / (&lt;span style="color: blue;"&gt;float&lt;/span&gt;)launch_dim.x,&lt;br /&gt;                                   ( (&lt;span style="color: blue;"&gt;float&lt;/span&gt;)launch_index.y + rnd( seed.y ) ) / (&lt;span style="color: blue;"&gt;float&lt;/span&gt;)launch_dim.y );&lt;br /&gt;&lt;br /&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: blue;"&gt;float3 &lt;/span&gt;origin = Light.position;&lt;br /&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: blue;"&gt;float3 &lt;/span&gt;direction = generateHemisphereLightPhoton( raySeed, Light.direction );&lt;br /&gt;&lt;br /&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: green;"&gt;//create our ray&lt;br /&gt;&lt;/span&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;optix::Ray ray(origin, direction, radiance_ray_type, scene_epsilon );&lt;br /&gt;&lt;br /&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: green;"&gt;//create our ray data packet and launch a ray&lt;br /&gt;&lt;/span&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;PerRayData_radiance prd;&lt;br /&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;prd.radiance = Light.color * Light.intensity * IndirectIntensity;&lt;br /&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;prd.bounce = 0;&lt;br /&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;prd.seed = seed;&lt;br /&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;prd.index = ( launch_index.y * launch_dim.x + launch_index.x ) * MaxBounces;&lt;br /&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;rtTrace( top_object, ray, prd );&lt;br /&gt;}&lt;/pre&gt;&lt;br /&gt;So here we cast a randomly oriented ray from a hemisphere oriented about the direction of the light. Once we have our ray, we setup a ray data packet that will collect data as this ray traverses the scene. To cast the ray we make a call to rtTrace, providing the ray and its data packet.&lt;br /&gt;&lt;br /&gt;Next we have our material program. This program is called when a ray hits the closest piece of geometry from the light. And it is responsible for updating the ray data packet, placing a VPL, and deciding to cast another ray recursively if we’re under the maximum number of bounces.&lt;br /&gt;&lt;br /&gt;&lt;pre class="mycode"&gt;RT_PROGRAM &lt;span style="color: blue;"&gt;void &lt;/span&gt;closest_hit_radiosity()&lt;br /&gt;{&lt;br /&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: green;"&gt;//convert the geometry's normal to world space&lt;br /&gt;&lt;/span&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: green;"&gt;//RT_OBJECT_TO_WORLD is an Optix provided transformation&lt;br /&gt;&lt;/span&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: blue;"&gt;float3 &lt;/span&gt;world_shading_normal   = normalize( rtTransformNormal( RT_OBJECT_TO_WORLD, shading_normal ) );&lt;br /&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: blue;"&gt;float3 &lt;/span&gt;world_geometric_normal = normalize( rtTransformNormal( RT_OBJECT_TO_WORLD, geometric_normal ) );&lt;br /&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: blue;"&gt;float3 &lt;/span&gt;ffnormal     = faceforward( world_shading_normal, -ray.direction, world_geometric_normal );&lt;br /&gt;&lt;br /&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: green;"&gt;//calculate the hitpoint of the ray&lt;br /&gt;&lt;/span&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: blue;"&gt;float3 &lt;/span&gt;hit_point = ray.origin + t_hit * ray.direction;&lt;br /&gt;&lt;br /&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: green;"&gt;//sample the texture for the geometry&lt;br /&gt;&lt;/span&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: blue;"&gt;float3 &lt;/span&gt;Kd = norm_rgb( &lt;span style="color: blue;"&gt;tex2D&lt;/span&gt;( diffuseTex, texcoord.x, texcoord.y ) );&lt;br /&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;Kd = pow3f( Kd, 2.2f ); &lt;span style="color: green;"&gt;//convert to linear space&lt;br /&gt;&lt;/span&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;Kd *= make_float3( diffuseColor ); &lt;span style="color: green;"&gt;//multiply the diffuse material color&lt;br /&gt;&lt;/span&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;prd_radiance.radiance = Kd * prd_radiance.radiance; &lt;span style="color: green;"&gt;//calculate the ray's new radiance value&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: green;"&gt;// We hit a diffuse surface; record hit if it has bounced at least once&lt;br /&gt;&lt;/span&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: blue;"&gt;if&lt;/span&gt;( prd_radiance.bounce &amp;gt;= 0 ) {&lt;br /&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: green;"&gt;//offset the light a bit from the hit point&lt;br /&gt;&lt;/span&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: blue;"&gt;float3 &lt;/span&gt;lightPos = ray.origin + ( t_hit - 0.1f ) * ray.direction;&lt;br /&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;VirtualPointLight&amp;amp; vpl = output_vpls[ prd_radiance.index + prd_radiance.bounce ];&lt;br /&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;vpl.position = lightPos;&lt;br /&gt;&lt;br /&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: green;"&gt;//the light's intensity is divided equally among the photons. Each photon starts out with an intensity&lt;br /&gt;&lt;/span&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: green;"&gt;//equal to the light. So here we must divide by the number of photons cast from the light.&lt;br /&gt;&lt;/span&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;vpl.radiance = prd_radiance.radiance * 1.0f / ( launch_dim.x * launch_dim.y );&lt;br /&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;}&lt;br /&gt;&lt;br /&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: green;"&gt;//if we're less than the max number of bounces shoot another ray&lt;br /&gt;&lt;/span&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: green;"&gt;//we could also implement Russion Roulette here so that we would have a less biased solution&lt;br /&gt;&lt;/span&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;prd_radiance.bounce++;&lt;br /&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: blue;"&gt;if &lt;/span&gt;( prd_radiance.bounce &amp;gt;= MaxBounces )&lt;br /&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: blue;"&gt;return&lt;/span&gt;;&lt;br /&gt;&lt;br /&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: green;"&gt;//here we "rotate" the seeds in order to have a little more variance&lt;br /&gt;&lt;/span&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;prd_radiance.seed.x = prd_radiance.seed.x ^ prd_radiance.bounce;&lt;br /&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;prd_radiance.seed.y = prd_radiance.seed.y ^ prd_radiance.bounce;&lt;br /&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: blue;"&gt;float2 &lt;/span&gt;seed_direction = make_float2( ( (&lt;span style="color: blue;"&gt;float&lt;/span&gt;)launch_index.x + rnd( prd_radiance.seed.x ) ) / (&lt;span style="color: blue;"&gt;float&lt;/span&gt;)launch_dim.x,&lt;br /&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: green;"&gt;       &lt;/span&gt;( (&lt;span style="color: blue;"&gt;float&lt;/span&gt;)launch_index.y + rnd( prd_radiance.seed.y ) ) / (&lt;span style="color: blue;"&gt;float&lt;/span&gt;)launch_dim.y );&lt;br /&gt;&lt;br /&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: green;"&gt;//generate a new ray in the hemisphere oriented to the surface&lt;br /&gt;&lt;/span&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: blue;"&gt;float3 &lt;/span&gt;new_ray_dir = generateHemisphereLightPhoton( seed_direction, ffnormal );&lt;br /&gt;&lt;br /&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: green;"&gt;//cast a new ray into the scene&lt;br /&gt;&lt;/span&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;optix::Ray new_ray( hit_point, new_ray_dir, radiance_ray_type, scene_epsilon );&lt;br /&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;rtTrace(top_object, new_ray, prd_radiance);&lt;br /&gt;}&lt;/pre&gt;&lt;br /&gt;With both of these programs created we need to launch our optix program in order to generate the VPLs. When we’re done running the optix program, we gather all the VPLs into a grid, merging lights that are in the same voxel. Once the VPLs are merged, we add them to the deferred renderer.&lt;br /&gt;&lt;br /&gt;&lt;pre class="mycode"&gt;&lt;span style="color: green;"&gt;//run our optix program&lt;br /&gt;&lt;/span&gt;mContext-&amp;gt;launch( 0, SqrtNumVPLs, SqrtNumVPLs );&lt;br /&gt;&lt;br /&gt;&lt;span style="color: green;"&gt;//get a pointer to the GPU buffer of virtual point lights.&lt;br /&gt;&lt;/span&gt;VirtualPointLight* lights = &lt;span style="color: blue;"&gt;static_cast&lt;/span&gt;&amp;lt; VirtualPointLight* &amp;gt;( mContext[&lt;span style="color: #a31515;"&gt;"output_vpls"&lt;/span&gt;]-&amp;gt;getBuffer()-&amp;gt;map() );&lt;br /&gt;&lt;br /&gt;&lt;span style="color: green;"&gt;//the following block merges the scattered vpls into a structured grid of vpls&lt;br /&gt;//this helps dramatically reduce the number of vpls we need in the scene&lt;br /&gt;&lt;/span&gt;&lt;span style="color: blue;"&gt;if&lt;/span&gt;( mMergeVPLs )&lt;br /&gt;{&lt;br /&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: green;"&gt;//Here we traverse over the VPLs and we merge all the lights that are in a cell&lt;br /&gt;&lt;/span&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: blue;"&gt;for&lt;/span&gt;( &lt;span style="color: blue;"&gt;int &lt;/span&gt;i = 0; i &amp;lt; TotalVPLs; ++i )&amp;nbsp;&lt;br /&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;{&lt;br /&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;optix::Aabb node = mBoundingBox;&lt;br /&gt;&lt;br /&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: green;"&gt;//start with the root cell and recursively traverse the grid to find the cell this vpl belongs to&lt;br /&gt;&lt;/span&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: blue;"&gt;int &lt;/span&gt;index = 0;&lt;br /&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: blue;"&gt;if&lt;/span&gt;( FindCellIndex( mBoundingBox, -1, mVoxelExtent, lights[ i ].position, index ) )&lt;br /&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;{&lt;br /&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: green;"&gt;//make sure we found a valid cell&lt;br /&gt;&lt;/span&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;assert( index &amp;gt;= mFirstLeafIndex );&lt;br /&gt;&lt;br /&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: green;"&gt;//subtract the first leaf index to find the zero based index of the vpl&lt;br /&gt;&lt;/span&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;index -= mFirstLeafIndex;&lt;br /&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: blue;"&gt;float3&lt;/span&gt;&amp;amp; light = mVPLs[ index ];&lt;br /&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;light += lights[ i ].radiance;&lt;br /&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;}&lt;br /&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;}&lt;br /&gt;&lt;br /&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: green;"&gt;//once the VPLs have been merged, add them to the renderer as indirect lights&lt;br /&gt;&lt;/span&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: blue;"&gt;int &lt;/span&gt;numLights = 0;&lt;br /&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: blue;"&gt;int &lt;/span&gt;lastIndex = -1;&lt;br /&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: blue;"&gt;for&lt;/span&gt;( &lt;span style="color: blue;"&gt;int &lt;/span&gt;i = 0; i &amp;lt; mVPLs.size(); ++i ) &lt;br /&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;{&lt;br /&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: blue;"&gt;const float3&lt;/span&gt;&amp;amp; vpl = mVPLs[i];&lt;br /&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: blue;"&gt;if&lt;/span&gt;( dot( vpl, vpl ) &amp;lt;= 0.0f )&lt;br /&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: blue;"&gt;continue&lt;/span&gt;;&lt;br /&gt;&lt;br /&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;numLights++;&lt;br /&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: blue;"&gt;float3 &lt;/span&gt;radiance = vpl;&lt;br /&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;D3DXVECTOR3 pos = *(D3DXVECTOR3*)&amp;amp;mVoxels[i].center();&lt;br /&gt;&lt;br /&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;Light light =    {    LIGHT_POINT,                                                &lt;span style="color: green;"&gt;//type&lt;br /&gt;&lt;/span&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: green;"&gt;  &lt;/span&gt;GetColorValue(radiance.x, radiance.y, radiance.z, 1.0f),    &lt;span style="color: green;"&gt;//diffuse&lt;br /&gt;&lt;/span&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: green;"&gt;       &lt;/span&gt;pos,                                                        &lt;span style="color: green;"&gt;//pos&lt;br /&gt;&lt;/span&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: green;"&gt;  &lt;/span&gt;Vector3Zero,                                                &lt;span style="color: green;"&gt;//direction&lt;br /&gt;&lt;/span&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: green;"&gt;       &lt;/span&gt;1.0f                                                        &lt;span style="color: green;"&gt;//intensity&lt;br /&gt;&lt;/span&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: green;"&gt;       &lt;/span&gt;};&lt;br /&gt;&lt;br /&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;renderer-&amp;gt;AddIndirectLight( light );&lt;br /&gt;&lt;br /&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: green;"&gt;//also add as a light source so we can visualize the VPLs&lt;br /&gt;&lt;/span&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;LightSource lightSource;&lt;br /&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;lightSource.light = light;&lt;br /&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;lightSource.Model = mLightModel;&lt;br /&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;renderer-&amp;gt;AddLightSource( lightSource );&lt;br /&gt;&lt;span style="color: green;"&gt;     &lt;/span&gt;}&lt;br /&gt;}&lt;/pre&gt;&lt;br /&gt;Now for some eye candy. The first set are your typical cornell box + dragon. In the Instant Radiosity shot you can see the light bleeding from the green and red walls onto the floor, the dragon and the box.&lt;br /&gt;&lt;br /&gt;Direct lighting:&lt;br /&gt;&lt;a href="http://lh5.ggpht.com/_VelpN_FHzhk/TZAsMYVX6mI/AAAAAAAAAsc/TIwd1_z6QIU/s1600-h/cornell_dl_0%5B4%5D.png"&gt;&lt;img alt="cornell_dl_0" height="360" src="http://lh3.ggpht.com/_VelpN_FHzhk/TZAsM_dHghI/AAAAAAAAAsg/7p6h0wSuMLc/cornell_dl_0_thumb%5B2%5D.png?imgmax=800" style="display: inline;" title="cornell_dl_0" width="480" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Direct Lighting + Indirect VPLs:&lt;br /&gt;&lt;a href="http://lh6.ggpht.com/_VelpN_FHzhk/TZAsNl_3-bI/AAAAAAAAAsk/jheGxDkMGXE/s1600-h/cornell_gi_0%5B10%5D.png"&gt;&lt;img alt="cornell_gi_0" height="360" src="http://lh6.ggpht.com/_VelpN_FHzhk/TZAsOOY9w_I/AAAAAAAAAss/h61MwZM4JSA/cornell_gi_0_thumb%5B6%5D.png?imgmax=800" style="display: inline;" title="cornell_gi_0" width="480" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Direct Lighting:&lt;br /&gt;&lt;a href="http://lh6.ggpht.com/_VelpN_FHzhk/TZAsOUuc8CI/AAAAAAAAAsw/w1xiM7Z87kU/s1600-h/cornell_dl_1%5B4%5D.png"&gt;&lt;img alt="cornell_dl_1" height="360" src="http://lh6.ggpht.com/_VelpN_FHzhk/TZAsO7GqwzI/AAAAAAAAAs0/0ZMIqsX2b0A/cornell_dl_1_thumb%5B2%5D.png?imgmax=800" style="display: inline;" title="cornell_dl_1" width="480" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Direct Lighting + Indirect VPLs:&lt;br /&gt;&lt;a href="http://lh3.ggpht.com/_VelpN_FHzhk/TZAsPimYO9I/AAAAAAAAAs4/JW7zzvdXhHI/s1600-h/cornell_gi_1%5B4%5D.png"&gt;&lt;img alt="cornell_gi_1" height="360" src="http://lh6.ggpht.com/_VelpN_FHzhk/TZAsQHCtn2I/AAAAAAAAAs8/OaUpeXtAKpQ/cornell_gi_1_thumb%5B2%5D.png?imgmax=800" style="display: inline;" title="cornell_gi_1" width="480" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;The next set is from the sponza scene. Here too you can notice the red bounced light from the draperies onto the floor and in the ambient lighting in the shadows.&lt;br /&gt;&lt;br /&gt;Direct Lighting:&lt;br /&gt;&lt;a href="http://lh6.ggpht.com/_VelpN_FHzhk/TZAsRXTIezI/AAAAAAAAAtA/36w5ZnHPtvc/s1600-h/sponza_dl_0%5B4%5D.png"&gt;&lt;img alt="sponza_dl_0" height="360" src="http://lh4.ggpht.com/_VelpN_FHzhk/TZAsSCCT9kI/AAAAAAAAAtE/C4oci8FmKu0/sponza_dl_0_thumb%5B2%5D.png?imgmax=800" style="display: inline;" title="sponza_dl_0" width="480" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Direct Lighting + Indirect VPLs:&lt;br /&gt;&lt;a href="http://lh5.ggpht.com/_VelpN_FHzhk/TZAsUBrwS7I/AAAAAAAAAtI/fYdVslsQGDY/s1600-h/sponza_gi_0%5B4%5D.png"&gt;&lt;img alt="sponza_gi_0" height="360" src="http://lh4.ggpht.com/_VelpN_FHzhk/TZAsU6fos5I/AAAAAAAAAtM/js1qQtFiX5k/sponza_gi_0_thumb%5B2%5D.png?imgmax=800" style="display: inline;" title="sponza_gi_0" width="480" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Direct Lighting:&lt;br /&gt;&lt;a href="http://lh5.ggpht.com/_VelpN_FHzhk/TZAsVoMC8eI/AAAAAAAAAtQ/aHwvcohrPcs/s1600-h/sponza_nobounce%5B4%5D.png"&gt;&lt;img alt="sponza_nobounce" height="360" src="http://lh3.ggpht.com/_VelpN_FHzhk/TZAseVupjNI/AAAAAAAAAtU/_B7YQvXdJgM/sponza_nobounce_thumb%5B2%5D.png?imgmax=800" style="display: inline;" title="sponza_nobounce" width="480" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Direct Lighting + Indirect VPLs:&lt;br /&gt;&lt;a href="http://lh4.ggpht.com/_VelpN_FHzhk/TZAshTcSnsI/AAAAAAAAAtY/-mCEzS8Exo4/s1600-h/spona_bounce%5B4%5D.png"&gt;&lt;img alt="spona_bounce" height="360" src="http://lh5.ggpht.com/_VelpN_FHzhk/TZAsjpdCFiI/AAAAAAAAAtc/1r7aBjPMCZI/spona_bounce_thumb%5B2%5D.png?imgmax=800" style="display: inline;" title="spona_bounce" width="480" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Direct Lighting:&lt;br /&gt;&lt;a href="http://lh3.ggpht.com/_VelpN_FHzhk/TZAsmuhA3UI/AAAAAAAAAtg/GvTiT0oJoGk/s1600-h/sponza_dl%5B5%5D.png"&gt;&lt;img alt="sponza_dl" height="360" src="http://lh5.ggpht.com/_VelpN_FHzhk/TZAsoeGngbI/AAAAAAAAAtk/xpTwK_epocQ/sponza_dl_thumb%5B3%5D.png?imgmax=800" style="display: inline;" title="sponza_dl" width="480" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Direct Lighting + Indirect VPLs:&lt;br /&gt;&lt;a href="http://lh4.ggpht.com/_VelpN_FHzhk/TZAsrvXd-pI/AAAAAAAAAto/zYOklIIoo_Q/s1600-h/spona_il%5B4%5D.png"&gt;&lt;img alt="spona_il" height="360" src="http://lh5.ggpht.com/_VelpN_FHzhk/TZAsuH54Z1I/AAAAAAAAAtw/LQjL9N-vcBI/spona_il_thumb%5B2%5D.png?imgmax=800" style="display: inline;" title="spona_il" width="480" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="color: #f79646;"&gt;&lt;b&gt;Notes&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;To Build the demo you’ll need boost 1.43 or later. To run the demo you’ll need at least an Nvidia 8800 series or later ( anything Computer 1.0 compliant ).&lt;br /&gt;&lt;br /&gt;Files of interest are in the Demo project: OptixEntity.cpp and InstantRadiosity.cu.&lt;br /&gt;&lt;br /&gt;Controls:&lt;br /&gt;Show VPLs : L&lt;br /&gt;Toggle GI : I&lt;br /&gt;Toggle Merge VPLs : M&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="color: #f79646;"&gt;&lt;b&gt;Download:&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Sorry for requiring two download links but skydrive limits file sizes to 50MB&lt;br /&gt;&lt;a href="http://cid-b80a3031b5bfa52b.office.live.com/self.aspx/Public/OptixInstantRadiosity%5E_Part1.zip"&gt;OptixInstantRadiosity Part 1&lt;/a&gt; - Code&lt;br /&gt;&lt;a href="http://cid-b80a3031b5bfa52b.office.live.com/self.aspx/Public/OptixInstantRadiosity%5E_Part2.zip"&gt;OptixInstantRadiosity Part 2&lt;/a&gt; - Assets&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1023441640234597436-9090660062173443464?l=graphicsrunner.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://graphicsrunner.blogspot.com/feeds/9090660062173443464/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=1023441640234597436&amp;postID=9090660062173443464' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1023441640234597436/posts/default/9090660062173443464'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1023441640234597436/posts/default/9090660062173443464'/><link rel='alternate' type='text/html' href='http://graphicsrunner.blogspot.com/2011/03/instant-radiosity-using-optix-and.html' title='Instant Radiosity using Optix and Deferred Rendering'/><author><name>Kyle Hayward</name><uri>http://www.blogger.com/profile/00654406875137720609</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://bp3.blogger.com/_VelpN_FHzhk/R-Lx4Kk_nkI/AAAAAAAAAN8/jn_4uDeKxNk/S220/me.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://lh3.ggpht.com/_VelpN_FHzhk/TZAsJQeN0AI/AAAAAAAAAsI/_Ii4vUsKX2E/s72-c/cornell_gi_0_thumb%5B3%5D.png?imgmax=800' height='72' width='72'/><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1023441640234597436.post-1504095458340646467</id><published>2011-03-19T16:29:00.002-04:00</published><updated>2011-03-19T16:31:18.800-04:00</updated><title type='text'>Instant Radiosity with Optix</title><content type='html'>I've been working on a new sample for the past few days, Instant Radiosity using Optix and DirectX. I should have a writeup and sample in the coming week.&lt;br /&gt;&lt;br /&gt;Here’s a few shots with the obligatory cornell and sponza scenes.&lt;br /&gt;&lt;br /&gt;Direct Lighting &lt;br /&gt;&lt;a href="http://lh3.ggpht.com/_VelpN_FHzhk/TYUSINBFzVI/AAAAAAAAArg/ijb1VnqRe-k/s1600-h/cornell_dl_0%5B8%5D.png"&gt;&lt;img alt="cornell_dl_0" height="361" src="http://lh3.ggpht.com/_VelpN_FHzhk/TYUSIiNz9OI/AAAAAAAAArk/0prTAk4kw9Y/cornell_dl_0_thumb%5B6%5D.png?imgmax=800" style="display: inline;" title="cornell_dl_0" width="480" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Direct + Indirect VPLs&lt;br /&gt;&lt;a href="http://lh4.ggpht.com/_VelpN_FHzhk/TYUSJp7IfGI/AAAAAAAAAro/ddD3z6QFlkc/s1600-h/cornell_gi_0%5B4%5D.png"&gt;&lt;img alt="cornell_gi_0" height="360" src="http://lh6.ggpht.com/_VelpN_FHzhk/TYUSJwkZ9kI/AAAAAAAAArs/CC6koAurp3w/cornell_gi_0_thumb%5B2%5D.png?imgmax=800" style="display: inline;" title="cornell_gi_0" width="480" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Direct Lighting &lt;br /&gt;&lt;a href="http://lh4.ggpht.com/_VelpN_FHzhk/TYUSLfdRtuI/AAAAAAAAArw/pYIwwMgmFxM/s1600-h/sponza_dl_0%5B4%5D.png"&gt;&lt;img alt="sponza_dl_0" height="360" src="http://lh6.ggpht.com/_VelpN_FHzhk/TYUSL7OJMGI/AAAAAAAAAr0/1elzwY-jJRY/sponza_dl_0_thumb%5B2%5D.png?imgmax=800" style="display: inline;" title="sponza_dl_0" width="480" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Direct + Indirect VPLs&lt;br /&gt;&lt;a href="http://lh3.ggpht.com/_VelpN_FHzhk/TYUSN08OrrI/AAAAAAAAAr4/BkQEeOUpe6Q/s1600-h/sponza_gi_0%5B4%5D.png"&gt;&lt;img alt="sponza_gi_0" height="360" src="http://lh6.ggpht.com/_VelpN_FHzhk/TYUSO9txEoI/AAAAAAAAAr8/Gnup4aBJBaQ/sponza_gi_0_thumb%5B2%5D.png?imgmax=800" style="display: inline;" title="sponza_gi_0" width="480" /&gt;&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1023441640234597436-1504095458340646467?l=graphicsrunner.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://graphicsrunner.blogspot.com/feeds/1504095458340646467/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=1023441640234597436&amp;postID=1504095458340646467' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1023441640234597436/posts/default/1504095458340646467'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1023441640234597436/posts/default/1504095458340646467'/><link rel='alternate' type='text/html' href='http://graphicsrunner.blogspot.com/2011/03/instant-radiosity-with-optix.html' title='Instant Radiosity with Optix'/><author><name>Kyle Hayward</name><uri>http://www.blogger.com/profile/00654406875137720609</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://bp3.blogger.com/_VelpN_FHzhk/R-Lx4Kk_nkI/AAAAAAAAAN8/jn_4uDeKxNk/S220/me.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://lh3.ggpht.com/_VelpN_FHzhk/TYUSIiNz9OI/AAAAAAAAArk/0prTAk4kw9Y/s72-c/cornell_dl_0_thumb%5B6%5D.png?imgmax=800' height='72' width='72'/><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1023441640234597436.post-4339035719162657233</id><published>2011-03-15T20:11:00.002-04:00</published><updated>2011-07-28T11:57:03.589-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Prey 2'/><title type='text'>Prey 2 Teaser Trailer</title><content type='html'>Human Head has been keeping me busy since I've been working there and I can finally say why: &lt;b style="color: orange;"&gt;Prey 2&lt;/b&gt;. The game was announced on Monday :) Here's the Prey 2 teaser trailer:&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" height="418" id="VideoPlayerLg51782" width="480"&gt;&lt;param name="movie" value="http://www.g4tv.com/lv3/51782" /&gt;&lt;param name="allowScriptAccess" value="always" /&gt;&lt;param name="allowFullScreen" value="true" /&gt;&lt;embed src="http://www.g4tv.com/lv3/51782" type="application/x-shockwave-flash" name="VideoPlayer" width="480" height="382" allowScriptAccess="always" allowFullScreen="true" /&gt;&lt;/object&gt;&lt;br /&gt;&lt;div style="color: #ff9b00; font-family: Arial,sans-serif; font-size: 12px; margin: 0pt; text-align: center; width: 480px;"&gt;&lt;a href="http://www.g4tv.com/games/xbox-360/index" style="color: #ff9b00;" target="_blank"&gt;Xbox 360 Games&lt;/a&gt; - &lt;a href="http://www.g4tv.com/e32011" style="color: #ff9b00;" target="_blank"&gt;E3 2011&lt;/a&gt; - &lt;a href="http://www.g4tv.com/games/xbox-360/41320/prey-2" style="color: #ff9b00;" target="_blank"&gt;Prey 2&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;a href="http://www.g4tv.com/videos/51782/Prey-2-Debut-Trailer---Exclusive-Premiere/"&gt;http://www.g4tv.com/videos/51782/Prey-2-Debut-Trailer---Exclusive-Premiere/&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1023441640234597436-4339035719162657233?l=graphicsrunner.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://graphicsrunner.blogspot.com/feeds/4339035719162657233/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=1023441640234597436&amp;postID=4339035719162657233' title='6 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1023441640234597436/posts/default/4339035719162657233'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1023441640234597436/posts/default/4339035719162657233'/><link rel='alternate' type='text/html' href='http://graphicsrunner.blogspot.com/2011/03/prey-2-teaser-trailer.html' title='Prey 2 Teaser Trailer'/><author><name>Kyle Hayward</name><uri>http://www.blogger.com/profile/00654406875137720609</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://bp3.blogger.com/_VelpN_FHzhk/R-Lx4Kk_nkI/AAAAAAAAAN8/jn_4uDeKxNk/S220/me.jpg'/></author><thr:total>6</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1023441640234597436.post-2832962133454660192</id><published>2010-08-03T04:23:00.004-04:00</published><updated>2010-09-02T10:13:24.376-04:00</updated><title type='text'>Animating Water Using Flow Maps</title><content type='html'>&lt;a href="http://lh4.ggpht.com/_VelpN_FHzhk/TFfR4fj3iSI/AAAAAAAAAq0/Q5ZDgxNAfYI/s1600-h/flow10.jpg"&gt;&lt;img alt="flow" height="418" src="http://lh6.ggpht.com/_VelpN_FHzhk/TFfR4tZYbiI/AAAAAAAAAq4/66JdwS-vusI/flow_thumb6.jpg?imgmax=800" style="display: block; float: none; margin-left: auto; margin-right: auto;" title="flow" width="540" /&gt;&lt;/a&gt; &lt;br /&gt;&lt;br /&gt;Last week I attended SIGGRAPH 2010, and among the many good presentations, Valve game a talk on the simple water shader they implemented for Left For Dead 2 and Portal 2. So on the plane ride back from LA, I whipped up this little sample from what I could remember of the talk. Edit: You can find the talk here: &lt;a href="http://advances.realtimerendering.com/s2010/index.html"&gt;http://advances.realtimerendering.com/s2010/index.html&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;The standard technique for animated water is scrolling normal maps, as I’ve previously written about. The problem with this is that it looks unnatural as water does not uniformly move in one direction. So Valve came up with the idea of using flow maps ( based on a flow viz paper from the mid 90s ). The basic idea of flow maps is that you create a 2D texture that you will map to your water. And this map will contain the flow directions that you want the water to flow, with each pixel in the flow map representing a flow vector. This allows you to have varying velocity ( based on length of the flow vector ), and varying flow directions ( based on the color of the flow vector ). You then use this flow map to alter the texture coordinates of the normal maps instead of scrolling them. Lets get to work :)&lt;br /&gt;&lt;br /&gt;&lt;span style="color: #ff8040; font-size: small;"&gt;&lt;b&gt;The Flow Map&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;First we need to create a flow map. Here’s what I came up with in a couple of minutes in Photoshop. This flow map was designed around the column with dragon scene as with the previous scene. Note, this flow map is greatly exaggerated to demonstrate the effect.&lt;a href="http://lh6.ggpht.com/_VelpN_FHzhk/TFfR5E7WqPI/AAAAAAAAAq8/W6T3dhTXU-g/s1600-h/flowmap4.png"&gt;&lt;img alt="flowmap" height="480" src="http://lh5.ggpht.com/_VelpN_FHzhk/TFfR5wtG9WI/AAAAAAAAArA/6OAS0TSTZC8/flowmap_thumb2.png?imgmax=800" style="display: block; float: none; margin-left: auto; margin-right: auto;" title="flowmap" width="480" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;span style="color: #ff8040; font-size: small;"&gt;&lt;b&gt;Using The Flow Map&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Now we need to use the flow map to alter the water normal maps. We do this by taking the texture coordinate of the current water pixel and offset it using the flow vector from the flow map based on a time offset. We then render the water as we did in the previous water sample. But there’s a problem with this, after awhile the texture coordinates will become so distorted that the normal maps will be stretched and will have nasty filtering artifacts. So to solve this we limit the amount of distortion of the texture coordinates by resetting the time offset. This solves the over-distortion, but now the water will reset every X seconds. So we introduce another layer, that is offset from the first by half a time cycle. This will ensure that while one layer is fading out and beginning to reset, the next layer is fading to where the last layer was. Here’s a diagram to visualize this phase-in phase-out of the 2 layers.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://lh6.ggpht.com/_VelpN_FHzhk/TFfR6Ng-zyI/AAAAAAAAArE/HHnyBy2o6Jc/s1600-h/graph7.jpg"&gt;&lt;img alt="graph" height="236" src="http://lh4.ggpht.com/_VelpN_FHzhk/TFfR6ShjouI/AAAAAAAAArI/2rjjpm5-1mE/graph_thumb5.jpg?imgmax=800" style="display: block; float: none; margin-left: auto; margin-right: auto;" title="graph" width="540" /&gt;&lt;/a&gt; &lt;br /&gt;&lt;br /&gt;The graph illustrates that during a cycle time from 0 to 1, we want the layer to be fully interpolated at the mid-point in the cycle, and fully un-interpolated at 0 and 1. Lets see the code:&lt;br /&gt;&lt;pre class="mycode"&gt;&lt;span style="color: green;"&gt;//get and uncompress the flow vector for this pixel&lt;br /&gt;&lt;/span&gt;&lt;span style="color: blue;"&gt;float2 &lt;/span&gt;flowmap = &lt;span style="color: blue;"&gt;tex2D&lt;/span&gt;( FlowMapS, tex0 ).rg * 2.0f - 1.0f;&lt;br /&gt;&lt;br /&gt;&lt;span style="color: blue;"&gt;float &lt;/span&gt;phase0 = FlowMapOffset0;&lt;br /&gt;&lt;span style="color: blue;"&gt;float &lt;/span&gt;phase1 = FlowMapOffset1;&lt;br /&gt;&lt;br /&gt;&lt;span style="color: green;"&gt;// Sample normal map.&lt;br /&gt;&lt;/span&gt;&lt;span style="color: blue;"&gt;float3 &lt;/span&gt;normalT0 = &lt;span style="color: blue;"&gt;tex2D&lt;/span&gt;(WaveMapS0, ( tex0 * TexScale ) + flowmap * phase0 );&lt;br /&gt;&lt;span style="color: blue;"&gt;float3 &lt;/span&gt;normalT1 = &lt;span style="color: blue;"&gt;tex2D&lt;/span&gt;(WaveMapS1, ( tex0 * TexScale ) + flowmap * phase1 );&lt;br /&gt;&lt;br /&gt;&lt;span style="color: blue;"&gt;float &lt;/span&gt;flowLerp = ( &lt;span style="color: blue;"&gt;abs&lt;/span&gt;( HalfCycle - FlowMapOffset0 ) / HalfCycle );&lt;br /&gt;&lt;span style="color: blue;"&gt;float3 &lt;/span&gt;offset = lerp( normalT0, normalT1, flowLerp );&lt;/pre&gt;In the code above, HalfCycle would be .5 if our cycle was from 0 to 1. We can see here that we unwrap the flow vector (as it is stored in [0,1] and we need it in [-1,1]), fetch the normals using the flow vector and then lerp between the two normals based on the cycle time. This however will lead to a subtle pulsing affect, which I couldn’t really notice when the water was rendered, but I included the fix for completeness. To fix this pulsing effect, we perturb the flow cycle at each pixel using a noise map.&lt;br /&gt;&lt;pre class="mycode"&gt;&lt;span style="color: green;"&gt;//get and uncompress the flow vector for this pixel&lt;br /&gt;&lt;/span&gt;&lt;span style="color: blue;"&gt;float2 &lt;/span&gt;flowmap = &lt;span style="color: blue;"&gt;tex2D&lt;/span&gt;( FlowMapS, tex0 ).rg * 2.0f - 1.0f;&lt;br /&gt;&lt;span style="color: blue;"&gt;float &lt;/span&gt;cycleOffset = &lt;span style="color: blue;"&gt;tex2D&lt;/span&gt;( NoiseMapS, tex0 ).r;&lt;br /&gt;&lt;br /&gt;&lt;span style="color: blue;"&gt;float &lt;/span&gt;phase0 = cycleOffset * .5f + FlowMapOffset0;&lt;br /&gt;&lt;span style="color: blue;"&gt;float &lt;/span&gt;phase1 = cycleOffset * .5f + FlowMapOffset1;&lt;br /&gt;&lt;br /&gt;&lt;span style="color: green;"&gt;// Sample normal map.&lt;br /&gt;&lt;/span&gt;&lt;span style="color: blue;"&gt;float3 &lt;/span&gt;normalT0 = &lt;span style="color: blue;"&gt;tex2D&lt;/span&gt;(WaveMapS0, ( tex0 * TexScale ) + flowmap * phase0 );&lt;br /&gt;&lt;span style="color: blue;"&gt;float3 &lt;/span&gt;normalT1 = &lt;span style="color: blue;"&gt;tex2D&lt;/span&gt;(WaveMapS1, ( tex0 * TexScale ) + flowmap * phase1 );&lt;br /&gt;&lt;br /&gt;&lt;span style="color: blue;"&gt;float &lt;/span&gt;flowLerp = ( &lt;span style="color: blue;"&gt;abs&lt;/span&gt;( HalfCycle - FlowMapOffset0 ) / HalfCycle );&lt;br /&gt;&lt;span style="color: blue;"&gt;float3 &lt;/span&gt;offset = lerp( normalT0, normalT1, flowLerp );&lt;/pre&gt;And that’s pretty much it. I’ll update the post/source when the slides are posted from SIGGRAPH in case I left anything out. Video time!&lt;br /&gt;&lt;br /&gt;&lt;div align="center"&gt;&lt;object height="340" width="560"&gt;&lt;param name="movie" value="http://www.youtube.com/v/VlGYImcuQE4&amp;amp;hl=en_US&amp;amp;fs=1?color1=0x3a3a3a&amp;amp;color2=0x999999"&gt;&lt;/param&gt;&lt;param name="allowFullScreen" value="true"&gt;&lt;/param&gt;&lt;param name="allowscriptaccess" value="always"&gt;&lt;/param&gt;&lt;embed src="http://www.youtube.com/v/VlGYImcuQE4&amp;amp;hl=en_US&amp;amp;fs=1?color1=0x3a3a3a&amp;amp;color2=0x999999" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="560" height="340"&gt;&lt;/embed&gt;&lt;/object&gt;&lt;/div&gt;&lt;br /&gt;Source/Demo:&lt;br /&gt;&lt;iframe frameborder="0" marginheight="0" marginwidth="0" scrolling="no" src="http://cid-b80a3031b5bfa52b.office.live.com/embedicon.aspx/Public/WaterFlowDemo.zip" style="background-color: #fcfcfc; height: 115px; padding: 0px; width: 98px;" title="Preview"&gt;&lt;/iframe&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1023441640234597436-2832962133454660192?l=graphicsrunner.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://graphicsrunner.blogspot.com/feeds/2832962133454660192/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=1023441640234597436&amp;postID=2832962133454660192' title='43 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1023441640234597436/posts/default/2832962133454660192'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1023441640234597436/posts/default/2832962133454660192'/><link rel='alternate' type='text/html' href='http://graphicsrunner.blogspot.com/2010/08/water-using-flow-maps.html' title='Animating Water Using Flow Maps'/><author><name>Kyle Hayward</name><uri>http://www.blogger.com/profile/00654406875137720609</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://bp3.blogger.com/_VelpN_FHzhk/R-Lx4Kk_nkI/AAAAAAAAAN8/jn_4uDeKxNk/S220/me.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://lh6.ggpht.com/_VelpN_FHzhk/TFfR4tZYbiI/AAAAAAAAAq4/66JdwS-vusI/s72-c/flow_thumb6.jpg?imgmax=800' height='72' width='72'/><thr:total>43</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1023441640234597436.post-8174255904004531552</id><published>2010-05-06T13:00:00.008-04:00</published><updated>2010-05-06T20:40:46.900-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Volume Rendering'/><category scheme='http://www.blogger.com/atom/ns#' term='Volume Ray-Casting'/><title type='text'>Volume Rendering 202: Shadows and Translucency</title><content type='html'>Finally, here is the last sample on volume rendering. It’s only taking me a year to get around to finishing it. Is anyone even visiting this page anymore? I’d better post this for my sanity anyhow.&lt;br /&gt;Last time I left you with some basic optimizations, one being a pseudo-empty space skipping. But as I noted, the volumes needed to be sorted in order for it to work completely. We sort the sub-volumes back to front with respect to distance to the camera. This insures that we have a smooth framerate no matter what angle the camera is at. A speedup we can do here is to only sort the volumes if the camera has moved 45 degrees since we last sorted.&lt;br /&gt;So now our subvolumes are sorted w.r.t. the camera. But we have alpha blending artifacts because depending on the view, the pixels of the subvolumes are not drawn in the correct order. What we can do to fix this is to draw a depth only pass, and ensure that we only draw pixels that will contribute to the final image.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://lh4.ggpht.com/_VelpN_FHzhk/S-JC9SFQRXI/AAAAAAAAAqA/daT_tY7ba3E/s1600-h/alpha_errors%5B9%5D.png"&gt;&lt;img alt="alpha_errors" height="197" src="http://lh6.ggpht.com/_VelpN_FHzhk/S-JC96CwnpI/AAAAAAAAAqE/0nBtE7gL-JU/alpha_errors_thumb%5B5%5D.png?imgmax=800" style="display: inline;" title="alpha_errors" width="199" /&gt;&lt;/a&gt; &lt;a href="http://lh3.ggpht.com/_VelpN_FHzhk/S-JC-jH6tAI/AAAAAAAAAqI/frWXNeyGx6w/s1600-h/no_alpha_errors%5B6%5D.png"&gt;&lt;img alt="no_alpha_errors" height="197" src="http://lh5.ggpht.com/_VelpN_FHzhk/S-JC_eCNUaI/AAAAAAAAAqM/po925yxz9q8/no_alpha_errors_thumb%5B4%5D.png?imgmax=800" style="display: inline;" title="no_alpha_errors" width="199" /&gt;&lt;/a&gt; &lt;br /&gt;Left: no depth prepass. Right: depth prepass&lt;br /&gt;&lt;br /&gt;&lt;b&gt;&lt;span style="color: #ff8000;"&gt;Translucency&lt;/span&gt;&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;The first sample includes an approximated translucency. It is far from realistic, but it gives fairly good results. The idea is very similar to depth mapping, compare the current pixels depth to that of the depth map, and either use this value to look up into a texture or perform an exponential falloff in the shader (the sample does the latter).&lt;br /&gt;&lt;br /&gt;&lt;a href="http://lh5.ggpht.com/_VelpN_FHzhk/S-JC_8__lDI/AAAAAAAAAqQ/UHQih7yE_Os/s1600-h/translucency%5B4%5D.png"&gt;&lt;img alt="translucency" height="308" src="http://lh4.ggpht.com/_VelpN_FHzhk/S-JDAG3ZfoI/AAAAAAAAAqU/1KvB5ouYV_M/translucency_thumb%5B2%5D.png?imgmax=800" style="display: inline;" title="translucency" width="404" /&gt;&lt;/a&gt; &lt;br /&gt;&lt;br /&gt;&lt;b&gt;&lt;span style="color: #ff8000;"&gt;Shadows&lt;/span&gt;&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;There isn’t much to say here. The sample below uses variance shadow mapping.&lt;br /&gt;&lt;br /&gt;&lt;b&gt; &lt;a href="http://lh5.ggpht.com/_VelpN_FHzhk/S-JDAoE4WNI/AAAAAAAAAqc/4svp-sUsnFw/s1600-h/shadow0%5B5%5D.png"&gt;&lt;img alt="shadow0" height="316" src="http://lh4.ggpht.com/_VelpN_FHzhk/S-JDBK2B6II/AAAAAAAAAqg/VjtPIFEVIXg/shadow0_thumb%5B3%5D.png?imgmax=800" style="display: inline;" title="shadow0" width="404" /&gt;&lt;/a&gt;&amp;nbsp;&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;&lt;a href="http://lh3.ggpht.com/_VelpN_FHzhk/S-JDBdG3uHI/AAAAAAAAAqk/ixHCcx4cwZ0/s1600-h/shadow1%5B4%5D.png"&gt;&lt;img alt="shadow1" height="397" src="http://lh5.ggpht.com/_VelpN_FHzhk/S-JDB5Sr1rI/AAAAAAAAAqo/Wxe-ATvaqkI/shadow1_thumb%5B2%5D.png?imgmax=800" style="display: inline;" title="shadow1" width="404" /&gt;&lt;/a&gt; &lt;br /&gt;&lt;div class="wlWriterEditableSmartContent" id="scid:5737277B-5D6D-4f48-ABFC-DD9C333F4C5D:14f4c2da-c8ff-4d61-bc9b-f9bcbe527cba" style="display: inline; float: none; margin: 0px; padding: 0px;"&gt;&lt;div id="b93d9547-b511-447a-a9d0-6fecec5c0d2b" style="display: inline; margin: 0px; padding: 0px;"&gt;&lt;br /&gt;&lt;br /&gt;&lt;div&gt;&lt;a href="http://www.youtube.com/watch?v=FFLzYKEjcGA&amp;amp;feature=youtube_gdata" target="_new"&gt;&lt;img alt="" galleryimg="no" onload="var downlevelDiv = document.getElementById('b93d9547-b511-447a-a9d0-6fecec5c0d2b'); downlevelDiv.innerHTML = &amp;quot;&amp;lt;div&amp;gt;&amp;lt;object width=\&amp;quot;425\&amp;quot; height=\&amp;quot;355\&amp;quot;&amp;gt;&amp;lt;param name=\&amp;quot;movie\&amp;quot; value=\&amp;quot;http://www.youtube.com/v/FFLzYKEjcGA&amp;amp;hl=en\&amp;quot;&amp;gt;&amp;lt;\/param&amp;gt;&amp;lt;embed src=\&amp;quot;http://www.youtube.com/v/FFLzYKEjcGA&amp;amp;hl=en\&amp;quot; type=\&amp;quot;application/x-shockwave-flash\&amp;quot; width=\&amp;quot;425\&amp;quot; height=\&amp;quot;355\&amp;quot;&amp;gt;&amp;lt;\/embed&amp;gt;&amp;lt;\/object&amp;gt;&amp;lt;\/div&amp;gt;&amp;quot;;" src="http://lh5.ggpht.com/_VelpN_FHzhk/S-JDCL66q2I/AAAAAAAAAqs/1lwOqPleeZY/videodc0a72c71db5%5B3%5D.jpg?imgmax=800" style="border-style: none;" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;br /&gt;Well, there it is. Anticlimactic wasn't it?&lt;br /&gt;&lt;br /&gt;&lt;iframe frameborder="0" marginheight="0" marginwidth="0" scrolling="no" src="http://cid-b80a3031b5bfa52b.skydrive.live.com/embedrowdetail.aspx/Public/VolumeRayCasting%5E_202%5E_Translucency.zip" style="background-color: white; border: 1px solid rgb(221, 229, 233); height: 66px; margin: 3px; padding: 0px; width: 240px;"&gt;&lt;/iframe&gt;&lt;br /&gt;&lt;br /&gt;&lt;iframe frameborder="0" marginheight="0" marginwidth="0" scrolling="no" src="http://cid-b80a3031b5bfa52b.skydrive.live.com/embedrowdetail.aspx/Public/VolumeRayCasting%5E_202%5E_Shadows.zip" style="background-color: white; border: 1px solid rgb(221, 229, 233); height: 66px; margin: 3px; padding: 0px; width: 240px;"&gt;&lt;/iframe&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1023441640234597436-8174255904004531552?l=graphicsrunner.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://graphicsrunner.blogspot.com/feeds/8174255904004531552/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=1023441640234597436&amp;postID=8174255904004531552' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1023441640234597436/posts/default/8174255904004531552'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1023441640234597436/posts/default/8174255904004531552'/><link rel='alternate' type='text/html' href='http://graphicsrunner.blogspot.com/2010/05/volume-rendering-202-shadows-and.html' title='Volume Rendering 202: Shadows and Translucency'/><author><name>Kyle Hayward</name><uri>http://www.blogger.com/profile/00654406875137720609</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://bp3.blogger.com/_VelpN_FHzhk/R-Lx4Kk_nkI/AAAAAAAAAN8/jn_4uDeKxNk/S220/me.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://lh6.ggpht.com/_VelpN_FHzhk/S-JC96CwnpI/AAAAAAAAAqE/0nBtE7gL-JU/s72-c/alpha_errors_thumb%5B5%5D.png?imgmax=800' height='72' width='72'/><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1023441640234597436.post-6522335732984279983</id><published>2010-05-05T22:15:00.004-04:00</published><updated>2010-06-15T11:51:08.903-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='SIGGRAPH'/><category scheme='http://www.blogger.com/atom/ns#' term='Human Head'/><title type='text'>Ground control to Major Tom</title><content type='html'>&lt;p&gt;Wow, it’s been over a year since the last post on volume rendering! I must sound like a broken record. Anyhow, I’ve had time to fix a couple of bugs with the last installment in the past couple of weeks and it should be coming online pretty soon.&lt;/p&gt;&lt;p&gt;So why have I been absent lately? Last spring I was recruited to work on American Sign Language teaching software for Purdue University. The project ranged from database implementation, to layered skeletal animation with additive blending support and facial animation, to creating a language and compiler for ASL scripts (Antlr was amazing for this). Also, our paper was accepted at SIGGRAPH in the Education section.&lt;/p&gt;&lt;p&gt;On top of that I accepted a job at Human Head Interactive in January as a tech programmer ( these ramblings actually paid off :) ). I’m really excited to be working with some smart and talented people. We have some cool rendering tech – thanks to our &lt;a href="http://graphicrants.blogspot.com/" target="_blank"&gt;lead graphics programmer&lt;/a&gt; – and pretty slick game play ideas.&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;span style="font-style: italic;"&gt;Also, sorry to anyone who has commented on a post and it hasn't been posted, I've been spammed by bots for awhile now.&lt;/span&gt;&lt;br /&gt;&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1023441640234597436-6522335732984279983?l=graphicsrunner.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://graphicsrunner.blogspot.com/feeds/6522335732984279983/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=1023441640234597436&amp;postID=6522335732984279983' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1023441640234597436/posts/default/6522335732984279983'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1023441640234597436/posts/default/6522335732984279983'/><link rel='alternate' type='text/html' href='http://graphicsrunner.blogspot.com/2010/05/ground-control-to-major-tom.html' title='Ground control to Major Tom'/><author><name>Kyle Hayward</name><uri>http://www.blogger.com/profile/00654406875137720609</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://bp3.blogger.com/_VelpN_FHzhk/R-Lx4Kk_nkI/AAAAAAAAAN8/jn_4uDeKxNk/S220/me.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1023441640234597436.post-2293423958263440245</id><published>2010-04-10T01:38:00.012-04:00</published><updated>2010-04-10T01:50:43.185-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Water'/><category scheme='http://www.blogger.com/atom/ns#' term='XNA'/><title type='text'>Water for your monies?</title><content type='html'>I got an email a couple of weeks ago from someone ( &lt;a href="http://maximinus.fr/missile.escape.html"&gt;Maximinus&lt;/a&gt; ) who actually put the water shader from the water game component to good use. Here's a description of the game on the xbox indie marketplace:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt;Missile Escape for Xbox Indies is simple : go flying, evade many&lt;/span&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt;missiles  and unlock new fighters along the way ! Warning : &lt;/span&gt;&lt;span style="border-bottom: 1px dashed rgb(0, 102, 204); cursor: pointer; font-style: italic;" class="yshortcuts" id="lw_1270878010_0"&gt;Fighter&lt;br /&gt;pilot&lt;/span&gt;&lt;span style="font-style: italic;"&gt; spirit  required.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;object height="320" width="440"&gt;&lt;param name="movie" value="http://www.youtube.com/v/EeCAmJ_il6I&amp;amp;hl=en_US&amp;amp;fs=1&amp;amp;rel=0"&gt;&lt;param name="allowFullScreen" value="true"&gt;&lt;param name="allowscriptaccess" value="always"&gt;&lt;embed src="http://www.youtube.com/v/EeCAmJ_il6I&amp;amp;hl=en_US&amp;amp;fs=1&amp;amp;rel=0" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" height="320" width="440"&gt;&lt;/embed&gt;&lt;/object&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1023441640234597436-2293423958263440245?l=graphicsrunner.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://graphicsrunner.blogspot.com/feeds/2293423958263440245/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=1023441640234597436&amp;postID=2293423958263440245' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1023441640234597436/posts/default/2293423958263440245'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1023441640234597436/posts/default/2293423958263440245'/><link rel='alternate' type='text/html' href='http://graphicsrunner.blogspot.com/2010/04/water-for-your-monies.html' title='Water for your monies?'/><author><name>Kyle Hayward</name><uri>http://www.blogger.com/profile/00654406875137720609</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://bp3.blogger.com/_VelpN_FHzhk/R-Lx4Kk_nkI/AAAAAAAAAN8/jn_4uDeKxNk/S220/me.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1023441640234597436.post-8409445255133174154</id><published>2009-08-27T02:45:00.003-04:00</published><updated>2009-08-27T02:49:54.690-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Particle Effects'/><title type='text'>Particle Spectrum Animation</title><content type='html'>Really cool video I found last night. Uses Particular and Adobe After Effects.&lt;br /&gt;&lt;br /&gt;&lt;object width="450" height="253"&gt;&lt;param name="allowfullscreen" value="true"&gt;&lt;param name="allowscriptaccess" value="always"&gt;&lt;param name="movie" value="http://vimeo.com/moogaloop.swf?clip_id=6045312&amp;amp;server=vimeo.com&amp;amp;show_title=1&amp;amp;show_byline=1&amp;amp;show_portrait=0&amp;amp;color=00ADEF&amp;amp;fullscreen=1"&gt;&lt;embed src="http://vimeo.com/moogaloop.swf?clip_id=6045312&amp;amp;server=vimeo.com&amp;amp;show_title=1&amp;amp;show_byline=1&amp;amp;show_portrait=0&amp;amp;color=00ADEF&amp;amp;fullscreen=1" type="application/x-shockwave-flash" allowfullscreen="true" allowscriptaccess="always" width="450" height="253"&gt;&lt;/embed&gt;&lt;/object&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1023441640234597436-8409445255133174154?l=graphicsrunner.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://graphicsrunner.blogspot.com/feeds/8409445255133174154/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=1023441640234597436&amp;postID=8409445255133174154' title='5 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1023441640234597436/posts/default/8409445255133174154'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1023441640234597436/posts/default/8409445255133174154'/><link rel='alternate' type='text/html' href='http://graphicsrunner.blogspot.com/2009/08/particle-spectrum-animation.html' title='Particle Spectrum Animation'/><author><name>Kyle Hayward</name><uri>http://www.blogger.com/profile/00654406875137720609</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://bp3.blogger.com/_VelpN_FHzhk/R-Lx4Kk_nkI/AAAAAAAAAN8/jn_4uDeKxNk/S220/me.jpg'/></author><thr:total>5</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1023441640234597436.post-6044407068329684581</id><published>2009-07-30T18:04:00.003-04:00</published><updated>2009-07-30T21:15:02.687-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Water'/><category scheme='http://www.blogger.com/atom/ns#' term='refractions'/><category scheme='http://www.blogger.com/atom/ns#' term='O3D'/><category scheme='http://www.blogger.com/atom/ns#' term='reflections'/><title type='text'>Water in Your Browser</title><content type='html'>&lt;p&gt;Recently I’ve been playing around with O3D. If you don’t know, O3D is Google’s new browser graphics API. It enables you to develop 3d interactive applications that run inside a browser window (and quite easily mind you). In fact it rivals XNA on getting an app up and running quickly.&lt;/p&gt;  &lt;p&gt;And on that note, I’ve ported the water sample to O3D (minus the camera animation). Besides a bug I encountered with the sample content converter (and promptly fixed by one of the o3d developers), it was a relatively painless conversion. All that was required was to setup a scene in max, apply materials, export to collada and convert to the o3d format. Setting up the render targets also took minimal effort :). The shaders, for the most part, remained untouched.&lt;/p&gt;  &lt;p&gt;Click the picture to have a go!&lt;/p&gt;  &lt;p&gt;&lt;a href="http://www.kylehayward.com/o3d/WaterScene.html"&gt;&lt;img style="display: inline;" title="waterscene" alt="waterscene" src="http://lh5.ggpht.com/_VelpN_FHzhk/SnIY_hY8e_I/AAAAAAAAApI/YkXj9x_TsxQ/waterscene%5B6%5D.jpg?imgmax=800" height="254" width="429" /&gt;&lt;/a&gt; &lt;/p&gt;  &lt;p&gt;If you’re interested in the max file or source you can get both here:&lt;/p&gt; &lt;iframe style="border: 1px solid rgb(221, 229, 233); margin: 3px; padding: 0px; background-color: rgb(255, 255, 255); width: 240px; height: 26px;" marginheight="0" src="http://cid-b80a3031b5bfa52b.skydrive.live.com/embedrow.aspx/Public/WaterSceneO3D.zip" marginwidth="0" frameborder="0" scrolling="no"&gt;&lt;/iframe&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1023441640234597436-6044407068329684581?l=graphicsrunner.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://graphicsrunner.blogspot.com/feeds/6044407068329684581/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=1023441640234597436&amp;postID=6044407068329684581' title='9 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1023441640234597436/posts/default/6044407068329684581'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1023441640234597436/posts/default/6044407068329684581'/><link rel='alternate' type='text/html' href='http://graphicsrunner.blogspot.com/2009/07/water-in-your-browser.html' title='Water in Your Browser'/><author><name>Kyle Hayward</name><uri>http://www.blogger.com/profile/00654406875137720609</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://bp3.blogger.com/_VelpN_FHzhk/R-Lx4Kk_nkI/AAAAAAAAAN8/jn_4uDeKxNk/S220/me.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://lh5.ggpht.com/_VelpN_FHzhk/SnIY_hY8e_I/AAAAAAAAApI/YkXj9x_TsxQ/s72-c/waterscene%5B6%5D.jpg?imgmax=800' height='72' width='72'/><thr:total>9</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1023441640234597436.post-3409892337243892221</id><published>2009-06-19T00:58:00.009-04:00</published><updated>2009-06-19T01:27:49.428-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='deferred rendering'/><category scheme='http://www.blogger.com/atom/ns#' term='SSAO'/><category scheme='http://www.blogger.com/atom/ns#' term='DirectX'/><category scheme='http://www.blogger.com/atom/ns#' term='SSGI'/><title type='text'>Non-photorealistic SSGI</title><content type='html'>I recently got back from my vacation to the Mediterranean and finally have had time to post this video.&lt;br /&gt;&lt;br /&gt;For one of my final projects last semester I implemented a deferred renderer with SSAO and simplified global illumination (SSGI). But I wanted to have a dream-like result, so I over emphasized the color bleeding and used the light accumulation buffer plus emissive color of objects; and lots of blurring. The result looks pretty neat I think, not at all accurate lighting but cool none the less. This one was done in C++/DirectX instead of the usual XNA fare.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://www.youtube.com/watch?v=YAnG2PB1qiI&amp;amp;fmt=22"&gt;Large HD video on youtube&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;object height="295" width="480"&gt;&lt;param name="movie" value="http://www.youtube.com/v/YAnG2PB1qiI&amp;amp;hl=en&amp;amp;fs=1&amp;amp;rel=0&amp;amp;color1=0x3a3a3a&amp;amp;color2=0x999999&amp;amp;hd=1"&gt;&lt;param name="allowFullScreen" value="true"&gt;&lt;param name="allowscriptaccess" value="always"&gt;&lt;embed src="http://www.youtube.com/v/YAnG2PB1qiI&amp;amp;hl=en&amp;amp;fs=1&amp;amp;rel=0&amp;amp;color1=0x3a3a3a&amp;amp;color2=0x999999&amp;amp;hd=1" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" height="295" width="480"&gt;&lt;/embed&gt;&lt;/object&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1023441640234597436-3409892337243892221?l=graphicsrunner.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://graphicsrunner.blogspot.com/feeds/3409892337243892221/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=1023441640234597436&amp;postID=3409892337243892221' title='10 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1023441640234597436/posts/default/3409892337243892221'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1023441640234597436/posts/default/3409892337243892221'/><link rel='alternate' type='text/html' href='http://graphicsrunner.blogspot.com/2009/06/non-photorealistic-ssgi.html' title='Non-photorealistic SSGI'/><author><name>Kyle Hayward</name><uri>http://www.blogger.com/profile/00654406875137720609</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://bp3.blogger.com/_VelpN_FHzhk/R-Lx4Kk_nkI/AAAAAAAAAN8/jn_4uDeKxNk/S220/me.jpg'/></author><thr:total>10</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1023441640234597436.post-3005017070977345264</id><published>2009-04-23T03:05:00.003-04:00</published><updated>2009-04-23T03:25:14.152-04:00</updated><title type='text'>Volume Rendering Update, DirectX blog</title><content type='html'>Wow, I can't believe it's almost May already. I also can't believe my last post was back in February! I've had the next installment in the volume rendering series basically done since January. I just have to put some finishing touches on it and write the post. I've just been very busy finishing my last semester, research, searching for a job, and considering graduate school.&lt;br /&gt;&lt;br /&gt;I've been working on a deferred renderer and delving into screen space ambient occlusion and global illumination for one of my final projects. I'll probably post some images of the final result when it's done.&lt;br /&gt;&lt;br /&gt;&lt;span class="largefont"&gt;Ysaneya &lt;/span&gt;has a &lt;span style="font-weight: bold;"&gt;very &lt;/span&gt;interesting post on &lt;a href="http://www.gamedev.net/community/forums/mod/journal/journal.asp?jn=263350&amp;amp;reply_id=3432126"&gt;deferred rendering and instant radiosity&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Also, if you haven't noticed, the DirectX team has a new blog: &lt;a href="http://blogs.msdn.com/DirectX/"&gt;http://blogs.msdn.com/DirectX/&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1023441640234597436-3005017070977345264?l=graphicsrunner.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://graphicsrunner.blogspot.com/feeds/3005017070977345264/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=1023441640234597436&amp;postID=3005017070977345264' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1023441640234597436/posts/default/3005017070977345264'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1023441640234597436/posts/default/3005017070977345264'/><link rel='alternate' type='text/html' href='http://graphicsrunner.blogspot.com/2009/04/volume-rendering-update-directx-blog.html' title='Volume Rendering Update, DirectX blog'/><author><name>Kyle Hayward</name><uri>http://www.blogger.com/profile/00654406875137720609</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://bp3.blogger.com/_VelpN_FHzhk/R-Lx4Kk_nkI/AAAAAAAAAN8/jn_4uDeKxNk/S220/me.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1023441640234597436.post-1765281081707323748</id><published>2009-02-04T22:42:00.007-05:00</published><updated>2010-06-14T23:31:00.187-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Volume Rendering'/><category scheme='http://www.blogger.com/atom/ns#' term='Volume Ray-Casting'/><title type='text'>Volume Rendering 201: Optimizations</title><content type='html'>&lt;p&gt;The discussion on hand this time is optimizing the performance of the volume renderer. I’ll cover a few of these optimizations and provide a rough implementation of one of them. &lt;/p&gt;&lt;p&gt;&lt;strong&gt;&lt;span style="color: #ff6633"&gt;Cache Efficiency and Memory Access&lt;/span&gt;&lt;/strong&gt;&amp;nbsp;&lt;strong&gt; &lt;/strong&gt;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;&lt;a href="http://lh5.ggpht.com/_VelpN_FHzhk/SYpgKgDwd9I/AAAAAAAAAns/uHlWC36rjDs/s1600-h/memory5.jpg"&gt;&lt;img style="display: inline" title="memory" alt="memory" src="http://lh4.ggpht.com/_VelpN_FHzhk/SYpgK2v-l8I/AAAAAAAAAnw/N_RB8CYPYOc/memory_thumb3.jpg?imgmax=800" width="402" height="240"&gt;&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;Picture from &lt;em&gt;Real-time Volume Graphics.&lt;/em&gt; &lt;/p&gt;&lt;p&gt;Currently we load our volume data into a linear layout in memory. However, the ray that is cast through the volume is not likely to access neighboring voxels as we traverse it through the volume when the data is in a linear layout. But we can improve the cache efficiency by converting the layout to a block-based manor through swizzling. With the data in a block format, the GPU is more likely to cache neighboring voxels as we walk through the volume, which will lead to an increase in memory performance. &lt;/p&gt;&lt;p&gt;&lt;strong&gt;&lt;span style="color: #ff6633"&gt;&lt;/span&gt;&lt;/strong&gt;&lt;strong&gt;&lt;span style="color: #ff6633"&gt;Empty-Space Leaping&lt;/span&gt;&lt;/strong&gt;&amp;nbsp; &lt;/p&gt;&lt;p&gt;&lt;a href="http://lh4.ggpht.com/_VelpN_FHzhk/SYpgLT5AHaI/AAAAAAAAAn0/jFZN2KzzUmM/s1600-h/esl04.png"&gt;&lt;img style="display: inline" title="esl0" alt="esl0" src="http://lh3.ggpht.com/_VelpN_FHzhk/SYpgLo8yTtI/AAAAAAAAAn4/5u2MpH6_FJQ/esl0_thumb2.png?imgmax=800" width="394" height="296"&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;In the previous samples we ray-casted against the entire bounding volume of the data set, even if we were just sampling samples with zero alpha along the way. But we can skip these samples all together, and only render parts of the volume that have a non-zero alpha in the transfer function. More on this in a bit. &lt;/p&gt;&lt;p&gt;&lt;strong&gt;&lt;span style="color: #ff6633"&gt;&lt;/span&gt;&lt;/strong&gt;&lt;strong&gt;&lt;span style="color: #ff6633"&gt;Occlusion Culling&lt;/span&gt;&lt;/strong&gt; &lt;/p&gt;&lt;p&gt;If we render the volume in a block-based fashion as above, and sort the blocks from front to back, we can use occlusion queries to determine which blocks are completely occluded by blocks in front of them. There are quite a few tutorials on occlusion queries on the net, including this one at &lt;a href="http://www.ziggyware.com/readarticle.php?article_id=234" target="_blank"&gt;ziggyware&lt;/a&gt;. &lt;/p&gt;&lt;p&gt;&lt;strong&gt;&lt;span style="color: #ff6633"&gt;Deferred Shading&lt;/span&gt;&lt;/strong&gt; &lt;/p&gt;&lt;p&gt;&lt;strong&gt;&lt;span style="color: #ff6633"&gt;&lt;/span&gt;&lt;/strong&gt;We can also boost performance by deferring the shading calculations. Instead shading every voxel during the ray-casting, we can just output the depth and color information into off-screen buffers. Then we render a full-screen quad and use the depth information to calculate normals in screen space and then continue to calculate the shading. Calculating normals this way also has the advantage of being smoother and have less artifacts that computing the gradients of the volume and storing them in a 3D texture. We also save memory this way since we don’t have to save the normals, only the isovalues in the 3D texture. &lt;/p&gt;&lt;p&gt;&lt;strong&gt;&lt;span style="color: #ff6633"&gt;Image Downscaling&lt;/span&gt;&lt;/strong&gt; &lt;/p&gt;&lt;p&gt;If the data we are rendering is low frequency (e.g. volumetric fog), we can render the volume into an off-screen buffer that is half the size of the window. Then we can up-scale this image during a final pass. This method is also included in the sample. &lt;/p&gt;&lt;p&gt;&lt;strong&gt;&lt;span style="color: #ff6633"&gt;&lt;/span&gt;&lt;/strong&gt;&lt;strong&gt;&lt;span style="color: #ff6633"&gt;Implementing Empty-Space Leaping&lt;/span&gt;&lt;/strong&gt; &lt;/p&gt;&lt;p&gt;To implement empty-space leaping we need to subdivide the volume into smaller volumes and we also need to determine if these smaller volumes have an opacity greater than zero. To subdivide the volume we follow an approach very similar to quadtree or octree construction. We start out with an original volume from [0, 0, 0] to [1, 1, 1]. The volume is then recursively subdivided until the volume width is say .1 (so we basically divide the volume along each dimension by 10). Here’s how we do that: &lt;/p&gt;&lt;pre class="mycode"&gt;&lt;p&gt;&lt;span style="color: blue"&gt;private void &lt;/span&gt;RecursiveVolumeBuild(&lt;span style="color: #2b91af"&gt;Cube &lt;/span&gt;C)&lt;br /&gt;{&lt;br /&gt;&lt;span style="color: green"&gt;     //stop when the current cube is 1/10 of the original volume&lt;br /&gt;&lt;/span&gt;&lt;span style="color: blue"&gt;&lt;span style="color: green" class="Apple-style-span"&gt;     &lt;/span&gt;if &lt;/span&gt;(C.Width &amp;lt;= 0.1f)&lt;br /&gt;&lt;span style="color: green" class="Apple-style-span"&gt;     &lt;/span&gt;{&lt;br /&gt;&lt;span style="color: green"&gt;&amp;nbsp; &lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;        &lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;//add the min/max vertex to the list&lt;/span&gt;&lt;span style="color: #2b91af"&gt;&lt;span style="color: green" class="Apple-style-span"&gt;&amp;nbsp; &lt;/span&gt;&lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;     &lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style="color: green" class="Apple-style-span"&gt;          &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Vector3 &lt;/span&gt;min = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Vector3&lt;/span&gt;(C.X, C.Y, C.Z);&lt;span style="color: #2b91af"&gt;&lt;span style="color: green" class="Apple-style-span"&gt;&amp;nbsp; &lt;/span&gt;&lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;     &lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style="color: green" class="Apple-style-span"&gt;&lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;&lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;&lt;/span&gt;&lt;span style="color: #2b91af"&gt;&lt;font color="#008000"&gt;          &lt;/font&gt;Vector3 &lt;/span&gt;max = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Vector3&lt;/span&gt;(C.X + C.Width, C.Y + C.Height, C.Z + C.Depth);&lt;span style="color: #2b91af"&gt;&lt;span style="color: green" class="Apple-style-span"&gt;&amp;nbsp; &lt;/span&gt;&lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;     &lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style="color: green" class="Apple-style-span"&gt;          &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Vector3 &lt;/span&gt;scale = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Vector3&lt;/span&gt;(mWidth, mHeight, mDepth);&lt;span style="color: green"&gt;&amp;nbsp; &lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;     &lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style="color: green" class="Apple-style-span"&gt;          &lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;//additively sample the transfer function and check if there are any&lt;/span&gt;&lt;span style="color: green"&gt;&amp;nbsp; &lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;     &lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style="color: green" class="Apple-style-span"&gt;&lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;         //samples that are greater than zero&lt;/span&gt;&lt;span style="color: blue"&gt;&lt;span style="color: green" class="Apple-style-span"&gt;&amp;nbsp; &lt;/span&gt;&lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;     &lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style="color: green" class="Apple-style-span"&gt;         &lt;/span&gt;&lt;span style="color: blue"&gt;float &lt;/span&gt;opacity = SampleVolume3DWithTransfer(min * scale, max * scale);&lt;span style="color: blue"&gt;&lt;span style="color: green" class="Apple-style-span"&gt;&amp;nbsp; &lt;/span&gt;&lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;     &lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style="color: green" class="Apple-style-span"&gt;         &lt;/span&gt;&lt;span style="color: blue"&gt;if&lt;/span&gt;(opacity &amp;gt; 0.0f)&lt;span style="color: green" class="Apple-style-span"&gt;&amp;nbsp; &lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;     &lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style="color: green" class="Apple-style-span"&gt;         &lt;/span&gt;{                 &lt;span style="color: green" class="Apple-style-span"&gt;&lt;/span&gt;&lt;span style="color: #2b91af"&gt;&lt;span style="color: green" class="Apple-style-span"&gt;&amp;nbsp; &lt;/span&gt;&lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;&amp;nbsp; &lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;     &lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style="color: green" class="Apple-style-span"&gt;               &lt;/span&gt;&lt;span style="color: #2b91af"&gt;BoundingBox &lt;/span&gt;box = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;BoundingBox&lt;/span&gt;(min, max);&lt;span style="color: green"&gt;&amp;nbsp; &lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;&amp;nbsp; &lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;     &lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style="color: green" class="Apple-style-span"&gt;               //add the corners of the bounding box&lt;/span&gt;&lt;span style="color: #2b91af"&gt;&lt;span style="color: green" class="Apple-style-span"&gt;&amp;nbsp; &lt;/span&gt;&lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;&amp;nbsp; &lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;&amp;nbsp; &lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style="color: green" class="Apple-style-span"&gt;               &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Vector3&lt;/span&gt;[] corners = box.GetCorners();&lt;span style="color: blue"&gt;&lt;span style="color: green" class="Apple-style-span"&gt;&amp;nbsp; &lt;/span&gt;&lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;&amp;nbsp; &lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;     &lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style="color: green" class="Apple-style-span"&gt;               &lt;/span&gt;&lt;span style="color: blue"&gt;for &lt;/span&gt;(&lt;span style="color: blue"&gt;int &lt;/span&gt;i = 0; i &amp;lt; 8; i++)&lt;span style="color: green" class="Apple-style-span"&gt;&amp;nbsp; &lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;&amp;nbsp; &lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;     &lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style="color: green" class="Apple-style-span"&gt;               &lt;/span&gt;{&lt;span style="color: green" class="Apple-style-span"&gt;&lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;&lt;/span&gt;&lt;span style="color: #2b91af"&gt;&lt;span style="color: green" class="Apple-style-span"&gt;&amp;nbsp; &lt;/span&gt;&lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;&amp;nbsp; &lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;&amp;nbsp; &lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;     &lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style="color: green" class="Apple-style-span"&gt;                    &lt;/span&gt;&lt;span style="color: #2b91af"&gt;VertexPositionColor &lt;/span&gt;v;&lt;span style="color: green" class="Apple-style-span"&gt;&amp;nbsp; &lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;&amp;nbsp; &lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;&amp;nbsp; &lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;     &lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style="color: green" class="Apple-style-span"&gt;                    &lt;/span&gt;v.Position = corners[i];&lt;span style="color: green" class="Apple-style-span"&gt;&lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;&lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;&lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;&amp;nbsp; &lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;&amp;nbsp; &lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;&amp;nbsp; &lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;     &lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style="color: green" class="Apple-style-span"&gt;                    &lt;/span&gt;v.Color = &lt;span style="color: #2b91af"&gt;Color&lt;/span&gt;.Blue;&lt;span style="color: green" class="Apple-style-span"&gt;&lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;&lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;&lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;&amp;nbsp; &lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;&amp;nbsp; &lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;&amp;nbsp; &lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;     &lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style="color: green" class="Apple-style-span"&gt;                    &lt;/span&gt;mVertices.Add(v);&lt;span style="color: green" class="Apple-style-span"&gt;&lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;&lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;&lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;&amp;nbsp; &lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;&amp;nbsp; &lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;     &lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style="color: green" class="Apple-style-span"&gt;               &lt;/span&gt;}&lt;span style="color: green" class="Apple-style-span"&gt;&lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;&lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;&amp;nbsp; &lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;     &lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style="color: green" class="Apple-style-span"&gt;          &lt;/span&gt;}&lt;span style="color: green" class="Apple-style-span"&gt;&lt;/span&gt;&lt;span style="color: blue"&gt;&lt;span style="color: green" class="Apple-style-span"&gt;     &lt;/span&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style="color: blue"&gt;&lt;span style="color: green" class="Apple-style-span"&gt;          &lt;/span&gt;return&lt;/span&gt;;&lt;br /&gt;     }&lt;br /&gt;&lt;br /&gt;&lt;span style="color: blue"&gt;&lt;span style="color: green" class="Apple-style-span"&gt;     &lt;/span&gt;float &lt;/span&gt;newWidth = C.Width / 2f;&lt;br /&gt;&lt;span style="color: blue"&gt;&lt;span style="color: green" class="Apple-style-span"&gt;     &lt;/span&gt;float &lt;/span&gt;newHeight = C.Height / 2f;&lt;br /&gt;&lt;span style="color: blue"&gt;&lt;span style="color: green" class="Apple-style-span"&gt;     &lt;/span&gt;float &lt;/span&gt;newDepth = C.Depth / 2f;&lt;br /&gt;&lt;br /&gt;&lt;span style="color: grey"&gt;&lt;span style="color: green" class="Apple-style-span"&gt;     &lt;/span&gt;///  &lt;/span&gt;&lt;span style="color: green"&gt;SubGrid        r  c  d&lt;br /&gt;&lt;/span&gt;&lt;span style="color: grey"&gt;&lt;span style="color: green" class="Apple-style-span"&gt;     &lt;/span&gt;///  &lt;/span&gt;&lt;span style="color: green"&gt;Front:&lt;br /&gt;&lt;/span&gt;&lt;span style="color: grey"&gt;&lt;span style="color: green" class="Apple-style-span"&gt;     &lt;/span&gt;///  &lt;/span&gt;&lt;span style="color: green"&gt;Top-Left    :  0   0   0&lt;br /&gt;&lt;/span&gt;&lt;span style="color: grey"&gt;&lt;span style="color: green" class="Apple-style-span"&gt;     &lt;/span&gt;///  &lt;/span&gt;&lt;span style="color: green"&gt;Top-Right   :  0   1   0&lt;br /&gt;&lt;/span&gt;&lt;span style="color: grey"&gt;&lt;span style="color: green" class="Apple-style-span"&gt;     &lt;/span&gt;///  &lt;/span&gt;&lt;span style="color: green"&gt;Bottom-Left :  1   0   0&lt;br /&gt;&lt;/span&gt;&lt;span style="color: grey"&gt;&lt;span style="color: green" class="Apple-style-span"&gt;     &lt;/span&gt;///  &lt;/span&gt;&lt;span style="color: green"&gt;Bottom-Right:  1   1   0&lt;br /&gt;&lt;/span&gt;&lt;span style="color: grey"&gt;&lt;span style="color: green" class="Apple-style-span"&gt;     &lt;/span&gt;///  &lt;/span&gt;&lt;span style="color: green"&gt;Back:&lt;br /&gt;&lt;/span&gt;&lt;span style="color: grey"&gt;&lt;span style="color: green" class="Apple-style-span"&gt;     &lt;/span&gt;///  &lt;/span&gt;&lt;span style="color: green"&gt;Top-Left    :  0   0   1&lt;br /&gt;&lt;/span&gt;&lt;span style="color: grey"&gt;&lt;span style="color: green" class="Apple-style-span"&gt;     &lt;/span&gt;///  &lt;/span&gt;&lt;span style="color: green"&gt;Top-Right   :  0   1   1&lt;br /&gt;&lt;/span&gt;&lt;span style="color: grey"&gt;&lt;span style="color: green" class="Apple-style-span"&gt;     &lt;/span&gt;///  &lt;/span&gt;&lt;span style="color: green"&gt;Bottom-Left :  1   0   1&lt;br /&gt;&lt;/span&gt;&lt;span style="color: grey"&gt;&lt;span style="color: green" class="Apple-style-span"&gt;     &lt;/span&gt;///  &lt;/span&gt;&lt;span style="color: green"&gt;Bottom-Right:  1   1   1&lt;br /&gt;&lt;/span&gt;&lt;span style="color: blue"&gt;&lt;span style="color: green" class="Apple-style-span"&gt;     &lt;/span&gt;for &lt;/span&gt;(&lt;span style="color: blue"&gt;float &lt;/span&gt;r = 0; r &amp;lt; 2; r++)&lt;br /&gt;&lt;span style="color: green" class="Apple-style-span"&gt;     &lt;/span&gt;{&lt;br /&gt;&lt;span style="color: blue"&gt;&lt;span style="color: green" class="Apple-style-span"&gt;&amp;nbsp; &lt;/span&gt;&lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;        &lt;/span&gt;&lt;span style="color: blue"&gt;for &lt;/span&gt;(&lt;span style="color: blue"&gt;float &lt;/span&gt;c = 0; c &amp;lt; 2; c++)&lt;span style="color: green" class="Apple-style-span"&gt;          &lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style="color: green" class="Apple-style-span"&gt;          &lt;/span&gt;{&lt;br /&gt;&lt;span style="color: blue"&gt;&lt;span style="color: green" class="Apple-style-span"&gt;&amp;nbsp; &lt;/span&gt;&lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;              &lt;/span&gt;&lt;span style="color: blue"&gt;for &lt;/span&gt;(&lt;span style="color: blue"&gt;float &lt;/span&gt;d = 0; d &amp;lt; 2; d++)&lt;span style="color: green" class="Apple-style-span"&gt;&amp;nbsp; &lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;     &lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style="color: green" class="Apple-style-span"&gt;                &lt;/span&gt;{&lt;span style="color: green" class="Apple-style-span"&gt;&lt;/span&gt;&lt;span style="color: #2b91af"&gt;&lt;span style="color: green" class="Apple-style-span"&gt;&amp;nbsp; &lt;/span&gt;&lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;&amp;nbsp; &lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;     &lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style="color: green" class="Apple-style-span"&gt;                     &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Cube &lt;/span&gt;cube = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Cube&lt;/span&gt;(C.Left + c * (newWidth),&lt;span style="color: green" class="Apple-style-span"&gt;&amp;nbsp; &lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;&amp;nbsp; &lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;     &lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style="color: green" class="Apple-style-span"&gt;                                          &lt;/span&gt;C.Top + r * (newHeight),&lt;span style="color: green" class="Apple-style-span"&gt;&lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;&lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;&amp;nbsp; &lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;&amp;nbsp; &lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style="color: green" class="Apple-style-span"&gt;                                          &lt;/span&gt;C.Front + d * (newDepth),&lt;span style="color: green" class="Apple-style-span"&gt;&lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;&lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;&lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;&amp;nbsp; &lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;&amp;nbsp; &lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;     &lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style="color: green" class="Apple-style-span"&gt;                                          &lt;/span&gt;newWidth,&lt;span style="color: green" class="Apple-style-span"&gt;&lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;&lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;&amp;nbsp; &lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;&amp;nbsp; &lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;     &lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style="color: green" class="Apple-style-span"&gt;                                          &lt;/span&gt;newHeight,&lt;span style="color: green" class="Apple-style-span"&gt;&lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;&lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;&amp;nbsp; &lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;&amp;nbsp; &lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;    &lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style="color: green" class="Apple-style-span"&gt;                                          &lt;/span&gt;newDepth);&lt;span style="color: green" class="Apple-style-span"&gt;&lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;&lt;/span&gt;&lt;span style="font-family: 'Times New Roman'; white-space: normal" class="Apple-style-span"&gt; &lt;span style="color: green"&gt; &lt;/span&gt; &lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;&amp;nbsp; &lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;&amp;nbsp; &lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;  &lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style="color: green" class="Apple-style-span"&gt;                                          &lt;/span&gt;RecursiveVolumeBuild(cube);&lt;span style="color: green" class="Apple-style-span"&gt;&lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;&lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;&amp;nbsp; &lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;     &lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;     &lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style="color: green" class="Apple-style-span"&gt;                &lt;/span&gt;}&lt;span style="color: green" class="Apple-style-span"&gt;&lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;         &lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style="color: green" class="Apple-style-span"&gt;          &lt;/span&gt;}&lt;br /&gt;&lt;span style="color: green" class="Apple-style-span"&gt;     &lt;/span&gt;}&lt;br /&gt;}&lt;/p&gt;&lt;/pre&gt;To determine whether a sub-volume contains any samples that have opacity, we simply loop over the volume and additively sample the transfer function: &lt;pre class="mycode"&gt;&lt;p&gt;&lt;span style="color: blue"&gt;private float &lt;/span&gt;SampleVolume3DWithTransfer(&lt;span style="color: #2b91af"&gt;Vector3 &lt;/span&gt;min, &lt;span style="color: #2b91af"&gt;Vector3 &lt;/span&gt;max)&lt;br /&gt;{&lt;br /&gt;&lt;span style="color: blue"&gt;&lt;span style="color: green" class="Apple-style-span"&gt;     &lt;/span&gt;float &lt;/span&gt;result = 0.0f;&lt;br /&gt;&lt;br /&gt;&lt;span style="color: blue"&gt;&lt;span style="color: green" class="Apple-style-span"&gt;     &lt;/span&gt;for &lt;/span&gt;(&lt;span style="color: blue"&gt;int &lt;/span&gt;x = (&lt;span style="color: blue"&gt;int&lt;/span&gt;)min.X; x &amp;lt;= (&lt;span style="color: blue"&gt;int&lt;/span&gt;)max.X; x++)&lt;br /&gt;&lt;span style="color: green" class="Apple-style-span"&gt;     &lt;/span&gt;{&lt;br /&gt;&lt;span style="color: blue"&gt;&lt;span style="color: green" class="Apple-style-span"&gt;&amp;nbsp; &lt;/span&gt;&lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;        &lt;/span&gt;&lt;span style="color: blue"&gt;for &lt;/span&gt;(&lt;span style="color: blue"&gt;int &lt;/span&gt;y = (&lt;span style="color: blue"&gt;int&lt;/span&gt;)min.Y; y &amp;lt;= (&lt;span style="color: blue"&gt;int&lt;/span&gt;)max.Y; y++)&lt;span style="color: green" class="Apple-style-span"&gt;&amp;nbsp; &lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;     &lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style="color: green" class="Apple-style-span"&gt;          &lt;/span&gt;{&lt;span style="color: green" class="Apple-style-span"&gt;&lt;/span&gt;&lt;span style="color: blue"&gt;&lt;span style="color: green" class="Apple-style-span"&gt;&amp;nbsp; &lt;/span&gt;&lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;&amp;nbsp; &lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;     &lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style="color: green" class="Apple-style-span"&gt;               &lt;/span&gt;&lt;span style="color: blue"&gt;for &lt;/span&gt;(&lt;span style="color: blue"&gt;int &lt;/span&gt;z = (&lt;span style="color: blue"&gt;int&lt;/span&gt;)min.Z; z &amp;lt;= (&lt;span style="color: blue"&gt;int&lt;/span&gt;)max.Z; z++)&lt;span style="color: green" class="Apple-style-span"&gt;&amp;nbsp; &lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;&amp;nbsp; &lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;     &lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style="color: green" class="Apple-style-span"&gt;               &lt;/span&gt;{&lt;span style="color: green" class="Apple-style-span"&gt;&lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;&lt;/span&gt;&lt;span style="color: green"&gt;&amp;nbsp; &lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;&amp;nbsp; &lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;&amp;nbsp; &lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;     &lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style="color: green" class="Apple-style-span"&gt;                    &lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;//sample the volume to get the iso value&lt;/span&gt;&lt;span style="color: green"&gt;&amp;nbsp; &lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;&amp;nbsp; &lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;&amp;nbsp; &lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;     &lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style="color: green" class="Apple-style-span"&gt;                    &lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;//it was stored [0, 1] so we need to scale to [0, 255]&lt;/span&gt;&lt;span style="color: blue"&gt;&lt;span style="color: green" class="Apple-style-span"&gt;&amp;nbsp; &lt;/span&gt;&lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;&amp;nbsp; &lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;&amp;nbsp; &lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;    &lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style="color: green" class="Apple-style-span"&gt;                    &lt;/span&gt;&lt;span style="color: blue"&gt;int &lt;/span&gt;isovalue = (&lt;span style="color: blue"&gt;int&lt;/span&gt;)(sampleVolume(x, y, z) * 255.0f);&lt;span style="color: green"&gt;&amp;nbsp; &lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;&amp;nbsp; &lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;&amp;nbsp; &lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;     &lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style="color: green" class="Apple-style-span"&gt;                    &lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;//accumulate the opacity from the transfer function&lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;&amp;nbsp; &lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;&amp;nbsp; &lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;&amp;nbsp; &lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;    &lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style="color: green" class="Apple-style-span"&gt;                    &lt;/span&gt;result += mTransferFunc[isovalue].W * 255.0f;&lt;span style="color: green" class="Apple-style-span"&gt;&lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;&lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;&lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;&amp;nbsp; &lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;&amp;nbsp; &lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;     &lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style="color: green" class="Apple-style-span"&gt;               &lt;/span&gt;}&lt;span style="color: green" class="Apple-style-span"&gt;&lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;&lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;&amp;nbsp; &lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;    &lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style="color: green" class="Apple-style-span"&gt;          &lt;/span&gt;}&lt;span style="color: green" class="Apple-style-span"&gt;&lt;/span&gt;&lt;span style="color: green" class="Apple-style-span"&gt;     &lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style="color: green" class="Apple-style-span"&gt;     &lt;/span&gt;}&lt;br /&gt;&lt;br /&gt;&lt;span style="color: blue"&gt;&lt;span style="color: green" class="Apple-style-span"&gt;     &lt;/span&gt;return &lt;/span&gt;result;&lt;br /&gt;}&lt;/p&gt;&lt;/pre&gt;&lt;p&gt;Depending on the transfer function (a lot of zero opacity samples), this method can increase our performance by 50%.&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;strong&gt;&lt;span style="color: #ff6633"&gt;&lt;/span&gt;&lt;/strong&gt;&lt;strong&gt;&lt;span style="color: #ff6633"&gt;Problems&lt;/span&gt;&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;&lt;span style="color: #ff6633"&gt;&lt;/span&gt;&lt;/strong&gt;Now, a problem that this method introduces is overdraw. You can see the effects of this when rotating the camera to view the back of the bear; here the frame rate drops considerably. To remedy this the sub-volumes need to be sorted front to back by their distance to the camera each time the view changes. I’ve left this as an exercise for the reader. The new demo implements empty space leaping and downscaling. And when rendering the teddy bear volume frame rates on my 8800GT are at about &lt;strong&gt;190 FPS&lt;/strong&gt;. Compare this to the the last demo from 102 at 30 FPS. All at a resolution of 800x600. Pretty good results! Next time I’ll be introducing soft shadows and translucent materials. &lt;/p&gt;&lt;p&gt;&lt;iframe style="border-bottom: rgb(221,229,233) 1px solid; border-left: rgb(221,229,233) 1px solid; padding-bottom: 0px; background-color: white; margin: 3px; padding-left: 0px; width: 240px; padding-right: 0px; height: 66px; border-top: rgb(221,229,233) 1px solid; border-right: rgb(221,229,233) 1px solid; padding-top: 0px" marginheight="0" src="http://cid-b80a3031b5bfa52b.skydrive.live.com/embedrowdetail.aspx/Public/VolumeRayCasting%7C_201.zip" frameborder="0" marginwidth="0" scrolling="no"&gt;&lt;/iframe&gt;&lt;/p&gt;&lt;p&gt;References: &lt;a href="http://www.amazon.com/Real-time-Graphics-Markus-Hadwiger/dp/1568812663"&gt;Real-time Volume Graphics&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;pre&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1023441640234597436-1765281081707323748?l=graphicsrunner.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://graphicsrunner.blogspot.com/feeds/1765281081707323748/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=1023441640234597436&amp;postID=1765281081707323748' title='15 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1023441640234597436/posts/default/1765281081707323748'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1023441640234597436/posts/default/1765281081707323748'/><link rel='alternate' type='text/html' href='http://graphicsrunner.blogspot.com/2009/02/volume-rendering-201-optimizations.html' title='Volume Rendering 201: Optimizations'/><author><name>Kyle Hayward</name><uri>http://www.blogger.com/profile/00654406875137720609</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://bp3.blogger.com/_VelpN_FHzhk/R-Lx4Kk_nkI/AAAAAAAAAN8/jn_4uDeKxNk/S220/me.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://lh4.ggpht.com/_VelpN_FHzhk/SYpgK2v-l8I/AAAAAAAAAnw/N_RB8CYPYOc/s72-c/memory_thumb3.jpg?imgmax=800' height='72' width='72'/><thr:total>15</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1023441640234597436.post-8243899355835401350</id><published>2009-01-20T18:59:00.003-05:00</published><updated>2009-01-20T21:11:13.255-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='XNA'/><category scheme='http://www.blogger.com/atom/ns#' term='Volume Rendering'/><category scheme='http://www.blogger.com/atom/ns#' term='Volume Ray-Casting'/><title type='text'>Volume Rendering 102: Transfer Functions</title><content type='html'>&lt;p&gt;Last time I introduced the concept of volume ray-casting. And to follow that up, today I'll talk about transfer functions and shading.&lt;/p&gt;&lt;p&gt;&lt;a href="http://lh5.ggpht.com/_VelpN_FHzhk/SXZagBYq_BI/AAAAAAAAAkg/RMvKkJ8QEcA/s1600-h/transfer%5B3%5D.png"&gt;&lt;img height="240" alt="transfer" src="http://lh5.ggpht.com/_VelpN_FHzhk/SXZag-HTa1I/AAAAAAAAAkk/eAe-du5iVPo/transfer_thumb%5B1%5D.png?imgmax=800" width="183" /&gt;&lt;/a&gt; &lt;a href="http://lh6.ggpht.com/_VelpN_FHzhk/SXZahSVyzpI/AAAAAAAAAko/gG6uFVtse-k/s1600-h/shade0%5B3%5D.png"&gt;&lt;img height="240" alt="shade0" src="http://lh6.ggpht.com/_VelpN_FHzhk/SXZah8mo_DI/AAAAAAAAAks/m17AiUh6P8c/shade0_thumb%5B1%5D.png?imgmax=800" width="206" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;a href="http://lh5.ggpht.com/_VelpN_FHzhk/SXZaiVlNKeI/AAAAAAAAAkw/efy8bk79cNU/s1600-h/male_noshadew0%5B13%5D.png"&gt;&lt;img height="195" alt="male_noshadew0" src="http://lh6.ggpht.com/_VelpN_FHzhk/SXZajDyzehI/AAAAAAAAAk0/5knApM-3FGc/male_noshadew0_thumb%5B9%5D.png?imgmax=800" width="197" /&gt;&lt;/a&gt; &lt;a href="http://lh5.ggpht.com/_VelpN_FHzhk/SXZajg6kEmI/AAAAAAAAAk4/JYDy_ASQloo/s1600-h/male_shadeb0%5B7%5D.png"&gt;&lt;img height="196" alt="male_shadeb0" src="http://lh3.ggpht.com/_VelpN_FHzhk/SXZakB6Yq7I/AAAAAAAAAk8/n29seExZnCE/male_shadeb0_thumb%5B5%5D.png?imgmax=800" width="205" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;Mummy (top) and Male (bottom) volumes colored with a transfer function (left) and shaded (right).&lt;/p&gt;&lt;p&gt;A transfer function is used to assign RGB and alpha values for every voxel in the volume. A 1D transfer function maps one RGBA value for every isovalue [0, 255]. Multi-dimensional transfer functions allow multiple RGBA values to be mapped to a single isovalue. These are however out of the scope of this tutorial, so I will just focus on 1D transfer functions.&lt;/p&gt;&lt;p&gt;The transfer function is used to "view" a certain part of the volume. As with the second set of pictures above, there is a skin layer/material and there is a skull layer/material. A transfer function could be designed to just look at the skin, the skull, or both (as is pictured). There are a few different ways of creating transfer functions. One is two manually define the transfer functions by specifying the RGBA values for the isovalues (what we will be doing), and another is through visual controls and widgets. Manually defining the transfer function is a lot like guess work and takes a bit of time, but is the easiest way to get your feet wet. Visually designing transfer functions is the easiest way to get good results quickly as it happens at run-time. But this method is quite complex to implement (at least for a tutorial).&lt;/p&gt;&lt;p&gt;&lt;strong&gt;&lt;span style="color:#ff8000;"&gt;Creating the transfer function:&lt;/span&gt;&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;To create a transfer function, we want to define the RGBA values for certain isovalues (control points or control knots) and then interpolate between these values to produce a smooth transition between layers/materials. Our transfer function will result in a 1D texture with a width of 256.&lt;/p&gt;&lt;p&gt;First we have the &lt;span style="color:#ff8000;"&gt;TransferControlPoint &lt;/span&gt;&lt;span style="color:#000000;"&gt;class. This class takes an RGB color or alpha value for a specific isovalue.&lt;/span&gt;&lt;/p&gt;&lt;pre class="mycode"&gt;&lt;span style="color:blue;"&gt;public class &lt;/span&gt;&lt;span style="color:#2b91af;"&gt;TransferControlPoint&lt;br /&gt;&lt;/span&gt;{&lt;br /&gt;    &lt;span style="color:blue;"&gt;public &lt;/span&gt;&lt;span style="color:#2b91af;"&gt;Vector4 &lt;/span&gt;Color;&lt;br /&gt;    &lt;span style="color:blue;"&gt;public int &lt;/span&gt;IsoValue;&lt;br /&gt;&lt;br /&gt;    &lt;span style="color:gray;"&gt;/// &amp;lt;summary&amp;gt;&lt;br /&gt;    /// &lt;/span&gt;&lt;span style="color:green;"&gt;Constructor for color control points.&lt;br /&gt;    &lt;/span&gt;&lt;span style="color:gray;"&gt;/// &lt;/span&gt;&lt;span style="color:green;"&gt;Takes rgb color components that specify the color at the supplied isovalue.&lt;br /&gt;    &lt;/span&gt;&lt;span style="color:gray;"&gt;/// &amp;lt;/summary&amp;gt;&lt;br /&gt;    /// &amp;lt;param name="x"&amp;gt;&amp;lt;/param&amp;gt;&lt;br /&gt;    /// &amp;lt;param name="y"&amp;gt;&amp;lt;/param&amp;gt;&lt;br /&gt;    /// &amp;lt;param name="z"&amp;gt;&amp;lt;/param&amp;gt;&lt;br /&gt;    /// &amp;lt;param name="isovalue"&amp;gt;&amp;lt;/param&amp;gt;&lt;br /&gt;    &lt;/span&gt;&lt;span style="color:blue;"&gt;public &lt;/span&gt;TransferControlPoint(&lt;span style="color:blue;"&gt;float &lt;/span&gt;r, &lt;span style="color:blue;"&gt;float &lt;/span&gt;g, &lt;span style="color:blue;"&gt;float &lt;/span&gt;b, &lt;span style="color:blue;"&gt;int &lt;/span&gt;isovalue)&lt;br /&gt;    {&lt;br /&gt;        Color.X = r;&lt;br /&gt;        Color.Y = g;&lt;br /&gt;        Color.Z = b;&lt;br /&gt;        Color.W = 1.0f;&lt;br /&gt;        IsoValue = isovalue;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    &lt;span style="color:gray;"&gt;/// &amp;lt;summary&amp;gt;&lt;br /&gt;    /// &lt;/span&gt;&lt;span style="color:green;"&gt;Constructor for alpha control points.&lt;br /&gt;    &lt;/span&gt;&lt;span style="color:gray;"&gt;/// &lt;/span&gt;&lt;span style="color:green;"&gt;Takes an alpha that specifies the aplpha at the supplied isovalue.&lt;br /&gt;    &lt;/span&gt;&lt;span style="color:gray;"&gt;/// &amp;lt;/summary&amp;gt;&lt;br /&gt;    /// &amp;lt;param name="alpha"&amp;gt;&amp;lt;/param&amp;gt;&lt;br /&gt;    /// &amp;lt;param name="isovalue"&amp;gt;&amp;lt;/param&amp;gt;&lt;br /&gt;    &lt;/span&gt;&lt;span style="color:blue;"&gt;public &lt;/span&gt;TransferControlPoint(&lt;span style="color:blue;"&gt;float &lt;/span&gt;alpha, &lt;span style="color:blue;"&gt;int &lt;/span&gt;isovalue)&lt;br /&gt;    {&lt;br /&gt;        Color.X = 0.0f;&lt;br /&gt;        Color.Y = 0.0f;&lt;br /&gt;        Color.Z = 0.0f;&lt;br /&gt;        Color.W = alpha;&lt;br /&gt;        IsoValue = isovalue;&lt;br /&gt;    }&lt;br /&gt;}&lt;/pre&gt;&lt;p&gt;This class will represent the control points that we will interpolate. I've added two lists to the &lt;span style="color:#ff8000;"&gt;Volume&lt;/span&gt; class, mAlphaKnots and mColorKnots. These will be the list of transfer control points that we will setup and interpolate to produce the transfer function. To produce the result for the Male dataset above, here are the transfer control points that we will define:&lt;/p&gt;&lt;pre class="mycode"&gt;mesh.ColorKnots = &lt;span style="color:blue;"&gt;new &lt;/span&gt;&lt;span style="color:#2b91af;"&gt;List&lt;/span&gt;&amp;lt;&lt;span style="color:#2b91af;"&gt;TransferControlPoint&lt;/span&gt;&amp;gt; {&lt;br /&gt;                        &lt;span style="color:blue;"&gt;new &lt;/span&gt;&lt;span style="color:#2b91af;"&gt;TransferControlPoint&lt;/span&gt;(.91f, .7f, .61f, 0),&lt;br /&gt;                        &lt;span style="color:blue;"&gt;new &lt;/span&gt;&lt;span style="color:#2b91af;"&gt;TransferControlPoint&lt;/span&gt;(.91f, .7f, .61f, 80),&lt;br /&gt;                        &lt;span style="color:blue;"&gt;new &lt;/span&gt;&lt;span style="color:#2b91af;"&gt;TransferControlPoint&lt;/span&gt;(1.0f, 1.0f, .85f, 82),&lt;br /&gt;                        &lt;span style="color:blue;"&gt;new &lt;/span&gt;&lt;span style="color:#2b91af;"&gt;TransferControlPoint&lt;/span&gt;(1.0f, 1.0f, .85f, 256)&lt;br /&gt;                        };&lt;br /&gt;&lt;br /&gt;mesh.AlphaKnots = &lt;span style="color:blue;"&gt;new &lt;/span&gt;&lt;span style="color:#2b91af;"&gt;List&lt;/span&gt;&amp;lt;&lt;span style="color:#2b91af;"&gt;TransferControlPoint&lt;/span&gt;&amp;gt; {&lt;br /&gt;                        &lt;span style="color:blue;"&gt;new &lt;/span&gt;&lt;span style="color:#2b91af;"&gt;TransferControlPoint&lt;/span&gt;(0.0f, 0),&lt;br /&gt;                        &lt;span style="color:blue;"&gt;new &lt;/span&gt;&lt;span style="color:#2b91af;"&gt;TransferControlPoint&lt;/span&gt;(0.0f, 40),&lt;br /&gt;                        &lt;span style="color:blue;"&gt;new &lt;/span&gt;&lt;span style="color:#2b91af;"&gt;TransferControlPoint&lt;/span&gt;(0.2f, 60),&lt;br /&gt;                        &lt;span style="color:blue;"&gt;new &lt;/span&gt;&lt;span style="color:#2b91af;"&gt;TransferControlPoint&lt;/span&gt;(0.05f, 63),&lt;br /&gt;                        &lt;span style="color:blue;"&gt;new &lt;/span&gt;&lt;span style="color:#2b91af;"&gt;TransferControlPoint&lt;/span&gt;(0.0f, 80),&lt;br /&gt;                        &lt;span style="color:blue;"&gt;new &lt;/span&gt;&lt;span style="color:#2b91af;"&gt;TransferControlPoint&lt;/span&gt;(0.9f, 82),&lt;br /&gt;                        &lt;span style="color:blue;"&gt;new &lt;/span&gt;&lt;span style="color:#2b91af;"&gt;TransferControlPoint&lt;/span&gt;(1f, 256)&lt;br /&gt;                        }; &lt;/pre&gt;&lt;p&gt;You need to specify &lt;strong&gt;at least two&lt;/strong&gt; control points at isovalues &lt;strong&gt;0&lt;/strong&gt; and &lt;strong&gt;256&lt;/strong&gt; for both alpha and color. Also the control points need to be ordered (low to high) by the isovalue. So the first entry in the list should always be the RGB/alpha value for the zero isovalue, and the last entry should always be the RGB/alpha value for the 256 isovalue. The above list of control points produce the following transfer function after interpolation:&lt;/p&gt;&lt;p&gt;&lt;a href="http://lh3.ggpht.com/_VelpN_FHzhk/SXZsiPEUEAI/AAAAAAAAAnc/h0TSYS9BeFA/s1600-h/transfer_func%5B5%5D.png"&gt;&lt;img title="transfer_func" style="DISPLAY: inline" height="26" alt="transfer_func" src="http://lh5.ggpht.com/_VelpN_FHzhk/SXZsiY1ZSMI/AAAAAAAAAng/I7YEq8iEQow/transfer_func_thumb%5B2%5D.png?imgmax=800" width="412" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;So we have defined a range of color for the skin and a longer range of color for the skull/bone. &lt;/p&gt;&lt;p&gt;But how do we interpolate between the control points? We will fit a cubic spline to the control points to produce a nice smooth interpolation between the knots. For a more in-depth discussion on cubic splines refer to my &lt;a href="http://graphicsrunner.blogspot.com/2008/05/camera-animation-part-ii.html" target="_blank"&gt;camera animation tutorial&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;Here is a simple graph representation of the spline that is fit to the control points.&lt;/p&gt;&lt;p&gt;&lt;a href="http://lh4.ggpht.com/_VelpN_FHzhk/SXZrre0cHsI/AAAAAAAAAlg/7l5GoK2rgc8/s1600-h/transfer_graph6.png"&gt;&lt;img height="385" alt="transfer_graph" src="http://lh5.ggpht.com/_VelpN_FHzhk/SXZrr3dnUfI/AAAAAAAAAlk/JGjckTy-qAg/transfer_graph_thumb4.png?imgmax=800" width="425" /&gt;&lt;/a&gt; &lt;/p&gt;&lt;p&gt;&lt;strong&gt;&lt;span style="color:#ff8000;"&gt;Using the transfer function:&lt;/span&gt;&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;&lt;span style="color:#000000;"&gt;So how do we put this transfer function to use? First we set it to a 1D texture and upload it to the graphics card. Then in the shader, we simply take the isovalue sampled from the 3d volume texture and use that to index the transfer function texture.&lt;/span&gt;&lt;br /&gt;&lt;/p&gt;&lt;pre class="code"&gt;value = &lt;span style="color:blue;"&gt;tex3Dlod&lt;/span&gt;(VolumeS, pos);     &lt;br /&gt;&lt;br /&gt;src = &lt;span style="color:blue;"&gt;tex1Dlod&lt;/span&gt;(TransferS, value.a);&lt;/pre&gt;&lt;p&gt;Now we have the color and opacity of the current sample. Next, we have to shade it. While this is simply diffuse shading I will go over how to calculate gradients (aka normals) for the 3D volume.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;&lt;span style="color:#ff8000;"&gt;Calculating Gradients:&lt;/span&gt;&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;The method we will use to calculate the gradients is the central differences scheme. This takes the last and next samples of the current sample to calculate the gradient/normal. This can be performed at run-time in the shader, but as it requires 6 extra texture fetches from the 3D texture, it is quite slow. So we will calculate the gradients and place them in the RGB components of our volume texture and move the isovalue to the alpha channel. This way we only need one volume texture for the data set instead of two: one for the gradients and one for the isovalues.&lt;/p&gt;&lt;p&gt;Calculating the gradients is pretty simple. We just loop through all the samples and find the difference between the next and previous sample to calculate the gradients:&lt;/p&gt;&lt;pre class="mycode"&gt;&lt;span style="color:gray;"&gt;/// &amp;lt;summary&amp;gt;&lt;br /&gt;/// &lt;/span&gt;&lt;span style="color:green;"&gt;Generates gradients using a central differences scheme.&lt;br /&gt;&lt;/span&gt;&lt;span style="color:gray;"&gt;/// &amp;lt;/summary&amp;gt;&lt;br /&gt;/// &amp;lt;param name="sampleSize"&amp;gt;&lt;/span&gt;&lt;span style="color:green;"&gt;The size/radius of the sample to take.&lt;/span&gt;&lt;span style="color:gray;"&gt;&amp;lt;/param&amp;gt;&lt;br /&gt;&lt;/span&gt;&lt;span style="color:blue;"&gt;private void &lt;/span&gt;generateGradients(&lt;span style="color:blue;"&gt;int &lt;/span&gt;sampleSize)&lt;br /&gt;{&lt;br /&gt;    &lt;span style="color:blue;"&gt;int &lt;/span&gt;n = sampleSize;&lt;br /&gt;    &lt;span style="color:#2b91af;"&gt;Vector3 &lt;/span&gt;normal = &lt;span style="color:#2b91af;"&gt;Vector3&lt;/span&gt;.Zero;&lt;br /&gt;    &lt;span style="color:#2b91af;"&gt;Vector3 &lt;/span&gt;s1, s2;&lt;br /&gt;&lt;br /&gt;    &lt;span style="color:blue;"&gt;int &lt;/span&gt;index = 0;&lt;br /&gt;    &lt;span style="color:blue;"&gt;for &lt;/span&gt;(&lt;span style="color:blue;"&gt;int &lt;/span&gt;z = 0; z &amp;lt; mDepth; z++)&lt;br /&gt;    {&lt;br /&gt;        &lt;span style="color:blue;"&gt;for &lt;/span&gt;(&lt;span style="color:blue;"&gt;int &lt;/span&gt;y = 0; y &amp;lt; mHeight; y++)&lt;br /&gt;        {&lt;br /&gt;            &lt;span style="color:blue;"&gt;for &lt;/span&gt;(&lt;span style="color:blue;"&gt;int &lt;/span&gt;x = 0; x &amp;lt; mWidth; x++)&lt;br /&gt;            {&lt;br /&gt;                s1.X = sampleVolume(x - n, y, z);&lt;br /&gt;                s2.X = sampleVolume(x + n, y, z);&lt;br /&gt;                s1.Y = sampleVolume(x, y - n, z);&lt;br /&gt;                s2.Y = sampleVolume(x, y + n, z);&lt;br /&gt;                s1.Z = sampleVolume(x, y, z - n);&lt;br /&gt;                s2.Z = sampleVolume(x, y, z + n);&lt;br /&gt;&lt;br /&gt;                mGradients[index++] = &lt;span style="color:#2b91af;"&gt;Vector3&lt;/span&gt;.Normalize(s2 - s1);&lt;br /&gt;                &lt;span style="color:blue;"&gt;if &lt;/span&gt;(&lt;span style="color:blue;"&gt;float&lt;/span&gt;.IsNaN(mGradients[index - 1].X))&lt;br /&gt;                    mGradients[index - 1] = &lt;span style="color:#2b91af;"&gt;Vector3&lt;/span&gt;.Zero;&lt;br /&gt;            }&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt;}&lt;/pre&gt;&lt;p&gt;Next we will filter the gradients to smooth them out and prevent any high irregularities. We achieve this by a simple NxNxN cube filter. A cube filter simply averages the surrounding N^3 - 1 samples. Here's the code for the gradient filtering:&lt;/p&gt;&lt;pre class="mycode"&gt;&lt;span style="color:gray;"&gt;/// &amp;lt;summary&amp;gt;&lt;br /&gt;/// &lt;/span&gt;&lt;span style="color:green;"&gt;Applies an NxNxN filter to the gradients.&lt;br /&gt;&lt;/span&gt;&lt;span style="color:gray;"&gt;/// &lt;/span&gt;&lt;span style="color:green;"&gt;Should be an odd number of samples. 3 used by default.&lt;br /&gt;&lt;/span&gt;&lt;span style="color:gray;"&gt;/// &amp;lt;/summary&amp;gt;&lt;br /&gt;/// &amp;lt;param name="n"&amp;gt;&amp;lt;/param&amp;gt;&lt;br /&gt;&lt;/span&gt;&lt;span style="color:blue;"&gt;private void &lt;/span&gt;filterNxNxN(&lt;span style="color:blue;"&gt;int &lt;/span&gt;n)&lt;br /&gt;{&lt;br /&gt;    &lt;span style="color:blue;"&gt;int &lt;/span&gt;index = 0;&lt;br /&gt;    &lt;span style="color:blue;"&gt;for &lt;/span&gt;(&lt;span style="color:blue;"&gt;int &lt;/span&gt;z = 0; z &amp;lt; mDepth; z++)&lt;br /&gt;    {&lt;br /&gt;        &lt;span style="color:blue;"&gt;for &lt;/span&gt;(&lt;span style="color:blue;"&gt;int &lt;/span&gt;y = 0; y &amp;lt; mHeight; y++)&lt;br /&gt;        {&lt;br /&gt;            &lt;span style="color:blue;"&gt;for &lt;/span&gt;(&lt;span style="color:blue;"&gt;int &lt;/span&gt;x = 0; x &amp;lt; mWidth; x++)&lt;br /&gt;            {&lt;br /&gt;                mGradients[index++] = sampleNxNxN(x, y, z, n);&lt;br /&gt;            }&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;span style="color:gray;"&gt;/// &amp;lt;summary&amp;gt;&lt;br /&gt;/// &lt;/span&gt;&lt;span style="color:green;"&gt;Samples the sub-volume graident volume and returns the average.&lt;br /&gt;&lt;/span&gt;&lt;span style="color:gray;"&gt;/// &lt;/span&gt;&lt;span style="color:green;"&gt;Should be an odd number of samples.&lt;br /&gt;&lt;/span&gt;&lt;span style="color:gray;"&gt;/// &amp;lt;/summary&amp;gt;&lt;br /&gt;/// &amp;lt;param name="x"&amp;gt;&amp;lt;/param&amp;gt;&lt;br /&gt;/// &amp;lt;param name="y"&amp;gt;&amp;lt;/param&amp;gt;&lt;br /&gt;/// &amp;lt;param name="z"&amp;gt;&amp;lt;/param&amp;gt;&lt;br /&gt;/// &amp;lt;param name="n"&amp;gt;&amp;lt;/param&amp;gt;&lt;br /&gt;/// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt;&lt;br /&gt;&lt;/span&gt;&lt;span style="color:blue;"&gt;private &lt;/span&gt;&lt;span style="color:#2b91af;"&gt;Vector3 &lt;/span&gt;sampleNxNxN(&lt;span style="color:blue;"&gt;int &lt;/span&gt;x, &lt;span style="color:blue;"&gt;int &lt;/span&gt;y, &lt;span style="color:blue;"&gt;int &lt;/span&gt;z, &lt;span style="color:blue;"&gt;int &lt;/span&gt;n)&lt;br /&gt;{&lt;br /&gt;    n = (n - 1) / 2;&lt;br /&gt;&lt;br /&gt;    &lt;span style="color:#2b91af;"&gt;Vector3 &lt;/span&gt;average = &lt;span style="color:#2b91af;"&gt;Vector3&lt;/span&gt;.Zero;&lt;br /&gt;    &lt;span style="color:blue;"&gt;int &lt;/span&gt;num = 0;&lt;br /&gt;&lt;br /&gt;    &lt;span style="color:blue;"&gt;for &lt;/span&gt;(&lt;span style="color:blue;"&gt;int &lt;/span&gt;k = z - n; k &amp;lt;= z + n; k++)&lt;br /&gt;    {&lt;br /&gt;        &lt;span style="color:blue;"&gt;for &lt;/span&gt;(&lt;span style="color:blue;"&gt;int &lt;/span&gt;j = y - n; j &amp;lt;= y + n; j++)&lt;br /&gt;        {&lt;br /&gt;            &lt;span style="color:blue;"&gt;for &lt;/span&gt;(&lt;span style="color:blue;"&gt;int &lt;/span&gt;i = x - n; i &amp;lt;= x + n; i++)&lt;br /&gt;            {&lt;br /&gt;                &lt;span style="color:blue;"&gt;if &lt;/span&gt;(isInBounds(i, j, k))&lt;br /&gt;                {&lt;br /&gt;                    average += sampleGradients(i, j, k);&lt;br /&gt;                    num++;&lt;br /&gt;                }&lt;br /&gt;            }&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    average /= (&lt;span style="color:blue;"&gt;float&lt;/span&gt;)num;&lt;br /&gt;    &lt;span style="color:blue;"&gt;if &lt;/span&gt;(average.X != 0.0f &amp;amp;&amp;amp; average.Y != 0.0f &amp;amp;&amp;amp; average.Z != 0.0f)&lt;br /&gt;        average.Normalize();&lt;br /&gt;&lt;br /&gt;    &lt;span style="color:blue;"&gt;return &lt;/span&gt;average;&lt;br /&gt;}&lt;/pre&gt;&lt;p&gt;This is a really simple and slow way of filtering the gradients. A better way is to use a seperable 3D Gaussian kernal to filter the gradients.&lt;/p&gt;&lt;p&gt;Now that we have the gradients we just fill the xyz components of the 3D volume texture and put the isovalue in the alpha channel:&lt;/p&gt;&lt;pre class="mycode"&gt;&lt;span style="color:green;"&gt;//transform the data to HalfVector4&lt;br /&gt;&lt;/span&gt;&lt;span style="color:#2b91af;"&gt;HalfVector4&lt;/span&gt;[] gradients = &lt;span style="color:blue;"&gt;new &lt;/span&gt;&lt;span style="color:#2b91af;"&gt;HalfVector4&lt;/span&gt;[mGradients.Length];&lt;br /&gt;&lt;span style="color:blue;"&gt;for &lt;/span&gt;(&lt;span style="color:blue;"&gt;int &lt;/span&gt;i = 0; i &amp;lt; mGradients.Length; i++)&lt;br /&gt;{&lt;br /&gt;    gradients[i] = &lt;span style="color:blue;"&gt;new &lt;/span&gt;&lt;span style="color:#2b91af;"&gt;HalfVector4&lt;/span&gt;(mGradients[i].X, mGradients[i].Y, mGradients[i].Z, mScalars[i].ToVector4().W);&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;mVolume.SetData&amp;lt;&lt;span style="color:#2b91af;"&gt;HalfVector4&lt;/span&gt;&amp;gt;(gradients);&lt;br /&gt;mEffect.Parameters[&lt;span style="color:#a31515;"&gt;"Volume"&lt;/span&gt;].SetValue(mVolume);&lt;/pre&gt;&lt;p&gt;And that's pretty much it. Creating a good transfer function can take a little time, but this simple 1D transfer function can still produce pretty good results. Here's a few captures of what this demo can produce:&lt;/p&gt;&lt;p&gt;A teapot with just transfer function:&lt;/p&gt;&lt;p&gt;&lt;a href="http://lh6.ggpht.com/_VelpN_FHzhk/SXZrseceCRI/AAAAAAAAAlo/5u4Z0dAp3WQ/s1600-h/teapot_noshade15.png"&gt;&lt;img height="317" alt="teapot_noshade1" src="http://lh3.ggpht.com/_VelpN_FHzhk/SXZrsi5SV6I/AAAAAAAAAls/0kPF1Ka_xqE/teapot_noshade1_thumb3.png?imgmax=800" width="412" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;The teapot with transfer function and shaded:&lt;/p&gt;&lt;p&gt;&lt;a href="http://lh5.ggpht.com/_VelpN_FHzhk/SXZrtWTFCoI/AAAAAAAAAlw/UkEpnaZD-W0/s1600-h/teapot_shade26.png"&gt;&lt;img height="309" alt="teapot_shade2" src="http://lh3.ggpht.com/_VelpN_FHzhk/SXZrt6Xt3BI/AAAAAAAAAl0/rif0QCdZtOQ/teapot_shade2_thumb4.png?imgmax=800" width="412" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;a href="http://lh5.ggpht.com/_VelpN_FHzhk/SXZruaf5spI/AAAAAAAAAl4/0hdlXU_tHrk/s1600-h/teapot_shade84.png"&gt;&lt;img height="309" alt="teapot_shade8" src="http://lh3.ggpht.com/_VelpN_FHzhk/SXZru3QRCII/AAAAAAAAAl8/Iq09zxrQVIk/teapot_shade8_thumb2.png?imgmax=800" width="412" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;a href="http://lh3.ggpht.com/_VelpN_FHzhk/SXZrvUiPDsI/AAAAAAAAAmA/eIC-mFUxQLY/s1600-h/teapot_shade94.png"&gt;&lt;img height="309" alt="teapot_shade9" src="http://lh4.ggpht.com/_VelpN_FHzhk/SXZrv_lyMmI/AAAAAAAAAmE/M5LH1DHS0HA/teapot_shade9_thumb2.png?imgmax=800" width="412" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;Now a CT scan of a male head. Just transfer function:&lt;/p&gt;&lt;p&gt;&lt;a href="http://lh3.ggpht.com/_VelpN_FHzhk/SXZrwU0P3KI/AAAAAAAAAmI/m1sxdfxtVak/s1600-h/male_noshadeb017.png"&gt;&lt;img title="male_noshadeb0" style="DISPLAY: inline" height="390" alt="male_noshadeb0" src="http://lh4.ggpht.com/_VelpN_FHzhk/SXZrxJblBAI/AAAAAAAAAmM/UpTly79j7mo/male_noshadeb0_thumb14.png?imgmax=800" width="412" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;Now with shading:&lt;/p&gt;&lt;p&gt;&lt;a href="http://lh5.ggpht.com/_VelpN_FHzhk/SXZrx49vfGI/AAAAAAAAAmQ/olJtWPd_H7o/s1600-h/male_shadeb26.png"&gt;&lt;img title="male_shadeb2" style="DISPLAY: inline" height="405" alt="male_shadeb2" src="http://lh4.ggpht.com/_VelpN_FHzhk/SXZrytizi6I/AAAAAAAAAmU/REzj4HAcitI/male_shadeb2_thumb3.png?imgmax=800" width="412" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;a href="http://lh4.ggpht.com/_VelpN_FHzhk/SXZrzEUlFnI/AAAAAAAAAmY/9eJGc_Q53aU/s1600-h/male_shadeb014.png"&gt;&lt;img height="386" alt="male_shadeb0" src="http://lh5.ggpht.com/_VelpN_FHzhk/SXZrztMEcxI/AAAAAAAAAmc/nM0wBClo94Q/male_shadeb0_thumb10.png?imgmax=800" width="412" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;a href="http://lh3.ggpht.com/_VelpN_FHzhk/SXZr0Q1DIxI/AAAAAAAAAmg/Yx9VgFs8938/s1600-h/male_shadeb15.png"&gt;&lt;img title="male_shadeb1" style="DISPLAY: inline" height="490" alt="male_shadeb1" src="http://lh3.ggpht.com/_VelpN_FHzhk/SXZr1NiYJMI/AAAAAAAAAmk/AKc7vKBRBTQ/male_shadeb1_thumb2.png?imgmax=800" width="412" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;a href="http://lh5.ggpht.com/_VelpN_FHzhk/SXZr1kSoW0I/AAAAAAAAAms/z5xMv3zJRT0/s1600-h/male_shadew05.png"&gt;&lt;img title="male_shadew0" style="DISPLAY: inline" height="407" alt="male_shadew0" src="http://lh5.ggpht.com/_VelpN_FHzhk/SXZr2RRxgjI/AAAAAAAAAmw/8nnHLMY7fj0/male_shadew0_thumb2.png?imgmax=800" width="412" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;a href="http://lh6.ggpht.com/_VelpN_FHzhk/SXZr26D8LlI/AAAAAAAAAm0/NbGEkM9hSQ8/s1600-h/male_shadew25.png"&gt;&lt;img title="male_shadew2" style="DISPLAY: inline" height="403" alt="male_shadew2" src="http://lh3.ggpht.com/_VelpN_FHzhk/SXZr3qu2YoI/AAAAAAAAAm4/-w1z9IHVJQk/male_shadew2_thumb2.png?imgmax=800" width="412" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;a href="http://lh6.ggpht.com/_VelpN_FHzhk/SXZr4T8GpJI/AAAAAAAAAm8/syofI7KSwpo/s1600-h/male_shadew15.png"&gt;&lt;img title="male_shadew1" style="DISPLAY: inline" height="460" alt="male_shadew1" src="http://lh3.ggpht.com/_VelpN_FHzhk/SXZr5J-QAiI/AAAAAAAAAnA/P6zvqnI4rlU/male_shadew1_thumb2.png?imgmax=800" width="412" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;a href="http://lh4.ggpht.com/_VelpN_FHzhk/SXZr5han6XI/AAAAAAAAAnE/hnTsHxZOw8o/s1600-h/male_shadew35.png"&gt;&lt;img title="male_shadew3" style="DISPLAY: inline" height="469" alt="male_shadew3" src="http://lh5.ggpht.com/_VelpN_FHzhk/SXZr6X1dwdI/AAAAAAAAAnI/r8oa1cmndeE/male_shadew3_thumb2.png?imgmax=800" width="412" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;And a mummy:&lt;/p&gt;&lt;p&gt;&lt;a href="http://lh3.ggpht.com/_VelpN_FHzhk/SXZr6zzdzAI/AAAAAAAAAnM/o4wo8TIOxgI/s1600-h/shade09.png"&gt;&lt;img height="480" alt="shade0" src="http://lh5.ggpht.com/_VelpN_FHzhk/SXZr7qr0BfI/AAAAAAAAAnQ/S13W2o-HUAE/shade0_thumb5.png?imgmax=800" width="413" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;a href="http://lh4.ggpht.com/_VelpN_FHzhk/SXZr8QFcrxI/AAAAAAAAAnU/-L_-DSSaDlY/s1600-h/shade14.png"&gt;&lt;img height="423" alt="shade1" src="http://lh5.ggpht.com/_VelpN_FHzhk/SXZr9HaOlTI/AAAAAAAAAnY/qOIwkyM1nFs/shade1_thumb2.png?imgmax=800" width="412" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;*The CT Male data set was converted from PVM format to RAW format with the V3 library. The original data set can be found here: &lt;a href="http://www9.informatik.uni-erlangen.de/External/vollib/"&gt;http://www9.informatik.uni-erlangen.de/External/vollib/&lt;/a&gt;&lt;/p&gt;&lt;iframe style="BORDER-RIGHT: #dde5e9 1px solid; PADDING-RIGHT: 0px; BORDER-TOP: #dde5e9 1px solid; PADDING-LEFT: 0px; PADDING-BOTTOM: 0px; MARGIN: 3px; BORDER-LEFT: #dde5e9 1px solid; WIDTH: 240px; PADDING-TOP: 0px; BORDER-BOTTOM: #dde5e9 1px solid; HEIGHT: 66px; BACKGROUND-COLOR: #ffffff" marginwidth="0" marginheight="0" src="http://cid-b80a3031b5bfa52b.skydrive.live.com/embedrowdetail.aspx/Public/VolumeRayCasting%7C_102.zip" frameborder="0" scrolling="no"&gt;&lt;/iframe&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1023441640234597436-8243899355835401350?l=graphicsrunner.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://graphicsrunner.blogspot.com/feeds/8243899355835401350/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=1023441640234597436&amp;postID=8243899355835401350' title='24 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1023441640234597436/posts/default/8243899355835401350'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1023441640234597436/posts/default/8243899355835401350'/><link rel='alternate' type='text/html' href='http://graphicsrunner.blogspot.com/2009/01/volume-rendering-102-transfer-functions.html' title='Volume Rendering 102: Transfer Functions'/><author><name>Kyle Hayward</name><uri>http://www.blogger.com/profile/00654406875137720609</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://bp3.blogger.com/_VelpN_FHzhk/R-Lx4Kk_nkI/AAAAAAAAAN8/jn_4uDeKxNk/S220/me.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://lh5.ggpht.com/_VelpN_FHzhk/SXZag-HTa1I/AAAAAAAAAkk/eAe-du5iVPo/s72-c/transfer_thumb%5B1%5D.png?imgmax=800' height='72' width='72'/><thr:total>24</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1023441640234597436.post-2905649749553319332</id><published>2009-01-15T22:58:00.004-05:00</published><updated>2009-01-16T00:30:02.556-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='XNA'/><category scheme='http://www.blogger.com/atom/ns#' term='Volume Rendering'/><category scheme='http://www.blogger.com/atom/ns#' term='Volume Ray-Casting'/><title type='text'>Volume Rendering 101</title><content type='html'>&lt;p&gt;&lt;a href="http://lh3.ggpht.com/_VelpN_FHzhk/SXAFmHnKRzI/AAAAAAAAAiU/P7Ik9j4Ck4A/s1600-h/fish%5B3%5D.png"&gt;&lt;img height="127" alt="fish" src="http://lh6.ggpht.com/_VelpN_FHzhk/SXAFmlB77RI/AAAAAAAAAiY/7eof43oZoZg/fish_thumb%5B1%5D.png?imgmax=800" width="240" /&gt;&lt;/a&gt; &lt;a href="http://lh6.ggpht.com/_VelpN_FHzhk/SXAFnUQsX-I/AAAAAAAAAic/X693GOmb5r4/s1600-h/bunny%5B7%5D.png"&gt;&lt;img height="160" alt="bunny" src="http://lh6.ggpht.com/_VelpN_FHzhk/SXAFn6Cm7PI/AAAAAAAAAig/pgxQoRn-ebE/bunny_thumb%5B5%5D.png?imgmax=800" width="154" /&gt;&lt;/a&gt; &lt;/p&gt;&lt;p&gt;Pictures above from: &lt;a href="http://www.cs.utah.edu/~jmk/simian/" target="_blank"&gt;http://www.cs.utah.edu/~jmk/simian/&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;a href="http://lh3.ggpht.com/_VelpN_FHzhk/SXAFobapBhI/AAAAAAAAAik/mKDxj1HktT0/s1600-h/translucent_l%5B5%5D.png"&gt;&lt;img height="226" alt="translucent_l" src="http://lh6.ggpht.com/_VelpN_FHzhk/SXAFpMARNaI/AAAAAAAAAio/IM3emUuVMqY/translucent_l_thumb%5B3%5D.png?imgmax=800" width="199" /&gt;&lt;/a&gt; &lt;a href="http://lh6.ggpht.com/_VelpN_FHzhk/SXAFpwLqHQI/AAAAAAAAAis/qL4za3lHygw/s1600-h/translucent_d%5B7%5D.png"&gt;&lt;img height="227" alt="translucent_d" src="http://lh6.ggpht.com/_VelpN_FHzhk/SXAFq87i0_I/AAAAAAAAAiw/54sx9164Xow/translucent_d_thumb%5B5%5D.png?imgmax=800" width="199" /&gt;&lt;/a&gt; &lt;/p&gt;&lt;p&gt;There is quite a bit of documentation and papers on volume rendering. But there aren't many good tutorials on the subject (that I have seen). So this tutorial will try to teach the basics of volume rendering, more specifically volume ray-casting (or volume ray marching).&lt;/p&gt;&lt;p&gt;What is volume ray-casting you ask? You didn't? Oh, well I'll tell you anyway. Volume rendering is a method for directly displaying a 3D scalar field without first fitting an intermediate representation to the data, such as triangles. How do we render a volume without geometry? There are two traditional ways of rendering a volume: slice-based rendering and volume ray-casting. This tutorial will be focusing on volume ray-casting. There are many advantages over slice-based rendering that ray-casting provides; such as empty space skipping, projection independence, simple to implement, and single pass.&lt;/p&gt;&lt;p&gt;Volume ray-casting (also called ray marching) is exactly how it sounds. &lt;span style="color:#ff6600;"&gt;[edit: volume ray-casting is not the same as ray-casting ala Doom or Nick's tutorials]&lt;/span&gt; Rays are cast through the volume and is sample along equally spaced intervals. As the ray is marched through the volume scalar values are mapped to optical properties through the&lt;br /&gt;use of a transfer function which results in an RGBA color value that includes the corresponding emission and absorption coefficients for the current sample point. This color is then composited by using front-to-back or back-to-front alpha blending.&lt;/p&gt;&lt;p&gt;This tutorial will focus specifically on how to intersect a ray with the volume and march it through the volume. In another tutorial I will focus on transfer functions and shading.&lt;/p&gt;&lt;p&gt;First we need to know how to read in the data. The data is simply scalar values (usually integers or floats) stored as slices [x, y, z], where x = width, y = height, and z = depth. Each slice is x units wide and y units high, and the total number of slices is equal to z. A common format for the data is to be stored in 8-bit or 16-bit RAW format. Once we have the data, we need to load it into a volume texture. Here's how we do the whole process:&lt;/p&gt;&lt;pre class="mycode"&gt;&lt;span style="color:green;"&gt;//create the scalar volume texture&lt;br /&gt;&lt;/span&gt;mVolume = &lt;span style="color:blue;"&gt;new &lt;/span&gt;Texture3D(&lt;span style="color:#2b91af;"&gt;Game&lt;/span&gt;.GraphicsDevice, mWidth, mHeight, mDepth, 0,&lt;br /&gt;                        &lt;span style="color:#2b91af;"&gt;TextureUsage&lt;/span&gt;.Linear, &lt;span style="color:#2b91af;"&gt;SurfaceFormat&lt;/span&gt;.Single);&lt;br /&gt;&lt;br /&gt;&lt;span style="color:blue;"&gt;private void &lt;/span&gt;loadRAWFile8(&lt;span style="color:#2b91af;"&gt;FileStream &lt;/span&gt;file)&lt;br /&gt;{&lt;br /&gt;    &lt;span style="color:#2b91af;"&gt;BinaryReader &lt;/span&gt;reader = &lt;span style="color:blue;"&gt;new &lt;/span&gt;&lt;span style="color:#2b91af;"&gt;BinaryReader&lt;/span&gt;(file);&lt;br /&gt;&lt;br /&gt;    &lt;span style="color:blue;"&gt;byte&lt;/span&gt;[] buffer = &lt;span style="color:blue;"&gt;new byte&lt;/span&gt;[mWidth * mHeight * mDepth];&lt;br /&gt;    &lt;span style="color:blue;"&gt;int &lt;/span&gt;size = &lt;span style="color:blue;"&gt;sizeof&lt;/span&gt;(&lt;span style="color:blue;"&gt;byte&lt;/span&gt;);&lt;br /&gt;&lt;br /&gt;    reader.Read(buffer, 0, size * buffer.Length);&lt;br /&gt;&lt;br /&gt;    reader.Close();&lt;br /&gt;&lt;br /&gt;    &lt;span style="color:green;"&gt;//scale the scalar values to [0, 1]&lt;br /&gt;    &lt;/span&gt;mScalars = &lt;span style="color:blue;"&gt;new float&lt;/span&gt;[buffer.Length];&lt;br /&gt;    &lt;span style="color:blue;"&gt;for &lt;/span&gt;(&lt;span style="color:blue;"&gt;int &lt;/span&gt;i = 0; i &amp;lt; buffer.Length; i++)&lt;br /&gt;    {&lt;br /&gt;        mScalars[i] = (&lt;span style="color:blue;"&gt;float&lt;/span&gt;)buffer[i] / &lt;span style="color:blue;"&gt;byte&lt;/span&gt;.MaxValue;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    mVolume.SetData(mScalars);&lt;br /&gt;    mEffect.Parameters[&lt;span style="color:#a31515;"&gt;"Volume"&lt;/span&gt;].SetValue(mVolume);&lt;br /&gt;}&lt;/pre&gt;&lt;p&gt;In order to render this texture we fit a bounding box or cube, that is from [0,0,0] to [1,1,1] to the volume. And we render the cube and sample the volume texture to render the volume. But we also need a way to find the ray that starts at the eye/camera and intersects the cube.&lt;/p&gt;&lt;p&gt;&lt;a href="http://lh5.ggpht.com/_VelpN_FHzhk/SXAFrVovOKI/AAAAAAAAAi0/o3-i2xcW600/s1600-h/ray%5B7%5D.png"&gt;&lt;img height="240" alt="ray" src="http://lh6.ggpht.com/_VelpN_FHzhk/SXAFr8CWl7I/AAAAAAAAAi4/zofgP7y38uw/ray_thumb%5B5%5D.png?imgmax=800" width="216" /&gt;&lt;/a&gt; &lt;/p&gt;&lt;p&gt;We could always calculate the intersection of the ray from the eye to the current pixel position with the cube by performing a ray-cube intersection in the shader. But a better and faster way to do this is to render the positions of the front and back facing triangles of the cube to textures. This easily gives us the starting and end positions of the ray, and in the shader we simply sample the textures to find the sampling ray.&lt;/p&gt;&lt;p&gt;Here's what the textures look like (Front, Back, Ray Direction):&lt;/p&gt;&lt;p&gt;&lt;a href="http://lh5.ggpht.com/_VelpN_FHzhk/SXAFsZLLoaI/AAAAAAAAAi8/bm94whKW3ao/s1600-h/front%5B4%5D.png"&gt;&lt;img height="134" alt="Front" src="http://lh6.ggpht.com/_VelpN_FHzhk/SXAFtWBYDTI/AAAAAAAAAjA/4zfoVSI7xNc/front_thumb%5B2%5D.png?imgmax=800" width="120" /&gt;&lt;/a&gt; &lt;a href="http://lh5.ggpht.com/_VelpN_FHzhk/SXAFtqE6cwI/AAAAAAAAAjE/A4IdP18U12s/s1600-h/back%5B5%5D.png"&gt;&lt;img height="134" alt="Back" src="http://lh6.ggpht.com/_VelpN_FHzhk/SXAFuXWwYJI/AAAAAAAAAjI/Z_mMPSBEDsc/back_thumb%5B3%5D.png?imgmax=800" width="120" /&gt;&lt;/a&gt; &lt;a href="http://lh4.ggpht.com/_VelpN_FHzhk/SXAFu3rU3jI/AAAAAAAAAjM/7eUJWeMjL0A/s1600-h/direction%5B4%5D.png"&gt;&lt;img height="135" alt="Ray Direction" src="http://lh3.ggpht.com/_VelpN_FHzhk/SXAFvfDBmsI/AAAAAAAAAjQ/0VFVioWO87I/direction_thumb%5B2%5D.png?imgmax=800" width="120" /&gt;&lt;/a&gt; &lt;/p&gt;&lt;p&gt;And here's the code to render the front and back positions:&lt;/p&gt;&lt;pre class="mycode"&gt;&lt;span style="color:green;"&gt;//draw front faces&lt;br /&gt;//draw the pixel positions to the texture&lt;br /&gt;&lt;/span&gt;Game.GraphicsDevice.SetRenderTarget(0, mFront);&lt;br /&gt;Game.GraphicsDevice.Clear(&lt;span style="color:#2b91af;"&gt;Color&lt;/span&gt;.Black);&lt;br /&gt;&lt;br /&gt;&lt;span style="color:blue;"&gt;base&lt;/span&gt;.DrawCustomEffect();&lt;br /&gt;&lt;br /&gt;Game.GraphicsDevice.SetRenderTarget(0, &lt;span style="color:blue;"&gt;null&lt;/span&gt;);&lt;br /&gt;&lt;span style="color:green;"&gt;//draw back faces&lt;br /&gt;//draw the pixel positions to the texture&lt;br /&gt;&lt;/span&gt;Game.GraphicsDevice.SetRenderTarget(0, mBack);&lt;br /&gt;Game.GraphicsDevice.Clear(&lt;span style="color:#2b91af;"&gt;Color&lt;/span&gt;.Black);&lt;br /&gt;Game.GraphicsDevice.RenderState.CullMode = &lt;span style="color:#2b91af;"&gt;CullMode&lt;/span&gt;.CullCounterClockwiseFace;&lt;br /&gt;&lt;br /&gt;&lt;span style="color:blue;"&gt;base&lt;/span&gt;.DrawCustomEffect();&lt;br /&gt;&lt;br /&gt;Game.GraphicsDevice.SetRenderTarget(0, &lt;span style="color:blue;"&gt;null&lt;/span&gt;);&lt;br /&gt;Game.GraphicsDevice.RenderState.CullMode = &lt;span style="color:#2b91af;"&gt;CullMode&lt;/span&gt;.CullClockwiseFace;&lt;/pre&gt;&lt;p&gt;Now, to perform the actual ray-casting of the volume, we render the front faces of the cube. In the shader we sample the front and back position textures to find the direction (back - front) and starting position (front) of the ray that will sample the volume. The volume is then iteratively sampled by advancing the current sampling position along the ray at equidistant steps. And we use front-to-back compositing to accumulate the pixel color.&lt;/p&gt;&lt;pre class="mycode"&gt;&lt;span style="color:blue;"&gt;float4 &lt;/span&gt;RayCastSimplePS(VertexShaderOutput input) : &lt;span style="color:navy;"&gt;COLOR0&lt;br /&gt;&lt;/span&gt;{&lt;br /&gt;    &lt;span style="color:green;"&gt;//calculate projective texture coordinates&lt;br /&gt;    //used to project the front and back position textures onto the cube&lt;br /&gt;    &lt;/span&gt;&lt;span style="color:blue;"&gt;float2 &lt;/span&gt;texC = input.pos.xy /= input.pos.w;&lt;br /&gt;    texC.x =  0.5f*texC.x + 0.5f;&lt;br /&gt;    texC.y = -0.5f*texC.y + 0.5f;&lt;br /&gt; &lt;br /&gt;    &lt;span style="color:blue;"&gt;float3 &lt;/span&gt;front = &lt;span style="color:blue;"&gt;tex2D&lt;/span&gt;(FrontS, texC).xyz;&lt;br /&gt;    &lt;span style="color:blue;"&gt;float3 &lt;/span&gt;back = &lt;span style="color:blue;"&gt;tex2D&lt;/span&gt;(BackS, texC).xyz;&lt;br /&gt; &lt;br /&gt;    &lt;span style="color:blue;"&gt;float3 &lt;/span&gt;dir = &lt;span style="color:blue;"&gt;normalize&lt;/span&gt;(back - front);&lt;br /&gt;    &lt;span style="color:blue;"&gt;float4 &lt;/span&gt;pos = &lt;span style="color:blue;"&gt;float4&lt;/span&gt;(front, 0);&lt;br /&gt; &lt;br /&gt;    &lt;span style="color:blue;"&gt;float4 &lt;/span&gt;dst = &lt;span style="color:blue;"&gt;float4&lt;/span&gt;(0, 0, 0, 0);&lt;br /&gt;    &lt;span style="color:blue;"&gt;float4 &lt;/span&gt;src = 0;&lt;br /&gt; &lt;br /&gt;    &lt;span style="color:blue;"&gt;float &lt;/span&gt;value = 0;&lt;br /&gt; &lt;br /&gt;    &lt;span style="color:blue;"&gt;float3 &lt;/span&gt;Step = dir * StepSize;&lt;br /&gt; &lt;br /&gt;    &lt;span style="color:blue;"&gt;for&lt;/span&gt;(&lt;span style="color:blue;"&gt;int &lt;/span&gt;i = 0; i &amp;lt; Iterations; i++)&lt;br /&gt;    {&lt;br /&gt;        pos.w = 0;&lt;br /&gt;        value = &lt;span style="color:blue;"&gt;tex3Dlod&lt;/span&gt;(VolumeS, pos).r;&lt;br /&gt;             &lt;br /&gt;        src = (&lt;span style="color:blue;"&gt;float4&lt;/span&gt;)value;&lt;br /&gt;        src.a *= .5f; &lt;span style="color:green;"&gt;//reduce the alpha to have a more transparent result &lt;br /&gt;         &lt;br /&gt;        //Front to back blending&lt;br /&gt;        // dst.rgb = dst.rgb + (1 - dst.a) * src.a * src.rgb&lt;br /&gt;        // dst.a   = dst.a   + (1 - dst.a) * src.a     &lt;br /&gt;        &lt;/span&gt;src.rgb *= src.a;&lt;br /&gt;        dst = (1.0f - dst.a)*src + dst;     &lt;br /&gt;     &lt;br /&gt;        &lt;span style="color:green;"&gt;//break from the loop when alpha gets high enough&lt;br /&gt;        &lt;/span&gt;&lt;span style="color:blue;"&gt;if&lt;/span&gt;(dst.a &amp;gt;= .95f)&lt;br /&gt;            break; &lt;br /&gt;     &lt;br /&gt;        &lt;span style="color:green;"&gt;//advance the current position&lt;br /&gt;        &lt;/span&gt;pos.xyz += Step;&lt;br /&gt;     &lt;br /&gt;        &lt;span style="color:green;"&gt;//break if the position is greater than &amp;lt;1, 1, 1&amp;gt;&lt;br /&gt;        &lt;/span&gt;&lt;span style="color:blue;"&gt;if&lt;/span&gt;(pos.x &amp;gt; 1.0f  pos.y &amp;gt; 1.0f  pos.z &amp;gt; 1.0f)&lt;br /&gt;            break;&lt;br /&gt;    }&lt;br /&gt; &lt;br /&gt;    &lt;span style="color:blue;"&gt;return &lt;/span&gt;dst;&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;p&gt;And here's the result when sampling a foot, teapot with a lobster inside, engine, bonsai tree,  ct scan of an aneurysm, skull, and a teddy bear:&lt;/p&gt;&lt;p&gt;&lt;a href="http://lh4.ggpht.com/_VelpN_FHzhk/SXAFvwj4nCI/AAAAAAAAAjU/azKvv9JYWss/s1600-h/foot%5B15%5D.png"&gt;&lt;img height="397" alt="foot" src="http://lh4.ggpht.com/_VelpN_FHzhk/SXAFwR5mjQI/AAAAAAAAAjY/XUF7QplR7-E/foot_thumb%5B11%5D.png?imgmax=800" width="404" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;a href="http://lh6.ggpht.com/_VelpN_FHzhk/SXAFw4bCKhI/AAAAAAAAAjc/LBrQlm_KzJ0/s1600-h/teapot%5B10%5D.png"&gt;&lt;img height="311" alt="teapot" src="http://lh6.ggpht.com/_VelpN_FHzhk/SXAFxtlX4CI/AAAAAAAAAjg/eRXxsa0Wj7E/teapot_thumb%5B6%5D.png?imgmax=800" width="408" border="0" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;a href="http://lh4.ggpht.com/_VelpN_FHzhk/SXAFyPOD7JI/AAAAAAAAAjk/tHDszMD79zE/s1600-h/engine%5B6%5D.png"&gt;&lt;img height="415" alt="engine" src="http://lh5.ggpht.com/_VelpN_FHzhk/SXAFyiAa71I/AAAAAAAAAjo/oBWSrml18xo/engine_thumb%5B4%5D.png?imgmax=800" width="410" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;a href="http://lh5.ggpht.com/_VelpN_FHzhk/SXAFyxX9ufI/AAAAAAAAAjs/jlxcjeIGiN0/s1600-h/bonsai%5B8%5D.png"&gt;&lt;img height="415" alt="bonsai" src="http://lh6.ggpht.com/_VelpN_FHzhk/SXAFzVR0fXI/AAAAAAAAAjw/sEYAjf3XQlE/bonsai_thumb%5B6%5D.png?imgmax=800" width="412" border="0" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;a href="http://lh3.ggpht.com/_VelpN_FHzhk/SXAFz8LPAGI/AAAAAAAAAj0/3dyBnfhUB-M/s1600-h/aneurism%5B4%5D.png"&gt;&lt;img height="352" alt="aneurism" src="http://lh5.ggpht.com/_VelpN_FHzhk/SXAF0UIjufI/AAAAAAAAAj4/Ifu9H3ZFcAI/aneurism_thumb%5B2%5D.png?imgmax=800" width="409" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;a href="http://lh5.ggpht.com/_VelpN_FHzhk/SXAZ54hhMnI/AAAAAAAAAkQ/Ckj6ONx2RnM/s1600-h/skull%5B5%5D.png"&gt;&lt;img height="370" alt="skull" src="http://lh5.ggpht.com/_VelpN_FHzhk/SXAZ6mUmTpI/AAAAAAAAAkU/c8gsPrZq_dg/skull_thumb%5B3%5D.png?imgmax=800" width="409" border="0" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;a href="http://lh6.ggpht.com/_VelpN_FHzhk/SXAZ62hOKaI/AAAAAAAAAkY/3JNC74MpT5A/s1600-h/teddy%5B5%5D.png"&gt;&lt;img height="395" alt="teddy" src="http://lh4.ggpht.com/_VelpN_FHzhk/SXAZ7acKVVI/AAAAAAAAAkc/x4ekKB8U2L0/teddy_thumb%5B3%5D.png?imgmax=800" width="412" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;So, not very colorful but pretty cool. When we get into transfer functions we will start shading the volumes. The volumes used here can be found at &lt;a href="http://www.gris.uni-tuebingen.de/edu/areas/scivis/volren/datasets/datasets.html" target="_blank"&gt;volvis.org&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;&lt;span style="color:#ff6600;"&gt;Notes:&lt;/span&gt;&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;Refer to the scene setup region in VolumeRayCasting.cs, Volume.cs, and RayCasting.fx for relevant implementation details.&lt;/p&gt;&lt;p&gt;Also, a Shader Model 3.0 card (Nvidia 6600GT or higher) is needed to run the sample.&lt;br /&gt;&lt;br /&gt;&lt;iframe style="BORDER-RIGHT: #dde5e9 1px solid; PADDING-RIGHT: 0px; BORDER-TOP: #dde5e9 1px solid; PADDING-LEFT: 0px; PADDING-BOTTOM: 0px; MARGIN: 3px; BORDER-LEFT: #dde5e9 1px solid; WIDTH: 240px; PADDING-TOP: 0px; BORDER-BOTTOM: #dde5e9 1px solid; HEIGHT: 66px; BACKGROUND-COLOR: #ffffff" marginwidth="0" marginheight="0" src="http://cid-b80a3031b5bfa52b.skydrive.live.com/embedrowdetail.aspx/Public/VolumeRayCasting%7C_101.zip" frameborder="0" scrolling="no"&gt;&lt;/iframe&gt;&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1023441640234597436-2905649749553319332?l=graphicsrunner.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://graphicsrunner.blogspot.com/feeds/2905649749553319332/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=1023441640234597436&amp;postID=2905649749553319332' title='41 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1023441640234597436/posts/default/2905649749553319332'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1023441640234597436/posts/default/2905649749553319332'/><link rel='alternate' type='text/html' href='http://graphicsrunner.blogspot.com/2009/01/volume-rendering-101.html' title='Volume Rendering 101'/><author><name>Kyle Hayward</name><uri>http://www.blogger.com/profile/00654406875137720609</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://bp3.blogger.com/_VelpN_FHzhk/R-Lx4Kk_nkI/AAAAAAAAAN8/jn_4uDeKxNk/S220/me.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://lh6.ggpht.com/_VelpN_FHzhk/SXAFmlB77RI/AAAAAAAAAiY/7eof43oZoZg/s72-c/fish_thumb%5B1%5D.png?imgmax=800' height='72' width='72'/><thr:total>41</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1023441640234597436.post-5580231081199704368</id><published>2008-11-13T17:32:00.019-05:00</published><updated>2008-12-01T22:53:26.047-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Water'/><category scheme='http://www.blogger.com/atom/ns#' term='refractions'/><category scheme='http://www.blogger.com/atom/ns#' term='XNA'/><category scheme='http://www.blogger.com/atom/ns#' term='reflections'/><title type='text'>Water Game Component</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_VelpN_FHzhk/SR2XHtoSfAI/AAAAAAAAAac/9xQ9F4dQs7w/s1600-h/water.png"&gt;&lt;img id="BLOGGER_PHOTO_ID_5268533297856805890" style="WIDTH: 400px; CURSOR: pointer; HEIGHT: 300px" alt="" src="http://4.bp.blogspot.com/_VelpN_FHzhk/SR2XHtoSfAI/AAAAAAAAAac/9xQ9F4dQs7w/s400/water.png" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;I had some requests awhile back for a more independent water effect that I had provided in my camera animation tutorials. And just the other day I remembered that I had totally forgot about this (doh!). So here is a DrawableGameComponent for the water effect. &lt;a href="http://graphicsrunner.blogspot.com/2008/05/camera-animation-part-ii.html"&gt;Have a look here to see it in action.&lt;/a&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;To setup the component we need to fill out a WaterOptions object that will be passed to the water component.&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="COLOR: rgb(43,145,175)"&gt;WaterOptions &lt;/span&gt;options = &lt;span style="color:blue;"&gt;new &lt;/span&gt;&lt;span style="COLOR: rgb(43,145,175)"&gt;WaterOptions&lt;/span&gt;();&lt;br /&gt;options.Width = 257;&lt;br /&gt;options.Height = 257;&lt;br /&gt;options.CellSpacing = 0.5f;&lt;br /&gt;options.WaveMapAsset0 = &lt;span style="COLOR: rgb(163,21,21)"&gt;"Textures/wave0"&lt;/span&gt;;&lt;br /&gt;options.WaveMapAsset1 = &lt;span style="COLOR: rgb(163,21,21)"&gt;"Textures/wave1"&lt;/span&gt;;&lt;br /&gt;options.WaveMapVelocity0 = &lt;span style="color:blue;"&gt;new &lt;/span&gt;&lt;span style="COLOR: rgb(43,145,175)"&gt;Vector2&lt;/span&gt;(0.01f, 0.03f);&lt;br /&gt;options.WaveMapVelocity1 = &lt;span style="color:blue;"&gt;new &lt;/span&gt;&lt;span style="COLOR: rgb(43,145,175)"&gt;Vector2&lt;/span&gt;(-0.01f, 0.03f);&lt;br /&gt;options.WaveMapScale = 2.5f;&lt;br /&gt;options.WaterColor = &lt;span style="color:blue;"&gt;new &lt;/span&gt;&lt;span style="COLOR: rgb(43,145,175)"&gt;Vector4&lt;/span&gt;(0.5f, 0.79f, 0.75f, 1.0f);&lt;br /&gt;options.SunColor = &lt;span style="color:blue;"&gt;new &lt;/span&gt;&lt;span style="COLOR: rgb(43,145,175)"&gt;Vector4&lt;/span&gt;(1.0f, 0.8f, 0.4f, 1.0f);&lt;br /&gt;options.SunDirection = &lt;span style="color:blue;"&gt;new &lt;/span&gt;&lt;span style="COLOR: rgb(43,145,175)"&gt;Vector3&lt;/span&gt;(2.6f, -1.0f, -1.5f);&lt;br /&gt;options.SunFactor = 1.5f;&lt;br /&gt;options.SunPower = 250.0f;&lt;br /&gt;&lt;br /&gt;mWaterMesh = &lt;span style="color:blue;"&gt;new &lt;/span&gt;&lt;span style="COLOR: rgb(43,145,175)"&gt;Water&lt;/span&gt;(&lt;span style="color:blue;"&gt;this&lt;/span&gt;);&lt;br /&gt;mWaterMesh.Options = options;&lt;br /&gt;mWaterMesh.EffectAsset = &lt;span style="COLOR: rgb(163,21,21)"&gt;"Shaders/Water"&lt;/span&gt;;&lt;br /&gt;mWaterMesh.World = &lt;span style="COLOR: rgb(43,145,175)"&gt;Matrix&lt;/span&gt;.CreateTranslation(&lt;span style="COLOR: rgb(43,145,175)"&gt;Vector3&lt;/span&gt;.UnitY * 2.0f);&lt;br /&gt;mWaterMesh.RenderObjects = DrawObjects;&lt;/pre&gt;&lt;br /&gt;So here will fill out various options such as width and height, cell spacing, the normal map asset names, etc. We then create the water component, and assign it the options object. We then provide the filename of the Water.fx shader, the water's position and we then assign its RenderObjects delegate a function that will be used to draw the objects in your scene.&lt;br /&gt;&lt;br /&gt;The component tries to be relatively independent of how your represent your game objects. All that it asks for is that you provide a function that takes a reflection matrix. This function should go through the objects that you want to be reflected/refracted and combine the reflection matrix with the object's world matrix.&lt;br /&gt;&lt;br /&gt;Here's an example of what your DrawObjects() function might look like.&lt;br /&gt;&lt;pre class="mycode"&gt;&lt;span style="color:blue;"&gt;private void &lt;/span&gt;DrawObjects(&lt;span style="COLOR: rgb(43,145,175)"&gt;Matrix &lt;/span&gt;reflMatrix)&lt;br /&gt;{&lt;br /&gt;&lt;span style="color:blue;"&gt;   foreach &lt;/span&gt;(&lt;span style="COLOR: rgb(43,145,175)"&gt;DrawableGameComponent &lt;/span&gt;mesh &lt;span style="color:blue;"&gt;in &lt;/span&gt;Components)&lt;br /&gt;{&lt;br /&gt;  &lt;span style="COLOR: rgb(43,145,175)"&gt;Matrix &lt;/span&gt;oldWorld = mesh.World;&lt;br /&gt;  mesh.World = oldWorld * reflMatrix;&lt;br /&gt;&lt;br /&gt;  mesh.Draw(mGameTime);&lt;br /&gt;&lt;br /&gt;  mesh.World = oldWorld;&lt;br /&gt;}&lt;br /&gt;}&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;mWaterMesh.RenderObjects is the delegate that has the signature of:&lt;span style="color:blue;"&gt; public delegate void &lt;/span&gt;&lt;span style="COLOR: rgb(43,145,175)"&gt;RenderObjects&lt;/span&gt;(&lt;span style="COLOR: rgb(43,145,175)"&gt;Matrix &lt;/span&gt;reflectionMatrix);&lt;br /&gt;&lt;br /&gt;Basically this function should just go through your game objects and render them.&lt;br /&gt;&lt;br /&gt;Lastly, before you draw your objects in the scene, you need to send to the water component the ViewProjection matrix and the camera's position by using WaterMesh.SetCamera(). And you need to call WaterMesh.UpdateWaterMaps() to update the reflection and refraction maps. After this, you can clear your framebuffer and draw your objects. For how this effect looks you can take a look at my camera animation tutorials.&lt;br /&gt;&lt;span style="FONT-WEIGHT: bold"&gt;&lt;br /&gt;Water GameComponent:&lt;/span&gt;&lt;br /&gt;&lt;pre class="mycode"&gt;&lt;span style="color:blue;"&gt;using &lt;/span&gt;System;&lt;br /&gt;&lt;span style="color:blue;"&gt;using &lt;/span&gt;System.Collections.Generic;&lt;br /&gt;&lt;span style="color:blue;"&gt;using &lt;/span&gt;System.Text;&lt;br /&gt;&lt;br /&gt;&lt;span style="color:blue;"&gt;using &lt;/span&gt;Microsoft.Xna.Framework;&lt;br /&gt;&lt;span style="color:blue;"&gt;using &lt;/span&gt;Microsoft.Xna.Framework.Graphics;&lt;br /&gt;&lt;span style="color:blue;"&gt;using &lt;/span&gt;Microsoft.Xna.Framework.Content;&lt;br /&gt;&lt;br /&gt;&lt;span style="color:blue;"&gt;namespace WaterSample&lt;/span&gt;&lt;br /&gt;{&lt;br /&gt;&lt;span style="color:green;"&gt;//delegate that the water component to call to render the objects in the scene&lt;br /&gt;&lt;/span&gt;&lt;span style="color:blue;"&gt;public delegate void &lt;/span&gt;&lt;span style="COLOR: rgb(43,145,175)"&gt;RenderObjects&lt;/span&gt;(&lt;span style="COLOR: rgb(43,145,175)"&gt;Matrix &lt;/span&gt;reflectionMatrix);&lt;br /&gt;&lt;br /&gt;&lt;span style="color:gray;"&gt;/// &amp;lt;summary&amp;gt;&lt;br /&gt;/// &lt;/span&gt;&lt;span style="color:green;"&gt;Options that must be passed to the water component before Initialization&lt;br /&gt;&lt;/span&gt;&lt;span style="color:gray;"&gt;/// &amp;lt;/summary&amp;gt;&lt;br /&gt;&lt;/span&gt;&lt;span style="color:blue;"&gt;public class &lt;/span&gt;&lt;span style="COLOR: rgb(43,145,175)"&gt;WaterOptions&lt;br /&gt;&lt;/span&gt;{&lt;br /&gt;&lt;span style="font-size:0;"&gt;&lt;span style="color:green;"&gt;//width and height must be of the form 2^n + 1&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="color:blue;"&gt;public int &lt;/span&gt;Width = 257;&lt;br /&gt;&lt;span style="color:blue;"&gt;public int &lt;/span&gt;Height = 257;&lt;br /&gt;&lt;span style="color:blue;"&gt;public float &lt;/span&gt;CellSpacing = .5f;&lt;br /&gt;&lt;br /&gt;&lt;span style="color:blue;"&gt;public float &lt;/span&gt;WaveMapScale = 1.0f;&lt;br /&gt;&lt;br /&gt;&lt;span style="color:blue;"&gt;public int &lt;/span&gt;RenderTargetSize = 512;&lt;br /&gt;&lt;br /&gt;&lt;span style="color:green;"&gt;//offsets for the texcoords of the wave maps updated every frame&lt;br /&gt;&lt;/span&gt;&lt;span style="color:blue;"&gt;public &lt;/span&gt;&lt;span style="COLOR: rgb(43,145,175)"&gt;Vector2 &lt;/span&gt;WaveMapOffset0;&lt;br /&gt;&lt;span style="color:blue;"&gt;public &lt;/span&gt;&lt;span style="COLOR: rgb(43,145,175)"&gt;Vector2 &lt;/span&gt;WaveMapOffset1;&lt;br /&gt;&lt;br /&gt;&lt;span style="color:green;"&gt;//the direction to offset the texcoords of the wave maps&lt;br /&gt;&lt;/span&gt;&lt;span style="color:blue;"&gt;public &lt;/span&gt;&lt;span style="COLOR: rgb(43,145,175)"&gt;Vector2 &lt;/span&gt;WaveMapVelocity0;&lt;br /&gt;&lt;span style="color:blue;"&gt;public &lt;/span&gt;&lt;span style="COLOR: rgb(43,145,175)"&gt;Vector2 &lt;/span&gt;WaveMapVelocity1;&lt;br /&gt;&lt;br /&gt;&lt;span style="color:green;"&gt;//asset names for the normal/wave maps&lt;br /&gt;&lt;/span&gt;&lt;span style="color:blue;"&gt;public string &lt;/span&gt;WaveMapAsset0;&lt;br /&gt;&lt;span style="color:blue;"&gt;public string &lt;/span&gt;WaveMapAsset1;&lt;br /&gt;&lt;br /&gt;&lt;span style="color:blue;"&gt;public &lt;/span&gt;&lt;span style="COLOR: rgb(43,145,175)"&gt;Vector4 &lt;/span&gt;WaterColor;&lt;br /&gt;&lt;span style="color:blue;"&gt;public &lt;/span&gt;&lt;span style="COLOR: rgb(43,145,175)"&gt;Vector4 &lt;/span&gt;SunColor;&lt;br /&gt;&lt;span style="color:blue;"&gt;public &lt;/span&gt;&lt;span style="COLOR: rgb(43,145,175)"&gt;Vector3 &lt;/span&gt;SunDirection;&lt;br /&gt;&lt;span style="color:blue;"&gt;public float &lt;/span&gt;SunFactor;&lt;br /&gt;&lt;span style="color:blue;"&gt;public float &lt;/span&gt;SunPower;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;span style="color:gray;"&gt;/// &amp;lt;summary&amp;gt;&lt;br /&gt;/// &lt;/span&gt;&lt;span style="color:green;"&gt;Drawable game component for water rendering. Renders the scene to reflection and refraction&lt;br /&gt;&lt;/span&gt;&lt;span style="color:gray;"&gt;/// &lt;/span&gt;&lt;span style="color:green;"&gt;maps that are projected onto the water plane and are distorted based on two scrolling normal&lt;br /&gt;&lt;/span&gt;&lt;span style="color:gray;"&gt;/// &lt;/span&gt;&lt;span style="color:green;"&gt;maps.&lt;br /&gt;&lt;/span&gt;&lt;span style="color:gray;"&gt;/// &amp;lt;/summary&amp;gt;&lt;br /&gt;&lt;/span&gt;&lt;span style="color:blue;"&gt;public class &lt;/span&gt;&lt;span style="COLOR: rgb(43,145,175)"&gt;Water &lt;/span&gt;: &lt;span style="COLOR: rgb(43,145,175)"&gt;DrawableGameComponent&lt;br /&gt;&lt;/span&gt;{&lt;br /&gt;&lt;span style="color:blue;"&gt;#region &lt;/span&gt;Fields&lt;br /&gt;&lt;span style="color:blue;"&gt;private &lt;/span&gt;&lt;span style="COLOR: rgb(43,145,175)"&gt;RenderObjects &lt;/span&gt;mDrawFunc;&lt;br /&gt;&lt;br /&gt;&lt;span style="color:green;"&gt;//vertex and index buffers for the water plane&lt;br /&gt;&lt;/span&gt;&lt;span style="color:blue;"&gt;private &lt;/span&gt;&lt;span style="COLOR: rgb(43,145,175)"&gt;VertexBuffer &lt;/span&gt;mVertexBuffer;&lt;br /&gt;&lt;span style="color:blue;"&gt;private &lt;/span&gt;&lt;span style="COLOR: rgb(43,145,175)"&gt;IndexBuffer &lt;/span&gt;mIndexBuffer;&lt;br /&gt;&lt;span style="color:blue;"&gt;private &lt;/span&gt;&lt;span style="COLOR: rgb(43,145,175)"&gt;VertexDeclaration &lt;/span&gt;mDecl;&lt;br /&gt;&lt;br /&gt;&lt;span style="color:green;"&gt;//water shader&lt;br /&gt;&lt;/span&gt;&lt;span style="color:blue;"&gt;private &lt;/span&gt;&lt;span style="COLOR: rgb(43,145,175)"&gt;Effect &lt;/span&gt;mEffect;&lt;br /&gt;&lt;span style="color:blue;"&gt;private string &lt;/span&gt;mEffectAsset;&lt;br /&gt;&lt;br /&gt;&lt;span style="color:green;"&gt;//camera properties&lt;br /&gt;&lt;/span&gt;&lt;span style="color:blue;"&gt;private &lt;/span&gt;&lt;span style="COLOR: rgb(43,145,175)"&gt;Vector3 &lt;/span&gt;mViewPos;&lt;br /&gt;&lt;span style="color:blue;"&gt;private &lt;/span&gt;&lt;span style="COLOR: rgb(43,145,175)"&gt;Matrix &lt;/span&gt;mViewProj;&lt;br /&gt;&lt;span style="color:blue;"&gt;private &lt;/span&gt;&lt;span style="COLOR: rgb(43,145,175)"&gt;Matrix &lt;/span&gt;mWorld;&lt;br /&gt;&lt;br /&gt;&lt;span style="color:green;"&gt;//maps to render the refraction/reflection to&lt;br /&gt;&lt;/span&gt;&lt;span style="color:blue;"&gt;private &lt;/span&gt;&lt;span style="COLOR: rgb(43,145,175)"&gt;RenderTarget2D &lt;/span&gt;mRefractionMap;&lt;br /&gt;&lt;span style="color:blue;"&gt;private &lt;/span&gt;&lt;span style="COLOR: rgb(43,145,175)"&gt;RenderTarget2D &lt;/span&gt;mReflectionMap;&lt;br /&gt;&lt;br /&gt;&lt;span style="color:green;"&gt;//scrolling normal maps that we will use as a&lt;br /&gt;//a normal for the water plane in the shader&lt;br /&gt;&lt;/span&gt;&lt;span style="color:blue;"&gt;private &lt;/span&gt;&lt;span style="COLOR: rgb(43,145,175)"&gt;Texture &lt;/span&gt;mWaveMap0;&lt;br /&gt;&lt;span style="color:blue;"&gt;private &lt;/span&gt;&lt;span style="COLOR: rgb(43,145,175)"&gt;Texture &lt;/span&gt;mWaveMap1;&lt;br /&gt;&lt;br /&gt;&lt;span style="color:green;"&gt;//user specified options to configure the water object&lt;br /&gt;&lt;/span&gt;&lt;span style="color:blue;"&gt;private &lt;/span&gt;&lt;span style="COLOR: rgb(43,145,175)"&gt;WaterOptions &lt;/span&gt;mOptions;&lt;br /&gt;&lt;br /&gt;&lt;span style="color:green;"&gt;//tells the water object if it needs to update the refraction&lt;br /&gt;//map itself or not. Since refraction just needs the scene drawn&lt;br /&gt;//regularly, we can:&lt;br /&gt;// --Draw the objects we want refracted&lt;br /&gt;// --Resolve the back buffer and send it to the water&lt;br /&gt;// --Skip computing the refraction map in the water object&lt;br /&gt;&lt;/span&gt;&lt;span style="color:blue;"&gt;private bool &lt;/span&gt;mGrabRefractionFromFB = &lt;span style="color:blue;"&gt;false&lt;/span&gt;;&lt;br /&gt;&lt;br /&gt;&lt;span style="color:blue;"&gt;private int &lt;/span&gt;mNumVertices;&lt;br /&gt;&lt;span style="color:blue;"&gt;private int &lt;/span&gt;mNumTris;&lt;br /&gt;&lt;span style="color:blue;"&gt;#endregion&lt;br /&gt;&lt;br /&gt;#region &lt;/span&gt;Properties&lt;br /&gt;&lt;br /&gt;&lt;span style="color:blue;"&gt;public &lt;/span&gt;&lt;span style="COLOR: rgb(43,145,175)"&gt;RenderObjects &lt;/span&gt;RenderObjects&lt;br /&gt;{&lt;br /&gt;&lt;span style="color:blue;"&gt;set &lt;/span&gt;{ mDrawFunc = &lt;span style="color:blue;"&gt;value&lt;/span&gt;; }&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;span style="color:gray;"&gt;/// &amp;lt;summary&amp;gt;&lt;br /&gt;/// &lt;/span&gt;&lt;span style="color:green;"&gt;Name of the asset for the Effect.&lt;br /&gt;&lt;/span&gt;&lt;span style="color:gray;"&gt;/// &amp;lt;/summary&amp;gt;&lt;br /&gt;&lt;/span&gt;&lt;span style="color:blue;"&gt;public string &lt;/span&gt;EffectAsset&lt;br /&gt;{&lt;br /&gt;&lt;span style="color:blue;"&gt;get &lt;/span&gt;{ &lt;span style="color:blue;"&gt;return &lt;/span&gt;mEffectAsset; }&lt;br /&gt;&lt;span style="color:blue;"&gt;set &lt;/span&gt;{ mEffectAsset = &lt;span style="color:blue;"&gt;value&lt;/span&gt;; }&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;span style="color:gray;"&gt;/// &amp;lt;summary&amp;gt;&lt;br /&gt;/// &lt;/span&gt;&lt;span style="color:green;"&gt;The render target that the refraction is rendered to.&lt;br /&gt;&lt;/span&gt;&lt;span style="color:gray;"&gt;/// &amp;lt;/summary&amp;gt;&lt;br /&gt;&lt;/span&gt;&lt;span style="color:blue;"&gt;public &lt;/span&gt;&lt;span style="COLOR: rgb(43,145,175)"&gt;RenderTarget2D &lt;/span&gt;RefractionMap&lt;br /&gt;{&lt;br /&gt;&lt;span style="color:blue;"&gt;get &lt;/span&gt;{ &lt;span style="color:blue;"&gt;return &lt;/span&gt;mRefractionMap; }&lt;br /&gt;&lt;span style="color:blue;"&gt;set &lt;/span&gt;{ mRefractionMap = &lt;span style="color:blue;"&gt;value&lt;/span&gt;; }&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;span style="color:gray;"&gt;/// &amp;lt;summary&amp;gt;&lt;br /&gt;/// &lt;/span&gt;&lt;span style="color:green;"&gt;The render target that the reflection is rendered to.&lt;br /&gt;&lt;/span&gt;&lt;span style="color:gray;"&gt;/// &amp;lt;/summary&amp;gt;&lt;br /&gt;&lt;/span&gt;&lt;span style="color:blue;"&gt;public &lt;/span&gt;&lt;span style="COLOR: rgb(43,145,175)"&gt;RenderTarget2D &lt;/span&gt;ReflectionMap&lt;br /&gt;{&lt;br /&gt;&lt;span style="color:blue;"&gt;get &lt;/span&gt;{ &lt;span style="color:blue;"&gt;return &lt;/span&gt;mReflectionMap; }&lt;br /&gt;&lt;span style="color:blue;"&gt;set &lt;/span&gt;{ mReflectionMap = &lt;span style="color:blue;"&gt;value&lt;/span&gt;; }&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;span style="color:gray;"&gt;/// &amp;lt;summary&amp;gt;&lt;br /&gt;/// &lt;/span&gt;&lt;span style="color:green;"&gt;Options to configure the water. Must be set before&lt;br /&gt;&lt;/span&gt;&lt;span style="color:gray;"&gt;/// &lt;/span&gt;&lt;span style="color:green;"&gt;the water is initialized. Should be set immediately&lt;br /&gt;&lt;/span&gt;&lt;span style="color:gray;"&gt;/// &lt;/span&gt;&lt;span style="color:green;"&gt;following the instantiation of the object.&lt;br /&gt;&lt;/span&gt;&lt;span style="color:gray;"&gt;/// &amp;lt;/summary&amp;gt;&lt;br /&gt;&lt;/span&gt;&lt;span style="color:blue;"&gt;public &lt;/span&gt;&lt;span style="COLOR: rgb(43,145,175)"&gt;WaterOptions &lt;/span&gt;Options&lt;br /&gt;{&lt;br /&gt;&lt;span style="color:blue;"&gt;get &lt;/span&gt;{ &lt;span style="color:blue;"&gt;return &lt;/span&gt;mOptions; }&lt;br /&gt;&lt;span style="color:blue;"&gt;set &lt;/span&gt;{ mOptions = &lt;span style="color:blue;"&gt;value&lt;/span&gt;; }&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;span style="color:gray;"&gt;/// &amp;lt;summary&amp;gt;&lt;br /&gt;/// &lt;/span&gt;&lt;span style="color:green;"&gt;The world matrix of the water.&lt;br /&gt;&lt;/span&gt;&lt;span style="color:gray;"&gt;/// &amp;lt;/summary&amp;gt;&lt;br /&gt;&lt;/span&gt;&lt;span style="color:blue;"&gt;public &lt;/span&gt;&lt;span style="COLOR: rgb(43,145,175)"&gt;Matrix &lt;/span&gt;World&lt;br /&gt;{&lt;br /&gt;&lt;span style="color:blue;"&gt;get &lt;/span&gt;{ &lt;span style="color:blue;"&gt;return &lt;/span&gt;mWorld; }&lt;br /&gt;&lt;span style="color:blue;"&gt;set &lt;/span&gt;{ mWorld = &lt;span style="color:blue;"&gt;value&lt;/span&gt;; }&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;span style="color:blue;"&gt;#endregion&lt;br /&gt;&lt;br /&gt;public &lt;/span&gt;Water(&lt;span style="COLOR: rgb(43,145,175)"&gt;Game &lt;/span&gt;game) : &lt;span style="color:blue;"&gt;base&lt;/span&gt;(game)&lt;br /&gt;{&lt;br /&gt;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;span style="color:blue;"&gt;public override void &lt;/span&gt;Initialize()&lt;br /&gt;{&lt;br /&gt;&lt;span style="color:blue;"&gt;base&lt;/span&gt;.Initialize();&lt;br /&gt;&lt;br /&gt;&lt;span style="color:green;"&gt;//build the water mesh&lt;br /&gt;&lt;/span&gt;mNumVertices = mOptions.Width * mOptions.Height;&lt;br /&gt;mNumTris = (mOptions.Width - 1) * (mOptions.Height - 1) * 2;&lt;br /&gt;&lt;span style="COLOR: rgb(43,145,175)"&gt;VertexPositionTexture&lt;/span&gt;[] vertices = &lt;span style="color:blue;"&gt;new &lt;/span&gt;&lt;span style="COLOR: rgb(43,145,175)"&gt;VertexPositionTexture&lt;/span&gt;[mNumVertices];&lt;br /&gt;&lt;br /&gt;&lt;span style="COLOR: rgb(43,145,175)"&gt;Vector3&lt;/span&gt;[] verts;&lt;br /&gt;&lt;span style="color:blue;"&gt;int&lt;/span&gt;[] indices;&lt;br /&gt;&lt;br /&gt;GenTriGrid(mOptions.Height, mOptions.Width, mOptions.CellSpacing, mOptions.CellSpacing,&lt;br /&gt;          &lt;span style="COLOR: rgb(43,145,175)"&gt;Vector3&lt;/span&gt;.Zero, &lt;span style="color:blue;"&gt;out &lt;/span&gt;verts, &lt;span style="color:blue;"&gt;out &lt;/span&gt;indices);&lt;br /&gt;&lt;br /&gt;&lt;span style="color:green;"&gt;//copy the verts into our PositionTextured array&lt;br /&gt;&lt;/span&gt;&lt;span style="color:blue;"&gt;for &lt;/span&gt;(&lt;span style="color:blue;"&gt;int &lt;/span&gt;i = 0; i &amp;lt; mOptions.Width; ++i)&lt;br /&gt;{&lt;br /&gt;  &lt;span style="color:blue;"&gt;for &lt;/span&gt;(&lt;span style="color:blue;"&gt;int &lt;/span&gt;j = 0; j &amp;lt; mOptions.Height; ++j)&lt;br /&gt;  {&lt;br /&gt;      &lt;span style="color:blue;"&gt;int &lt;/span&gt;index = i * mOptions.Width + j;&lt;br /&gt;      vertices[index].Position = verts[index];&lt;br /&gt;      vertices[index].TextureCoordinate = &lt;span style="color:blue;"&gt;new &lt;/span&gt;&lt;span style="COLOR: rgb(43,145,175)"&gt;Vector2&lt;/span&gt;((&lt;span style="color:blue;"&gt;float&lt;/span&gt;)j / mOptions.Width, (&lt;span style="color:blue;"&gt;float&lt;/span&gt;)i / mOptions.Height);&lt;br /&gt;  }&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;mVertexBuffer = &lt;span style="color:blue;"&gt;new &lt;/span&gt;&lt;span style="COLOR: rgb(43,145,175)"&gt;VertexBuffer&lt;/span&gt;(Game.GraphicsDevice,&lt;br /&gt;                               &lt;span style="COLOR: rgb(43,145,175)"&gt;VertexPositionTexture&lt;/span&gt;.SizeInBytes * mOptions.Width * mOptions.Height,&lt;br /&gt;                               &lt;span style="COLOR: rgb(43,145,175)"&gt;BufferUsage&lt;/span&gt;.WriteOnly);&lt;br /&gt;mVertexBuffer.SetData(vertices);&lt;br /&gt;&lt;br /&gt;mIndexBuffer = &lt;span style="color:blue;"&gt;new &lt;/span&gt;&lt;span style="COLOR: rgb(43,145,175)"&gt;IndexBuffer&lt;/span&gt;(Game.GraphicsDevice, &lt;span style="color:blue;"&gt;typeof&lt;/span&gt;(&lt;span style="color:blue;"&gt;int&lt;/span&gt;), indices.Length, &lt;span style="COLOR: rgb(43,145,175)"&gt;BufferUsage&lt;/span&gt;.WriteOnly);&lt;br /&gt;mIndexBuffer.SetData(indices);&lt;br /&gt;&lt;br /&gt;mDecl = &lt;span style="color:blue;"&gt;new &lt;/span&gt;&lt;span style="COLOR: rgb(43,145,175)"&gt;VertexDeclaration&lt;/span&gt;(Game.GraphicsDevice, &lt;span style="COLOR: rgb(43,145,175)"&gt;VertexPositionTexture&lt;/span&gt;.VertexElements);&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;span style="color:blue;"&gt;protected override void  &lt;/span&gt;LoadContent()&lt;br /&gt;{&lt;br /&gt;&lt;span style="color:blue;"&gt;base&lt;/span&gt;.LoadContent();&lt;br /&gt;&lt;br /&gt;mWaveMap0 = Game.Content.Load&amp;lt;&lt;span style="COLOR: rgb(43,145,175)"&gt;Texture2D&lt;/span&gt;&amp;gt;(mOptions.WaveMapAsset0);&lt;br /&gt;mWaveMap1 = Game.Content.Load&amp;lt;&lt;span style="COLOR: rgb(43,145,175)"&gt;Texture2D&lt;/span&gt;&amp;gt;(mOptions.WaveMapAsset1);&lt;br /&gt;&lt;br /&gt;&lt;span style="COLOR: rgb(43,145,175)"&gt;PresentationParameters &lt;/span&gt;pp = Game.GraphicsDevice.PresentationParameters;&lt;br /&gt;&lt;span style="COLOR: rgb(43,145,175)"&gt;SurfaceFormat &lt;/span&gt;format = pp.BackBufferFormat;&lt;br /&gt;&lt;span style="COLOR: rgb(43,145,175)"&gt;MultiSampleType &lt;/span&gt;msType = pp.MultiSampleType;&lt;br /&gt;&lt;span style="color:blue;"&gt;int &lt;/span&gt;msQuality = pp.MultiSampleQuality;&lt;br /&gt;&lt;br /&gt;mRefractionMap = &lt;span style="color:blue;"&gt;new &lt;/span&gt;&lt;span style="COLOR: rgb(43,145,175)"&gt;RenderTarget2D&lt;/span&gt;(Game.GraphicsDevice, mOptions.RenderTargetSize, mOptions.RenderTargetSize,&lt;br /&gt;                                  1, format, msType, msQuality);&lt;br /&gt;mReflectionMap = &lt;span style="color:blue;"&gt;new &lt;/span&gt;&lt;span style="COLOR: rgb(43,145,175)"&gt;RenderTarget2D&lt;/span&gt;(Game.GraphicsDevice, mOptions.RenderTargetSize, mOptions.RenderTargetSize,&lt;br /&gt;                                  1, format, msType, msQuality);&lt;br /&gt;&lt;br /&gt;mEffect = Game.Content.Load&amp;lt;&lt;span style="COLOR: rgb(43,145,175)"&gt;Effect&lt;/span&gt;&amp;gt;(mEffectAsset);&lt;br /&gt;&lt;br /&gt;&lt;span style="color:green;"&gt;//set the parameters that shouldn't change.&lt;br /&gt;//Some of these might need to change every once in awhile,&lt;br /&gt;//move them to updateEffectParams if you need that functionality.&lt;br /&gt;&lt;/span&gt;&lt;span style="color:blue;"&gt;if &lt;/span&gt;(mEffect != &lt;span style="color:blue;"&gt;null&lt;/span&gt;)&lt;br /&gt;{&lt;br /&gt;  mEffect.Parameters[&lt;span style="COLOR: rgb(163,21,21)"&gt;"WaveMap0"&lt;/span&gt;].SetValue(mWaveMap0);&lt;br /&gt;  mEffect.Parameters[&lt;span style="COLOR: rgb(163,21,21)"&gt;"WaveMap1"&lt;/span&gt;].SetValue(mWaveMap1);&lt;br /&gt;&lt;br /&gt;  mEffect.Parameters[&lt;span style="COLOR: rgb(163,21,21)"&gt;"TexScale"&lt;/span&gt;].SetValue(mOptions.WaveMapScale);&lt;br /&gt;&lt;br /&gt;  mEffect.Parameters[&lt;span style="COLOR: rgb(163,21,21)"&gt;"WaterColor"&lt;/span&gt;].SetValue(mOptions.WaterColor);&lt;br /&gt;  mEffect.Parameters[&lt;span style="COLOR: rgb(163,21,21)"&gt;"SunColor"&lt;/span&gt;].SetValue(mOptions.SunColor);&lt;br /&gt;  mEffect.Parameters[&lt;span style="COLOR: rgb(163,21,21)"&gt;"SunDirection"&lt;/span&gt;].SetValue(mOptions.SunDirection);&lt;br /&gt;  mEffect.Parameters[&lt;span style="COLOR: rgb(163,21,21)"&gt;"SunFactor"&lt;/span&gt;].SetValue(mOptions.SunFactor);&lt;br /&gt;  mEffect.Parameters[&lt;span style="COLOR: rgb(163,21,21)"&gt;"SunPower"&lt;/span&gt;].SetValue(mOptions.SunPower);&lt;br /&gt;&lt;br /&gt;  mEffect.Parameters[&lt;span style="COLOR: rgb(163,21,21)"&gt;"World"&lt;/span&gt;].SetValue(mWorld);&lt;br /&gt;}&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;span style="color:blue;"&gt;public override void &lt;/span&gt;Update(&lt;span style="COLOR: rgb(43,145,175)"&gt;GameTime &lt;/span&gt;gameTime)&lt;br /&gt;{&lt;br /&gt;&lt;span style="color:blue;"&gt;float &lt;/span&gt;timeDelta = (&lt;span style="color:blue;"&gt;float&lt;/span&gt;)gameTime.ElapsedGameTime.TotalSeconds;&lt;br /&gt;&lt;br /&gt;mOptions.WaveMapOffset0 += mOptions.WaveMapVelocity0 * timeDelta;&lt;br /&gt;mOptions.WaveMapOffset1 += mOptions.WaveMapVelocity1 * timeDelta;&lt;br /&gt;&lt;br /&gt;&lt;span style="color:blue;"&gt;if &lt;/span&gt;(mOptions.WaveMapOffset0.X &amp;gt;= 1.0f  mOptions.WaveMapOffset0.X &amp;lt;= -1.0f)&lt;br /&gt;  mOptions.WaveMapOffset0.X = 0.0f;&lt;br /&gt;&lt;span style="color:blue;"&gt;if &lt;/span&gt;(mOptions.WaveMapOffset1.X &amp;gt;= 1.0f  mOptions.WaveMapOffset1.X &amp;lt;= -1.0f)&lt;br /&gt;  mOptions.WaveMapOffset1.X = 0.0f;&lt;br /&gt;&lt;span style="color:blue;"&gt;if &lt;/span&gt;(mOptions.WaveMapOffset0.Y &amp;gt;= 1.0f  mOptions.WaveMapOffset0.Y &amp;lt;= -1.0f)&lt;br /&gt;  mOptions.WaveMapOffset0.Y = 0.0f;&lt;br /&gt;&lt;span style="color:blue;"&gt;if &lt;/span&gt;(mOptions.WaveMapOffset1.Y &amp;gt;= 1.0f  mOptions.WaveMapOffset1.Y &amp;lt;= -1.0f)&lt;br /&gt;  mOptions.WaveMapOffset1.Y = 0.0f;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;span style="color:blue;"&gt;public override void &lt;/span&gt;Draw(&lt;span style="COLOR: rgb(43,145,175)"&gt;GameTime &lt;/span&gt;gameTime)&lt;br /&gt;{&lt;br /&gt;UpdateEffectParams();&lt;br /&gt;&lt;br /&gt;Game.GraphicsDevice.Indices = mIndexBuffer;&lt;br /&gt;Game.GraphicsDevice.Vertices[0].SetSource(mVertexBuffer, 0, &lt;span style="COLOR: rgb(43,145,175)"&gt;VertexPositionTexture&lt;/span&gt;.SizeInBytes);&lt;br /&gt;Game.GraphicsDevice.VertexDeclaration = mDecl;&lt;br /&gt;&lt;br /&gt;mEffect.Begin(&lt;span style="COLOR: rgb(43,145,175)"&gt;SaveStateMode&lt;/span&gt;.None);&lt;br /&gt;&lt;br /&gt;&lt;span style="color:blue;"&gt;foreach &lt;/span&gt;(&lt;span style="COLOR: rgb(43,145,175)"&gt;EffectPass &lt;/span&gt;pass &lt;span style="color:blue;"&gt;in &lt;/span&gt;mEffect.CurrentTechnique.Passes)&lt;br /&gt;{&lt;br /&gt;  pass.Begin();&lt;br /&gt;  Game.GraphicsDevice.DrawIndexedPrimitives(&lt;span style="COLOR: rgb(43,145,175)"&gt;PrimitiveType&lt;/span&gt;.TriangleList, 0, 0, mNumVertices, 0, mNumTris);&lt;br /&gt;  pass.End();&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;mEffect.End();&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;span style="color:gray;"&gt;/// &amp;lt;summary&amp;gt;&lt;br /&gt;/// &lt;/span&gt;&lt;span style="color:green;"&gt;Set the ViewProjection matrix and position of the Camera.&lt;br /&gt;&lt;/span&gt;&lt;span style="color:gray;"&gt;/// &amp;lt;/summary&amp;gt;&lt;br /&gt;/// &amp;lt;param name="viewProj"&amp;gt;&amp;lt;/param&amp;gt;&lt;br /&gt;/// &amp;lt;param name="pos"&amp;gt;&amp;lt;/param&amp;gt;&lt;br /&gt;&lt;/span&gt;&lt;span style="color:blue;"&gt;public void &lt;/span&gt;SetCamera(&lt;span style="COLOR: rgb(43,145,175)"&gt;Matrix &lt;/span&gt;viewProj, &lt;span style="COLOR: rgb(43,145,175)"&gt;Vector3 &lt;/span&gt;pos)&lt;br /&gt;{&lt;br /&gt;mViewProj = viewProj;&lt;br /&gt;mViewPos = pos;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;span style="color:gray;"&gt;/// &amp;lt;summary&amp;gt;&lt;br /&gt;/// &lt;/span&gt;&lt;span style="color:green;"&gt;Updates the reflection and refraction maps. Called&lt;br /&gt;&lt;/span&gt;&lt;span style="color:gray;"&gt;/// &lt;/span&gt;&lt;span style="color:green;"&gt;on update.&lt;br /&gt;&lt;/span&gt;&lt;span style="color:gray;"&gt;/// &amp;lt;/summary&amp;gt;&lt;br /&gt;/// &amp;lt;param name="gameTime"&amp;gt;&amp;lt;/param&amp;gt;&lt;br /&gt;&lt;/span&gt;&lt;span style="color:blue;"&gt;public void &lt;/span&gt;UpdateWaterMaps(&lt;span style="COLOR: rgb(43,145,175)"&gt;GameTime &lt;/span&gt;gameTime)&lt;br /&gt;{&lt;br /&gt;&lt;span style="color:green;"&gt;/*------------------------------------------------------------------------------------------&lt;br /&gt;* Render to the Reflection Map&lt;br /&gt;*/&lt;br /&gt;//clip objects below the water line, and render the scene upside down&lt;br /&gt;&lt;/span&gt;GraphicsDevice.RenderState.CullMode = &lt;span style="COLOR: rgb(43,145,175)"&gt;CullMode&lt;/span&gt;.CullClockwiseFace;&lt;br /&gt;&lt;br /&gt;GraphicsDevice.SetRenderTarget(0, mReflectionMap);&lt;br /&gt;GraphicsDevice.Clear(&lt;span style="COLOR: rgb(43,145,175)"&gt;ClearOptions&lt;/span&gt;.Target  &lt;span style="COLOR: rgb(43,145,175)"&gt;ClearOptions&lt;/span&gt;.DepthBuffer, mOptions.WaterColor, 1.0f, 0);&lt;br /&gt;&lt;br /&gt;&lt;span style="color:green;"&gt;//reflection plane in local space&lt;br /&gt;&lt;/span&gt;&lt;span style="COLOR: rgb(43,145,175)"&gt;Vector4 &lt;/span&gt;waterPlaneL = &lt;span style="color:blue;"&gt;new &lt;/span&gt;&lt;span style="COLOR: rgb(43,145,175)"&gt;Vector4&lt;/span&gt;(0.0f, -1.0f, 0.0f, 0.0f);&lt;br /&gt;&lt;br /&gt;&lt;span style="COLOR: rgb(43,145,175)"&gt;Matrix &lt;/span&gt;wInvTrans = &lt;span style="COLOR: rgb(43,145,175)"&gt;Matrix&lt;/span&gt;.Invert(mWorld);&lt;br /&gt;wInvTrans = &lt;span style="COLOR: rgb(43,145,175)"&gt;Matrix&lt;/span&gt;.Transpose(wInvTrans);&lt;br /&gt;&lt;br /&gt;&lt;span style="color:green;"&gt;//reflection plane in world space&lt;br /&gt;&lt;/span&gt;&lt;span style="COLOR: rgb(43,145,175)"&gt;Vector4 &lt;/span&gt;waterPlaneW = &lt;span style="COLOR: rgb(43,145,175)"&gt;Vector4&lt;/span&gt;.Transform(waterPlaneL, wInvTrans);&lt;br /&gt;&lt;br /&gt;&lt;span style="COLOR: rgb(43,145,175)"&gt;Matrix &lt;/span&gt;wvpInvTrans = &lt;span style="COLOR: rgb(43,145,175)"&gt;Matrix&lt;/span&gt;.Invert(mWorld * mViewProj);&lt;br /&gt;wvpInvTrans = &lt;span style="COLOR: rgb(43,145,175)"&gt;Matrix&lt;/span&gt;.Transpose(wvpInvTrans);&lt;br /&gt;&lt;br /&gt;&lt;span style="color:green;"&gt;//reflection plane in homogeneous space&lt;br /&gt;&lt;/span&gt;&lt;span style="COLOR: rgb(43,145,175)"&gt;Vector4 &lt;/span&gt;waterPlaneH = &lt;span style="COLOR: rgb(43,145,175)"&gt;Vector4&lt;/span&gt;.Transform(waterPlaneL, wvpInvTrans);&lt;br /&gt;&lt;br /&gt;GraphicsDevice.ClipPlanes[0].IsEnabled = &lt;span style="color:blue;"&gt;true&lt;/span&gt;;&lt;br /&gt;GraphicsDevice.ClipPlanes[0].Plane = &lt;span style="color:blue;"&gt;new &lt;/span&gt;&lt;span style="COLOR: rgb(43,145,175)"&gt;Plane&lt;/span&gt;(waterPlaneH);&lt;br /&gt;&lt;br /&gt;&lt;span style="COLOR: rgb(43,145,175)"&gt;Matrix &lt;/span&gt;reflectionMatrix = &lt;span style="COLOR: rgb(43,145,175)"&gt;Matrix&lt;/span&gt;.CreateReflection(&lt;span style="color:blue;"&gt;new &lt;/span&gt;&lt;span style="COLOR: rgb(43,145,175)"&gt;Plane&lt;/span&gt;(waterPlaneW));&lt;br /&gt;&lt;br /&gt;&lt;span style="color:blue;"&gt;if &lt;/span&gt;(mDrawFunc != &lt;span style="color:blue;"&gt;null&lt;/span&gt;)&lt;br /&gt;  mDrawFunc(reflectionMatrix);&lt;br /&gt;&lt;br /&gt;GraphicsDevice.RenderState.CullMode = &lt;span style="COLOR: rgb(43,145,175)"&gt;CullMode&lt;/span&gt;.CullCounterClockwiseFace;&lt;br /&gt;&lt;br /&gt;GraphicsDevice.SetRenderTarget(0, &lt;span style="color:blue;"&gt;null&lt;/span&gt;);&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="color:green;"&gt;/*------------------------------------------------------------------------------------------&lt;br /&gt;* Render to the Refraction Map&lt;br /&gt;*/&lt;br /&gt;&lt;br /&gt;//if the application is going to send us the refraction map&lt;br /&gt;//exit early. The refraction map must be given to the water component&lt;br /&gt;//before it renders&lt;br /&gt;&lt;/span&gt;&lt;span style="color:blue;"&gt;if &lt;/span&gt;(mGrabRefractionFromFB)&lt;br /&gt;{&lt;br /&gt;  GraphicsDevice.ClipPlanes[0].IsEnabled = &lt;span style="color:blue;"&gt;false&lt;/span&gt;;&lt;br /&gt;  &lt;span style="color:blue;"&gt;return&lt;/span&gt;;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;span style="color:green;"&gt;//update the refraction map, clip objects above the water line&lt;br /&gt;//so we don't get artifacts&lt;br /&gt;&lt;/span&gt;GraphicsDevice.SetRenderTarget(0, mRefractionMap);&lt;br /&gt;GraphicsDevice.Clear(&lt;span style="COLOR: rgb(43,145,175)"&gt;ClearOptions&lt;/span&gt;.Target  &lt;span style="COLOR: rgb(43,145,175)"&gt;ClearOptions&lt;/span&gt;.DepthBuffer, mOptions.WaterColor, 1.0f, 1);&lt;br /&gt;&lt;br /&gt;&lt;span style="color:green;"&gt;//reflection plane in local space&lt;br /&gt;&lt;/span&gt;waterPlaneL.W = 2.5f;&lt;br /&gt;&lt;br /&gt;&lt;span style="color:green;"&gt;//if we're below the water line, don't perform clipping.&lt;br /&gt;//this allows us to see the distorted objects from under the water&lt;br /&gt;&lt;/span&gt;&lt;span style="color:blue;"&gt;if &lt;/span&gt;(mViewPos.Y &amp;lt; mWorld.Translation.Y)&lt;br /&gt;{&lt;br /&gt;  GraphicsDevice.ClipPlanes[0].IsEnabled = &lt;span style="color:blue;"&gt;false&lt;/span&gt;;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;span style="color:blue;"&gt;if &lt;/span&gt;(mDrawFunc != &lt;span style="color:blue;"&gt;null&lt;/span&gt;)&lt;br /&gt;  mDrawFunc(&lt;span style="COLOR: rgb(43,145,175)"&gt;Matrix&lt;/span&gt;.Identity);&lt;br /&gt;&lt;br /&gt;GraphicsDevice.ClipPlanes[0].IsEnabled = &lt;span style="color:blue;"&gt;false&lt;/span&gt;;&lt;br /&gt;&lt;br /&gt;GraphicsDevice.SetRenderTarget(0, &lt;span style="color:blue;"&gt;null&lt;/span&gt;);&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;span style="color:gray;"&gt;/// &amp;lt;summary&amp;gt;&lt;br /&gt;/// &lt;/span&gt;&lt;span style="color:green;"&gt;Updates effect parameters related to the water shader&lt;br /&gt;&lt;/span&gt;&lt;span style="color:gray;"&gt;/// &amp;lt;/summary&amp;gt;&lt;br /&gt;&lt;/span&gt;&lt;span style="color:blue;"&gt;private void &lt;/span&gt;UpdateEffectParams()&lt;br /&gt;{&lt;br /&gt;&lt;span style="color:green;"&gt;//update the reflection and refraction textures&lt;br /&gt;&lt;/span&gt;mEffect.Parameters[&lt;span style="COLOR: rgb(163,21,21)"&gt;"ReflectMap"&lt;/span&gt;].SetValue(mReflectionMap.GetTexture());&lt;br /&gt;mEffect.Parameters[&lt;span style="COLOR: rgb(163,21,21)"&gt;"RefractMap"&lt;/span&gt;].SetValue(mRefractionMap.GetTexture());&lt;br /&gt;&lt;br /&gt;&lt;span style="color:green;"&gt;//normal map offsets&lt;br /&gt;&lt;/span&gt;mEffect.Parameters[&lt;span style="COLOR: rgb(163,21,21)"&gt;"WaveMapOffset0"&lt;/span&gt;].SetValue(mOptions.WaveMapOffset0);&lt;br /&gt;mEffect.Parameters[&lt;span style="COLOR: rgb(163,21,21)"&gt;"WaveMapOffset1"&lt;/span&gt;].SetValue(mOptions.WaveMapOffset1);&lt;br /&gt;&lt;br /&gt;mEffect.Parameters[&lt;span style="COLOR: rgb(163,21,21)"&gt;"WorldViewProj"&lt;/span&gt;].SetValue(mWorld * mViewProj);&lt;br /&gt;&lt;br /&gt;mEffect.Parameters[&lt;span style="COLOR: rgb(163,21,21)"&gt;"EyePos"&lt;/span&gt;].SetValue(mViewPos);&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;span style="color:gray;"&gt;/// &amp;lt;summary&amp;gt;&lt;br /&gt;/// &lt;/span&gt;&lt;span style="color:green;"&gt;Generates a grid of vertices to use for the water plane.&lt;br /&gt;&lt;/span&gt;&lt;span style="color:gray;"&gt;/// &amp;lt;/summary&amp;gt;&lt;br /&gt;/// &amp;lt;param name="numVertRows"&amp;gt;&lt;/span&gt;&lt;span style="color:green;"&gt;Number of rows. Must be 2^n + 1. Ex. 129, 257, 513.&lt;/span&gt;&lt;span style="color:gray;"&gt;&amp;lt;/param&amp;gt;&lt;br /&gt;/// &amp;lt;param name="numVertCols"&amp;gt;&lt;/span&gt;&lt;span style="color:green;"&gt;Number of columns. Must be 2^n + 1. Ex. 129, 257, 513.&lt;/span&gt;&lt;span style="color:gray;"&gt;&amp;lt;/param&amp;gt;&lt;br /&gt;/// &amp;lt;param name="dx"&amp;gt;&lt;/span&gt;&lt;span style="color:green;"&gt;Cell spacing in the x dimension.&lt;/span&gt;&lt;span style="color:gray;"&gt;&amp;lt;/param&amp;gt;&lt;br /&gt;/// &amp;lt;param name="dz"&amp;gt;&lt;/span&gt;&lt;span style="color:green;"&gt;Cell spacing in the y dimension.&lt;/span&gt;&lt;span style="color:gray;"&gt;&amp;lt;/param&amp;gt;&lt;br /&gt;/// &amp;lt;param name="center"&amp;gt;&lt;/span&gt;&lt;span style="color:green;"&gt;Center of the plane.&lt;/span&gt;&lt;span style="color:gray;"&gt;&amp;lt;/param&amp;gt;&lt;br /&gt;/// &amp;lt;param name="verts"&amp;gt;&lt;/span&gt;&lt;span style="color:green;"&gt;Outputs the constructed vertices for the plane.&lt;/span&gt;&lt;span style="color:gray;"&gt;&amp;lt;/param&amp;gt;&lt;br /&gt;/// &amp;lt;param name="indices"&amp;gt;&lt;/span&gt;&lt;span style="color:green;"&gt;Outpus the constructed triangle indices for the plane.&lt;/span&gt;&lt;span style="color:gray;"&gt;&amp;lt;/param&amp;gt;&lt;br /&gt;&lt;/span&gt;&lt;span style="color:blue;"&gt;private void &lt;/span&gt;GenTriGrid(&lt;span style="color:blue;"&gt;int &lt;/span&gt;numVertRows, &lt;span style="color:blue;"&gt;int &lt;/span&gt;numVertCols, &lt;span style="color:blue;"&gt;float &lt;/span&gt;dx, &lt;span style="color:blue;"&gt;float &lt;/span&gt;dz,&lt;br /&gt;                  &lt;span style="COLOR: rgb(43,145,175)"&gt;Vector3 &lt;/span&gt;center, &lt;span style="color:blue;"&gt;out &lt;/span&gt;&lt;span style="COLOR: rgb(43,145,175)"&gt;Vector3&lt;/span&gt;[] verts, &lt;span style="color:blue;"&gt;out int&lt;/span&gt;[] indices)&lt;br /&gt;{&lt;br /&gt;&lt;span style="color:blue;"&gt;int &lt;/span&gt;numVertices = numVertRows * numVertCols;&lt;br /&gt;&lt;span style="color:blue;"&gt;int &lt;/span&gt;numCellRows = numVertRows - 1;&lt;br /&gt;&lt;span style="color:blue;"&gt;int &lt;/span&gt;numCellCols = numVertCols - 1;&lt;br /&gt;&lt;br /&gt;&lt;span style="color:blue;"&gt;int &lt;/span&gt;mNumTris = numCellRows * numCellCols * 2;&lt;br /&gt;&lt;br /&gt;&lt;span style="color:blue;"&gt;float &lt;/span&gt;width = (&lt;span style="color:blue;"&gt;float&lt;/span&gt;)numCellCols * dx;&lt;br /&gt;&lt;span style="color:blue;"&gt;float &lt;/span&gt;depth = (&lt;span style="color:blue;"&gt;float&lt;/span&gt;)numCellRows * dz;&lt;br /&gt;&lt;br /&gt;&lt;span style="color:green;"&gt;//===========================================&lt;br /&gt;// Build vertices.&lt;br /&gt;&lt;br /&gt;// We first build the grid geometry centered about the origin and on&lt;br /&gt;// the xz-plane, row-by-row and in a top-down fashion.  We then translate&lt;br /&gt;// the grid vertices so that they are centered about the specified&lt;br /&gt;// parameter 'center'.&lt;br /&gt;&lt;br /&gt;//verts.resize(numVertices);&lt;br /&gt;&lt;/span&gt;verts = &lt;span style="color:blue;"&gt;new &lt;/span&gt;&lt;span style="COLOR: rgb(43,145,175)"&gt;Vector3&lt;/span&gt;[numVertices];&lt;br /&gt;&lt;br /&gt;&lt;span style="color:green;"&gt;// Offsets to translate grid from quadrant 4 to center of&lt;br /&gt;// coordinate system.&lt;br /&gt;&lt;/span&gt;&lt;span style="color:blue;"&gt;float &lt;/span&gt;xOffset = -width * 0.5f;&lt;br /&gt;&lt;span style="color:blue;"&gt;float &lt;/span&gt;zOffset = depth * 0.5f;&lt;br /&gt;&lt;br /&gt;&lt;span style="color:blue;"&gt;int &lt;/span&gt;k = 0;&lt;br /&gt;&lt;span style="color:blue;"&gt;for &lt;/span&gt;(&lt;span style="color:blue;"&gt;float &lt;/span&gt;i = 0; i &amp;lt; numVertRows; ++i)&lt;br /&gt;{&lt;br /&gt;  &lt;span style="color:blue;"&gt;for &lt;/span&gt;(&lt;span style="color:blue;"&gt;float &lt;/span&gt;j = 0; j &amp;lt; numVertCols; ++j)&lt;br /&gt;  {&lt;br /&gt;      &lt;span style="color:green;"&gt;// Negate the depth coordinate to put in quadrant four.&lt;br /&gt;      // Then offset to center about coordinate system.&lt;br /&gt;      &lt;/span&gt;verts[k] = &lt;span style="color:blue;"&gt;new &lt;/span&gt;&lt;span style="COLOR: rgb(43,145,175)"&gt;Vector3&lt;/span&gt;(0, 0, 0);&lt;br /&gt;      verts[k].X = j * dx + xOffset;&lt;br /&gt;      verts[k].Z = -i * dz + zOffset;&lt;br /&gt;      verts[k].Y = 0.0f;&lt;br /&gt;&lt;br /&gt;      &lt;span style="COLOR: rgb(43,145,175)"&gt;Matrix &lt;/span&gt;translation = &lt;span style="COLOR: rgb(43,145,175)"&gt;Matrix&lt;/span&gt;.CreateTranslation(center);&lt;br /&gt;      verts[k] = &lt;span style="COLOR: rgb(43,145,175)"&gt;Vector3&lt;/span&gt;.Transform(verts[k], translation);&lt;br /&gt;&lt;br /&gt;      ++k; &lt;span style="color:green;"&gt;// Next vertex&lt;br /&gt;  &lt;/span&gt;}&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;span style="color:green;"&gt;//===========================================&lt;br /&gt;// Build indices.&lt;br /&gt;&lt;br /&gt;//indices.resize(mNumTris * 3);&lt;br /&gt;&lt;/span&gt;indices = &lt;span style="color:blue;"&gt;new int&lt;/span&gt;[mNumTris * 3];&lt;br /&gt;&lt;br /&gt;&lt;span style="color:green;"&gt;// Generate indices for each quad.&lt;br /&gt;&lt;/span&gt;k = 0;&lt;br /&gt;&lt;span style="color:blue;"&gt;for &lt;/span&gt;(&lt;span style="color:blue;"&gt;int &lt;/span&gt;i = 0; i &amp;lt; numCellRows; ++i)&lt;br /&gt;{&lt;br /&gt;  &lt;span style="color:blue;"&gt;for &lt;/span&gt;(&lt;span style="color:blue;"&gt;int &lt;/span&gt;j = 0; j &amp;lt; numCellCols; ++j)&lt;br /&gt;  {&lt;br /&gt;      indices[k] = i * numVertCols + j;&lt;br /&gt;      indices[k + 1] = i * numVertCols + j + 1;&lt;br /&gt;      indices[k + 2] = (i + 1) * numVertCols + j;&lt;br /&gt;&lt;br /&gt;      indices[k + 3] = (i + 1) * numVertCols + j;&lt;br /&gt;      indices[k + 4] = i * numVertCols + j + 1;&lt;br /&gt;      indices[k + 5] = (i + 1) * numVertCols + j + 1;&lt;br /&gt;&lt;br /&gt;      &lt;span style="color:green;"&gt;// next quad&lt;br /&gt;      &lt;/span&gt;k += 6;&lt;br /&gt;  }&lt;br /&gt;}&lt;br /&gt;}&lt;br /&gt;}&lt;br /&gt;}&lt;/pre&gt;&lt;span style="FONT-WEIGHT: bold"&gt;&lt;br /&gt;Water.fx shader:&lt;/span&gt;&lt;br /&gt;&lt;pre class="mycode"&gt;&lt;span style="color:green;"&gt;//Water effect shader that uses reflection and refraction maps projected onto the water.&lt;br /&gt;//These maps are distorted based on the two scrolling normal maps.&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style="color:blue;"&gt;float4x4 &lt;/span&gt;World;&lt;br /&gt;&lt;span style="color:blue;"&gt;float4x4 &lt;/span&gt;WorldViewProj;&lt;br /&gt;&lt;br /&gt;&lt;span style="color:blue;"&gt;float4  &lt;/span&gt;WaterColor;&lt;br /&gt;&lt;span style="color:blue;"&gt;float3    &lt;/span&gt;SunDirection;&lt;br /&gt;&lt;span style="color:blue;"&gt;float4  &lt;/span&gt;SunColor;&lt;br /&gt;&lt;span style="color:blue;"&gt;float    &lt;/span&gt;SunFactor; &lt;span style="color:green;"&gt;//the intensity of the sun specular term.&lt;br /&gt;&lt;/span&gt;&lt;span style="color:blue;"&gt;float   &lt;/span&gt;SunPower; &lt;span style="color:green;"&gt;//how shiny we want the sun specular term on the water to be.&lt;br /&gt;&lt;/span&gt;&lt;span style="color:blue;"&gt;float3  &lt;/span&gt;EyePos;&lt;br /&gt;&lt;br /&gt;&lt;span style="color:green;"&gt;// Texture coordinate offset vectors for scrolling&lt;br /&gt;// normal maps.&lt;br /&gt;&lt;/span&gt;&lt;span style="color:blue;"&gt;float2  &lt;/span&gt;WaveMapOffset0;&lt;br /&gt;&lt;span style="color:blue;"&gt;float2  &lt;/span&gt;WaveMapOffset1;&lt;br /&gt;&lt;br /&gt;&lt;span style="color:green;"&gt;// Two normal maps and the reflection/refraction maps&lt;br /&gt;&lt;/span&gt;&lt;span style="color:blue;"&gt;texture &lt;/span&gt;WaveMap0;&lt;br /&gt;&lt;span style="color:blue;"&gt;texture &lt;/span&gt;WaveMap1;&lt;br /&gt;&lt;span style="color:blue;"&gt;texture &lt;/span&gt;ReflectMap;&lt;br /&gt;&lt;span style="color:blue;"&gt;texture &lt;/span&gt;RefractMap;&lt;br /&gt;&lt;br /&gt;&lt;span style="color:green;"&gt;//scale used on the wave maps&lt;br /&gt;&lt;/span&gt;&lt;span style="color:blue;"&gt;float &lt;/span&gt;TexScale;&lt;br /&gt;&lt;br /&gt;&lt;span style="color:blue;"&gt;static const float      &lt;/span&gt;R0 = 0.02037f;&lt;br /&gt;&lt;br /&gt;&lt;span style="color:blue;"&gt;sampler &lt;/span&gt;WaveMapS0 = &lt;span style="color:blue;"&gt;sampler_state&lt;br /&gt;&lt;/span&gt;{&lt;br /&gt;Texture = &amp;lt;WaveMap0&amp;gt;;&lt;br /&gt;&lt;span style="color:blue;"&gt;MinFilter &lt;/span&gt;= &lt;span style="color:navy;"&gt;LINEAR&lt;/span&gt;;&lt;br /&gt;&lt;span style="color:blue;"&gt;MagFilter &lt;/span&gt;= &lt;span style="color:navy;"&gt;LINEAR&lt;/span&gt;;&lt;br /&gt;&lt;span style="color:blue;"&gt;MipFilter &lt;/span&gt;= &lt;span style="color:navy;"&gt;LINEAR&lt;/span&gt;;&lt;br /&gt;&lt;span style="color:blue;"&gt;AddressU  &lt;/span&gt;= &lt;span style="color:navy;"&gt;WRAP&lt;/span&gt;;&lt;br /&gt;&lt;span style="color:blue;"&gt;AddressV  &lt;/span&gt;= &lt;span style="color:navy;"&gt;WRAP&lt;/span&gt;;&lt;br /&gt;};&lt;br /&gt;&lt;br /&gt;&lt;span style="color:blue;"&gt;sampler &lt;/span&gt;WaveMapS1 = &lt;span style="color:blue;"&gt;sampler_state&lt;br /&gt;&lt;/span&gt;{&lt;br /&gt;Texture = &amp;lt;WaveMap1&amp;gt;;&lt;br /&gt;&lt;span style="color:blue;"&gt;MinFilter &lt;/span&gt;= &lt;span style="color:navy;"&gt;LINEAR&lt;/span&gt;;&lt;br /&gt;&lt;span style="color:blue;"&gt;MagFilter &lt;/span&gt;= &lt;span style="color:navy;"&gt;LINEAR&lt;/span&gt;;&lt;br /&gt;&lt;span style="color:blue;"&gt;MipFilter &lt;/span&gt;= &lt;span style="color:navy;"&gt;LINEAR&lt;/span&gt;;&lt;br /&gt;&lt;span style="color:blue;"&gt;AddressU  &lt;/span&gt;= &lt;span style="color:navy;"&gt;WRAP&lt;/span&gt;;&lt;br /&gt;&lt;span style="color:blue;"&gt;AddressV  &lt;/span&gt;= &lt;span style="color:navy;"&gt;WRAP&lt;/span&gt;;&lt;br /&gt;};&lt;br /&gt;&lt;br /&gt;&lt;span style="color:blue;"&gt;sampler &lt;/span&gt;ReflectMapS = &lt;span style="color:blue;"&gt;sampler_state&lt;br /&gt;&lt;/span&gt;{&lt;br /&gt;Texture = &amp;lt;ReflectMap&amp;gt;;&lt;br /&gt;&lt;span style="color:blue;"&gt;MinFilter &lt;/span&gt;= &lt;span style="color:navy;"&gt;LINEAR&lt;/span&gt;;&lt;br /&gt;&lt;span style="color:blue;"&gt;MagFilter &lt;/span&gt;= &lt;span style="color:navy;"&gt;LINEAR&lt;/span&gt;;&lt;br /&gt;&lt;span style="color:blue;"&gt;MipFilter &lt;/span&gt;= &lt;span style="color:navy;"&gt;LINEAR&lt;/span&gt;;&lt;br /&gt;&lt;span style="color:blue;"&gt;AddressU  &lt;/span&gt;= &lt;span style="color:navy;"&gt;CLAMP&lt;/span&gt;;&lt;br /&gt;&lt;span style="color:blue;"&gt;AddressV  &lt;/span&gt;= &lt;span style="color:navy;"&gt;CLAMP&lt;/span&gt;;&lt;br /&gt;};&lt;br /&gt;&lt;br /&gt;&lt;span style="color:blue;"&gt;sampler &lt;/span&gt;RefractMapS = &lt;span style="color:blue;"&gt;sampler_state&lt;br /&gt;&lt;/span&gt;{&lt;br /&gt;Texture = &amp;lt;RefractMap&amp;gt;;&lt;br /&gt;&lt;span style="color:blue;"&gt;MinFilter &lt;/span&gt;= &lt;span style="color:navy;"&gt;LINEAR&lt;/span&gt;;&lt;br /&gt;&lt;span style="color:blue;"&gt;MagFilter &lt;/span&gt;= &lt;span style="color:navy;"&gt;LINEAR&lt;/span&gt;;&lt;br /&gt;&lt;span style="color:blue;"&gt;MipFilter &lt;/span&gt;= &lt;span style="color:navy;"&gt;LINEAR&lt;/span&gt;;&lt;br /&gt;&lt;span style="color:blue;"&gt;AddressU  &lt;/span&gt;= &lt;span style="color:navy;"&gt;CLAMP&lt;/span&gt;;&lt;br /&gt;&lt;span style="color:blue;"&gt;AddressV  &lt;/span&gt;= &lt;span style="color:navy;"&gt;CLAMP&lt;/span&gt;;&lt;br /&gt;};&lt;br /&gt;&lt;br /&gt;&lt;span style="color:blue;"&gt;struct &lt;/span&gt;OutputVS&lt;br /&gt;{&lt;br /&gt;&lt;span style="color:blue;"&gt;float4 &lt;/span&gt;posH            : &lt;span style="color:navy;"&gt;POSITION0&lt;/span&gt;;&lt;br /&gt;&lt;span style="color:blue;"&gt;float3 &lt;/span&gt;toEyeW        : &lt;span style="color:navy;"&gt;TEXCOORD0&lt;/span&gt;;&lt;br /&gt;&lt;span style="color:blue;"&gt;float2 &lt;/span&gt;tex0            : &lt;span style="color:navy;"&gt;TEXCOORD1&lt;/span&gt;;&lt;br /&gt;&lt;span style="color:blue;"&gt;float2 &lt;/span&gt;tex1            : &lt;span style="color:navy;"&gt;TEXCOORD2&lt;/span&gt;;&lt;br /&gt;&lt;span style="color:blue;"&gt;float4 &lt;/span&gt;projTexC        : &lt;span style="color:navy;"&gt;TEXCOORD3&lt;/span&gt;;&lt;br /&gt;&lt;span style="color:blue;"&gt;float4 &lt;/span&gt;pos            : &lt;span style="color:navy;"&gt;TEXCOORD4&lt;/span&gt;;&lt;br /&gt;};&lt;br /&gt;&lt;br /&gt;OutputVS WaterVS( &lt;span style="color:blue;"&gt;float3 &lt;/span&gt;posL    : &lt;span style="color:navy;"&gt;POSITION0&lt;/span&gt;,&lt;br /&gt;         &lt;span style="color:blue;"&gt;float2 &lt;/span&gt;texC   : &lt;span style="color:navy;"&gt;TEXCOORD0&lt;/span&gt;)&lt;br /&gt;{&lt;br /&gt;&lt;span style="color:green;"&gt;// Zero out our output.&lt;br /&gt;&lt;/span&gt;OutputVS outVS = (OutputVS)0;&lt;br /&gt;&lt;br /&gt;&lt;span style="color:green;"&gt;// Transform vertex position to world space.&lt;br /&gt;&lt;/span&gt;&lt;span style="color:blue;"&gt;float3 &lt;/span&gt;posW  = &lt;span style="color:blue;"&gt;mul&lt;/span&gt;(&lt;span style="color:blue;"&gt;float4&lt;/span&gt;(posL, 1.0f), World).xyz;&lt;br /&gt;outVS.pos.xyz = posW;&lt;br /&gt;outVS.pos.w = 1.0f;&lt;br /&gt;&lt;br /&gt;&lt;span style="color:green;"&gt;// Compute the unit vector from the vertex to the eye.&lt;br /&gt;&lt;/span&gt;outVS.toEyeW = posW - EyePos;&lt;br /&gt;&lt;br /&gt;&lt;span style="color:green;"&gt;// Transform to homogeneous clip space.&lt;br /&gt;&lt;/span&gt;outVS.posH = &lt;span style="color:blue;"&gt;mul&lt;/span&gt;(&lt;span style="color:blue;"&gt;float4&lt;/span&gt;(posL, 1.0f), WorldViewProj);&lt;br /&gt;&lt;br /&gt;&lt;span style="color:green;"&gt;// Scroll texture coordinates.&lt;br /&gt;&lt;/span&gt;outVS.tex0 = (texC * TexScale) + WaveMapOffset0;&lt;br /&gt;outVS.tex1 = (texC * TexScale) + WaveMapOffset1;&lt;br /&gt;&lt;br /&gt;&lt;span style="color:green;"&gt;// Generate projective texture coordinates from camera's perspective.&lt;br /&gt;&lt;/span&gt;outVS.projTexC = outVS.posH;&lt;br /&gt;&lt;br /&gt;&lt;span style="color:green;"&gt;// Done--return the output.&lt;br /&gt;&lt;/span&gt;&lt;span style="color:blue;"&gt;return &lt;/span&gt;outVS;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;span style="color:blue;"&gt;float4 &lt;/span&gt;WaterPS( &lt;span style="color:blue;"&gt;float3 &lt;/span&gt;toEyeW        : &lt;span style="color:navy;"&gt;TEXCOORD0&lt;/span&gt;,&lt;br /&gt;       &lt;span style="color:blue;"&gt;float2 &lt;/span&gt;tex0            : &lt;span style="color:navy;"&gt;TEXCOORD1&lt;/span&gt;,&lt;br /&gt;       &lt;span style="color:blue;"&gt;float2 &lt;/span&gt;tex1            : &lt;span style="color:navy;"&gt;TEXCOORD2&lt;/span&gt;,&lt;br /&gt;       &lt;span style="color:blue;"&gt;float4 &lt;/span&gt;projTexC        : &lt;span style="color:navy;"&gt;TEXCOORD3&lt;/span&gt;,&lt;br /&gt;       &lt;span style="color:blue;"&gt;float4 &lt;/span&gt;pos            : &lt;span style="color:navy;"&gt;TEXCOORD4&lt;/span&gt;) : &lt;span style="color:navy;"&gt;COLOR&lt;br /&gt;&lt;/span&gt;{&lt;br /&gt;projTexC.xyz /= projTexC.w;&lt;br /&gt;projTexC.x =  0.5f*projTexC.x + 0.5f;&lt;br /&gt;projTexC.y = -0.5f*projTexC.y + 0.5f;&lt;br /&gt;projTexC.z = .1f / projTexC.z;&lt;br /&gt;&lt;br /&gt;toEyeW    = &lt;span style="color:blue;"&gt;normalize&lt;/span&gt;(toEyeW);&lt;br /&gt;SunDirection = &lt;span style="color:blue;"&gt;normalize&lt;/span&gt;(SunDirection);&lt;br /&gt;&lt;br /&gt;&lt;span style="color:green;"&gt;// Light vector is opposite the direction of the light.&lt;br /&gt;&lt;/span&gt;&lt;span style="color:blue;"&gt;float3 &lt;/span&gt;lightVecW = -SunDirection;&lt;br /&gt;&lt;br /&gt;&lt;span style="color:green;"&gt;// Sample normal map.&lt;br /&gt;&lt;/span&gt;&lt;span style="color:blue;"&gt;float3 &lt;/span&gt;normalT0 = &lt;span style="color:blue;"&gt;tex2D&lt;/span&gt;(WaveMapS0, tex0);&lt;br /&gt;&lt;span style="color:blue;"&gt;float3 &lt;/span&gt;normalT1 = &lt;span style="color:blue;"&gt;tex2D&lt;/span&gt;(WaveMapS1, tex1);&lt;br /&gt;&lt;br /&gt;&lt;span style="color:green;"&gt;//unroll the normals retrieved from the normalmaps&lt;br /&gt;&lt;/span&gt;normalT0.yz = normalT0.zy;&lt;br /&gt;normalT1.yz = normalT1.zy;&lt;br /&gt;&lt;br /&gt;normalT0 = 2.0f*normalT0 - 1.0f;&lt;br /&gt;normalT1 = 2.0f*normalT1 - 1.0f;&lt;br /&gt;&lt;br /&gt;&lt;span style="color:blue;"&gt;float3 &lt;/span&gt;normalT = &lt;span style="color:blue;"&gt;normalize&lt;/span&gt;(0.5f*(normalT0 + normalT1));&lt;br /&gt;&lt;span style="color:blue;"&gt;float3 &lt;/span&gt;n1 = &lt;span style="color:blue;"&gt;float3&lt;/span&gt;(0,1,0); &lt;span style="color:green;"&gt;//we'll just use the y unit vector for spec reflection.&lt;br /&gt;&lt;br /&gt;//get the reflection vector from the eye&lt;br /&gt;&lt;/span&gt;&lt;span style="color:blue;"&gt;float3 &lt;/span&gt;R = &lt;span style="color:blue;"&gt;normalize&lt;/span&gt;(&lt;span style="color:blue;"&gt;reflect&lt;/span&gt;(toEyeW,normalT));&lt;br /&gt;&lt;br /&gt;&lt;span style="color:blue;"&gt;float4 &lt;/span&gt;finalColor;&lt;br /&gt;finalColor.a = 1;&lt;br /&gt;&lt;br /&gt;&lt;span style="color:green;"&gt;//compute the fresnel term to blend reflection and refraction maps&lt;br /&gt;&lt;/span&gt;&lt;span style="color:blue;"&gt;float &lt;/span&gt;ang = &lt;span style="color:blue;"&gt;saturate&lt;/span&gt;(&lt;span style="color:blue;"&gt;dot&lt;/span&gt;(-toEyeW,n1));&lt;br /&gt;&lt;span style="color:blue;"&gt;float &lt;/span&gt;f = R0 + (1.0f-R0) * &lt;span style="color:blue;"&gt;pow&lt;/span&gt;(1.0f-ang,5.0);&lt;br /&gt;&lt;br /&gt;&lt;span style="color:green;"&gt;//also blend based on distance&lt;br /&gt;&lt;/span&gt;f = &lt;span style="color:blue;"&gt;min&lt;/span&gt;(1.0f, f + 0.007f * EyePos.y);&lt;br /&gt;&lt;br /&gt;&lt;span style="color:green;"&gt;//compute the reflection from sunlight, hacked in color, should be a variable&lt;br /&gt;&lt;/span&gt;&lt;span style="color:blue;"&gt;float &lt;/span&gt;sunFactor = SunFactor;&lt;br /&gt;&lt;span style="color:blue;"&gt;float &lt;/span&gt;sunPower = SunPower;&lt;br /&gt;&lt;br /&gt;&lt;span style="color:blue;"&gt;if&lt;/span&gt;(EyePos.y &amp;lt; pos.y)&lt;br /&gt;{&lt;br /&gt;sunFactor = 7.0f; &lt;span style="color:green;"&gt;//these could also be sent to the shader&lt;br /&gt;&lt;/span&gt;sunPower = 55.0f;&lt;br /&gt;}&lt;br /&gt;&lt;span style="color:blue;"&gt;float3 &lt;/span&gt;sunlight = sunFactor * &lt;span style="color:blue;"&gt;pow&lt;/span&gt;(&lt;span style="color:blue;"&gt;saturate&lt;/span&gt;(&lt;span style="color:blue;"&gt;dot&lt;/span&gt;(R, lightVecW)), sunPower) * SunColor;&lt;br /&gt;&lt;br /&gt;&lt;span style="color:blue;"&gt;float4 &lt;/span&gt;refl = &lt;span style="color:blue;"&gt;tex2D&lt;/span&gt;(ReflectMapS, projTexC.xy + projTexC.z * normalT.xz);&lt;br /&gt;&lt;span style="color:blue;"&gt;float4 &lt;/span&gt;refr = &lt;span style="color:blue;"&gt;tex2D&lt;/span&gt;(RefractMapS, projTexC.xy - projTexC.z * normalT.xz);&lt;br /&gt;&lt;br /&gt;&lt;span style="color:green;"&gt;//only use the refraction map if we're under water&lt;br /&gt;&lt;/span&gt;&lt;span style="color:blue;"&gt;if&lt;/span&gt;(EyePos.y &amp;lt; pos.y)&lt;br /&gt;f = 0.0f;&lt;br /&gt;&lt;br /&gt;&lt;span style="color:green;"&gt;//interpolate the reflection and refraction maps based on the fresnel term and add the sunlight&lt;br /&gt;&lt;/span&gt;finalColor.rgb = WaterColor * &lt;span style="color:blue;"&gt;lerp&lt;/span&gt;( refr, refl, f) + sunlight;&lt;br /&gt;&lt;br /&gt;&lt;span style="color:blue;"&gt;return &lt;/span&gt;finalColor;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;span style="color:blue;"&gt;technique &lt;/span&gt;WaterTech&lt;br /&gt;{&lt;br /&gt;&lt;span style="color:blue;"&gt;pass &lt;/span&gt;Pass1&lt;br /&gt;{&lt;br /&gt;&lt;span style="color:green;"&gt;// Specify the vertex and pixel shader associated with this pass.&lt;br /&gt;&lt;/span&gt;vertexShader = &lt;span style="color:blue;"&gt;compile &lt;/span&gt;&lt;span style="color:purple;"&gt;vs_2_0 &lt;/span&gt;WaterVS();&lt;br /&gt;pixelShader  = &lt;span style="color:blue;"&gt;compile &lt;/span&gt;&lt;span style="color:purple;"&gt;ps_2_0 &lt;/span&gt;WaterPS();&lt;br /&gt;&lt;br /&gt;&lt;span style="color:gray;"&gt;CullMode &lt;/span&gt;= None;&lt;br /&gt;}&lt;br /&gt;}&lt;/pre&gt;&lt;br /&gt;&lt;span style="color:#ff6600;"&gt;&lt;strong&gt;Edit:&lt;/strong&gt; A demo of the water component is now available.&lt;/span&gt;&lt;br /&gt;&lt;iframe style="BORDER-RIGHT: #dde5e9 1px solid; PADDING-RIGHT: 0px; BORDER-TOP: #dde5e9 1px solid; PADDING-LEFT: 0px; PADDING-BOTTOM: 0px; MARGIN: 3px; BORDER-LEFT: #dde5e9 1px solid; WIDTH: 240px; PADDING-TOP: 0px; BORDER-BOTTOM: #dde5e9 1px solid; HEIGHT: 66px; BACKGROUND-COLOR: #ffffff" marginwidth="0" marginheight="0" src="http://cid-b80a3031b5bfa52b.skydrive.live.com/embedrowdetail.aspx/Public/WaterComponentDemo.zip" frameborder="0" scrolling="no"&gt;&lt;/iframe&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1023441640234597436-5580231081199704368?l=graphicsrunner.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://graphicsrunner.blogspot.com/feeds/5580231081199704368/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=1023441640234597436&amp;postID=5580231081199704368' title='27 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1023441640234597436/posts/default/5580231081199704368'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1023441640234597436/posts/default/5580231081199704368'/><link rel='alternate' type='text/html' href='http://graphicsrunner.blogspot.com/2008/11/water-game-component.html' title='Water Game Component'/><author><name>Kyle Hayward</name><uri>http://www.blogger.com/profile/00654406875137720609</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://bp3.blogger.com/_VelpN_FHzhk/R-Lx4Kk_nkI/AAAAAAAAAN8/jn_4uDeKxNk/S220/me.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/_VelpN_FHzhk/SR2XHtoSfAI/AAAAAAAAAac/9xQ9F4dQs7w/s72-c/water.png' height='72' width='72'/><thr:total>27</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1023441640234597436.post-2340644652116651787</id><published>2008-11-07T01:20:00.003-05:00</published><updated>2008-11-07T01:22:48.676-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Scientific Visualization'/><category scheme='http://www.blogger.com/atom/ns#' term='Volume Rendering'/><title type='text'>Scientific Visualization</title><content type='html'>&lt;p&gt;This semester I've been taking a class in scientific visualization. It's pretty interesting and it covers a lot of techniques such as color visualization, human vision and color perception, contours, isosurfacing, volume rendering, flow visualization such as stream lines and stream surfaces and texture based methods.&lt;/p&gt;  &lt;p&gt;There are good and bad parts to this course. The bad part is that it is a fairly new graduate course and has no prerequisites. The professor is just trying to build up interest in the course. This equates to us not actually coding the different algorithms, but using a visualization framework, VTK, and c++/java/python/tcl to implement the techniques. The good part is that this  semester is one of the busiest I've had, so minimizing my work is a good thing :)&lt;/p&gt;  &lt;p&gt;Now on to some pictures.&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;Contours, Heightmaps:&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt; &lt;a href="http://lh6.ggpht.com/_VelpN_FHzhk/SRPeD-mLHYI/AAAAAAAAAX4/Egjul2Nd0wo/s1600-h/brain1%5B5%5D.png"&gt;&lt;img style="border: 0px none ;" alt="brain1" src="http://lh4.ggpht.com/_VelpN_FHzhk/SRPeEJEAUyI/AAAAAAAAAX8/UzFxRcTaq8w/brain1_thumb%5B3%5D.png?imgmax=800" border="0" height="224" width="200" /&gt;&lt;/a&gt; &lt;a href="http://lh5.ggpht.com/_VelpN_FHzhk/SRPeEpp3z1I/AAAAAAAAAYA/NeMo2cVJotw/s1600-h/brainHM2%5B6%5D.jpg"&gt;&lt;img style="border: 0px none ;" alt="brainHM2" src="http://lh4.ggpht.com/_VelpN_FHzhk/SRPeEw0FGNI/AAAAAAAAAYE/ibm0-pUaQXs/brainHM2_thumb%5B4%5D.jpg?imgmax=800" border="0" height="193" width="200" /&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;Isosurfacing:&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;&lt;a href="http://lh3.ggpht.com/_VelpN_FHzhk/SRPeFIfvVRI/AAAAAAAAAYI/Fr7lqYtDwkA/s1600-h/2%5B9%5D.jpg"&gt;&lt;img style="border: 0px none ;" alt="2" src="http://lh5.ggpht.com/_VelpN_FHzhk/SRPeFkv7ewI/AAAAAAAAAYM/1Fq-rdqWZKg/2_thumb%5B7%5D.jpg?imgmax=800" border="0" height="195" width="200" /&gt;&lt;/a&gt;&lt;a href="http://lh3.ggpht.com/_VelpN_FHzhk/SRPeFyZEakI/AAAAAAAAAYQ/mZiU_3cUMJM/s1600-h/5%5B7%5D.jpg"&gt;&lt;img style="border: 0px none ;" alt="5" src="http://lh4.ggpht.com/_VelpN_FHzhk/SRPeGWePSpI/AAAAAAAAAYU/NBA5ZIVcljE/5_thumb%5B5%5D.jpg?imgmax=800" border="0" height="207" width="170" /&gt;&lt;/a&gt; &lt;/p&gt;  &lt;p&gt;&lt;a href="http://lh5.ggpht.com/_VelpN_FHzhk/SRPeGhajEEI/AAAAAAAAAYY/gaWhL770q80/s1600-h/2_0%5B5%5D.jpg"&gt;&lt;img style="border: 0px none ;" alt="2_0" src="http://lh4.ggpht.com/_VelpN_FHzhk/SRPeGxR5lJI/AAAAAAAAAYc/lMH4rArFX-4/2_0_thumb%5B3%5D.jpg?imgmax=800" border="0" height="193" width="200" /&gt;&lt;/a&gt; &lt;a href="http://lh5.ggpht.com/_VelpN_FHzhk/SRPeHAUV3SI/AAAAAAAAAYg/tpFXCu4ekyI/s1600-h/6%5B5%5D.jpg"&gt;&lt;img style="border: 0px none ;" alt="6" src="http://lh3.ggpht.com/_VelpN_FHzhk/SRPeHWYqzHI/AAAAAAAAAYk/xBnfC_F6Qnc/6_thumb%5B3%5D.jpg?imgmax=800" border="0" height="175" width="200" /&gt;&lt;/a&gt; &lt;/p&gt;  &lt;p&gt;&lt;a href="http://lh4.ggpht.com/_VelpN_FHzhk/SRPeHryOwGI/AAAAAAAAAYo/8WREDDOsdkw/s1600-h/7%5B5%5D.jpg"&gt;&lt;img style="border: 0px none ;" alt="7" src="http://lh4.ggpht.com/_VelpN_FHzhk/SRPeH5bp3QI/AAAAAAAAAYs/FAb0KpydiWo/7_thumb%5B3%5D.jpg?imgmax=800" border="0" height="171" width="200" /&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;Volume Rendering:&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt; &lt;a href="http://lh5.ggpht.com/_VelpN_FHzhk/SRPeIT6AzOI/AAAAAAAAAYw/M31u8146FAE/s1600-h/dist_0_25%5B5%5D.jpg"&gt;&lt;img style="border: 0px none ;" alt="dist_0_25" src="http://lh4.ggpht.com/_VelpN_FHzhk/SRPeIdxzbMI/AAAAAAAAAY0/rJ6MLqCUWbQ/dist_0_25_thumb%5B3%5D.jpg?imgmax=800" border="0" height="226" width="200" /&gt;&lt;/a&gt; &lt;a href="http://lh3.ggpht.com/_VelpN_FHzhk/SRPeIrn9xlI/AAAAAAAAAY4/qTewGfZKN_A/s1600-h/vol1_inv%5B5%5D.jpg"&gt;&lt;img style="border: 0px none ;" alt="vol1_inv" src="http://lh3.ggpht.com/_VelpN_FHzhk/SRPeIweIVsI/AAAAAAAAAY8/EGkA1U-ezbI/vol1_inv_thumb%5B3%5D.jpg?imgmax=800" border="0" height="227" width="200" /&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt; &lt;a href="http://lh6.ggpht.com/_VelpN_FHzhk/SRPeJa_50cI/AAAAAAAAAZA/jYbcKVVPOX0/s1600-h/mip1%5B4%5D.png"&gt;&lt;img style="border: 0px none ;" alt="mip1" src="http://lh4.ggpht.com/_VelpN_FHzhk/SRPeJ29Nk7I/AAAAAAAAAZE/ycJXxRNGPH0/mip1_thumb%5B2%5D.png?imgmax=800" border="0" height="240" width="188" /&gt;&lt;/a&gt; &lt;/p&gt;  &lt;p&gt;&lt;strong&gt;Glyphs, Stream lines, stream surfaces:&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;&lt;a href="http://lh5.ggpht.com/_VelpN_FHzhk/SRPeKNrRwTI/AAAAAAAAAZI/2l26rcyH5Gs/s1600-h/glyph2%5B5%5D.png"&gt;&lt;img style="border: 0px none ;" alt="glyph2" src="http://lh5.ggpht.com/_VelpN_FHzhk/SRPeKYPws5I/AAAAAAAAAZM/qDmz5C96TQM/glyph2_thumb%5B3%5D.png?imgmax=800" border="0" height="154" width="200" /&gt;&lt;/a&gt; &lt;a href="http://lh3.ggpht.com/_VelpN_FHzhk/SRPeK-KAguI/AAAAAAAAAZQ/2fjsFpKxm8E/s1600-h/glyph0%5B5%5D.png"&gt;&lt;img style="border: 0px none ;" alt="glyph0" src="http://lh5.ggpht.com/_VelpN_FHzhk/SRPeLBvph2I/AAAAAAAAAZU/7ajm9uvdu-E/glyph0_thumb%5B3%5D.png?imgmax=800" border="0" height="154" width="200" /&gt;&lt;/a&gt; &lt;/p&gt;  &lt;p&gt;&lt;a href="http://lh6.ggpht.com/_VelpN_FHzhk/SRPeLVeNrqI/AAAAAAAAAZY/yh8_5Px1iZE/s1600-h/streamlines0%5B5%5D.png"&gt;&lt;img style="border: 0px none ;" alt="streamlines0" src="http://lh4.ggpht.com/_VelpN_FHzhk/SRPeLjnedzI/AAAAAAAAAZc/0EUCdUXWcFg/streamlines0_thumb%5B3%5D.png?imgmax=800" border="0" height="154" width="200" /&gt;&lt;/a&gt; &lt;a href="http://lh6.ggpht.com/_VelpN_FHzhk/SRPeMB_qWaI/AAAAAAAAAZg/Z_mCSi_ii3s/s1600-h/streamlines3%5B6%5D.png"&gt;&lt;img style="border: 0px none ;" alt="streamlines3" src="http://lh3.ggpht.com/_VelpN_FHzhk/SRPeMTDSlqI/AAAAAAAAAZk/wlTkSnq0r-s/streamlines3_thumb%5B4%5D.png?imgmax=800" border="0" height="154" width="200" /&gt;&lt;/a&gt; &lt;/p&gt;  &lt;p&gt;&lt;a href="http://lh4.ggpht.com/_VelpN_FHzhk/SRPeM38VQII/AAAAAAAAAZo/fELgLJW3FhI/s1600-h/streamtubes0%5B5%5D.png"&gt;&lt;img style="border: 0px none ;" alt="streamtubes0" src="http://lh5.ggpht.com/_VelpN_FHzhk/SRPeNCMly_I/AAAAAAAAAZs/mfTNEvfZzI8/streamtubes0_thumb%5B3%5D.png?imgmax=800" border="0" height="154" width="200" /&gt;&lt;/a&gt; &lt;a href="http://lh3.ggpht.com/_VelpN_FHzhk/SRPeNvNNhxI/AAAAAAAAAZw/aKRooqccMxs/s1600-h/streamtubes5%5B5%5D.png"&gt;&lt;img style="border: 0px none ;" alt="streamtubes5" src="http://lh6.ggpht.com/_VelpN_FHzhk/SRPeN_UXDJI/AAAAAAAAAZ0/Cwr39SiXaOQ/streamtubes5_thumb%5B3%5D.png?imgmax=800" border="0" height="154" width="200" /&gt;&lt;/a&gt; &lt;/p&gt;  &lt;p&gt;&lt;a href="http://lh5.ggpht.com/_VelpN_FHzhk/SRPeOUkMa-I/AAAAAAAAAZ4/CWOI4tr-hFM/s1600-h/streamsurface0%5B5%5D.png"&gt;&lt;img style="border: 0px none ;" alt="streamsurface0" src="http://lh3.ggpht.com/_VelpN_FHzhk/SRPeOxQE4WI/AAAAAAAAAZ8/QMS_xOXlX5Q/streamsurface0_thumb%5B3%5D.png?imgmax=800" border="0" height="154" width="200" /&gt;&lt;/a&gt; &lt;a href="http://lh3.ggpht.com/_VelpN_FHzhk/SRPePqT9EsI/AAAAAAAAAaA/O8HFUWMC3w4/s1600-h/streamsurface5%5B5%5D.png"&gt;&lt;img style="border: 0px none ;" alt="streamsurface5" src="http://lh4.ggpht.com/_VelpN_FHzhk/SRPeQH5Y1SI/AAAAAAAAAaE/AM8w3OaryDk/streamsurface5_thumb%5B3%5D.png?imgmax=800" border="0" height="154" width="200" /&gt;&lt;/a&gt; &lt;/p&gt;  &lt;p&gt;&lt;a href="http://lh6.ggpht.com/_VelpN_FHzhk/SRPeQv10MYI/AAAAAAAAAaI/GDqbdbOPqoE/s1600-h/streamsurface3%5B5%5D.png"&gt;&lt;img style="border: 0px none ;" alt="streamsurface3" src="http://lh6.ggpht.com/_VelpN_FHzhk/SRPeQ-3HM4I/AAAAAAAAAaQ/DxF4xzxzs78/streamsurface3_thumb%5B3%5D.png?imgmax=800" border="0" height="154" width="200" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1023441640234597436-2340644652116651787?l=graphicsrunner.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://graphicsrunner.blogspot.com/feeds/2340644652116651787/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=1023441640234597436&amp;postID=2340644652116651787' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1023441640234597436/posts/default/2340644652116651787'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1023441640234597436/posts/default/2340644652116651787'/><link rel='alternate' type='text/html' href='http://graphicsrunner.blogspot.com/2008/11/scientific-visualization.html' title='Scientific Visualization'/><author><name>Kyle Hayward</name><uri>http://www.blogger.com/profile/00654406875137720609</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://bp3.blogger.com/_VelpN_FHzhk/R-Lx4Kk_nkI/AAAAAAAAAN8/jn_4uDeKxNk/S220/me.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://lh4.ggpht.com/_VelpN_FHzhk/SRPeEJEAUyI/AAAAAAAAAX8/UzFxRcTaq8w/s72-c/brain1_thumb%5B3%5D.png?imgmax=800' height='72' width='72'/><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1023441640234597436.post-2450625763349097143</id><published>2008-10-07T15:38:00.007-04:00</published><updated>2008-10-10T18:45:46.271-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='occlusion camera'/><category scheme='http://www.blogger.com/atom/ns#' term='Non-Pinhole Impostors'/><category scheme='http://www.blogger.com/atom/ns#' term='General'/><category scheme='http://www.blogger.com/atom/ns#' term='Impostors'/><title type='text'>Interviews, research, and future posts</title><content type='html'>I don't have a new sample/tutorial today. I have been very busy with interviews, research, and school as of late.&lt;br /&gt;&lt;br /&gt;As this is my last year of school, I've been interviewing with various companies and trying to get an on-line portfolio together. I recently had a great time in Portland meeting with Intel.&lt;br /&gt;&lt;br /&gt;Last spring I was involved in the research of using non-pinhole impostors for reflections and refractions. We submitted to Eurographics (specifically EGSR), but unfortunately didn't get accepted. So this semester we are looking to work on the short comings that some of the reviewers noted and resubmit to I3D. So until I get that out of the way there probably won't be any new samples/tutorials for a couple of weeks.&lt;br /&gt;&lt;br /&gt;As far as for future posts, I've been working on rain as a particle system and as a post process (see Tatarchuck's AMD/ATi paper on Rain). Besides this I've been wanting to have a series of samples/tutorials on different lighting methods. We all know Gouraud and [Blinn-]Phong shading, but I wanted to cover other methods such as Cook-Torrance, Oren-Nayar, Ward lighting and others. I also might do a tutorial on Depth Impostors that would build off of the Billboard Impostor tutorial I wrote earlier in the year.&lt;br /&gt;&lt;br /&gt;For now, here's a couple of teaser images that we submitted to EGSR. You're looking through a glass bunny's ear. You can see that with regular depth impostors, you are missing a significant amount of data for the teapot lid.&lt;br /&gt;&lt;br /&gt;Planar Pinhole Camera Depth Impostor&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_VelpN_FHzhk/SOu_FnH0FTI/AAAAAAAAAVc/kh4Ay8U3030/s1600-h/Picture1.png"&gt;&lt;img style="cursor: pointer;" src="http://4.bp.blogspot.com/_VelpN_FHzhk/SOu_FnH0FTI/AAAAAAAAAVc/kh4Ay8U3030/s400/Picture1.png" alt="" id="BLOGGER_PHOTO_ID_5254503493379822898" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Non-Pinhole Camera Depth Impostor&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_VelpN_FHzhk/SOu_WImAQAI/AAAAAAAAAVk/WF5F0IjCk_E/s1600-h/Picture2.png"&gt;&lt;img style="cursor: pointer;" src="http://2.bp.blogspot.com/_VelpN_FHzhk/SOu_WImAQAI/AAAAAAAAAVk/WF5F0IjCk_E/s400/Picture2.png" alt="" id="BLOGGER_PHOTO_ID_5254503777242726402" border="0" /&gt;&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1023441640234597436-2450625763349097143?l=graphicsrunner.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://graphicsrunner.blogspot.com/feeds/2450625763349097143/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=1023441640234597436&amp;postID=2450625763349097143' title='5 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1023441640234597436/posts/default/2450625763349097143'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1023441640234597436/posts/default/2450625763349097143'/><link rel='alternate' type='text/html' href='http://graphicsrunner.blogspot.com/2008/10/interviews-research-and-future-posts.html' title='Interviews, research, and future posts'/><author><name>Kyle Hayward</name><uri>http://www.blogger.com/profile/00654406875137720609</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://bp3.blogger.com/_VelpN_FHzhk/R-Lx4Kk_nkI/AAAAAAAAAN8/jn_4uDeKxNk/S220/me.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/_VelpN_FHzhk/SOu_FnH0FTI/AAAAAAAAAVc/kh4Ay8U3030/s72-c/Picture1.png' height='72' width='72'/><thr:total>5</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1023441640234597436.post-7357492889530229299</id><published>2008-07-30T12:09:00.003-04:00</published><updated>2008-07-30T12:39:17.119-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='General'/><title type='text'>Other recent blog posts</title><content type='html'>There are a few posts that I have seen other blogs/sites that I think are interesting, and would like to share with anyone who reads my blog.&lt;br /&gt;&lt;br /&gt;Andy Patrick has a series of useful "efficient development" posts regarding speeding up and making game development easier. Check it out:&lt;br /&gt;&lt;br /&gt;&lt;a href="http://bittermanandy.wordpress.com/2008/07/26/efficient-development-part-one/"&gt;Efficient Development, Part I&lt;/a&gt;&lt;br /&gt;&lt;a href="http://bittermanandy.wordpress.com/2008/07/28/efficient-development-part-two/"&gt;Efficient Development, Part II&lt;/a&gt;&lt;br /&gt;&lt;a href="http://bittermanandy.wordpress.com/2008/07/28/efficient-development-part-three/"&gt;Efficient Development, Part III&lt;/a&gt;&lt;br /&gt;&lt;a href="http://bittermanandy.wordpress.com/2008/07/30/efficient-development-part-four/"&gt;Efficient Development, Part IV&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Next up there were a couple of articles on Gamasutra related to 2D fluid dynamics that are also pretty interesting.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://www.gamasutra.com/view/feature/1549/practical_fluid_dynamics_part_1.php"&gt;Fluid Dynamics, Part I&lt;/a&gt;&lt;br /&gt;&lt;a href="http://www.gamasutra.com/view/feature/1615/practical_fluid_dynamics_part_2.php"&gt;Fluid Dynamics, Part II&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Christer Ericson has an interesting post on using cellular automata for path finding. And one of the guys over at XNAInfo already has a working XNA demo implementing the idea.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://realtimecollisiondetection.net/blog/?p=57"&gt;Path finding with cellular automata&lt;/a&gt;&lt;br /&gt;&lt;a href="http://www.xnainfo.com/content.php?content=21"&gt;Game of Life on the GPU&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Got any interesting links? Post 'em in the comments!&lt;br /&gt;&lt;span style="text-decoration: underline;"&gt;&lt;span style="font-weight: bold;"&gt;&lt;/span&gt;&lt;/span&gt;&lt;a style="font-weight: bold;" href="http://en.wikipedia.org/wiki/Cellular_automata"&gt;&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1023441640234597436-7357492889530229299?l=graphicsrunner.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://graphicsrunner.blogspot.com/feeds/7357492889530229299/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=1023441640234597436&amp;postID=7357492889530229299' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1023441640234597436/posts/default/7357492889530229299'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1023441640234597436/posts/default/7357492889530229299'/><link rel='alternate' type='text/html' href='http://graphicsrunner.blogspot.com/2008/07/other-recent-blog-posts.html' title='Other recent blog posts'/><author><name>Kyle Hayward</name><uri>http://www.blogger.com/profile/00654406875137720609</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://bp3.blogger.com/_VelpN_FHzhk/R-Lx4Kk_nkI/AAAAAAAAAN8/jn_4uDeKxNk/S220/me.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1023441640234597436.post-6530893776925234870</id><published>2008-07-18T10:31:00.004-04:00</published><updated>2008-11-18T13:34:02.541-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='shadow mapping'/><category scheme='http://www.blogger.com/atom/ns#' term='Dual-Paraboloid'/><category scheme='http://www.blogger.com/atom/ns#' term='VSM'/><title type='text'>Dual-Paraboloid Variance Shadow Mapping</title><content type='html'>&lt;p&gt;&lt;a href="http://lh4.ggpht.com/GraphicsRunner/SICpRA9vBNI/AAAAAAAAATw/66qZcihDngQ/dp_vsm%5B4%5D.jpg"&gt;&lt;img height="327" alt="dp_vsm" src="http://lh5.ggpht.com/GraphicsRunner/SICpR7bdUxI/AAAAAAAAAT0/gKxbjPbGlxk/dp_vsm_thumb%5B2%5D.jpg" width="416" /&gt;&lt;/a&gt; &lt;/p&gt;&lt;p&gt;&lt;span style="color:#ff6600;"&gt;Edit: Added the video that I recently made&lt;/span&gt;&lt;/p&gt;&lt;object height="344" width="425"&gt;&lt;param name="movie" value="http://www.youtube.com/v/RuPntqfZf44&amp;amp;hl=en&amp;amp;fs=1"&gt;&lt;param name="allowFullScreen" value="true"&gt;&lt;param name="allowscriptaccess" value="always"&gt;&lt;embed src="http://www.youtube.com/v/RuPntqfZf44&amp;hl=en&amp;fs=1" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="425" height="344"&gt;&lt;/embed&gt;&lt;/object&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;I have to say, I really like variance shadow mapping. It's such a simple(ingenious) technique to implement, but it provides such nice looking results. I haven't had the need to implement the technique before, but I'm glad I did. Last post we implemented dual-paraboloid shadow mapping. And those of you with a PS 3.0 graphics card were able to have semi-soft shadows with percentage closer filtering. But now when we get rid of the PCF filter, and replace it with variance shadow mapping, we can fit all the code inside the PS 2.0 standard. Anyway, on to the code.&lt;/p&gt;&lt;p&gt;&lt;a href="http://www.punkuser.net/vsm/"&gt;Variance Shadow Mapping Paper + Demo&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;&lt;span style="color:#ff6600;"&gt;Building the shadow maps:&lt;/span&gt;&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;Variance shadow mapping is really simple to implement. First thing we need to change is to create either a RG32F or RG16F surface format for our front and rear shadow maps (instead of R32F/R16F). This allows us to store the depth of the pixel in the red channel and the squared depth of the pixel in the green channel. So our new pixel shader for building the depth/shadow maps is this:&lt;/p&gt;&lt;p&gt;&lt;span style="font-family:tre;color:#969696;"&gt;return float4(z, z * z, 0, 0, 1);&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;&lt;span style="color:#ff6600;"&gt;Blurring the shadow maps:&lt;/span&gt;&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;Variance shadow mapping improves upon standard shadow mapping by storing a distribution of depths at each pixel (z * z, and z) instead of the single depth (as with standard shadow mapping). And because it stores a distribution of depth, we can blur the shadow maps. This would produce some funky/incorrect results if we were just doing standard shadow mapping with a PCF filter.&lt;/p&gt;&lt;p&gt;So, after we have created our depth maps, we will blur them with a separable Gaussian blur. This will perform two passes on each shadow map; the first will perform a horizontal blur and the second will perform a vertical blur. There is a wealth of information on the internet on how to do this so I won't explicitly cover this. Here's what our front shadow map looks like after being blurred:&lt;/p&gt;&lt;p&gt;&lt;a href="http://lh6.ggpht.com/GraphicsRunner/SICpSYa0vRI/AAAAAAAAAT4/OjVwM6wxjkw/depth_front%5B4%5D.png"&gt;&lt;img height="297" alt="depth_front" src="http://lh5.ggpht.com/GraphicsRunner/SICpSiZaUYI/AAAAAAAAAT8/d_sR-JtJJyc/depth_front_thumb%5B2%5D.png" width="297" /&gt;&lt;/a&gt; &lt;/p&gt;&lt;p&gt;&lt;strong&gt;&lt;span style="color:#ff6600;"&gt;Variance shadow mapping:&lt;/span&gt;&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;We build our texture coordinates exactly the same as the previous method of shadow mapping. But the depth comparison is a little different. You can refer to the VSM paper for an in-depth discussion, but here is the gist of it. Since we filtered our shadow maps with a Gaussian blur, we need to recover the moments over that filter region. The moments are simple the depth and squared depth we stored in the texture. From these we can build the mean depth and the variance at the pixel. And as such the variance can be interpreted as a quantitative measure of the width of a distribution (Donelly/Lauritzen). This measure places a bound on the distribution and can be represented by Chebychev's inequality.&lt;/p&gt;&lt;pre class="mycode"&gt;&lt;span style="color:blue;"&gt;float &lt;/span&gt;depth;&lt;br /&gt;&lt;span style="color:blue;"&gt;float &lt;/span&gt;mydepth;&lt;br /&gt;&lt;span style="color:blue;"&gt;float2 &lt;/span&gt;moments;&lt;br /&gt;&lt;span style="color:blue;"&gt;if&lt;/span&gt;(alpha &amp;gt;= 0.5f)&lt;br /&gt;{&lt;br /&gt;    moments = &lt;span style="color:blue;"&gt;tex2D&lt;/span&gt;(ShadowFrontS, P0.xy).xy;&lt;br /&gt;    depth = moments.x;&lt;br /&gt;    mydepth = P0.z;&lt;br /&gt;}&lt;br /&gt;&lt;span style="color:blue;"&gt;else&lt;br /&gt;&lt;/span&gt;{&lt;br /&gt;    moments = &lt;span style="color:blue;"&gt;tex2D&lt;/span&gt;(ShadowBackS, P1.xy).xy;&lt;br /&gt;    depth = moments.x;&lt;br /&gt;    mydepth = P1.z;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;span style="color:blue;"&gt;float &lt;/span&gt;lit_factor = (mydepth &amp;lt;= moments[0]);&lt;br /&gt; &lt;br /&gt;&lt;span style="color:blue;"&gt;float &lt;/span&gt;E_x2 = moments.y;&lt;br /&gt;&lt;span style="color:blue;"&gt;float &lt;/span&gt;Ex_2 = moments.x * moments.x;&lt;br /&gt;&lt;span style="color:blue;"&gt;float &lt;/span&gt;variance = &lt;span style="color:blue;"&gt;min&lt;/span&gt;(&lt;span style="color:blue;"&gt;max&lt;/span&gt;(E_x2 - Ex_2, 0.0) + SHADOW_EPSILON, 1.0);&lt;br /&gt;&lt;span style="color:blue;"&gt;float &lt;/span&gt;m_d = (moments.x - mydepth);&lt;br /&gt;&lt;span style="color:blue;"&gt;float &lt;/span&gt;p = variance / (variance + m_d * m_d); &lt;span style="color:green;"&gt;//Chebychev's inequality&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;texColor.xyz *= &lt;span style="color:blue;"&gt;max&lt;/span&gt;(lit_factor, p + .2f); &lt;span style="color:green;"&gt;//lighten the shadow just a bit (with the + .2f)&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style="color:blue;"&gt;return &lt;/span&gt;texColor;&lt;/pre&gt;&lt;p&gt;&lt;span style="color:#ff6600;"&gt;5x5 Guassian Blur&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;a href="http://lh6.ggpht.com/GraphicsRunner/SICpS0jeMfI/AAAAAAAAAUA/ZivDUeZeghY/dp_vsm2%5B4%5D.jpg"&gt;&lt;img height="342" alt="dp_vsm2" src="http://lh3.ggpht.com/GraphicsRunner/SICpTYZln1I/AAAAAAAAAUE/VcsOkoBfA5w/dp_vsm2_thumb%5B2%5D.jpg" width="435" /&gt;&lt;/a&gt; &lt;/p&gt;&lt;br /&gt;&lt;p&gt;&lt;span style="color:#ff6600;"&gt;9x9 Guassian Blur&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;a href="http://lh6.ggpht.com/GraphicsRunner/SICpTiJvL2I/AAAAAAAAAUI/UJZvqg3i3PI/dp_vsm3%5B5%5D.jpg"&gt;&lt;img height="346" alt="dp_vsm3" src="http://lh5.ggpht.com/GraphicsRunner/SICpUFEsPXI/AAAAAAAAAUM/1UWpPlNavwk/dp_vsm3_thumb%5B3%5D.jpg" width="437" /&gt;&lt;/a&gt; &lt;/p&gt;&lt;p&gt;And there you go. Nice looking dual-paraboloid soft shadows thanks to variance shadow mapping.&lt;/p&gt;&lt;p&gt;As before, your card needs to support either RG16F or RG32F formats (sorry again Charles :) ). You can refer to the VSM paper and demo on how to map 2 floats to a single ARGB32 pixel if your card doesn't support the floating point surface formats.&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;br /&gt;&lt;iframe style="BORDER-RIGHT: #dde5e9 1px solid; PADDING-RIGHT: 0px; BORDER-TOP: #dde5e9 1px solid; PADDING-LEFT: 0px; PADDING-BOTTOM: 0px; MARGIN: 3px; BORDER-LEFT: #dde5e9 1px solid; WIDTH: 240px; PADDING-TOP: 0px; BORDER-BOTTOM: #dde5e9 1px solid; HEIGHT: 66px; BACKGROUND-COLOR: #ffffff" marginwidth="0" marginheight="0" src="http://cid-b80a3031b5bfa52b.skydrive.live.com/embedrowdetail.aspx/Public/Dual-Paraboloid%20VSM.zip" frameborder="0" scrolling="no"&gt;&lt;/iframe&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1023441640234597436-6530893776925234870?l=graphicsrunner.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://graphicsrunner.blogspot.com/feeds/6530893776925234870/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=1023441640234597436&amp;postID=6530893776925234870' title='16 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1023441640234597436/posts/default/6530893776925234870'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1023441640234597436/posts/default/6530893776925234870'/><link rel='alternate' type='text/html' href='http://graphicsrunner.blogspot.com/2008/07/dual-paraboloid-variance-shadow-mapping.html' title='Dual-Paraboloid Variance Shadow Mapping'/><author><name>Kyle Hayward</name><uri>http://www.blogger.com/profile/00654406875137720609</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://bp3.blogger.com/_VelpN_FHzhk/R-Lx4Kk_nkI/AAAAAAAAAN8/jn_4uDeKxNk/S220/me.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://lh5.ggpht.com/GraphicsRunner/SICpR7bdUxI/AAAAAAAAAT0/gKxbjPbGlxk/s72-c/dp_vsm_thumb%5B2%5D.jpg' height='72' width='72'/><thr:total>16</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1023441640234597436.post-7070871978569574886</id><published>2008-07-17T12:34:00.005-04:00</published><updated>2008-07-17T13:51:43.463-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='shadow mapping'/><category scheme='http://www.blogger.com/atom/ns#' term='Dual-Paraboloid'/><title type='text'>Dual-Paraboloid Shadow Maps</title><content type='html'>&lt;p&gt;&lt;a href="http://lh4.ggpht.com/GraphicsRunner/SH90g3clirI/AAAAAAAAATY/KwezlEeTnS0/DPShadow2%5B8%5D.jpg"&gt;&lt;img height="326" alt="DPShadow2" src="http://lh5.ggpht.com/GraphicsRunner/SH90hb1QWnI/AAAAAAAAATc/ROvaa6Boy_8/DPShadow2_thumb%5B6%5D.jpg" width="415" /&gt;&lt;/a&gt; &lt;/p&gt;&lt;p&gt;Last time I introduced using dual-paraboloid environment mapping for reflections. Well now we're going to apply the same process to shadows. So if you haven't looked at my previous post, read it over before going on.&lt;/p&gt;&lt;p&gt;Creating the depth/shadow maps is exactly the same as when we created the reflection maps with one exception. Instead of outputting color in the pixel shader, we output the depth of the 3d pixel, like so:&lt;/p&gt;&lt;p&gt;&lt;span style="font-family:Tahoma;color:#7c7c7c;"&gt;return depth.x / depth.y;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style="color:#404040;"&gt;Where depth.x is the depth of the pixel and depth.y is the w component. And here is the resulting depth/shadow map for the front hemisphere.&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;a href="http://lh6.ggpht.com/GraphicsRunner/SH90hpgk20I/AAAAAAAAATg/RiTmqfhE71Y/depth_f%5B5%5D.png"&gt;&lt;img height="269" alt="depth_f" src="http://lh6.ggpht.com/GraphicsRunner/SH90h2cvZcI/AAAAAAAAATk/h5-bwOO2jBg/depth_f_thumb%5B3%5D.png" width="269" /&gt;&lt;/a&gt; &lt;/p&gt;&lt;p&gt;&lt;span style="color:#000000;"&gt;&lt;span style="color:#404040;"&gt;Now, to map the shadows the process is also very similar to how we generated the reflections. We follow a similar process in the pixel shader:&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;span style="color:#404040;"&gt;Generate the texture coordinates for the front and rear paraboloids&lt;/span&gt; &lt;/li&gt;&lt;li&gt;Generate the depth of the pixel &lt;/li&gt;&lt;li&gt;Test to see if the pixel is in shadow &lt;/li&gt;&lt;/ul&gt;&lt;p&gt;We generate the texture coordinates exactly as when we generated the reflection texture coordinates. To generate the depth of the pixel we take the length of the vector from the vertex to the origin of the paraboloid (0, 0, 0) and divide by the light attenuation. Also to check which hemisphere we are in, we calculate an alpha that is the Z value of the transformed vertex and offset by .5f;&lt;/p&gt;&lt;pre class="mycode"&gt;&lt;span style="color:blue;"&gt;float &lt;/span&gt;L = &lt;span style="color:blue;"&gt;length&lt;/span&gt;(pos);&lt;br /&gt;&lt;span style="color:blue;"&gt;float3 &lt;/span&gt;P0 = pos / L;&lt;br /&gt;&lt;br /&gt;&lt;span style="color:blue;"&gt;float &lt;/span&gt;alpha = .5f + pos.z / LightAttenuation;&lt;br /&gt;&lt;span style="color:green;"&gt;//generate texture coords for the front hemisphere&lt;/span&gt;&lt;br /&gt;P0.z = P0.z + 1;&lt;br /&gt;P0.x = P0.x / P0.z;&lt;br /&gt;P0.y = P0.y / P0.z;&lt;br /&gt;P0.z = L / LightAttenuation;&lt;br /&gt;&lt;br /&gt;P0.x = .5f * P0.x + .5f;&lt;br /&gt;P0.y = -.5f * P0.y + .5f;&lt;br /&gt;&lt;br /&gt;&lt;span style="color:blue;"&gt;float3 &lt;/span&gt;P1 = pos / L;&lt;br /&gt;&lt;span style="color:green;"&gt;//generate texture coords for the rear hemisphere&lt;/span&gt;&lt;br /&gt;P1.z = 1 - P1.z;&lt;br /&gt;P1.x = P1.x / P1.z;&lt;br /&gt;P1.y = P1.y / P1.z;&lt;br /&gt;P1.z = L / LightAttenuation;&lt;br /&gt;&lt;br /&gt;P1.x = .5f * P1.x + .5f;&lt;br /&gt;P1.y = -.5f * P1.y + .5f;&lt;/pre&gt;&lt;p&gt;Now that we have generated our texture coordinates we need to test the depth of the pixel against the depth in the shadow map. To do this we index either the front or rear shadow map with the texture coordinates we generated to get the depth and compare this to our depth. If the depth is less than our depth, then the pixel is in shadow.&lt;/p&gt;&lt;pre class="mycode"&gt;&lt;span style="color:blue;"&gt;float &lt;/span&gt;depth;&lt;br /&gt;&lt;span style="color:blue;"&gt;float &lt;/span&gt;mydepth;&lt;br /&gt;&lt;span style="color:blue;"&gt;if&lt;/span&gt;(alpha &amp;gt;= 0.5f)&lt;br /&gt;{&lt;br /&gt;    depth = &lt;span style="color:blue;"&gt;tex2D&lt;/span&gt;(ShadowFrontS, P0.xy).x;&lt;br /&gt;    mydepth = P0.z;&lt;br /&gt;}&lt;br /&gt;&lt;span style="color:blue;"&gt;else&lt;br /&gt;&lt;/span&gt;{&lt;br /&gt;    depth = &lt;span style="color:blue;"&gt;tex2D&lt;/span&gt;(ShadowBackS, P1.xy).x;&lt;br /&gt;    mydepth = P1.z;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;span style="color:green;"&gt;//lighten the shadow just a bit so it isn't completely black&lt;br /&gt;&lt;/span&gt;&lt;span style="color:blue;"&gt;if&lt;/span&gt;((depth + SHADOW_EPSILON) &amp;lt; mydepth)&lt;br /&gt;    texColor.xyz *= 0.3f;&lt;br /&gt;&lt;br /&gt;&lt;span style="color:blue;"&gt;return &lt;/span&gt;texColor;&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;&lt;a href="http://lh6.ggpht.com/GraphicsRunner/SH90jYqByQI/AAAAAAAAATo/ub-U1PGGhKs/DPShadow%5B4%5D.jpg"&gt;&lt;img height="324" alt="DPShadow" src="http://lh5.ggpht.com/GraphicsRunner/SH90kCAdZ5I/AAAAAAAAATs/RwEl6sSzhFw/DPShadow_thumb%5B2%5D.jpg" width="412" /&gt;&lt;/a&gt; &lt;/p&gt;&lt;p&gt;And that's it. Now we have dual-paraboloid shadow mapping. If you have a pixel shader 3.0 graphics card, then the shadow also has a percentage closer filter applied to it. You also may notice seams in the shadows. This is because the splitting plane of the paraboloids is the x-axis (since the paraboloids look down the +/- z-axis). This is one of the problems of using paraboloid mapping for shadows. One has to be careful where they place the split plane to avoid this situation. Pixels that are in the center of either hemisphere suffer little distortion. But this is just a tutorial so I didn't worry too much about it.&lt;/p&gt;&lt;p&gt;Also you're graphics card must be able to support R32F or R16F surface formats to run the demo out of the box (sorry Charles ;) ). Otherwise, you must use the ARGB32 format and pack the depth values in all 4 channels. Here is some code to pack/unpack to/from an ARGB32 surface format. You pass the depth value to the pack method when you render to the shadow maps, and you pass the float4 color to the unpack method when you fetch from the shadow maps. I decided not to implement this so the code wouldn't become complicated by something that doesn't add to the tutorial.&lt;/p&gt;&lt;pre class="mycode"&gt;&lt;span style="color:green;"&gt;//pack the depth in a 32-bit rgba color&lt;br /&gt;&lt;/span&gt;&lt;span style="color:blue;"&gt;float4 &lt;/span&gt;mapDepthToARGB32(&lt;span style="color:blue;"&gt;const float &lt;/span&gt;value)&lt;br /&gt;{&lt;br /&gt;    &lt;span style="color:blue;"&gt;const float4 &lt;/span&gt;bitSh = &lt;span style="color:blue;"&gt;float4&lt;/span&gt;(256.0 * 256.0 * 256.0, 256.0 * 256.0, 256.0, 1.0);&lt;br /&gt;    &lt;span style="color:blue;"&gt;const float4 &lt;/span&gt;mask = &lt;span style="color:blue;"&gt;float4&lt;/span&gt;(0.0, 1.0 / 256.0, 1.0 / 256.0, 1.0 / 256.0);&lt;br /&gt;    &lt;span style="color:blue;"&gt;float4 &lt;/span&gt;res = &lt;span style="color:blue;"&gt;frac&lt;/span&gt;(value * bitSh);&lt;br /&gt;    res -= res.xxyz * mask;&lt;br /&gt;    &lt;span style="color:blue;"&gt;return &lt;/span&gt;res;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;span style="color:green;"&gt;//unpack the depth from a 32-bit rgba color&lt;br /&gt;&lt;/span&gt;&lt;span style="color:blue;"&gt;float &lt;/span&gt;getDepthFromARGB32(&lt;span style="color:blue;"&gt;const float4 &lt;/span&gt;value)&lt;br /&gt;{&lt;br /&gt;    &lt;span style="color:blue;"&gt;const float4 &lt;/span&gt;bitSh = &lt;span style="color:blue;"&gt;float4&lt;/span&gt;(1.0 / (256.0 * 256.0 * 256.0), 1.0 / (256.0 * 256.0), 1.0 / 256.0, 1.0);&lt;br /&gt;    &lt;span style="color:blue;"&gt;return&lt;/span&gt;(&lt;span style="color:blue;"&gt;dot&lt;/span&gt;(value, bitSh));&lt;br /&gt;}&lt;/pre&gt;&lt;p&gt;Next time I'll introduce using variance shadow mapping with our dual-paraboloid shadow mapping to give nice soft shadows that we can still use with pixel shader 2.0 cards.&lt;/p&gt;&lt;br /&gt;&lt;iframe style="BORDER-RIGHT: #dde5e9 1px solid; PADDING-RIGHT: 0px; BORDER-TOP: #dde5e9 1px solid; PADDING-LEFT: 0px; PADDING-BOTTOM: 0px; MARGIN: 3px; BORDER-LEFT: #dde5e9 1px solid; WIDTH: 240px; PADDING-TOP: 0px; BORDER-BOTTOM: #dde5e9 1px solid; HEIGHT: 66px; BACKGROUND-COLOR: #ffffff" marginwidth="0" marginheight="0" src="http://cid-b80a3031b5bfa52b.skydrive.live.com/embedrowdetail.aspx/Public/Dual-Paraboloid%20Shadow%20Maps.zip" frameborder="0" scrolling="no"&gt;&lt;/iframe&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1023441640234597436-7070871978569574886?l=graphicsrunner.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://graphicsrunner.blogspot.com/feeds/7070871978569574886/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=1023441640234597436&amp;postID=7070871978569574886' title='15 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1023441640234597436/posts/default/7070871978569574886'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1023441640234597436/posts/default/7070871978569574886'/><link rel='alternate' type='text/html' href='http://graphicsrunner.blogspot.com/2008/07/dual-paraboloid-shadow-maps.html' title='Dual-Paraboloid Shadow Maps'/><author><name>Kyle Hayward</name><uri>http://www.blogger.com/profile/00654406875137720609</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://bp3.blogger.com/_VelpN_FHzhk/R-Lx4Kk_nkI/AAAAAAAAAN8/jn_4uDeKxNk/S220/me.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://lh5.ggpht.com/GraphicsRunner/SH90hb1QWnI/AAAAAAAAATc/ROvaa6Boy_8/s72-c/DPShadow2_thumb%5B6%5D.jpg' height='72' width='72'/><thr:total>15</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1023441640234597436.post-4913038228316931145</id><published>2008-07-16T11:29:00.010-04:00</published><updated>2008-08-28T19:05:11.956-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Dual-Paraboloid'/><category scheme='http://www.blogger.com/atom/ns#' term='XNA'/><category scheme='http://www.blogger.com/atom/ns#' term='reflections'/><title type='text'>Dual-Paraboloid Reflections</title><content type='html'>&lt;p&gt;&lt;a href="http://lh3.ggpht.com/GraphicsRunner/SH4Tn0OBd-I/AAAAAAAAASo/85vkjSUidPw/dragon5.png"&gt;&lt;img height="318" alt="dragon" src="http://lh4.ggpht.com/GraphicsRunner/SH4TooD_JiI/AAAAAAAAASs/OdFKgTKhoDI/dragon_thumb3.png" width="402" /&gt;&lt;/a&gt; &lt;/p&gt;&lt;p&gt;I recently had to investigate dual-paraboloid reflections at work for an unnamed console. What are these you ask? Great question! :) Lets start with some background.&lt;/p&gt;&lt;p&gt;The standard way of calculating reflections is to use an environment map, more specifically a cube map. In my last tutorial on reflections, this basic type of reflection mapping was used to compare against billboard impostor reflections. Now, cubemaps are great for static scenes, and are relatively low cost to perform a texture fetch on in a shader. However if you have a dynamic scene you have to update all 6 sides of the cubemap (this is not technically true, aggressive culling and other optimizations can guarantee at most 5 sides). Holy crap, now we have to render our scene 6 times!&lt;/p&gt;&lt;p&gt;This is where dual-paraboloid reflections come in. They are a view-independent method of rendering reflections just like cubemaps. Except you only have to update 2 textures, not 6! The downside is that you are going to lose quality for speed, but unless you have to have high-quality reflections, paraboloid reflections will probably provide sufficient results.&lt;/p&gt;&lt;p&gt;Reference articles:&lt;/p&gt;&lt;p&gt;&lt;a href="http://www.cs.ubc.ca/~heidrich/Papers/GH.98.pdf"&gt;View-Independent Environment Maps&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;a href="http://www.mpi-inf.mpg.de/~tannen/papers/cgi_02.pdf"&gt;Shadow Mapping for Hemispherical and Omnidirectional Light Sources&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;a href="http://www.gamedev.net/columns/hardcore/dualparaboloid/"&gt;Dual Paraboloid Mapping in the Vertex Shader&lt;/a&gt;&lt;/p&gt;&lt;p&gt;In the interest of keeping this post from getting too long, I won't go into great detail on the mathematical process. I suggest you refer to the first and third papers for an in-depth discussion on the details.&lt;/p&gt;&lt;p&gt;Now lets move on to what exactly paraboloid mapping is. Lets look at what a paraboloid is.&lt;/p&gt;&lt;p&gt;&lt;a href="http://lh3.ggpht.com/GraphicsRunner/SH4TpImkomI/AAAAAAAAASw/Tsc2v951RAw/paraboloid5.jpg"&gt;&lt;img height="198" alt="paraboloid" src="http://lh4.ggpht.com/GraphicsRunner/SH4TpsGL2DI/AAAAAAAAAS0/tPahxfnQF24/paraboloid_thumb3.jpg" width="252" /&gt;&lt;/a&gt; &lt;/p&gt;&lt;p&gt;The basic idea is that for any origin O, we can divide the scene into two hemispheres, front and rear. For each of these hemispheres there exists a paraboloid surface that will focus any ray traveling in the direction of O into the direction of the the hemisphere. Here is a 2d picture demonstrating the idea:&lt;/p&gt;&lt;p&gt;&lt;a href="http://lh6.ggpht.com/GraphicsRunner/SH4Tpy9gE_I/AAAAAAAAAS4/kFkghUJJDiI/paraboloid2d9.jpg"&gt;&lt;img height="271" alt="paraboloid2d" src="http://lh4.ggpht.com/GraphicsRunner/SH4Tqj3j8fI/AAAAAAAAAS8/oENn9zX8D6Y/paraboloid2d_thumb7.jpg" width="263" /&gt;&lt;/a&gt; &lt;/p&gt;&lt;p&gt;&lt;strong&gt;&lt;span style="color:#ff6600;"&gt;A brief math overview:&lt;/span&gt;&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;What we need to find is the intersection point where the incident ray intersects the paraboloid surface. To do this we need to know the incident ray and the reflected ray. Now because the paraboloid reflects rays in the same direction, it is easy to compute the reflection vector: it's the forward direction of the hemisphere! So the front hemisphere's reflection vector will always be &amp;lt;0, 0, 1&amp;gt; and the rear hemisphere's reflection vector will always be &amp;lt;0, 0, -1&amp;gt;. Easy! And the incident ray is calculated the same as with environment mapping by reflecting the ray from the pixel position to the eye across the normal of the 3D pixel.&lt;/p&gt;&lt;p&gt;Now all we have to do is find the normal of the intersection which we will use to map our vertices into paraboloid space. To find the normal, we add the incident and reflected vectors and divide the x and y components by the z value.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;&lt;span style="color:#ff6600;"&gt;Generating the Paraboloid maps:&lt;/span&gt;&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;What we are basically going to do is, in the vertex shader, place each vertex ourselves that has been distorted by the paraboloid. First we need to transform the vertex by the view matrix of the paraboloid 'camera'. We don't apply the projection matrix since we're going to place the point ourselves&lt;/p&gt;&lt;span style="font-family:trebuchet ms;color:#666666;"&gt;output.Position = &lt;span style="color:#3333ff;"&gt;mul&lt;/span&gt;(input.Position, WorldViewProj);&lt;/span&gt; &lt;p&gt;Next we need to find the vector from the the vertex to the origin of the paraboloid, which is simply:&lt;/p&gt;&lt;span style="font-family:trebuchet ms;color:#666666;"&gt;float L = &lt;span style="color:#3333ff;"&gt;length&lt;/span&gt;( output.Position.xyz );&lt;br /&gt;output.Position = output.Position / L;&lt;/span&gt; &lt;p&gt;Now we need to find the x and y coordinates of the point where the incident ray intersects the paraboloid surface.&lt;/p&gt;&lt;span style="font-family:trebuchet ms;color:#666666;"&gt;output.Position.z = output.Position.z + 1;&lt;br /&gt;output.Position.x = output.Position.x / output.Position.z;&lt;br /&gt;output.Position.y = output.Position.y / output.Position.z;&lt;/span&gt; &lt;p&gt;Finally we set the z value as the distance from the vertex to the origin of the paraboloid, scaled and biased by the near and far planes of the paraboloid 'camera'.&lt;/p&gt;&lt;p&gt;&lt;span style="font-family:trebuchet ms;color:#666666;"&gt;output.Position.z = (L - NearPlane) / (FarPlane - NearPlane);&lt;br /&gt;output.Position.w = 1;&lt;/span&gt; &lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;And the only thing we need to add in the pixel shader is to make sure and clip vertices that are behind the viewpoint using the intrinsic clip() function of HLSL.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;a href="http://lh5.ggpht.com/GraphicsRunner/SH4TrNbSLxI/AAAAAAAAATA/DY7HQSe1NR4/front4.jpg"&gt;&lt;img height="258" alt="front" src="http://lh4.ggpht.com/GraphicsRunner/SH4TrbnQCfI/AAAAAAAAATE/NAmPzc8TJ_A/front_thumb2.jpg" width="338" /&gt;&lt;/a&gt; &lt;/p&gt;&lt;p&gt;&lt;a href="http://lh3.ggpht.com/GraphicsRunner/SH4TskGKIQI/AAAAAAAAATI/ijNI1MaeCN4/frontWF5.jpg"&gt;&lt;img height="258" alt="frontWF" src="http://lh6.ggpht.com/GraphicsRunner/SH4Ts_uQD2I/AAAAAAAAATM/Xr1NfeDgVtc/frontWF_thumb3.jpg" width="338" /&gt;&lt;/a&gt; &lt;/p&gt;&lt;p&gt;&lt;strong&gt;&lt;span style="color:#ff6600;"&gt;Reflections with paraboloid maps:&lt;/span&gt;&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;In the reflection pixel shader we will: generate the reflection vector the same way as cube mapping, generate texture coordinates for both the front and rear paraboloids' textures, and blend the samples taken from the textures.&lt;/p&gt;&lt;p&gt;The texture coordinates are generated exactly as how we generated them before in the generation step. We also scale and bias them to correctly index a D3D texture. And then we take a sample from each map and pick the sample with the greater color value:&lt;/p&gt;&lt;p&gt;&lt;pre class="mycode"&gt;&lt;span style="color:green;"&gt;// calculate the front paraboloid map texture coordinates&lt;/span&gt;&lt;br /&gt;&lt;span style="color:blue;"&gt;float2 &lt;/span&gt;front;&lt;br /&gt;front.x = R.x / (R.z + 1);&lt;br /&gt;front.y = R.y / (R.z + 1);&lt;br /&gt;front.x = .5f * front.x + .5f; &lt;span style="color:green;"&gt;//bias and scale to correctly sample a d3d texture&lt;/span&gt;&lt;br /&gt;front.y = -.5f * front.y + .5f;&lt;br /&gt;&lt;br /&gt;&lt;span style="color:green;"&gt;// calculate the back paraboloid map texture coordinates&lt;/span&gt;&lt;br /&gt;&lt;span style="color:blue;"&gt;float2 &lt;/span&gt;back;&lt;br /&gt;back.x = R.x / (1 - R.z);&lt;br /&gt;back.y = R.y / (1 - R.z);&lt;br /&gt;back.x = .5f * back.x + .5f; &lt;span style="color:green;"&gt;//bias and scale to correctly sample a d3d texture&lt;/span&gt;&lt;br /&gt;back.y = -.5f * back.y + .5f;&lt;br /&gt;&lt;br /&gt;&lt;span style="color:blue;"&gt;float4 &lt;/span&gt;forward = &lt;span style="color:blue;"&gt;tex2D&lt;/span&gt;( FrontTex, front );    &lt;span style="color:green;"&gt;// sample the front paraboloid map&lt;/span&gt;&lt;br /&gt;&lt;span style="color:blue;"&gt;float4 &lt;/span&gt;backward = &lt;span style="color:blue;"&gt;tex2D&lt;/span&gt;( BackTex, back );    &lt;span style="color:green;"&gt;// sample the back paraboloid map&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="color:blue;"&gt;float4 &lt;/span&gt;finalColor = &lt;span style="color:blue;"&gt;max&lt;/span&gt;(forward, backward);&lt;/pre&gt;&lt;a href="http://www.blogger.com/%3Ca" 20href=""&gt;&lt;/a&gt;&lt;p&gt;&lt;p&gt;&lt;a href="http://lh5.ggpht.com/GraphicsRunner/SH4TuTnFusI/AAAAAAAAATQ/zWHuWWsBhJY/ss14.png"&gt;&lt;img height="312" alt="ss1" src="http://lh3.ggpht.com/GraphicsRunner/SH4TvaUrKBI/AAAAAAAAATU/kRuYcwleFKA/ss1_thumb2.png" width="399" /&gt;&lt;/a&gt; &lt;/p&gt;&lt;p&gt;&lt;strong&gt;&lt;span style="color:#ff6600;"&gt;Optimizations:&lt;/span&gt;&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;If you align the paraboloid 'camera' such that it is always facing down the +/- z axis, you don't need to transform the vertices by the view matrix of the camera. You only need to do a simple translation of the vertex by the camera position.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;&lt;span style="color:#ff6600;"&gt;Conclusion:&lt;/span&gt;&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;As you can see, paraboloid maps give pretty good results. The won't give you the quality of cubemaps, but they are faster to update and require less memory. And in the console world, requiring less is almost reason enough to pick this method over cubemaps.&lt;/p&gt;&lt;p&gt;One drawback of paraboloid maps is that the environment geometry has to be sufficiently tessellated or will we will have noticeable artifacts on our reflector. Another drawback is that on spherical objects we will see seems. However with objects that are reasonably complex (such as the Stanford bunny or dragon) and are not simple shapes, the seams will not be as noticeable. &lt;/p&gt;&lt;p&gt;Next time I will present dual-paraboloid mapping for use with real-time omnidirectional shadow mapping of point lights.&lt;br /&gt;&lt;br /&gt;&lt;iframe style="BORDER-RIGHT: #dde5e9 1px solid; PADDING-RIGHT: 0px; BORDER-TOP: #dde5e9 1px solid; PADDING-LEFT: 0px; PADDING-BOTTOM: 0px; MARGIN: 3px; BORDER-LEFT: #dde5e9 1px solid; WIDTH: 240px; PADDING-TOP: 0px; BORDER-BOTTOM: #dde5e9 1px solid; HEIGHT: 66px; BACKGROUND-COLOR: #ffffff" marginwidth="0" marginheight="0" src="http://cid-b80a3031b5bfa52b.skydrive.live.com/embedrowdetail.aspx/Public/Dual-Paraboloid%20Reflections.zip" frameborder="0" scrolling="no"&gt;&lt;/iframe&gt;&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1023441640234597436-4913038228316931145?l=graphicsrunner.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://graphicsrunner.blogspot.com/feeds/4913038228316931145/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=1023441640234597436&amp;postID=4913038228316931145' title='36 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1023441640234597436/posts/default/4913038228316931145'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1023441640234597436/posts/default/4913038228316931145'/><link rel='alternate' type='text/html' href='http://graphicsrunner.blogspot.com/2008/07/dual-paraboloid-reflections.html' title='Dual-Paraboloid Reflections'/><author><name>Kyle Hayward</name><uri>http://www.blogger.com/profile/00654406875137720609</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://bp3.blogger.com/_VelpN_FHzhk/R-Lx4Kk_nkI/AAAAAAAAAN8/jn_4uDeKxNk/S220/me.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://lh4.ggpht.com/GraphicsRunner/SH4TooD_JiI/AAAAAAAAASs/OdFKgTKhoDI/s72-c/dragon_thumb3.png' height='72' width='72'/><thr:total>36</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1023441640234597436.post-3557752850451120778</id><published>2008-06-20T13:24:00.004-04:00</published><updated>2008-06-24T09:19:19.345-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Atmospheric Scattering'/><category scheme='http://www.blogger.com/atom/ns#' term='Terrain'/><category scheme='http://www.blogger.com/atom/ns#' term='managed'/><category scheme='http://www.blogger.com/atom/ns#' term='DirectX'/><title type='text'>Terrain and Atmospheric Scattering Source</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_VelpN_FHzhk/R-Kfuak_nQI/AAAAAAAAALc/8iLrjRYigWc/s1600-h/SunsetWater04.png"&gt;&lt;img id="BLOGGER_PHOTO_ID_5179878141186776322" style="CURSOR: pointer" alt="" src="http://1.bp.blogspot.com/_VelpN_FHzhk/R-Kfuak_nQI/AAAAAAAAALc/8iLrjRYigWc/s200/SunsetWater04.png" border="0" /&gt;&lt;/a&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_VelpN_FHzhk/R-Kg8Kk_nYI/AAAAAAAAAMc/1NviqjMFl8g/s1600-h/Terrain+2007-11-27+23-49-29-48.bmp"&gt;&lt;img id="BLOGGER_PHOTO_ID_5179879476921605506" style="CURSOR: pointer" height="158" alt="" src="http://4.bp.blogspot.com/_VelpN_FHzhk/R-Kg8Kk_nYI/AAAAAAAAAMc/1NviqjMFl8g/s200/Terrain+2007-11-27+23-49-29-48.bmp" width="216" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;I figured I would release my source for the terrain rendering demo. There aren't that many terrain and atmospheric scattering examples on the net, so I figured I might as well post mine for anyone that would like to see it. &lt;/p&gt;&lt;p&gt;It's still a work in progress (that I paused about a year ago), so it's kind of immature. But it is the same code that produced the images I posted awhile back. You'll probably need at least a 6 series Nvidia graphics card (or ATi equivalent). For the terrain, the demo uses a simple quad tree with frustum culling, so it's not the greatest performer for anything above 512x512 heigtmaps. Also, it is in Managed DirectX, but it shouldn't be too hard to port to XNA.&lt;br /&gt;&lt;br /&gt;&lt;iframe style="BORDER-RIGHT: #dde5e9 1px solid; PADDING-RIGHT: 0px; BORDER-TOP: #dde5e9 1px solid; PADDING-LEFT: 0px; PADDING-BOTTOM: 0px; MARGIN: 3px; BORDER-LEFT: #dde5e9 1px solid; WIDTH: 240px; PADDING-TOP: 0px; BORDER-BOTTOM: #dde5e9 1px solid; HEIGHT: 66px; BACKGROUND-COLOR: #ffffff" marginwidth="0" marginheight="0" src="http://cid-b80a3031b5bfa52b.skydrive.live.com/embedrowdetail.aspx/Public/Terrain%20+%20Scattering.zip" frameborder="0" scrolling="no"&gt;&lt;/iframe&gt;&lt;br /&gt;&lt;br /&gt;Anyway, enough of that. If you didn't know, the &lt;a href="http://www.spore.com/"&gt;Creature Creator for Spore&lt;/a&gt; has been released, and it's an absolute blast to mess around with. The possibilities on what you can create are pretty much endless. Here's a couple that I made: &lt;/p&gt;&lt;p&gt;&lt;a href="http://lh3.ggpht.com/GraphicsRunner/SFvnqml9lFI/AAAAAAAAASY/M8Qk7ctreTw/CRE_spider-0685c592_sml%5B6%5D.jpg"&gt;&lt;img height="290" alt="CRE_spider-0685c592_sml" src="http://lh4.ggpht.com/GraphicsRunner/SFvnsRIn2xI/AAAAAAAAASc/V1AGe7LXPzA/CRE_spider-0685c592_sml_thumb%5B4%5D.jpg" width="381" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;a href="http://lh5.ggpht.com/GraphicsRunner/SFvntDmRZjI/AAAAAAAAASg/fOVcacYVPi8/CRE_Graptilosaurus-0685c593_sml%5B9%5D.jpg"&gt;&lt;img height="301" alt="CRE_Graptilosaurus-0685c593_sml" src="http://lh3.ggpht.com/GraphicsRunner/SFvntU5mBBI/AAAAAAAAASk/TpfuEGyglB8/CRE_Graptilosaurus-0685c593_sml_thumb%5B7%5D.jpg" width="381" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1023441640234597436-3557752850451120778?l=graphicsrunner.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://graphicsrunner.blogspot.com/feeds/3557752850451120778/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=1023441640234597436&amp;postID=3557752850451120778' title='27 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1023441640234597436/posts/default/3557752850451120778'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1023441640234597436/posts/default/3557752850451120778'/><link rel='alternate' type='text/html' href='http://graphicsrunner.blogspot.com/2008/06/terrain-and-scattering-source.html' title='Terrain and Atmospheric Scattering Source'/><author><name>Kyle Hayward</name><uri>http://www.blogger.com/profile/00654406875137720609</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://bp3.blogger.com/_VelpN_FHzhk/R-Lx4Kk_nkI/AAAAAAAAAN8/jn_4uDeKxNk/S220/me.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/_VelpN_FHzhk/R-Kfuak_nQI/AAAAAAAAALc/8iLrjRYigWc/s72-c/SunsetWater04.png' height='72' width='72'/><thr:total>27</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1023441640234597436.post-6583110030590751854</id><published>2008-06-03T11:28:00.019-04:00</published><updated>2008-07-16T14:36:54.287-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Post Processing'/><category scheme='http://www.blogger.com/atom/ns#' term='depth of field'/><category scheme='http://www.blogger.com/atom/ns#' term='XNA'/><category scheme='http://www.blogger.com/atom/ns#' term='Bloom'/><title type='text'>Post Process Framework Sample</title><content type='html'>&lt;p&gt;&lt;a href="http://lh5.ggpht.com/GraphicsRunner/SEVjFEt3e1I/AAAAAAAAAR4/LkiiYoDVkVA/dof5.png"&gt;&lt;img height="318" alt="dof" src="http://lh3.ggpht.com/GraphicsRunner/SEVjFkt3e2I/AAAAAAAAAR8/U5YPKrPxiK0/dof_thumb3.png" width="402" /&gt;&lt;/a&gt; &lt;/p&gt;&lt;p&gt;Today I'm posting a post processing framework sample. For those that don't know, post processing is any manipulation of the frame buffer after the scene has been rendered (but usually before the UI), hence the word "post" in the name. This process could be a simple blurring, or it could be a motion blur or depth of field technique. But what if you have many post processes? For instance, maybe we have bloom, motion blur, heat haze, depth of field, and other post processes that we need to apply after we render our scene. Connecting all these post processes together can get a little hairy, and that's what this sample helps us do.&lt;/p&gt;&lt;p&gt;So lets talk about how we're going to structure our framework. At the very bottom of the hierarchy, we have a &lt;strong&gt;&lt;span style="color:#ff6600;"&gt;PostProcessComponent&lt;/span&gt;&lt;/strong&gt; (component from here on out). And what this class represents, is a single atomic process, that is, it cannot be broken up into smaller pieces/objects. Each component has an input and an output in the form of textures. A component is also able to update the backbuffer. It is the simplest object in the hierarchy and is combined with other components to form a &lt;strong&gt;&lt;span style="color:#ff6600;"&gt;PostProcessEffect&lt;/span&gt;&lt;/strong&gt; (effect from here on out). &lt;/p&gt;&lt;p&gt;An effect contains a chain of components to implement a single post process such as bloom or motion blur. An effect also handles output from one component to input to another. In this way the components can be independent of one another, and the effect handles linking all the components that it owns. Because of this, each component is very simple. Also, like components, effects have inputs and outputs in the form of textures. Effects can also be enabled/disabled dynamically at runtime.&lt;/p&gt;&lt;p&gt;Next we have the &lt;span style="color:#ff6600;"&gt;&lt;strong&gt;PostProcessManager&lt;/strong&gt;&lt;/span&gt;(aka manager). The manager is comprised of a chain of effects, and it handles linking the output of one effect to the input of the next effect. And just like with components, this enables each effect to be independent of the next. The manager also takes care of linking the backbuffer and depth buffer to components that need it.&lt;/p&gt;&lt;p&gt;Because each component is not dependent on other components, each one is very simple in its implementation. This not only helps in understanding the code, but also in robustness and tracking down any possible errors in a component. Another nice feature about this framework is that you do not need to create a separate class deriving from PostProcessEffect for each effect. In this way, you can dynamically create your effects at run time. Most of the "magic" occurs in the LoadContent() function inside PostProcessManager. So lets have a look at it:&lt;/p&gt;&lt;pre class="mycode"&gt;&lt;span style="color:blue;"&gt;public void &lt;/span&gt;LoadContent()&lt;br /&gt;{&lt;br /&gt;   &lt;span style="color:blue;"&gt;#region &lt;/span&gt;Create common textures to be used by the effects&lt;br /&gt;   &lt;span style="COLOR: rgb(43,145,175)"&gt;PresentationParameters &lt;/span&gt;pp = mGraphicsDevice.PresentationParameters;&lt;br /&gt;   &lt;span style="color:blue;"&gt;int &lt;/span&gt;width = pp.BackBufferWidth;&lt;br /&gt;   &lt;span style="color:blue;"&gt;int &lt;/span&gt;height = pp.BackBufferHeight;&lt;br /&gt;&lt;br /&gt;   &lt;span style="COLOR: rgb(43,145,175)"&gt;SurfaceFormat &lt;/span&gt;format = pp.BackBufferFormat;&lt;br /&gt;&lt;br /&gt;   resolveTexture = &lt;span style="color:blue;"&gt;new &lt;/span&gt;&lt;span style="COLOR: rgb(43,145,175)"&gt;ResolveTexture2D&lt;/span&gt;(mGraphicsDevice, width, height, 1, format);&lt;br /&gt;   &lt;span style="color:blue;"&gt;#endregion&lt;br /&gt;&lt;br /&gt;   int &lt;/span&gt;i = 0;&lt;br /&gt;   &lt;span style="color:blue;"&gt;foreach &lt;/span&gt;(&lt;span style="COLOR: rgb(43,145,175)"&gt;PostProcessEffect &lt;/span&gt;effect &lt;span style="color:blue;"&gt;in &lt;/span&gt;effects)&lt;br /&gt;   {&lt;br /&gt;       effect.LoadContent();&lt;br /&gt;&lt;br /&gt;       &lt;span style="color:blue;"&gt;int &lt;/span&gt;j = 0;&lt;br /&gt;       &lt;span style="color:green;"&gt;//if a component requires a backbuffer, add their function to the event handler&lt;br /&gt;       &lt;/span&gt;&lt;span style="color:blue;"&gt;foreach &lt;/span&gt;(&lt;span style="COLOR: rgb(43,145,175)"&gt;PostProcessComponent &lt;/span&gt;component &lt;span style="color:blue;"&gt;in &lt;/span&gt;effect.Components)&lt;br /&gt;       {&lt;br /&gt;           &lt;span style="color:green;"&gt;//if the component updates/modifies the "scene texture"&lt;br /&gt;           //find all the components who need an up to date scene texture&lt;br /&gt;           &lt;/span&gt;&lt;span style="color:blue;"&gt;if &lt;/span&gt;(component.UpdatesSceneTexture)&lt;br /&gt;           {&lt;br /&gt;               &lt;span style="color:blue;"&gt;int &lt;/span&gt;k = 0;&lt;br /&gt;               &lt;span style="color:blue;"&gt;foreach &lt;/span&gt;(&lt;span style="COLOR: rgb(43,145,175)"&gt;PostProcessEffect &lt;/span&gt;e &lt;span style="color:blue;"&gt;in &lt;/span&gt;effects)&lt;br /&gt;               {&lt;br /&gt;                   &lt;span style="color:blue;"&gt;int &lt;/span&gt;l = 0;&lt;br /&gt;                   &lt;span style="color:blue;"&gt;foreach &lt;/span&gt;(&lt;span style="COLOR: rgb(43,145,175)"&gt;PostProcessComponent &lt;/span&gt;c &lt;span style="color:blue;"&gt;in &lt;/span&gt;e.Components)&lt;br /&gt;                   {&lt;br /&gt;                       &lt;span style="color:green;"&gt;//skip previous components and ourself&lt;br /&gt;                       &lt;/span&gt;&lt;span style="color:blue;"&gt;if &lt;/span&gt;(k &amp;lt; i)&lt;br /&gt;                       {&lt;br /&gt;                           &lt;span style="color:blue;"&gt;continue&lt;/span&gt;;&lt;br /&gt;                       }&lt;br /&gt;                       &lt;span style="color:blue;"&gt;else if &lt;/span&gt;(k == i &amp;amp;&amp;amp; l &amp;lt;= j)&lt;br /&gt;                       {&lt;br /&gt;                           l++;&lt;br /&gt;                           &lt;span style="color:blue;"&gt;continue&lt;/span&gt;;&lt;br /&gt;                       }&lt;br /&gt;                       &lt;span style="color:blue;"&gt;else if &lt;/span&gt;(c.RequiresSceneTexture)&lt;br /&gt;                       {&lt;br /&gt;                           component.OnUpdateSceneTexture += &lt;span style="color:blue;"&gt;new &lt;/span&gt;&lt;span style="COLOR: rgb(43,145,175)"&gt;UpdateSceneTextureEventHandler&lt;/span&gt;(c.UpdateSceneTexture);&lt;br /&gt;                       }&lt;br /&gt;&lt;br /&gt;                       l++;&lt;br /&gt;                   }&lt;br /&gt;&lt;br /&gt;                   k++;&lt;br /&gt;               }&lt;br /&gt;           }&lt;br /&gt;&lt;br /&gt;           &lt;span style="color:green;"&gt;//add the compontent's UpdateBackBuffer method to the event handler&lt;br /&gt;           &lt;/span&gt;&lt;span style="color:blue;"&gt;if &lt;/span&gt;(component.RequiresBackbuffer&lt;br /&gt;               (effect == effects[0] &amp;amp;&amp;amp; component == effect.Components[0]))&lt;br /&gt;               OnBackBufferResolve += &lt;span style="color:blue;"&gt;new &lt;/span&gt;&lt;span style="COLOR: rgb(43,145,175)"&gt;BackBufferResolveEventHandler&lt;/span&gt;(component.UpdateBackbuffer);&lt;br /&gt;&lt;br /&gt;           &lt;span style="color:blue;"&gt;if &lt;/span&gt;(component.RequiresDepthBuffer)&lt;br /&gt;               OnDepthBufferResolve += &lt;span style="color:blue;"&gt;new &lt;/span&gt;&lt;span style="COLOR: rgb(43,145,175)"&gt;DepthBufferResolveEventHandler&lt;/span&gt;(component.UpdateDepthBuffer);&lt;br /&gt;&lt;br /&gt;           j++;&lt;br /&gt;       } &lt;span style="color:green;"&gt;//components foreach&lt;br /&gt;&lt;br /&gt;       &lt;/span&gt;i++;&lt;br /&gt;   } &lt;span style="color:green;"&gt;//effects foreach&lt;br /&gt;&lt;br /&gt;   &lt;/span&gt;&lt;span style="color:blue;"&gt;if &lt;/span&gt;(effects.Count &amp;gt; 0)&lt;br /&gt;   {&lt;br /&gt;       &lt;span style="color:green;"&gt;//ensure the last component renders to the backbuffer&lt;br /&gt;       &lt;/span&gt;effects[effects.Count - 1].IsFinal = &lt;span style="color:blue;"&gt;true&lt;/span&gt;;&lt;br /&gt;&lt;br /&gt;       &lt;span style="color:blue;"&gt;if &lt;/span&gt;(OnDepthBufferResolve != &lt;span style="color:blue;"&gt;null&lt;/span&gt;)&lt;br /&gt;       {&lt;br /&gt;           depthBuffer = &lt;span style="color:blue;"&gt;new &lt;/span&gt;&lt;span style="COLOR: rgb(43,145,175)"&gt;BuildZBufferComponent&lt;/span&gt;(mContent, mGraphicsDevice);&lt;br /&gt;           depthBuffer.Camera = camera;&lt;br /&gt;           depthBuffer.Models = models;&lt;br /&gt;           depthBuffer.LoadContent();&lt;br /&gt;       }&lt;br /&gt;   }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;Here we first setup the resolve texture that will resolve the backbuffer every frame. Then we search through the components, looking for ones that update the backbuffer(scene texture). If a component does this, then we need to find all the components after it that need the most recent version of the backbuffer and assign its update function to the OnUpdateSceneTexture event. Next, we search through all the effects, looking for components that need the backbuffer and depth buffer and attach its backbuffer update function to the OnBackBufferResolve and OnDepthBufferResolve events.&lt;/p&gt;&lt;p&gt;Below is what a sample post process effect chain could look like:&lt;/p&gt;&lt;p&gt;&lt;a href="http://lh5.ggpht.com/GraphicsRunner/SEVjGEt3e3I/AAAAAAAAASA/sFH9nlQdwOY/PostProcessDiagram5.jpg"&gt;&lt;img height="348" alt="PostProcessDiagram" src="http://lh3.ggpht.com/GraphicsRunner/SEVjGkt3e4I/AAAAAAAAASE/zCZ0fOZ7r7o/PostProcessDiagram_thumb3.jpg" width="428" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;Here we have three post process effects: bloom, motion blur, and depth of field. Each contains its own components that perform operations on the texture given to it. Some components need to blend with the backbuffer, and therefore need to update the backbuffer in turn so that other components are able to have the most recent copy of the backbuffer (components linked with red lines). &lt;/p&gt;&lt;p&gt;Here is an example of how we could setup the above effect chain:&lt;/p&gt;&lt;pre class="mycode"&gt;&lt;span style="COLOR: rgb(43,145,175)"&gt;PostProcessEffect &lt;/span&gt;bloom = &lt;span style="color:blue;"&gt;new &lt;/span&gt;&lt;span style="COLOR: rgb(43,145,175)"&gt;PostProcessEffect&lt;/span&gt;(Content, Game.GraphicsDevice);&lt;br /&gt;bloom.AddComponent(&lt;span style="color:blue;"&gt;new &lt;/span&gt;&lt;span style="COLOR: rgb(43,145,175)"&gt;BrightPassComponent&lt;/span&gt;(Content, Game.GraphicsDevice));&lt;br /&gt;bloom.AddComponent(&lt;span style="color:blue;"&gt;new &lt;/span&gt;&lt;span style="COLOR: rgb(43,145,175)"&gt;GaussBlurComponent&lt;/span&gt;(Content, Game.GraphicsDevice));&lt;br /&gt;bloom.AddComponent(&lt;span style="color:blue;"&gt;new &lt;/span&gt;&lt;span style="COLOR: rgb(43,145,175)"&gt;BloomCompositeComponent&lt;/span&gt;(Content, Game.GraphicsDevice));&lt;br /&gt;&lt;br /&gt;&lt;span style="COLOR: rgb(43,145,175)"&gt;PostProcessEffect &lt;/span&gt;motionblur = &lt;span style="color:blue;"&gt;new &lt;/span&gt;&lt;span style="COLOR: rgb(43,145,175)"&gt;PostProcessEffect&lt;/span&gt;(Content, Game.GraphicsDevice);&lt;br /&gt;motionblur.AddComponent(&lt;span style="color:blue;"&gt;new &lt;/span&gt;&lt;span style="COLOR: rgb(43,145,175)"&gt;MotionVelocityComponent&lt;/span&gt;(Content, Game.GraphicsDevice));&lt;br /&gt;motionblur.AddComponent(&lt;span style="color:blue;"&gt;new &lt;/span&gt;&lt;span style="COLOR: rgb(43,145,175)"&gt;MotionBlurHighComponent&lt;/span&gt;(Content, Game.GraphicsDevice));&lt;br /&gt;&lt;br /&gt;&lt;span style="COLOR: rgb(43,145,175)"&gt;PostProcessEffect &lt;/span&gt;depthOfField = &lt;span style="color:blue;"&gt;new &lt;/span&gt;&lt;span style="COLOR: rgb(43,145,175)"&gt;PostProcessEffect&lt;/span&gt;(Content, Game.GraphicsDevice);&lt;br /&gt;depthOfField.AddComponent(&lt;span style="color:blue;"&gt;new &lt;/span&gt;&lt;span style="COLOR: rgb(43,145,175)"&gt;DownFilterComponent&lt;/span&gt;(Content, Game.GraphicsDevice));&lt;br /&gt;depthOfField.AddComponent(&lt;span style="color:blue;"&gt;new &lt;/span&gt;&lt;span style="COLOR: rgb(43,145,175)"&gt;PoissonBlurComponent&lt;/span&gt;(Content, Game.GraphicsDevice));&lt;br /&gt;depthOfField.AddComponent(&lt;span style="color:blue;"&gt;new &lt;/span&gt;&lt;span style="COLOR: rgb(43,145,175)"&gt;DepthOfFieldComponent&lt;/span&gt;(Content, Game.GraphicsDevice));&lt;br /&gt;&lt;br /&gt;postManager.AddEffect(bloom);&lt;br /&gt;postManager.AddEffect(motionblur);&lt;br /&gt;postManager.AddEffect(depthOfField);&lt;/pre&gt;&lt;p&gt;In the sample, I have included 3 post processes: bloom, depth of field, and a distortion process. I've documented the classes and functions pretty well, so these combined with this write up should be enough to help you understand how everything works. Should you have any questions, just post in the comments.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;&lt;span style="color:#ff6600;"&gt;Homework for the reader:&lt;/span&gt;&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;&lt;span style="color:#000000;"&gt;In this sample, I create a new render target fore every component that needs one. This is bad. In my own implementation that I use for my own projects, I create a render target pool. The render target pool stores and fetches render targets. When a componet requests a render target, the pool will check to see if one of the specified dimensions and format has been stored already. If it has it will return it, if it hasn't it will return a new render target. Now we won't create a new render target for every component. The pool allows us to reuse render targets which helps save gpu memory and increase performance. So if we have 5 effects with 3 components each, we might only create 5 render targets (depending on how many share similar formats and dimensions) instead of 15.&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style="color:#ff0000;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;object height="350" width="425"&gt;&lt;param name="movie" value="http://www.youtube.com/v/MQF19IiKmbs"&gt;&lt;embed src="http://www.youtube.com/v/MQF19IiKmbs" type="application/x-shockwave-flash" width="425" height="350"&gt;&lt;/embed&gt;&lt;/object&gt;&lt;br /&gt;&lt;br /&gt;&lt;iframe style="BORDER-RIGHT: #dde5e9 1px solid; PADDING-RIGHT: 0px; BORDER-TOP: #dde5e9 1px solid; PADDING-LEFT: 0px; PADDING-BOTTOM: 0px; MARGIN: 3px; BORDER-LEFT: #dde5e9 1px solid; WIDTH: 240px; PADDING-TOP: 0px; BORDER-BOTTOM: #dde5e9 1px solid; HEIGHT: 26px; BACKGROUND-COLOR: #ffffff" marginwidth="0" marginheight="0" src="http://cid-b80a3031b5bfa52b.skydrive.live.com/embedrow.aspx/Public/Postprocess%20Framework%20Sample.wmv" frameborder="0" scrolling="no"&gt;&lt;/iframe&gt;&lt;/p&gt;&lt;p&gt;&lt;span style="color:#ff6600;"&gt;&lt;strong&gt;Edit (6/6/2008):&lt;/strong&gt;Added support for cards that do not support the R32F (Single) SurfaceFormat (hopefully :) ). Removed in-progress auto focusing code in depth-of-field effect.&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style="color:#ff6600;"&gt;&lt;strong&gt;Edit (6/5/2008):&lt;/strong&gt; Added some new functionality, namely being able to specify the states and color channel modulation of the spritebatch.&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;&lt;iframe style="BORDER-RIGHT: #dde5e9 1px solid; PADDING-RIGHT: 0px; BORDER-TOP: #dde5e9 1px solid; PADDING-LEFT: 0px; PADDING-BOTTOM: 0px; MARGIN: 3px; BORDER-LEFT: #dde5e9 1px solid; WIDTH: 240px; PADDING-TOP: 0px; BORDER-BOTTOM: #dde5e9 1px solid; HEIGHT: 66px; BACKGROUND-COLOR: #ffffff" marginwidth="0" marginheight="0" src="http://cid-b80a3031b5bfa52b.skydrive.live.com/embedrowdetail.aspx/Public/PostProcessing.zip" frameborder="0" scrolling="no"&gt;&lt;/iframe&gt;&lt;br /&gt;&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1023441640234597436-6583110030590751854?l=graphicsrunner.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://graphicsrunner.blogspot.com/feeds/6583110030590751854/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=1023441640234597436&amp;postID=6583110030590751854' title='10 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1023441640234597436/posts/default/6583110030590751854'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1023441640234597436/posts/default/6583110030590751854'/><link rel='alternate' type='text/html' href='http://graphicsrunner.blogspot.com/2008/06/post-process-framework-sample.html' title='Post Process Framework Sample'/><author><name>Kyle Hayward</name><uri>http://www.blogger.com/profile/00654406875137720609</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://bp3.blogger.com/_VelpN_FHzhk/R-Lx4Kk_nkI/AAAAAAAAAN8/jn_4uDeKxNk/S220/me.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://lh3.ggpht.com/GraphicsRunner/SEVjFkt3e2I/AAAAAAAAAR8/U5YPKrPxiK0/s72-c/dof_thumb3.png' height='72' width='72'/><thr:total>10</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1023441640234597436.post-7515737701963111452</id><published>2008-05-20T16:49:00.003-04:00</published><updated>2008-05-20T17:03:28.079-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Post Processing'/><category scheme='http://www.blogger.com/atom/ns#' term='XNA'/><category scheme='http://www.blogger.com/atom/ns#' term='Wii'/><title type='text'>New XNA site and other happenings</title><content type='html'>If you didn't know there's a new version of the &lt;a href="http://creators.xna.com/"&gt;creators site&lt;/a&gt; up, go check it out.&lt;br /&gt;&lt;br /&gt;I've been pretty busy lately. This summer I have an internship with a local game developer, Gabriel Entertainment. They have me working with a Wii dev kit to help bring one of their PC games to the Wii. It's pretty cool stuff.&lt;br /&gt;&lt;br /&gt;I'm also working on a remake of the &lt;a href="http://dxinteractive.com/"&gt;Missile Game 3D&lt;/a&gt; flash game in XNA in my free time. I'll be posting the post process manager that I'm using for this project once I port it from XNA 1.0 to 2.0. So stay tuned for this. I'll probably include with this motion blur and depth-of-field post processes.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1023441640234597436-7515737701963111452?l=graphicsrunner.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://graphicsrunner.blogspot.com/feeds/7515737701963111452/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=1023441640234597436&amp;postID=7515737701963111452' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1023441640234597436/posts/default/7515737701963111452'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1023441640234597436/posts/default/7515737701963111452'/><link rel='alternate' type='text/html' href='http://graphicsrunner.blogspot.com/2008/05/new-xna-site-and-other-happenings.html' title='New XNA site and other happenings'/><author><name>Kyle Hayward</name><uri>http://www.blogger.com/profile/00654406875137720609</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://bp3.blogger.com/_VelpN_FHzhk/R-Lx4Kk_nkI/AAAAAAAAAN8/jn_4uDeKxNk/S220/me.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1023441640234597436.post-6804398499228700445</id><published>2008-05-03T12:45:00.013-04:00</published><updated>2008-09-19T02:20:36.710-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Water'/><category scheme='http://www.blogger.com/atom/ns#' term='refractions'/><category scheme='http://www.blogger.com/atom/ns#' term='XNA'/><category scheme='http://www.blogger.com/atom/ns#' term='reflections'/><category scheme='http://www.blogger.com/atom/ns#' term='camera animation'/><title type='text'>Camera Animation, Part II</title><content type='html'>Camera Animation with a cubic spline:&lt;br /&gt;&lt;br /&gt;&lt;object width="320" height="266" class="BLOG_video_class" id="BLOG_video-6fd7e53df9f0eca3" classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,40,0"&gt;&lt;param name="movie" value="http://www.youtube.com/get_player"&gt;&lt;param name="bgcolor" value="#FFFFFF"&gt;&lt;param name="allowfullscreen" value="true"&gt;&lt;param name="flashvars" value="flvurl=http://v24.nonxt5.googlevideo.com/videoplayback?id%3D6fd7e53df9f0eca3%26itag%3D5%26app%3Dblogger%26ip%3D0.0.0.0%26ipbits%3D0%26expire%3D1330218805%26sparams%3Did,itag,ip,ipbits,expire%26signature%3D76C86A4A1F60EA08B84D9BA8514D11E62EFBF44A.830669E7321509A3848D4B4472CEC62628253C63%26key%3Dck1&amp;amp;iurl=http://video.google.com/ThumbnailServer2?app%3Dblogger%26contentid%3D6fd7e53df9f0eca3%26offsetms%3D5000%26itag%3Dw160%26sigh%3DNkDMM4BmNx3EJz0_bBHYfBB-gjk&amp;amp;autoplay=0&amp;amp;ps=blogger"&gt;&lt;embed src="http://www.youtube.com/get_player" type="application/x-shockwave-flash"width="320" height="266" bgcolor="#FFFFFF"flashvars="flvurl=http://v24.nonxt5.googlevideo.com/videoplayback?id%3D6fd7e53df9f0eca3%26itag%3D5%26app%3Dblogger%26ip%3D0.0.0.0%26ipbits%3D0%26expire%3D1330218805%26sparams%3Did,itag,ip,ipbits,expire%26signature%3D76C86A4A1F60EA08B84D9BA8514D11E62EFBF44A.830669E7321509A3848D4B4472CEC62628253C63%26key%3Dck1&amp;iurl=http://video.google.com/ThumbnailServer2?app%3Dblogger%26contentid%3D6fd7e53df9f0eca3%26offsetms%3D5000%26itag%3Dw160%26sigh%3DNkDMM4BmNx3EJz0_bBHYfBB-gjk&amp;autoplay=0&amp;ps=blogger"allowFullScreen="true" /&gt;&lt;/object&gt;&lt;br /&gt;&lt;br /&gt;&lt;iframe style="BORDER-RIGHT: #dde5e9 1px solid; PADDING-RIGHT: 0px; BORDER-TOP: #dde5e9 1px solid; PADDING-LEFT: 0px; PADDING-BOTTOM: 0px; MARGIN: 3px; BORDER-LEFT: #dde5e9 1px solid; WIDTH: 240px; PADDING-TOP: 0px; BORDER-BOTTOM: #dde5e9 1px solid; HEIGHT: 26px; BACKGROUND-COLOR: #ffffff" marginwidth="0" marginheight="0" src="http://cid-b80a3031b5bfa52b.skydrive.live.com/embedrow.aspx/Public/Cut%20Scene%20II.wmv" frameborder="0" scrolling="no"&gt;&lt;/iframe&gt;&lt;br /&gt;&lt;br /&gt;So last time I lied a little bit about our camera path. I showed this picture:&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://img365.imageshack.us/img365/895/camerapathhn1.jpg"&gt;&lt;img style="WIDTH: 415px; CURSOR: pointer; HEIGHT: 129px" alt="" src="http://img365.imageshack.us/img365/895/camerapathhn1.jpg" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;to describe the path that we wanted. But with linear interpolation, what we really ended up with was something like:&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://img166.imageshack.us/img166/6742/camerapathtruthcw0.jpg"&gt;&lt;img style="WIDTH: 421px; CURSOR: pointer; HEIGHT: 132px" alt="" src="http://img166.imageshack.us/img166/6742/camerapathtruthcw0.jpg" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;But today, I'll introduce cubic spline interpolation, and this will produce a truly smooth curve. Most of the code is the same, the only thing that has changed is that I have added a cubic option to the build function and have changed the animation to be reliant on time.&lt;br /&gt;&lt;br /&gt;Building the natural cubic spline is more math intensive than with linear interpolation, so to start out here's a few links on cubic splines:&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;&lt;a href="http://mathworld.wolfram.com/CubicSpline.html"&gt;http://mathworld.wolfram.com/CubicSpline.html&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://www.physics.utah.edu/~detar/phycs6720/handouts/cubic_spline/cubic_spline/node1.html"&gt;http://www.physics.utah.edu/~detar/phycs6720/handouts/cubic_spline/cubic_spline/node1.html&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://www.cse.unsw.edu.au/~lambert/splines/"&gt;http://www.cse.unsw.edu.au/~lambert/splines/&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://www.amazon.com/Introduction-Computer-Graphics-Geometric-Modeling/dp/1558604006/ref=sr_1_1?ie=UTF8&amp;amp;s=books&amp;amp;qid=1209838977&amp;amp;sr=8-1"&gt;An Introduction to Splines for Use in Computer Graphics and Geometric Modelling&lt;/a&gt;&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;pre class="mycode"&gt;&lt;span style="color:green;"&gt;//a point on a cubic spline is affected by every other point&lt;br /&gt;//so we need to build the cubic equations for each control point&lt;br /&gt;//by looking at the entire curve. This is what calculateCubicSpline does&lt;br /&gt;&lt;/span&gt;&lt;span style="color:#2b91af;"&gt;Vector3&lt;/span&gt;[] looks = &lt;span style="color:blue;"&gt;new &lt;/span&gt;&lt;span style="color:#2b91af;"&gt;Vector3&lt;/span&gt;[mKeyFrames.Count];&lt;br /&gt;&lt;span style="color:#2b91af;"&gt;Vector3&lt;/span&gt;[] positions = &lt;span style="color:blue;"&gt;new &lt;/span&gt;&lt;span style="color:#2b91af;"&gt;Vector3&lt;/span&gt;[mKeyFrames.Count];&lt;br /&gt;&lt;span style="color:#2b91af;"&gt;Vector3&lt;/span&gt;[] ups = &lt;span style="color:blue;"&gt;new &lt;/span&gt;&lt;span style="color:#2b91af;"&gt;Vector3&lt;/span&gt;[mKeyFrames.Count];&lt;br /&gt;&lt;br /&gt;&lt;span style="color:blue;"&gt;for &lt;/span&gt;(&lt;span style="color:blue;"&gt;int &lt;/span&gt;i = 0; i &amp;lt; mKeyFrames.Count; i++)&lt;br /&gt;{&lt;br /&gt;    looks[i] = mKeyFrames[i].Look;&lt;br /&gt;    positions[i] = mKeyFrames[i].Position;&lt;br /&gt;    ups[i] = mKeyFrames[i].Up;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;span style="color:#2b91af;"&gt;Cubic&lt;/span&gt;[] pos_cubic = calculateCubicSpline(mKeyFrames.Count - 1, positions);&lt;br /&gt;&lt;span style="color:#2b91af;"&gt;Cubic&lt;/span&gt;[] look_cubic = calculateCubicSpline(mKeyFrames.Count - 1, looks);&lt;br /&gt;&lt;span style="color:#2b91af;"&gt;Cubic&lt;/span&gt;[] up_cubic = calculateCubicSpline(mKeyFrames.Count - 1, ups);&lt;br /&gt;&lt;br /&gt;&lt;span style="color:blue;"&gt;for &lt;/span&gt;(&lt;span style="color:blue;"&gt;int &lt;/span&gt;i = 0; i &amp;lt; mKeyFrames.Count - 1; i++)&lt;br /&gt;{&lt;br /&gt;    &lt;span style="color:blue;"&gt;for &lt;/span&gt;(&lt;span style="color:blue;"&gt;int &lt;/span&gt;j = 0; j &amp;lt; mPathSteps; j++)&lt;br /&gt;    {&lt;br /&gt;        &lt;span style="color:blue;"&gt;float &lt;/span&gt;k = (&lt;span style="color:blue;"&gt;float&lt;/span&gt;)j / (&lt;span style="color:blue;"&gt;float&lt;/span&gt;)(mPathSteps - 1);&lt;br /&gt;&lt;br /&gt;        &lt;span style="color:#2b91af;"&gt;Vector3 &lt;/span&gt;center = pos_cubic[i].GetPointOnSpline(k);&lt;br /&gt;        &lt;span style="color:#2b91af;"&gt;Vector3 &lt;/span&gt;up = up_cubic[i].GetPointOnSpline(k);&lt;br /&gt;        &lt;span style="color:#2b91af;"&gt;Vector3 &lt;/span&gt;look = look_cubic[i].GetPointOnSpline(k);&lt;br /&gt;&lt;br /&gt;        &lt;span style="color:#2b91af;"&gt;Vector3 &lt;/span&gt;r = &lt;span style="color:#2b91af;"&gt;Vector3&lt;/span&gt;.Cross(up, look);&lt;br /&gt;        &lt;span style="color:#2b91af;"&gt;Vector3 &lt;/span&gt;u = &lt;span style="color:#2b91af;"&gt;Vector3&lt;/span&gt;.Cross(look, r * -1f);&lt;br /&gt;&lt;br /&gt;        &lt;span style="color:#2b91af;"&gt;Camera &lt;/span&gt;cam = &lt;span style="color:blue;"&gt;new &lt;/span&gt;&lt;span style="color:#2b91af;"&gt;Camera&lt;/span&gt;();&lt;br /&gt;        cam.SetLens(mFOV, mAspect, mNearZ, mFarZ);&lt;br /&gt;        cam.Place(center, look, u);&lt;br /&gt;&lt;br /&gt;        cam.BuildView();&lt;br /&gt;&lt;br /&gt;        mCameraPath.Add(cam);&lt;br /&gt;    }&lt;br /&gt;}&lt;/pre&gt;So the first thing we do is build the cubic polynomials for each control point. And because each point controls the shape of the spline, we have to consider each point when building the polynomials. In this way, it is very different from linear interpolation in that we have to know information about the whole curve to perform a cubic interpolation rather than just the current and next control point as with linear interpolation. You might be thinking that "well Vector3 already contains a SmoothStep function that performs cubic interpolation". And you're right it does, but just interpolating the two points without knowledge of the whole curve still produces segmented(i.e. not smooth) animation. Once we have the polynomials for each control point, we can perform a cubic interpolation by just looking at the current and next control point, in the same fashion that we did with linear interpolation.&lt;br /&gt;&lt;br /&gt;This is what the calculateCubicSpline function does, it builds polynomials for each control point, successively building off of the last control point's polynomial. Let's look at it:&lt;br /&gt;&lt;br /&gt;&lt;pre class="mycode"&gt;&lt;span style="color:#2b91af;"&gt;Vector3&lt;/span&gt;[] gamma = &lt;span style="color:blue;"&gt;new &lt;/span&gt;&lt;span style="color:#2b91af;"&gt;Vector3&lt;/span&gt;[n + 1];&lt;br /&gt;&lt;span style="color:#2b91af;"&gt;Vector3&lt;/span&gt;[] delta = &lt;span style="color:blue;"&gt;new &lt;/span&gt;&lt;span style="color:#2b91af;"&gt;Vector3&lt;/span&gt;[n + 1];&lt;br /&gt;&lt;span style="color:#2b91af;"&gt;Vector3&lt;/span&gt;[] D = &lt;span style="color:blue;"&gt;new &lt;/span&gt;&lt;span style="color:#2b91af;"&gt;Vector3&lt;/span&gt;[n + 1];&lt;br /&gt;&lt;span style="color:blue;"&gt;int &lt;/span&gt;i;&lt;br /&gt;&lt;span style="color:green;"&gt;/* We need to solve the equation&lt;br /&gt; * taken from: http://mathworld.wolfram.com/CubicSpline.html&lt;br /&gt;   [2 1       ] [D[0]]   [3(v[1] - v[0])  ]&lt;br /&gt;   : 1 4 1    : :D[1]:   :3(v[2] - v[0])  :&lt;br /&gt;   :  1 4 1   : :  . : = :       .        :&lt;br /&gt;   :  .....   : :  . :   :       .        :&lt;br /&gt;   :     1 4 1: :  . :   :3(v[n] - v[n-2]):&lt;br /&gt;   [       1 2] [D[n]]   [3(v[n] - v[n-1])]&lt;br /&gt;&lt;br /&gt;   by converting the matrix to upper triangular.&lt;br /&gt;   The D[i] are the derivatives at the control points.&lt;br /&gt; */&lt;br /&gt;&lt;br /&gt;//this builds the coefficients of the left matrix&lt;br /&gt;&lt;/span&gt;gamma[0] = &lt;span style="color:#2b91af;"&gt;Vector3&lt;/span&gt;.Zero;&lt;br /&gt;gamma[0].X = 1.0f / 2.0f;&lt;br /&gt;gamma[0].Y = 1.0f / 2.0f;&lt;br /&gt;gamma[0].Z = 1.0f / 2.0f;&lt;br /&gt;&lt;span style="color:blue;"&gt;for &lt;/span&gt;(i = 1; i &amp;lt; n; i++)&lt;br /&gt;{&lt;br /&gt;    gamma[i] = &lt;span style="color:#2b91af;"&gt;Vector3&lt;/span&gt;.One / ((4 * &lt;span style="color:#2b91af;"&gt;Vector3&lt;/span&gt;.One) - gamma[i - 1]);&lt;br /&gt;}&lt;br /&gt;gamma[n] = &lt;span style="color:#2b91af;"&gt;Vector3&lt;/span&gt;.One / ((2 * &lt;span style="color:#2b91af;"&gt;Vector3&lt;/span&gt;.One) - gamma[n - 1]);&lt;br /&gt;&lt;br /&gt;delta[0] = 3 * (v[1] - v[0]) * gamma[0];&lt;br /&gt;&lt;span style="color:blue;"&gt;for &lt;/span&gt;(i = 1; i &amp;lt; n; i++)&lt;br /&gt;{&lt;br /&gt;    delta[i] = (3 * (v[i + 1] - v[i - 1]) - delta[i - 1]) * gamma[i];&lt;br /&gt;}&lt;br /&gt;delta[n] = (3 * (v[n] - v[n - 1]) - delta[n - 1]) * gamma[n];&lt;br /&gt;&lt;br /&gt;D[n] = delta[n];&lt;br /&gt;&lt;span style="color:blue;"&gt;for &lt;/span&gt;(i = n - 1; i &amp;gt;= 0; i--)&lt;br /&gt;{&lt;br /&gt;    D[i] = delta[i] - gamma[i] * D[i + 1];&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;span style="color:green;"&gt;// now compute the coefficients of the cubics&lt;br /&gt;&lt;/span&gt;&lt;span style="color:#2b91af;"&gt;Cubic&lt;/span&gt;[] C = &lt;span style="color:blue;"&gt;new &lt;/span&gt;&lt;span style="color:#2b91af;"&gt;Cubic&lt;/span&gt;[n];&lt;br /&gt;&lt;span style="color:blue;"&gt;for &lt;/span&gt;(i = 0; i &amp;lt; n; i++)&lt;br /&gt;{&lt;br /&gt;    C[i] = &lt;span style="color:blue;"&gt;new &lt;/span&gt;&lt;span style="color:#2b91af;"&gt;Cubic&lt;/span&gt;(v[i], D[i], 3 * (v[i + 1] - v[i]) - 2 * D[i] - D[i + 1],&lt;br /&gt;             2 * (v[i] - v[i + 1]) + D[i] + D[i + 1]);&lt;br /&gt;}&lt;br /&gt;&lt;span style="color:blue;"&gt;return &lt;/span&gt;C;&lt;/pre&gt;Once we find the cubic equation for each control point (key frame), we can use GetPointOnSpline() uses the cubic equation to calculate the intermediate point along the cubic spline.&lt;br /&gt;&lt;br /&gt;&lt;pre class="code"&gt;&lt;span style="color:blue;"&gt;public &lt;/span&gt;&lt;span style="color:#2b91af;"&gt;Vector3 &lt;/span&gt;GetPointOnSpline(&lt;span style="color:blue;"&gt;float &lt;/span&gt;s)&lt;br /&gt;{&lt;br /&gt;    &lt;span style="color:blue;"&gt;return &lt;/span&gt;(((d * s) + c) * s + b) * s + a;&lt;br /&gt;}&lt;/pre&gt;And that's it. Now we have a smooth animation of our camera. There are many more ways to calculate the curve, including: hermite curves, b-splines, bezier curves, catmull-rom splines, NURBS(Nonuniform Rational B-Splines) and others. Microsoft also has a sample on creating a camera path that appears to be using hermite curves. But they hide all the details from you, and it's just more interesting to know the math behind it isn't it ;) ?&lt;br /&gt;&lt;br /&gt;&lt;iframe style="BORDER-RIGHT: #dde5e9 1px solid; PADDING-RIGHT: 0px; BORDER-TOP: #dde5e9 1px solid; PADDING-LEFT: 0px; PADDING-BOTTOM: 0px; MARGIN: 3px; BORDER-LEFT: #dde5e9 1px solid; WIDTH: 240px; PADDING-TOP: 0px; BORDER-BOTTOM: #dde5e9 1px solid; HEIGHT: 66px; BACKGROUND-COLOR: #ffffff" marginwidth="0" marginheight="0" src="http://cid-b80a3031b5bfa52b.skydrive.live.com/embedrowdetail.aspx/Public/Camera%20Animation%20II.zip" frameborder="0" scrolling="no"&gt;&lt;/iframe&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1023441640234597436-6804398499228700445?l=graphicsrunner.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='enclosure' type='video/mp4' href='http://www.blogger.com/video-play.mp4?contentId=6fd7e53df9f0eca3&amp;type=video%2Fmp4' length='0'/><link rel='replies' type='application/atom+xml' href='http://graphicsrunner.blogspot.com/feeds/6804398499228700445/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=1023441640234597436&amp;postID=6804398499228700445' title='8 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1023441640234597436/posts/default/6804398499228700445'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1023441640234597436/posts/default/6804398499228700445'/><link rel='alternate' type='text/html' href='http://graphicsrunner.blogspot.com/2008/05/camera-animation-part-ii.html' title='Camera Animation, Part II'/><author><name>Kyle Hayward</name><uri>http://www.blogger.com/profile/00654406875137720609</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://bp3.blogger.com/_VelpN_FHzhk/R-Lx4Kk_nkI/AAAAAAAAAN8/jn_4uDeKxNk/S220/me.jpg'/></author><thr:total>8</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1023441640234597436.post-7622832534151670720</id><published>2008-05-01T14:13:00.008-04:00</published><updated>2008-05-01T17:27:26.770-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Water'/><category scheme='http://www.blogger.com/atom/ns#' term='refractions'/><category scheme='http://www.blogger.com/atom/ns#' term='XNA'/><category scheme='http://www.blogger.com/atom/ns#' term='reflections'/><category scheme='http://www.blogger.com/atom/ns#' term='camera animation'/><title type='text'>Camera Animation, Part I</title><content type='html'>&lt;a href="http://img84.imageshack.us/img84/7769/capture0dr5.jpg"&gt;&lt;img style="WIDTH: 434px; CURSOR: hand; HEIGHT: 272px" height="198" alt="" src="http://img84.imageshack.us/img84/7769/capture0dr5.jpg" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;object width="320" height="266" class="BLOG_video_class" id="BLOG_video-f1cf571ff7db4689" classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,40,0"&gt;&lt;param name="movie" value="http://www.youtube.com/get_player"&gt;&lt;param name="bgcolor" value="#FFFFFF"&gt;&lt;param name="allowfullscreen" value="true"&gt;&lt;param name="flashvars" value="flvurl=http://v2.nonxt8.googlevideo.com/videoplayback?id%3Df1cf571ff7db4689%26itag%3D5%26app%3Dblogger%26ip%3D0.0.0.0%26ipbits%3D0%26expire%3D1330218805%26sparams%3Did,itag,ip,ipbits,expire%26signature%3D437435608C4A69A448B66EA208EF074E8AF4334.32270ADAA6DDF54AD19BB9C4924CF7313D26BE44%26key%3Dck1&amp;amp;iurl=http://video.google.com/ThumbnailServer2?app%3Dblogger%26contentid%3Df1cf571ff7db4689%26offsetms%3D5000%26itag%3Dw160%26sigh%3DSsFRuRE4Ayd1THiShFokfDPw6fk&amp;amp;autoplay=0&amp;amp;ps=blogger"&gt;&lt;embed src="http://www.youtube.com/get_player" type="application/x-shockwave-flash"width="320" height="266" bgcolor="#FFFFFF"flashvars="flvurl=http://v2.nonxt8.googlevideo.com/videoplayback?id%3Df1cf571ff7db4689%26itag%3D5%26app%3Dblogger%26ip%3D0.0.0.0%26ipbits%3D0%26expire%3D1330218805%26sparams%3Did,itag,ip,ipbits,expire%26signature%3D437435608C4A69A448B66EA208EF074E8AF4334.32270ADAA6DDF54AD19BB9C4924CF7313D26BE44%26key%3Dck1&amp;iurl=http://video.google.com/ThumbnailServer2?app%3Dblogger%26contentid%3Df1cf571ff7db4689%26offsetms%3D5000%26itag%3Dw160%26sigh%3DSsFRuRE4Ayd1THiShFokfDPw6fk&amp;autoplay=0&amp;ps=blogger"allowFullScreen="true" /&gt;&lt;/object&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;Due to the video being such high frequency, the blogger, youtube video compressors always make the video look like crap. Original can be found here:&lt;br /&gt;&lt;br /&gt;&lt;iframe style="BORDER-RIGHT: #dde5e9 1px solid; PADDING-RIGHT: 0px; BORDER-TOP: #dde5e9 1px solid; PADDING-LEFT: 0px; PADDING-BOTTOM: 0px; MARGIN: 3px; BORDER-LEFT: #dde5e9 1px solid; WIDTH: 240px; PADDING-TOP: 0px; BORDER-BOTTOM: #dde5e9 1px solid; HEIGHT: 26px; BACKGROUND-COLOR: #ffffff" marginwidth="0" marginheight="0" src="http://cid-b80a3031b5bfa52b.skydrive.live.com/embedrow.aspx/Public/Cut%20Scene.wmv" frameborder="0" scrolling="no"&gt;&lt;/iframe&gt;&lt;br /&gt;&lt;br /&gt;So today I'll be giving a tutorial/sample on cut-scene animation, more specifically camera animation. This will be a 2 part tutorial, and this one will focus on linear interpolation of camera key frames.&lt;/p&gt;&lt;p&gt;So what are key frames? Key frames are the control points of an animation. They are the main/key points that you specifically define where the camera should be, and everything else in between is interpolated. You can also think of them as an outline of what the animation should look like. Here's a picture to help describe what I'm talking about.&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;a href="http://lh6.ggpht.com/GraphicsRunner/SBoJTYPC7DI/AAAAAAAAARY/OX_chavbkBg/s1600-h/camerapathhn1%5B10%5D.jpg"&gt;&lt;img style="BORDER-TOP-WIDTH: 0px; BORDER-LEFT-WIDTH: 0px; BORDER-BOTTOM-WIDTH: 0px; WIDTH: 441px; HEIGHT: 139px; BORDER-RIGHT-WIDTH: 0px" alt="camerapathhn1" src="http://lh4.ggpht.com/GraphicsRunner/SBoJT4PC7EI/AAAAAAAAARg/eMnAv3NfH2Q/camerapathhn1_thumb%5B6%5D.jpg?imgmax=800" border="0" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;Here, the green line is the path we want to follow, the orange dots are our key frames, and the red dots are the intermediate cameras that we want to generate.&lt;/p&gt;&lt;p&gt;So how do we create these intermediate cameras?&lt;/p&gt;&lt;pre class="mycode"&gt;&lt;span style="color:blue;"&gt;for &lt;/span&gt;(&lt;span style="color:blue;"&gt;int &lt;/span&gt;i = 0; i &amp;lt; mKeyFrames.Count - 1; i++)&lt;br /&gt;{&lt;br /&gt;    &lt;span style="color:blue;"&gt;for &lt;/span&gt;(&lt;span style="color:blue;"&gt;int &lt;/span&gt;j = 0; j &amp;lt; mPathSteps; j++)&lt;br /&gt;    {&lt;br /&gt;        &lt;span style="color:green;"&gt;//We could alternatively use the Vector3.lerp function&lt;br /&gt;        //but I wanted to show the math behind performing the linear interpolation&lt;br /&gt;&lt;br /&gt;        &lt;/span&gt;Vector3 diff = mKeyFrames[i + 1].Position - mKeyFrames[i].Position;&lt;br /&gt;        diff = Vector3.Multiply(diff, (&lt;span style="color:blue;"&gt;float&lt;/span&gt;)j / (&lt;span style="color:blue;"&gt;float&lt;/span&gt;)(mPathSteps - 1));&lt;br /&gt;        Vector3 center = mKeyFrames[i].Position + diff;&lt;br /&gt;&lt;br /&gt;        diff = mKeyFrames[i + 1].Look - mKeyFrames[i].Look;&lt;br /&gt;        diff = Vector3.Multiply(diff, (&lt;span style="color:blue;"&gt;float&lt;/span&gt;)j / (&lt;span style="color:blue;"&gt;float&lt;/span&gt;)(mPathSteps - 1));&lt;br /&gt;        Vector3 look = mKeyFrames[i].Look + diff;&lt;br /&gt;&lt;br /&gt;        diff = (mKeyFrames[i + 1].Up - mKeyFrames[i].Up);&lt;br /&gt;        diff = Vector3.Multiply(diff, (&lt;span style="color:blue;"&gt;float&lt;/span&gt;)j / (&lt;span style="color:blue;"&gt;float&lt;/span&gt;)(mPathSteps - 1));&lt;br /&gt;        Vector3 up = mKeyFrames[i].Up + diff;&lt;br /&gt;&lt;br /&gt;        Vector3 r = Vector3.Cross(up, look);&lt;br /&gt;        Vector3 u = Vector3.Cross(look, r * -1f);&lt;br /&gt;&lt;br /&gt;        Camera cam = &lt;span style="color:blue;"&gt;new &lt;/span&gt;Camera();&lt;br /&gt;        cam.SetLens(mFOV, mAspect, mNearZ, mFarZ);&lt;br /&gt;        cam.Place(center, look, u);&lt;br /&gt;&lt;br /&gt;        cam.BuildView();&lt;br /&gt;&lt;br /&gt;        mCameraPath.Add(cam);&lt;br /&gt;    }&lt;br /&gt;}&lt;/pre&gt;Pretty self explanatory, a basic linear interpolation. XNA is handy in that we can cut out all of that code and just use Vector3.Lerp().&lt;br /&gt;&lt;br /&gt;&lt;p&gt;The included demo has a pre defined path that is shown in the video. But you can also create your own path at run-time. Here are the controls: &lt;/p&gt;&lt;ul&gt;&lt;li&gt;1 - Default camera view &lt;/li&gt;&lt;li&gt;2 - Play the camera animation &lt;/li&gt;&lt;li&gt;3 - Rewind the camera animation &lt;/li&gt;&lt;li&gt;4 - Pause the camera animation &lt;/li&gt;&lt;li&gt;C - Clear the current key frames &lt;/li&gt;&lt;li&gt;K - Add the current camera to the key frames list &lt;/li&gt;&lt;li&gt;B - Build the camera path from the current key frames &lt;/li&gt;&lt;li&gt;O - Save the current key frames to an XML file &lt;/li&gt;&lt;/ul&gt;&lt;p&gt;When you start the demo, the predefined path is already loaded, all you have to do is hit '2', and it will play. If you want to make your own path first clear the key frames by pressing 'C', and then navigate to where you want to be along your path and press 'K' to add your key frames. Once you're happy with your path, press 'B' to build the camera animation, and then you can play it by pressing '2'. Normally, I would use winForms or a GUI such as neoforce for the controls, but this adds a lot of clutter.&lt;/p&gt;&lt;p&gt;So there's probably one thing that you might notice about the camera animation, and that is that it's not very smooth. Next time I will introduce cubic interpolation to smooth out the animation.&lt;/p&gt;&lt;p&gt;&lt;iframe style="BORDER-RIGHT: rgb(221,229,233) 1px solid; PADDING-RIGHT: 0pt; BORDER-TOP: rgb(221,229,233) 1px solid; PADDING-LEFT: 0pt; PADDING-BOTTOM: 0pt; MARGIN: 3px; BORDER-LEFT: rgb(221,229,233) 1px solid; WIDTH: 240px; PADDING-TOP: 0pt; BORDER-BOTTOM: rgb(221,229,233) 1px solid; HEIGHT: 66px; BACKGROUND-COLOR: rgb(255,255,255)" marginwidth="0" marginheight="0" src="http://cid-b80a3031b5bfa52b.skydrive.live.com/embedrowdetail.aspx/Public/Camera%20Animation%20I.zip" frameborder="0" scrolling="no"&gt;&lt;/iframe&gt;&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1023441640234597436-7622832534151670720?l=graphicsrunner.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='enclosure' type='video/mp4' href='http://www.blogger.com/video-play.mp4?contentId=f1cf571ff7db4689&amp;type=video%2Fmp4' length='0'/><link rel='replies' type='application/atom+xml' href='http://graphicsrunner.blogspot.com/feeds/7622832534151670720/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=1023441640234597436&amp;postID=7622832534151670720' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1023441640234597436/posts/default/7622832534151670720'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1023441640234597436/posts/default/7622832534151670720'/><link rel='alternate' type='text/html' href='http://graphicsrunner.blogspot.com/2008/05/camera-animation-part-i.html' title='Camera Animation, Part I'/><author><name>Kyle Hayward</name><uri>http://www.blogger.com/profile/00654406875137720609</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://bp3.blogger.com/_VelpN_FHzhk/R-Lx4Kk_nkI/AAAAAAAAAN8/jn_4uDeKxNk/S220/me.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://lh4.ggpht.com/GraphicsRunner/SBoJT4PC7EI/AAAAAAAAARg/eMnAv3NfH2Q/s72-c/camerapathhn1_thumb%5B6%5D.jpg?imgmax=800' height='72' width='72'/><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1023441640234597436.post-7878342866708255239</id><published>2008-04-17T12:00:00.030-04:00</published><updated>2008-04-23T16:27:25.744-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='XNA'/><category scheme='http://www.blogger.com/atom/ns#' term='reflections'/><category scheme='http://www.blogger.com/atom/ns#' term='C#'/><category scheme='http://www.blogger.com/atom/ns#' term='Impostors'/><title type='text'>Reflections with Billboard Impostors</title><content type='html'>Last time I mentioned that I would post a tutorial on generating reflections using billboard impostors, well here it is. Amidst the craziness of school right now, I've found time (or rather worked on this since it's more fun) to write up this tutorial. So lets get started.&lt;br /&gt;&lt;br /&gt;Some prerequisites for this tutorial:&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Shader Model 3.0 compliant graphics card&lt;/li&gt;&lt;li&gt;Experience with RenderTargets&lt;/li&gt;&lt;li&gt;Experience with Custom Content Processors (not too important, I just won't go over these specifically)&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;Anyways, &lt;a href="http://blogs.msdn.com/shawnhar/archive/2008/03/04/cubemaps-the-salt-of-computer-graphics.aspx"&gt;Shawn Hargreaves&lt;/a&gt; posted about using environment maps for reflections a few weeks ago. But we're going to take it one step further and use impostors as well as the environment map for reflections. The main problem with environment mapped refections is that everything is assumed to be infinitely far away, and thus objects appear distorted and don't intersect with geometry correctly. Using billboarded impostors tries to alleviate some of this problem by taking the reflected rays and intersecting them with billboards of the scene geometry.&lt;br /&gt;&lt;br /&gt;So here's a couple of images to demonstrate the limitations of environment mapping.&lt;br /&gt;&lt;br /&gt;Environment mapping:&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://img398.imageshack.us/img398/9968/envmaptl1.jpg"&gt;&lt;img style="width: 492px; cursor: pointer; height: 378px;" alt="" src="http://img398.imageshack.us/img398/9968/envmaptl1.jpg" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;As you can see the sphere looks like it's floating above the quad when it is actually intersecting it. Also, the dragon's reflection is too far away in the reflection.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Impostors:&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://img175.imageshack.us/img175/8959/sceneimpostoror9.png"&gt;&lt;img style="width: 492px; cursor: pointer; height: 378px;" alt="" src="http://img175.imageshack.us/img175/8959/sceneimpostoror9.png" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Now with the impostors, the reflection of the dragon is much more accurate, and the sphere now correctly intersects the floor quad.&lt;br /&gt;&lt;br /&gt;The details:&lt;br /&gt;First we want to render each diffuse object from the reflector's point of view. So we first get the bounding box corners of the impostor geometry and setup the camera to be positioned at the center of the reflector, and looking at the center of the diffuse geometry.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;pre class="mycode"&gt;&lt;span style="color:green;"&gt;// Get the 8 corners of the bounding box and transform by the world matrix&lt;br /&gt;&lt;/span&gt;&lt;span style="color: rgb(43, 145, 175);"&gt;Vector3&lt;/span&gt;[] corners = &lt;span style="color:blue;"&gt;new &lt;/span&gt;&lt;span style="color: rgb(43, 145, 175);"&gt;Vector3&lt;/span&gt;[8];&lt;br /&gt;&lt;span style="color: rgb(43, 145, 175);"&gt;Vector3&lt;/span&gt;.Transform(mBoundBox.GetCorners(), &lt;span style="color:blue;"&gt;ref &lt;/span&gt;mWorld, corners);&lt;br /&gt;&lt;br /&gt;&lt;span style="color:green;"&gt;// Get the transformed center of the mesh&lt;br /&gt;// alternatively, we could use mBoundingSphere.Center as sometimes it works better&lt;br /&gt;&lt;/span&gt;&lt;span style="color: rgb(43, 145, 175);"&gt;Vector3 &lt;/span&gt;meshCenter = Center;&lt;br /&gt;&lt;br /&gt;&lt;span style="color:green;"&gt;// The quad is a special case. Since it's so much bigger than the reflector&lt;br /&gt;// we have to make sure that our camera is looking straight at the quad and&lt;br /&gt;// not it's center or we'll get a distorted view of the quad.&lt;br /&gt;&lt;/span&gt;&lt;span style="color:blue;"&gt;if &lt;/span&gt;(mMeshType == &lt;span style="color: rgb(43, 145, 175);"&gt;MeshType&lt;/span&gt;.Quad)&lt;br /&gt;{&lt;br /&gt;   meshCenter.X = reflectorCenter.X;&lt;br /&gt;   meshCenter.Z = reflectorCenter.Z;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;span style="color:green;"&gt;// Construct a camera to render our impostor&lt;br /&gt;&lt;/span&gt;&lt;span style="color: rgb(43, 145, 175);"&gt;Camera &lt;/span&gt;impostorCam = &lt;span style="color:blue;"&gt;new &lt;/span&gt;&lt;span style="color: rgb(43, 145, 175);"&gt;Camera&lt;/span&gt;(camera);&lt;br /&gt;&lt;br /&gt;&lt;span style="color:green;"&gt;// Set the camera to be at the center of the reflector and to look at the diffuse mesh&lt;br /&gt;&lt;/span&gt;impostorCam.LookAt(reflectorCenter, meshCenter);&lt;br /&gt;impostorCam.BuildView();&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Next we want to project these corners to screen space and find the bounding box that fits around these projected corners:&lt;br /&gt;&lt;br /&gt;&lt;pre class="mycode"&gt;&lt;span style="color:green;"&gt;// Now we project the vertices to screen space, so we can find the AABB of the screen&lt;br /&gt;// space vertices&lt;br /&gt;&lt;/span&gt;&lt;span style="color: rgb(43, 145, 175);"&gt;Vector3&lt;/span&gt;[] screenVerts = &lt;span style="color:blue;"&gt;new &lt;/span&gt;&lt;span style="color: rgb(43, 145, 175);"&gt;Vector3&lt;/span&gt;[8];&lt;br /&gt;&lt;span style="color:blue;"&gt;for &lt;/span&gt;(&lt;span style="color:blue;"&gt;int &lt;/span&gt;i = 0; i &amp;lt; 8; i++)&lt;br /&gt;{&lt;br /&gt;   screenVerts[i] = mGraphicsDevice.Viewport.Project(corners[i],&lt;br /&gt;                           impostorCam.Projection, impostorCam.View, mWorld);&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;span style="color:green;"&gt;// compute the screen space AABB&lt;br /&gt;&lt;/span&gt;&lt;span style="color: rgb(43, 145, 175);"&gt;Vector3 &lt;/span&gt;min, max;&lt;br /&gt;ComputeBoundingBoxFromPoints(screenVerts, &lt;span style="color:blue;"&gt;out &lt;/span&gt;min, &lt;span style="color:blue;"&gt;out &lt;/span&gt;max);&lt;br /&gt;&lt;br /&gt;&lt;span style="color:green;"&gt;// construct the quad that will represent our diffuse mesh&lt;br /&gt;&lt;/span&gt;&lt;span style="color: rgb(43, 145, 175);"&gt;Vector3&lt;/span&gt;[] screenQuadVerts = &lt;span style="color:blue;"&gt;new &lt;/span&gt;&lt;span style="color: rgb(43, 145, 175);"&gt;Vector3&lt;/span&gt;[4];&lt;br /&gt;screenQuadVerts[0] = &lt;span style="color:blue;"&gt;new &lt;/span&gt;&lt;span style="color: rgb(43, 145, 175);"&gt;Vector3&lt;/span&gt;(min.X, min.Y, min.Z);&lt;br /&gt;screenQuadVerts[1] = &lt;span style="color:blue;"&gt;new &lt;/span&gt;&lt;span style="color: rgb(43, 145, 175);"&gt;Vector3&lt;/span&gt;(max.X, min.Y, min.Z);&lt;br /&gt;screenQuadVerts[2] = &lt;span style="color:blue;"&gt;new &lt;/span&gt;&lt;span style="color: rgb(43, 145, 175);"&gt;Vector3&lt;/span&gt;(max.X, max.Y, min.Z);&lt;br /&gt;screenQuadVerts[3] = &lt;span style="color:blue;"&gt;new &lt;/span&gt;&lt;span style="color: rgb(43, 145, 175);"&gt;Vector3&lt;/span&gt;(min.X, max.Y, min.Z);&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Now we want to unproject these screen space vertices into world space so that we can form a 3D impostor quad of our geometry. We will use this quad later in the reflection shader for a reflective object.&lt;br /&gt;&lt;br /&gt;We also render our diffuse object to a RenderTarget that will be the texture for our impostor quad. To do this, we setup an Orthographic Projection so that we don't have any of the perspective distortion that comes with a regular perspective projection matrix. We clear the scene with zero alpha, and we render the impostor with full alpha so that we only capture the diffuse object and nothing else.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;pre class="mycode" style="height: 600pt;"&gt;&lt;span style="color:green;"&gt;//now unproject the screen space quad and save the&lt;br /&gt;//vertices for when we render the impostor quad&lt;br /&gt;&lt;/span&gt;&lt;span style="color:blue;"&gt;for &lt;/span&gt;(&lt;span style="color:blue;"&gt;int &lt;/span&gt;i = 0; i &amp;lt; 4; i++)&lt;br /&gt;{&lt;br /&gt;   mImpostorVerts[i] = mGraphicsDevice.Viewport.Unproject(screenQuadVerts[i],&lt;br /&gt;                           impostorCam.Projection, impostorCam.View, mWorld);&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;span style="color:green;"&gt;//compute the center of the quad&lt;br /&gt;&lt;/span&gt;mImpostorCenter = &lt;span style="color: rgb(43, 145, 175);"&gt;Vector3&lt;/span&gt;.Zero;&lt;br /&gt;mImpostorCenter = mImpostorVerts[0] + mImpostorVerts[1] + mImpostorVerts[2] + mImpostorVerts[3];&lt;br /&gt;mImpostorCenter *= .25f;&lt;br /&gt;&lt;br /&gt;&lt;span style="color:green;"&gt;// calculate the width and height of the imposter's vertices&lt;br /&gt;&lt;/span&gt;&lt;span style="color:blue;"&gt;float &lt;/span&gt;width = (mImpostorVerts[1] - mImpostorVerts[0]).Length() * 1.2f;&lt;br /&gt;&lt;span style="color:blue;"&gt;float &lt;/span&gt;height = (mImpostorVerts[3] - mImpostorVerts[0]).Length() * 1.2f;&lt;br /&gt;&lt;br /&gt;&lt;span style="color:green;"&gt;// We construct an Orthographic projection to get rid of the projection distortion&lt;br /&gt;// which we don't want for our impostor texture&lt;br /&gt;&lt;/span&gt;impostorCam.Projection = &lt;span style="color: rgb(43, 145, 175);"&gt;Matrix&lt;/span&gt;.CreateOrthographic(width, height, .1f, 100);&lt;br /&gt;impostorCam.BuildView();&lt;br /&gt;&lt;br /&gt;&lt;span style="color:green;"&gt;//save the WorldViewProjection matrix so we can use it in the shader&lt;br /&gt;&lt;/span&gt;mWorldViewProj = impostorCam.ViewProj;&lt;br /&gt;&lt;br /&gt;mGraphicsDevice.SetRenderTarget(0, mRenderTarget);&lt;br /&gt;mGraphicsDevice.Clear(&lt;span style="color: rgb(43, 145, 175);"&gt;ClearOptions&lt;/span&gt;.Target, &lt;span style="color:blue;"&gt;new &lt;/span&gt;&lt;span style="color: rgb(43, 145, 175);"&gt;Color&lt;/span&gt;(1, 0, 0, 0), 1.0f, 1);&lt;br /&gt;&lt;br /&gt;Draw(impostorCam);&lt;br /&gt;&lt;br /&gt;mGraphicsDevice.SetRenderTarget(0, &lt;span style="color:blue;"&gt;null&lt;/span&gt;);&lt;br /&gt;&lt;br /&gt;&lt;span style="color:green;"&gt;&lt;br /&gt;// finally, compute the normal for the impostor quad, and push the vertices&lt;br /&gt;// back to the center of the diffuse mesh&lt;br /&gt;&lt;/span&gt;&lt;span style="color: rgb(43, 145, 175);"&gt;Vector3 &lt;/span&gt;trans = meshCenter - mImpostorCenter;&lt;br /&gt;&lt;span style="color:blue;"&gt;for &lt;/span&gt;(&lt;span style="color:blue;"&gt;int &lt;/span&gt;i = 0; i &amp;lt; 4; ++i)&lt;br /&gt;{&lt;br /&gt;   mImpostorVerts[i] += trans;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;mNormal = impostorCam.Look;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Here's our resulting impostor image:&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://img520.imageshack.us/img520/9351/rtor5.png"&gt;&lt;img style="width: 424px; cursor: pointer; height: 317px;" alt="" src="http://img520.imageshack.us/img520/9351/rtor5.png" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Now that we have constructed our impostor quad, it's time to render our reflective object. Here's the pixel shader code that we will use to do this:&lt;br /&gt;&lt;br /&gt;&lt;pre class="mycode" style="height: 600pt;"&gt;&lt;span style="color:blue;"&gt;float4 &lt;/span&gt;PixelShaderFunction(VertexShaderOutput input) : &lt;span style="color:blue;"&gt;COLOR0&lt;br /&gt;&lt;/span&gt;{&lt;br /&gt;   input.NormalW = &lt;span style="color:blue;"&gt;normalize&lt;/span&gt;(input.NormalW);&lt;br /&gt;  &lt;br /&gt;   &lt;span style="color:blue;"&gt;float3 &lt;/span&gt;fromEyeW = &lt;span style="color:blue;"&gt;normalize&lt;/span&gt;(input.PosW - EyePos);&lt;br /&gt;  &lt;br /&gt;   &lt;span style="color:green;"&gt;//find the relflected ray by reflecting the fromEyeW vector across the normal&lt;br /&gt;   &lt;/span&gt;&lt;span style="color:blue;"&gt;float3 &lt;/span&gt;reflectedRay = &lt;span style="color:blue;"&gt;reflect&lt;/span&gt;(fromEyeW, input.NormalW);&lt;br /&gt;  &lt;br /&gt;   &lt;span style="color:blue;"&gt;float4 &lt;/span&gt;finalColor = texCUBE(EnvTex, reflectedRay);&lt;br /&gt;  &lt;br /&gt;   &lt;span style="color:blue;"&gt;if&lt;/span&gt;(UseImpostors)&lt;br /&gt;   {&lt;br /&gt;       &lt;span style="color:blue;"&gt;for&lt;/span&gt;(&lt;span style="color:blue;"&gt;int &lt;/span&gt;i = 0; i &amp;lt; 2; i++)&lt;br /&gt;       {&lt;br /&gt;           &lt;span style="color:green;"&gt;//take the dot product of the reflected ray and the normal of the impostor&lt;br /&gt;           //quad to see how orthogonal the ray is to the quad&lt;br /&gt;           //if a = 0, then the ray is parallel to the quad&lt;br /&gt;           //if a = 1, then it is orthogonal to the quad&lt;br /&gt;           &lt;/span&gt;&lt;span style="color:blue;"&gt;float &lt;/span&gt;a = &lt;span style="color:blue;"&gt;dot&lt;/span&gt;(-reflectedRay, Impostors[i].Normal);&lt;br /&gt;&lt;br /&gt;           &lt;span style="color:green;"&gt;//if less than this, the ray is nearly or entirely parallel to the plane&lt;br /&gt;           &lt;/span&gt;&lt;span style="color:blue;"&gt;if&lt;/span&gt;(a &amp;gt; 0.001f)&lt;br /&gt;           {                   &lt;br /&gt;               &lt;span style="color:green;"&gt;//we construct the vector from a point on the quad to the pixel position   &lt;br /&gt;               &lt;/span&gt;&lt;span style="color:blue;"&gt;float3 &lt;/span&gt;vec = Impostors[i].Vertex - input.PosW;&lt;br /&gt;              &lt;br /&gt;               &lt;span style="color:green;"&gt;//the signed distance from the pixel position to the quad is given by the negative&lt;br /&gt;               //dot product of the quad normal and vec&lt;br /&gt;               &lt;/span&gt;&lt;span style="color:blue;"&gt;float &lt;/span&gt;b = -&lt;span style="color:blue;"&gt;dot&lt;/span&gt;(vec, Impostors[i].Normal);&lt;br /&gt;&lt;br /&gt;               &lt;span style="color:green;"&gt;//divide b by a to get our distance from the world pixel position&lt;br /&gt;               &lt;/span&gt;&lt;span style="color:blue;"&gt;float &lt;/span&gt;r = b / a;&lt;br /&gt;&lt;br /&gt;               &lt;span style="color:blue;"&gt;if&lt;/span&gt;(r &amp;gt;= 0)&lt;br /&gt;               {&lt;br /&gt;                   &lt;span style="color:green;"&gt;//Using the Ray equation: P(t) = S + tV. Where S is the orgin, V is the direction,&lt;br /&gt;                   //and t is the distance along V&lt;br /&gt;                   //We find the intersection by starting at the origin (PosW), and walk to the end&lt;br /&gt;                   //of the ray by multiplying the reflectedRay by r - the distance to the quad&lt;br /&gt;                   //and adding it to the orgin&lt;br /&gt;                   &lt;/span&gt;&lt;span style="color:blue;"&gt;float3 &lt;/span&gt;intersection = input.PosW + r * reflectedRay;&lt;br /&gt;&lt;br /&gt;                   &lt;span style="color:blue;"&gt;float2 &lt;/span&gt;texC;&lt;br /&gt;                  &lt;br /&gt;                   &lt;span style="color:green;"&gt;//project the intersection point with the WVP matrix used to render the quad&lt;br /&gt;                   &lt;/span&gt;&lt;span style="color:blue;"&gt;float4 &lt;/span&gt;projIntersect = &lt;span style="color:blue;"&gt;mul&lt;/span&gt;(&lt;span style="color:blue;"&gt;float4&lt;/span&gt;(intersection, 1.0), Impostors[i].WVP);&lt;br /&gt;&lt;br /&gt;                   &lt;span style="color:green;"&gt;//perform the perspective divide and transform to NDC coordinates [0, 1]&lt;br /&gt;                   &lt;/span&gt;texC = projIntersect.xy / projIntersect.w * .5 + .5;&lt;br /&gt;&lt;br /&gt;                   &lt;span style="color:green;"&gt;//make sure the intersection is in the bounds of the image [0, 1]&lt;br /&gt;                   &lt;/span&gt;&lt;span style="color:blue;"&gt;if&lt;/span&gt;((texC.x &amp;lt;= 1 &amp;amp;&amp;amp; texC.y &amp;lt;= 1) &amp;amp;&amp;amp;&lt;br /&gt;                      (texC.x &amp;gt;= 0 &amp;amp;&amp;amp; texC.y &amp;gt;= 0))&lt;br /&gt;                   {&lt;br /&gt;                       &lt;span style="color:blue;"&gt;float4 &lt;/span&gt;color = &lt;span style="color:blue;"&gt;tex2D&lt;/span&gt;(ImpostorSamplers[i], &lt;span style="color:blue;"&gt;float2&lt;/span&gt;(texC.x, 1-texC.y));&lt;br /&gt;                      &lt;br /&gt;                       &lt;span style="color:green;"&gt;//blend based on the alpha of the sampled color&lt;br /&gt;                       &lt;/span&gt;finalColor = &lt;span style="color:blue;"&gt;lerp&lt;/span&gt;(finalColor, color, color.a);&lt;br /&gt;                   }&lt;br /&gt;               }&lt;br /&gt;           }&lt;br /&gt;       }&lt;br /&gt;   }&lt;br /&gt;  &lt;br /&gt;   finalColor.rgb *= MaterialColor;&lt;br /&gt;   &lt;span style="color:blue;"&gt;return &lt;/span&gt;finalColor;&lt;br /&gt;}&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;So, we iterate over each impostor in the Impostors array. We find the reflected ray as with environment mapping, and we use this ray to see if it intersects any of the impostor quads in our scene. If we find an intersection, we get the color from the impostor texture and blend with the environment map color based on the alpha component of the color from the impostor texture (the alpha will be zero for any part of the texture that isn't part of the diffuse object).&lt;br /&gt;&lt;br /&gt;That's pretty much all there is to it. Not too complicated, and still very fast. The demo runs pretty fast: ~400 FPS with 16x MSAA at 1024x768 on my 8800GT.&lt;br /&gt;&lt;br /&gt;Limitations:&lt;br /&gt;One of the problems with billboard impostors is that there is no motion parallax. Also, the intersection of complex objects is not possible. This is where depth impostors and non-pinhole impostors come in. Depth impostors allow correct intersections and motion parallax. And non-pinhole impostors add on to depth impostors by allowing you to see almost the entire diffuse object from one viewpoint (whereas depth impostors suffer from occlusion errors because it can only capture the front face of any object).&lt;br /&gt;&lt;br /&gt;&lt;iframe style="border: 1px solid rgb(221, 229, 233); margin: 3px; padding: 0pt; width: 240px; height: 66px; background-color: rgb(255, 255, 255);" marginwidth="0" marginheight="0" src="http://cid-b80a3031b5bfa52b.skydrive.live.com/embedrowdetail.aspx/Public/BillboardImpostors.zip" frameborder="0" scrolling="no"&gt;&lt;/iframe&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1023441640234597436-7878342866708255239?l=graphicsrunner.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://graphicsrunner.blogspot.com/feeds/7878342866708255239/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=1023441640234597436&amp;postID=7878342866708255239' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1023441640234597436/posts/default/7878342866708255239'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1023441640234597436/posts/default/7878342866708255239'/><link rel='alternate' type='text/html' href='http://graphicsrunner.blogspot.com/2008/04/reflections-with-billboard-impostors.html' title='Reflections with Billboard Impostors'/><author><name>Kyle Hayward</name><uri>http://www.blogger.com/profile/00654406875137720609</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://bp3.blogger.com/_VelpN_FHzhk/R-Lx4Kk_nkI/AAAAAAAAAN8/jn_4uDeKxNk/S220/me.jpg'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1023441640234597436.post-2687538904566360534</id><published>2008-04-08T14:00:00.005-04:00</published><updated>2008-04-08T20:56:12.069-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Non-Pinhole Impostors'/><category scheme='http://www.blogger.com/atom/ns#' term='Eurographics'/><title type='text'>Paper Submitted to Eurographics</title><content type='html'>Well we finally got our paper submitted to Eurographics Symposium on Rendering, with 5 minutes to spare before deadline :). I think I had about 3 hours of sleep in the last few days. Hopefully we'll get accepted. The title of our paper was &lt;span style="font-style: italic;"&gt;Non-Pinhole Impostors.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;So now I have to implement my own malloc for Operating Systems, and a databases project due in the next week. I'm going to try to work on the Billboard Impostors tutorial that I mentioned last post also. Busy times.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1023441640234597436-2687538904566360534?l=graphicsrunner.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://graphicsrunner.blogspot.com/feeds/2687538904566360534/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=1023441640234597436&amp;postID=2687538904566360534' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1023441640234597436/posts/default/2687538904566360534'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1023441640234597436/posts/default/2687538904566360534'/><link rel='alternate' type='text/html' href='http://graphicsrunner.blogspot.com/2008/04/paper-submitted-to-eurographics.html' title='Paper Submitted to Eurographics'/><author><name>Kyle Hayward</name><uri>http://www.blogger.com/profile/00654406875137720609</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://bp3.blogger.com/_VelpN_FHzhk/R-Lx4Kk_nkI/AAAAAAAAAN8/jn_4uDeKxNk/S220/me.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1023441640234597436.post-8290726161118861185</id><published>2008-03-31T23:13:00.008-04:00</published><updated>2008-04-01T17:09:26.853-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='depth impostors'/><category scheme='http://www.blogger.com/atom/ns#' term='occlusion camera'/><category scheme='http://www.blogger.com/atom/ns#' term='CG'/><category scheme='http://www.blogger.com/atom/ns#' term='reflections'/><category scheme='http://www.blogger.com/atom/ns#' term='OpenGL'/><title type='text'>Reflections with Depth Impostors</title><content type='html'>Since the beginning of the semester I've been researching reflections using Occlusion Camera Depth Impostors. The occlusion camera had been developed in the same graphics lab a few years ago, and my task was to apply it to depth impostors. The idea of using depth impostors is that, you can render a diffuse object to a buffer, and then use the color and depth information to correctly intersect the depth map with reflected rays from a reflective surface. Now the problem here is that we can only see so much from the viewpoint of the reflector. Meaning that some rays would actually intersect the real diffuse object but won't intersect our depth map because we don't have enough information.&lt;br /&gt;&lt;br /&gt;What the occlusion camera brings to the table is the ability to store more depth information from the diffuse object. It does this by distorting rays such that we can sample the top and bottom of the diffuse object, where if we were to use a regular camera we wouldn't capture these areas. Yes, kind of vague and hard to visualize without pictures, but those will come in due time. It's a really cool topic of research that I will discuss in more depth once we submit our paper to the Eurographics Symposium.&lt;br /&gt;&lt;br /&gt;But for now I'll leave you with some pictures of some early work that I did as a prerequisite for the research. Also, in the coming days (read- after our paper deadline) I will post a tutorial on using billboard impostors for reflections in XNA.&lt;br /&gt;&lt;br /&gt;A reflected bunny with correct intersections&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://img225.imageshack.us/img225/6931/bunnyintersectqt0.jpg"&gt;&lt;img style="cursor: pointer; width: 321px; height: 239px;" src="http://img225.imageshack.us/img225/6931/bunnyintersectqt0.jpg" alt="" border="0" /&gt;&lt;/a&gt; &lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://img398.imageshack.us/img398/3383/bunnyintersect2lm4.jpg"&gt;&lt;img style="cursor: pointer; width: 321px; height: 239px;" src="http://img398.imageshack.us/img398/3383/bunnyintersect2lm4.jpg" alt="" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;3rd order and 2nd order reflections showing the impostor normals&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://img120.imageshack.us/img120/579/interreflectedteapotnorrr8.jpg"&gt;&lt;img style="cursor: pointer; width: 321px; height: 240px;" src="http://img120.imageshack.us/img120/579/interreflectedteapotnorrr8.jpg" alt="" border="0" /&gt;&lt;/a&gt; &lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://img120.imageshack.us/img120/2406/reflectednormalsuk0.jpg"&gt;&lt;img style="cursor: pointer; width: 322px; height: 240px;" src="http://img120.imageshack.us/img120/2406/reflectednormalsuk0.jpg" alt="" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;2nd order reflections&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://img398.imageshack.us/img398/5521/reflectiveteapots2cc9.jpg"&gt;&lt;img style="cursor: pointer; width: 321px; height: 240px;" src="http://img398.imageshack.us/img398/5521/reflectiveteapots2cc9.jpg" alt="" border="0" /&gt;&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1023441640234597436-8290726161118861185?l=graphicsrunner.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://graphicsrunner.blogspot.com/feeds/8290726161118861185/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=1023441640234597436&amp;postID=8290726161118861185' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1023441640234597436/posts/default/8290726161118861185'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1023441640234597436/posts/default/8290726161118861185'/><link rel='alternate' type='text/html' href='http://graphicsrunner.blogspot.com/2008/03/reflections-with-depth-impostors.html' title='Reflections with Depth Impostors'/><author><name>Kyle Hayward</name><uri>http://www.blogger.com/profile/00654406875137720609</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://bp3.blogger.com/_VelpN_FHzhk/R-Lx4Kk_nkI/AAAAAAAAAN8/jn_4uDeKxNk/S220/me.jpg'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1023441640234597436.post-2665908072013789746</id><published>2008-03-25T02:56:00.016-04:00</published><updated>2008-03-26T17:54:57.613-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Post Processing'/><category scheme='http://www.blogger.com/atom/ns#' term='managed'/><category scheme='http://www.blogger.com/atom/ns#' term='Volumetric Clouds'/><category scheme='http://www.blogger.com/atom/ns#' term='C#'/><category scheme='http://www.blogger.com/atom/ns#' term='DirectX'/><title type='text'>Volumetric Clouds</title><content type='html'>Last summer I did some experiments with an unusual way of rendering volumetric clouds. The usual way to render volumetric clouds is with a bunch of quads/voxels/slices. But this approach is much simpler, and at least for the exterior, gives really good results.&lt;br /&gt;&lt;br /&gt;The problem came when rendering multiple clouds in a field. Due to the way the cloud is rendered, overlapping clouds would produce artifacts, and short of rendering each cloud by itself I couldn't figure out how to eliminate the artifacts. But then again, I didn't spend too much time on them either.&lt;br /&gt;&lt;br /&gt;The general approach is as follows:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Draw your scene as usual&lt;/li&gt;&lt;li&gt;Then render a low poly cloud mesh - this could be created in Max/Maya with a group of spheres/ellipsoids in the shape of a cloud&lt;/li&gt;&lt;li&gt;Copy the backbuffer to a render target, and clear the alpha channel&lt;/li&gt;&lt;li&gt;Render the clouds with full alpha and your lighting of choice (I only implemented simple phong shading, a better lighting algorithm would have helped). We'll call this the cloud map.&lt;/li&gt;&lt;li&gt;Blur the cloud map using a Gaussian filter, or another if you like.&lt;/li&gt;&lt;li&gt;After blurring we need to distort the blurred cloud map. To do this, we place a quad at the center of each cloud and billboard each vertex to the camera (make sure the billboard covers the entire cloud from any angle).&lt;/li&gt;&lt;li&gt;Now we render this quad to distort the cloud map. In the pixel shader for the quad we use the projected position as the texture coordinates and shift the texture coordinates based on the angles to the X and Y axis. We then sample a 2-channel fractal/noise texture with these texture coordinates to obtain our distortion offset. Next, we sample the blurred cloud map using the texture coordinates distorted by the distortion offset and the distance from the quad to the camera:&lt;/li&gt;&lt;li&gt;float4 distortedColor = tex2D(BlurredCloudSampler, texC + offset/ dist);&lt;/li&gt;&lt;li&gt;Optionally, after we have distorted our cloud map, we can perform a radial blur for a softer look.&lt;br /&gt;&lt;/li&gt;&lt;li&gt;Finally we merge our render target with the back buffer.&lt;/li&gt;&lt;/ul&gt;A visual representation of the process:&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://www.inframez.com/events_volclouds_slide18.htm"&gt;&lt;img style="cursor: pointer; width: 436px; height: 281px;" src="http://img180.imageshack.us/img180/3819/eventsvolclouds18uz1.jpg" alt="" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;A really simple algorithm that produces a pretty good result. And as you can see, it is essentially a post processing technique. However, it seems that this approach is more suitable to large volcanic or nuclear plumes rather than a dense field of many cumulus clouds (e.g. a large cloud model with many mega particles, as can be seen in the author's images).&lt;br /&gt;&lt;br /&gt;My experiment:&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_VelpN_FHzhk/R-imZ6k_noI/AAAAAAAAAOc/J5r8Rl_0CTE/s1600-h/Cloud.PNG"&gt;&lt;img style="cursor: pointer; width: 441px; height: 270px;" src="http://img406.imageshack.us/img406/4960/cloudzr9.png" alt="" id="BLOGGER_PHOTO_ID_5181574335441116802" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_VelpN_FHzhk/R-imaqk_npI/AAAAAAAAAOk/AuHYeHCdwu0/s1600-h/clouds1.png"&gt;&lt;img style="cursor: pointer; width: 441px; height: 221px;" src="http://img247.imageshack.us/img247/2046/clouds1qw5.png" alt="" id="BLOGGER_PHOTO_ID_5181574348326018706" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Author's images:&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://www.inframez.com/events_volclouds_slide15.htm"&gt;&lt;img style="cursor: pointer; width: 448px; height: 248px;" src="http://img264.imageshack.us/img264/1492/eventsvolclouds15py0.jpg" alt="" id="BLOGGER_PHOTO_ID_5181576040543133346" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://www.inframez.com/events_volclouds_slide16.htm"&gt;&lt;img style="cursor: pointer; width: 448px; height: 240px;" src="http://img248.imageshack.us/img248/7189/eventsvolclouds16ut4.jpg" alt="" id="BLOGGER_PHOTO_ID_5181576049133067954" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;There's an article on this technique in Shader X5 and &lt;a href="http://www.inframez.com/events_volclouds_slide01.htm"&gt;you can find the slides here.&lt;/a&gt;  If you're thinking of trying this technique out, you really don't need the book. The slides are more than you need to implement it. The only detail is that you don't actually use a fractal cube like the slides say, you really just use a billboarded quad.&lt;a href="http://www.inframez.com/events_volclouds_slide01.htm"&gt;&lt;/a&gt;&lt;a href="http://www.inframez.com/events_volclouds_slide01.htm"&gt;&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1023441640234597436-2665908072013789746?l=graphicsrunner.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://graphicsrunner.blogspot.com/feeds/2665908072013789746/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=1023441640234597436&amp;postID=2665908072013789746' title='17 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1023441640234597436/posts/default/2665908072013789746'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1023441640234597436/posts/default/2665908072013789746'/><link rel='alternate' type='text/html' href='http://graphicsrunner.blogspot.com/2008/03/volumetric-clouds.html' title='Volumetric Clouds'/><author><name>Kyle Hayward</name><uri>http://www.blogger.com/profile/00654406875137720609</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://bp3.blogger.com/_VelpN_FHzhk/R-Lx4Kk_nkI/AAAAAAAAAN8/jn_4uDeKxNk/S220/me.jpg'/></author><thr:total>17</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1023441640234597436.post-3575756194628865054</id><published>2008-03-22T13:15:00.010-04:00</published><updated>2008-03-23T23:47:31.457-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Water'/><category scheme='http://www.blogger.com/atom/ns#' term='fluid dynamics'/><category scheme='http://www.blogger.com/atom/ns#' term='fire'/><category scheme='http://www.blogger.com/atom/ns#' term='smoke'/><title type='text'>Fluid Dynamics</title><content type='html'>Fluid dynamics has always been an interest of mine. I've just never found the time to research any of the techniques. Hopefully sometime this summer I will have time...&lt;br /&gt;&lt;br /&gt;Anyways, the work by Ron Fedkiw is just amazing. Take a look at his website to see for your self.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://physbam.stanford.edu/%7Efedkiw/"&gt;http://physbam.stanford.edu/~fedkiw/&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://physbam.stanford.edu/%7Efedkiw/images/lighthouse.png"&gt;&lt;img style="cursor: pointer; width: 211px; height: 157px;" src="http://img156.imageshack.us/img156/3082/lighthousezr7.png" alt="" border="0" /&gt;&lt;/a&gt; &lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://physbam.stanford.edu/%7Efedkiw/images/fireball.png"&gt;&lt;img style="cursor: pointer; width: 68px; height: 157px;" src="http://img156.imageshack.us/img156/1485/fireballpq6.png" alt="" border="0" /&gt; &lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://physbam.stanford.edu/%7Efedkiw/images/dragon-flame.png"&gt;&lt;img style="cursor: pointer; width: 200px;" src="http://img509.imageshack.us/img509/3174/dragonflamexh3.png" alt="" border="0" /&gt;&lt;/a&gt; &lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://physbam.stanford.edu/%7Efedkiw/images/fireball.png"&gt; &lt;/a&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://physbam.stanford.edu/%7Efedkiw/images/fireball-smoke.png"&gt;&lt;img style="cursor: pointer; width: 120px; height: 149px;" src="http://img509.imageshack.us/img509/6214/fireballsmokexa2.png" alt="" border="0" /&gt;&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1023441640234597436-3575756194628865054?l=graphicsrunner.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='related' href='http://physbam.stanford.edu/~fedkiw/' title='Fluid Dynamics'/><link rel='replies' type='application/atom+xml' href='http://graphicsrunner.blogspot.com/feeds/3575756194628865054/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=1023441640234597436&amp;postID=3575756194628865054' title='5 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1023441640234597436/posts/default/3575756194628865054'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1023441640234597436/posts/default/3575756194628865054'/><link rel='alternate' type='text/html' href='http://graphicsrunner.blogspot.com/2008/03/fluid-dynamics.html' title='Fluid Dynamics'/><author><name>Kyle Hayward</name><uri>http://www.blogger.com/profile/00654406875137720609</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://bp3.blogger.com/_VelpN_FHzhk/R-Lx4Kk_nkI/AAAAAAAAAN8/jn_4uDeKxNk/S220/me.jpg'/></author><thr:total>5</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1023441640234597436.post-6125361246568668246</id><published>2008-03-20T19:49:00.005-04:00</published><updated>2008-03-22T00:06:14.923-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='shadow mapping'/><category scheme='http://www.blogger.com/atom/ns#' term='normal mapping'/><category scheme='http://www.blogger.com/atom/ns#' term='parallax mapping'/><category scheme='http://www.blogger.com/atom/ns#' term='Software Rendering'/><category scheme='http://www.blogger.com/atom/ns#' term='C#'/><title type='text'>More Software Rendering...</title><content type='html'>Quick update. Just found a video I had made of the software renderer.&lt;br /&gt;&lt;br /&gt;&lt;object width="425" height="350"&gt; &lt;param name="movie" value="http://www.youtube.com/v/0o6E5D3V_F0"&gt; &lt;/param&gt; &lt;embed src="http://www.youtube.com/v/0o6E5D3V_F0" type="application/x-shockwave-flash" width="425" height="350"&gt; &lt;/embed&gt; &lt;/object&gt;&lt;br /&gt;&lt;br /&gt;Not perfect, but pretty good I'd say.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://technorati.com/claim/b49gse6v5s" rel="me"&gt;Technorati Profile&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1023441640234597436-6125361246568668246?l=graphicsrunner.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://graphicsrunner.blogspot.com/feeds/6125361246568668246/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=1023441640234597436&amp;postID=6125361246568668246' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1023441640234597436/posts/default/6125361246568668246'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1023441640234597436/posts/default/6125361246568668246'/><link rel='alternate' type='text/html' href='http://graphicsrunner.blogspot.com/2008/03/more-software-rendering.html' title='More Software Rendering...'/><author><name>Kyle Hayward</name><uri>http://www.blogger.com/profile/00654406875137720609</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://bp3.blogger.com/_VelpN_FHzhk/R-Lx4Kk_nkI/AAAAAAAAAN8/jn_4uDeKxNk/S220/me.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1023441640234597436.post-4227279975616617836</id><published>2008-03-20T12:30:00.018-04:00</published><updated>2008-03-22T22:57:01.937-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Atmospheric Scattering'/><category scheme='http://www.blogger.com/atom/ns#' term='Post Processing'/><category scheme='http://www.blogger.com/atom/ns#' term='Terrain'/><category scheme='http://www.blogger.com/atom/ns#' term='Water'/><category scheme='http://www.blogger.com/atom/ns#' term='managed'/><category scheme='http://www.blogger.com/atom/ns#' term='C#'/><category scheme='http://www.blogger.com/atom/ns#' term='Bloom'/><category scheme='http://www.blogger.com/atom/ns#' term='DirectX'/><title type='text'>Terrain Rendering and Atmospheric Scattering</title><content type='html'>Terrain has always been an interest of mine, ever since I loaded my first 8-bit raw heightmap. And last summer I decided to dive into Atmospheric Scattering after reading &lt;a href="http://www.gamedev.net/community/forums/topic.asp?topic_id=487342"&gt;Ysaneya's&lt;/a&gt; developer journal over on gamedev for quite some time.&lt;br /&gt;&lt;br /&gt;So I read &lt;a href="http://ati.amd.com/developer/dx9/ATI-LightScattering.pdf"&gt;ATi's paper&lt;/a&gt; and looked at their demo and  set out to work. Now, I'm not math genious (having only taken calculus and linear algebra), but implementing atmospheric scattering is not for the faint of heart. However, after about a month I had a pretty good working implementation based on the Hoffman and Preetham paper.&lt;br /&gt;&lt;br /&gt;Then I started on the water rendering component. There were two articles in Shader X3/4 that were of great help when it came to getting the color just right.&lt;br /&gt;&lt;br /&gt;After a couple of months I had a pretty good looking demo. The terrain wasn't anything special, it was just broken down into a quadtree, so I was only able to render 2048x2048 terrain. In order to do this I made the water be at half the height of the terrain and culled the non-visible areas when i was above/below the water, so I was effectively only rendering half of the terrain when I was above/below the water line.&lt;br /&gt;&lt;br /&gt;Anyways, on to the pictures:                                      &lt;br /&gt;Scene details:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;1024x1024 terrain with multi-texturing and aerial perspective&lt;/li&gt;&lt;li&gt;Sky dome with sun and skylight scattering&lt;/li&gt;&lt;li&gt;2048x2048 water plane(size not number of vertices)&lt;/li&gt;&lt;li&gt;Bloom post processing&lt;/li&gt;&lt;li&gt;Written in c# and managed directx&lt;/li&gt;&lt;/ul&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_VelpN_FHzhk/R-Kfuak_nQI/AAAAAAAAALc/8iLrjRYigWc/s1600-h/SunsetWater04.png"&gt;&lt;img style="cursor: pointer;" src="http://1.bp.blogspot.com/_VelpN_FHzhk/R-Kfuak_nQI/AAAAAAAAALc/8iLrjRYigWc/s200/SunsetWater04.png" alt="" id="BLOGGER_PHOTO_ID_5179878141186776322" border="0" /&gt;&lt;/a&gt; &lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_VelpN_FHzhk/R-Kfuqk_nRI/AAAAAAAAALk/f4fE_hJDDVk/s1600-h/Terrain+2007-11-25+20-10-53-46.bmp"&gt;&lt;img style="cursor: pointer;" src="http://2.bp.blogspot.com/_VelpN_FHzhk/R-Kfuqk_nRI/AAAAAAAAALk/f4fE_hJDDVk/s200/Terrain+2007-11-25+20-10-53-46.bmp" alt="" id="BLOGGER_PHOTO_ID_5179878145481743634" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_VelpN_FHzhk/R-Kfu6k_nSI/AAAAAAAAALs/v0aLVESERqk/s1600-h/Terrain+2007-11-25+20-12-32-48.bmp"&gt;&lt;img style="cursor: pointer;" src="http://3.bp.blogspot.com/_VelpN_FHzhk/R-Kfu6k_nSI/AAAAAAAAALs/v0aLVESERqk/s200/Terrain+2007-11-25+20-12-32-48.bmp" alt="" id="BLOGGER_PHOTO_ID_5179878149776710946" border="0" /&gt;&lt;/a&gt; &lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_VelpN_FHzhk/R-Kfu6k_nTI/AAAAAAAAAL0/YPRZLRjkZqY/s1600-h/Terrain+2007-11-25+20-13-05-28.bmp"&gt;&lt;img style="cursor: pointer;" src="http://3.bp.blogspot.com/_VelpN_FHzhk/R-Kfu6k_nTI/AAAAAAAAAL0/YPRZLRjkZqY/s200/Terrain+2007-11-25+20-13-05-28.bmp" alt="" id="BLOGGER_PHOTO_ID_5179878149776710962" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_VelpN_FHzhk/R-KfvKk_nUI/AAAAAAAAAL8/JDUfrqeS42U/s1600-h/Terrain+2007-11-25+20-13-16-15.bmp"&gt;&lt;img style="cursor: pointer;" src="http://4.bp.blogspot.com/_VelpN_FHzhk/R-KfvKk_nUI/AAAAAAAAAL8/JDUfrqeS42U/s200/Terrain+2007-11-25+20-13-16-15.bmp" alt="" id="BLOGGER_PHOTO_ID_5179878154071678274" border="0" /&gt;&lt;/a&gt; &lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_VelpN_FHzhk/R-Kg7Kk_nVI/AAAAAAAAAME/uIdTXl5_MOc/s1600-h/Terrain+2007-11-25+20-13-57-20.bmp"&gt;&lt;img style="cursor: pointer;" src="http://4.bp.blogspot.com/_VelpN_FHzhk/R-Kg7Kk_nVI/AAAAAAAAAME/uIdTXl5_MOc/s200/Terrain+2007-11-25+20-13-57-20.bmp" alt="" id="BLOGGER_PHOTO_ID_5179879459741736274" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_VelpN_FHzhk/R-Kg7qk_nWI/AAAAAAAAAMM/T7-oPOqeQmE/s1600-h/Terrain+2007-11-25+20-15-52-90.bmp"&gt;&lt;img style="cursor: pointer;" src="http://2.bp.blogspot.com/_VelpN_FHzhk/R-Kg7qk_nWI/AAAAAAAAAMM/T7-oPOqeQmE/s200/Terrain+2007-11-25+20-15-52-90.bmp" alt="" id="BLOGGER_PHOTO_ID_5179879468331670882" border="0" /&gt;&lt;/a&gt; &lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_VelpN_FHzhk/R-Kg76k_nXI/AAAAAAAAAMU/WNq0k6Na9Hk/s1600-h/Terrain+2007-11-25+20-45-05-14.bmp"&gt;&lt;img style="cursor: pointer;" src="http://3.bp.blogspot.com/_VelpN_FHzhk/R-Kg76k_nXI/AAAAAAAAAMU/WNq0k6Na9Hk/s200/Terrain+2007-11-25+20-45-05-14.bmp" alt="" id="BLOGGER_PHOTO_ID_5179879472626638194" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_VelpN_FHzhk/R-Kg8Kk_nYI/AAAAAAAAAMc/1NviqjMFl8g/s1600-h/Terrain+2007-11-27+23-49-29-48.bmp"&gt;&lt;img style="cursor: pointer;" src="http://4.bp.blogspot.com/_VelpN_FHzhk/R-Kg8Kk_nYI/AAAAAAAAAMc/1NviqjMFl8g/s200/Terrain+2007-11-27+23-49-29-48.bmp" alt="" id="BLOGGER_PHOTO_ID_5179879476921605506" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;And a video of it in action:&lt;br /&gt;&lt;object height="350" width="425"&gt; &lt;param name="movie" value="http://www.youtube.com/v/EqJhx3IDQ4s"&gt;  &lt;embed src="http://www.youtube.com/v/EqJhx3IDQ4s" type="application/x-shockwave-flash" height="350" width="425"&gt;&lt;/embed&gt;  &lt;/object&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;The water has realistic coastal coloring, soft edges when it intersects the terrain, under water fogging, and depth fogging.&lt;br /&gt;&lt;br /&gt;This summer I plan to rewrite the whole application in c++. I also want to extend the terrain rendering with geomipmapping, fix the huge sun, and add volumetric clouds. I had tried implementing volumetric clouds using mega particles, but didn't turn out too well in a dense cloud field. Worked really well for volcanic or nuclear plumes though. More on this in a later post.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1023441640234597436-4227279975616617836?l=graphicsrunner.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://graphicsrunner.blogspot.com/feeds/4227279975616617836/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=1023441640234597436&amp;postID=4227279975616617836' title='7 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1023441640234597436/posts/default/4227279975616617836'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1023441640234597436/posts/default/4227279975616617836'/><link rel='alternate' type='text/html' href='http://graphicsrunner.blogspot.com/2008/03/terrain-rendering-and-atmospheric.html' title='Terrain Rendering and Atmospheric Scattering'/><author><name>Kyle Hayward</name><uri>http://www.blogger.com/profile/00654406875137720609</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://bp3.blogger.com/_VelpN_FHzhk/R-Lx4Kk_nkI/AAAAAAAAAN8/jn_4uDeKxNk/S220/me.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/_VelpN_FHzhk/R-Kfuak_nQI/AAAAAAAAALc/8iLrjRYigWc/s72-c/SunsetWater04.png' height='72' width='72'/><thr:total>7</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1023441640234597436.post-4528968809243349765</id><published>2008-03-19T15:09:00.016-04:00</published><updated>2008-03-22T13:30:17.816-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='fog'/><category scheme='http://www.blogger.com/atom/ns#' term='shadow mapping'/><category scheme='http://www.blogger.com/atom/ns#' term='normal mapping'/><category scheme='http://www.blogger.com/atom/ns#' term='depth of field'/><category scheme='http://www.blogger.com/atom/ns#' term='PCF'/><category scheme='http://www.blogger.com/atom/ns#' term='parallax mapping'/><category scheme='http://www.blogger.com/atom/ns#' term='Software Rendering'/><category scheme='http://www.blogger.com/atom/ns#' term='reflections'/><category scheme='http://www.blogger.com/atom/ns#' term='C#'/><title type='text'>Software Rendering</title><content type='html'>About a year ago, I took a class on software rasterization. It really helped me understand everything that DirectX/Opengl does underneath the hood. We started out by rasterizing 2D lines and images and then moved onto a full 3D renderer all done in software (i.e. on the cpu). It was a pretty cool class. The focus of the class was mainly on implementation rather than performance, so it isn't as fast as other software renderers.&lt;br /&gt;&lt;br /&gt;I wrote the rasterizer using c#, and the Tao opengl framework (only for sending the pixel information to the graphics card with glDrawPixels()). I posted this on image of the day at gamedev:  &lt;a href="http://www.gamedev.net/community/forums/topic.asp?topic_id=446142"&gt;http://www.gamedev.net/community/forums/topic.asp?topic_id=446142&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Here's what I had accomplished by the end of the semester:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Gouraud shading&lt;/li&gt;&lt;li&gt; Phong shading&lt;/li&gt;&lt;li&gt; Blinn and Phong specular reflection&lt;/li&gt;&lt;li&gt; Directional and Point lights&lt;/li&gt;&lt;li&gt; Perspective correct texture mapping&lt;/li&gt;&lt;li&gt; Normal Mapping&lt;/li&gt;&lt;li&gt; Parallax Mapping&lt;/li&gt;&lt;li&gt; Projective Texturing&lt;/li&gt;&lt;li&gt; Shadow Mapping&lt;/li&gt;&lt;li&gt; Environment Mapping - for skybox and distant reflections&lt;/li&gt;&lt;li&gt; Distance Fog&lt;/li&gt;&lt;li&gt; Depth of Field&lt;/li&gt;&lt;li&gt; Bilinear Filtering&lt;/li&gt;&lt;li&gt; Camera Interpolation, for movie creation&lt;/li&gt;&lt;/ul&gt;environment mapped reflections:&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_VelpN_FHzhk/R-Fnqak_mEI/AAAAAAAAAAM/kK7xT2GlmaA/s1600-h/6.jpg"&gt;&lt;img style="margin: 0pt 10px 10px 0pt; float: left; cursor: pointer;" src="http://4.bp.blogspot.com/_VelpN_FHzhk/R-Fnqak_mEI/AAAAAAAAAAM/kK7xT2GlmaA/s320/6.jpg" alt="" id="BLOGGER_PHOTO_ID_5179535024839432258" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;perspective correct texture mapping:&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_VelpN_FHzhk/R-FoAqk_mFI/AAAAAAAAAAU/FLeD_HlOOlo/s1600-h/1.jpg"&gt;&lt;img style="margin: 0pt 10px 10px 0pt; float: left; cursor: pointer;" src="http://1.bp.blogspot.com/_VelpN_FHzhk/R-FoAqk_mFI/AAAAAAAAAAU/FLeD_HlOOlo/s320/1.jpg" alt="" id="BLOGGER_PHOTO_ID_5179535407091521618" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;depth of field:&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_VelpN_FHzhk/R-FoNak_mGI/AAAAAAAAAAc/LOtHTzIHwe8/s1600-h/2.jpg"&gt;&lt;img style="margin: 0pt 10px 10px 0pt; float: left; cursor: pointer;" src="http://4.bp.blogspot.com/_VelpN_FHzhk/R-FoNak_mGI/AAAAAAAAAAc/LOtHTzIHwe8/s320/2.jpg" alt="" id="BLOGGER_PHOTO_ID_5179535626134853730" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;distance based fog:&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_VelpN_FHzhk/R-Fojak_mHI/AAAAAAAAAAk/hGHsEzhB2mQ/s1600-h/3.jpg"&gt;&lt;img style="margin: 0pt 10px 10px 0pt; float: left; cursor: pointer;" src="http://4.bp.blogspot.com/_VelpN_FHzhk/R-Fojak_mHI/AAAAAAAAAAk/hGHsEzhB2mQ/s320/3.jpg" alt="" id="BLOGGER_PHOTO_ID_5179536004091975794" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;parallax mapping:&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_VelpN_FHzhk/R-FpB6k_mII/AAAAAAAAAAs/BzfyIfzb1vE/s1600-h/4.jpg"&gt;&lt;img style="margin: 0pt 10px 10px 0pt; float: left; cursor: pointer;" src="http://2.bp.blogspot.com/_VelpN_FHzhk/R-FpB6k_mII/AAAAAAAAAAs/BzfyIfzb1vE/s320/4.jpg" alt="" id="BLOGGER_PHOTO_ID_5179536528077985922" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;shadow mapping with percentage closer filtering:&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_VelpN_FHzhk/R-FpTak_mJI/AAAAAAAAAA0/TfPQUnR4h9I/s1600-h/5.jpg"&gt;&lt;img style="margin: 0pt 10px 10px 0pt; float: left; cursor: pointer;" src="http://4.bp.blogspot.com/_VelpN_FHzhk/R-FpTak_mJI/AAAAAAAAAA0/TfPQUnR4h9I/s320/5.jpg" alt="" id="BLOGGER_PHOTO_ID_5179536828725696658" border="0" /&gt;&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1023441640234597436-4528968809243349765?l=graphicsrunner.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://graphicsrunner.blogspot.com/feeds/4528968809243349765/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=1023441640234597436&amp;postID=4528968809243349765' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1023441640234597436/posts/default/4528968809243349765'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1023441640234597436/posts/default/4528968809243349765'/><link rel='alternate' type='text/html' href='http://graphicsrunner.blogspot.com/2008/03/software-rendering.html' title='Software Rendering'/><author><name>Kyle Hayward</name><uri>http://www.blogger.com/profile/00654406875137720609</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://bp3.blogger.com/_VelpN_FHzhk/R-Lx4Kk_nkI/AAAAAAAAAN8/jn_4uDeKxNk/S220/me.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/_VelpN_FHzhk/R-Fnqak_mEI/AAAAAAAAAAM/kK7xT2GlmaA/s72-c/6.jpg' height='72' width='72'/><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1023441640234597436.post-9057713556926561874</id><published>2008-03-19T14:53:00.003-04:00</published><updated>2008-03-19T15:06:55.360-04:00</updated><title type='text'>Yes... another blog</title><content type='html'>Why am I starting this blog you ask? Well, after reading other graphics related blogs for some time now, I figured it would be a good place for me to document not only current work but previous work.&lt;br /&gt;&lt;br /&gt;I've been developing 3D demos/applications for a couple of years now. It all started when I took a class on game development for the Sony line of mobile phones. After that I started working with DirectX and haven't stopped since.&lt;br /&gt;&lt;br /&gt;My interests lie mainly in terrain and atmosphere effects, but more recently I've become interested in post processing techniques as well. This semester I began researching reflections with my professor. We're developing a method to produce accurate reflections of objects using non-pinhole camera depth impostors. This will allow us to have reflected rays intersect the depth map that would not be able to using a regular depth map impostor (more on this to come).&lt;br /&gt;&lt;br /&gt;The first few posts will most likely be historical. I'll mainly be posting these previous few projects as sort of documentation for them.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1023441640234597436-9057713556926561874?l=graphicsrunner.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://graphicsrunner.blogspot.com/feeds/9057713556926561874/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=1023441640234597436&amp;postID=9057713556926561874' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1023441640234597436/posts/default/9057713556926561874'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1023441640234597436/posts/default/9057713556926561874'/><link rel='alternate' type='text/html' href='http://graphicsrunner.blogspot.com/2008/03/yes-another-blog.html' title='Yes... another blog'/><author><name>Kyle Hayward</name><uri>http://www.blogger.com/profile/00654406875137720609</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://bp3.blogger.com/_VelpN_FHzhk/R-Lx4Kk_nkI/AAAAAAAAAN8/jn_4uDeKxNk/S220/me.jpg'/></author><thr:total>3</thr:total></entry></feed>
