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

<channel>
	<title>CodeItNow &#187; Global Illumination</title>
	<atom:link href="http://www.rorydriscoll.com/category/graphics/global-illumination/feed/" rel="self" type="application/rss+xml" />
	<link>http://www.rorydriscoll.com</link>
	<description></description>
	<lastBuildDate>Mon, 23 Jan 2012 01:50:36 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.3.1</generator>
		<item>
		<title>What&#8217;s wrong with this picture?</title>
		<link>http://www.rorydriscoll.com/2010/01/30/whats-wrong-with-this-picture/</link>
		<comments>http://www.rorydriscoll.com/2010/01/30/whats-wrong-with-this-picture/#comments</comments>
		<pubDate>Sun, 31 Jan 2010 00:31:58 +0000</pubDate>
		<dc:creator>Rory</dc:creator>
				<category><![CDATA[Global Illumination]]></category>
		<category><![CDATA[Graphics]]></category>

		<guid isPermaLink="false">http://www.rorydriscoll.com/?p=393</guid>
		<description><![CDATA[Well, you could point out a number of things to answer that question. There&#8217;s some pretty obvious aliasing, a random pixel on the ground which should be in shadow but isn&#8217;t, it&#8217;s noisy, boring etc. But that&#8217;s not my point. The point is: It&#8217;s too dark! I know it&#8217;s too dark because I know how [...]]]></description>
			<content:encoded><![CDATA[<p><img class="aligncenter size-full wp-image-398" title="montecarlo256samples2bounces" src="http://www.rorydriscoll.com/wp-content/uploads/2010/01/montecarlo256samples2bounces.png" alt="montecarlo256samples2bounces" width="656" height="396"/></p>
<p>Well, you could point out a number of things to answer that question. There&#8217;s some pretty obvious aliasing, a random pixel on the ground which should be in shadow but isn&#8217;t, it&#8217;s noisy, boring etc. But that&#8217;s not my point. The point is: It&#8217;s too dark!</p>
<p>I know it&#8217;s too dark because I know how I rendered it, and I rendered it wrong. It still kind of looks acceptable (well to me at least) though. I&#8217;m not sure that I would say that it&#8217;s implausibly dark if I didn&#8217;t know it.</p>
<p><span id="more-393"></span></p>
<h2>How many bounces are enough?</h2>
<p>I rendered this image using a Monte Carlo estimator with two bounces of indirect light. Each bounce estimated the irradiance at the intersection point using 256 rays in a cosine-weighted stratified-sampling pattern. It&#8217;s the two bounce part that makes it wrong. Any light that bounced more than twice before heading toward the camera is totally ignored. Since this approach doesn&#8217;t converge on the correct solution to the rendering equation, it&#8217;s classified as &#8216;biased&#8217;.</p>
<p>How much does light that bounced more than twice really contribute to a scene? Of course that depends on the materials in the scene quite a bit, but in this case, there&#8217;s a noticeable difference. I rendered the same exact scene using the Monte Carlo estimator for the first bounce, but for the subsequent bounces I used a path tracer. By using Russian Roulette (a topic unto itself) to terminate the path, you can get an unbiased approximation of the irradiance.</p>
<p><img class="aligncenter size-full wp-image-399" title="pathtracer4096paths80percentsurvival" src="http://www.rorydriscoll.com/wp-content/uploads/2010/01/pathtracer4096paths80percentsurvival.png" alt="pathtracer4096paths80percentsurvival" width="656" height="396" /></p>
<p>Ok, great. It&#8217;s brighter, but is it actually correct? I was wondering about this, then I happened to come across an idea while reading <a href="http://www.amazon.com/Physically-Based-Rendering-Implementation-Interactive/dp/012553180X">Physically Based Rendering</a> to compare the results of my integrators with something that has an analytical solution to the rendering equation.</p>
<h2>I now present an analytical solution to the Rendering Equation!</h2>
<h5>(&#8230; in a very simple case)</h5>
<p>Solving the rendering equation analytically for most scenes is just impossible, that&#8217;s why we have to rely on numerical methods like Monte Carlo Estimation. However, the book suggests a <i>very</i> simple scene for which it can be solved. The suggested setup is that of light bouncing around the inside of a sphere. The sphere emits light internally, and reflects it diffusely to other points on the inside of the sphere. Since the sphere is rotationally invariant and the reflections are diffuse, every point on the sphere reflects and emits the same radiance in all directions.</p>
<p>So how do you solve the rendering equation for this situation? It&#8217;s fairly easy. Recall the rendering equation:</p>
<p><a href="http://www.codecogs.com/eqnedit.php?latex=L(\vec{x},\omega)=L_e@plus;\int_{\Omega}\rho(\vec{x},\omega,{\omega}')L_i(\vec{x},{\omega}')\cos\theta\,\delta{\omega}'" target="_blank"><img src="http://latex.codecogs.com/gif.latex?L(\vec{x},\omega)=L_e+\int_{\Omega}\rho(\vec{x},\omega,{\omega}')L_i(\vec{x},{\omega}')\cos\theta\,\delta{\omega}'" title="L(\vec{x},\omega)=L_e+\int_{\Omega}\rho(\vec{x},\omega,{\omega}')L_i(\vec{x},{\omega}')\cos\theta\,\delta{\omega}'" /></a></p>
<p>In this setup, we are using a diffuse BRDF with reflectance <i>d</i>. It&#8217;s also normalized to maintain energy conservation</p>
<p><a href="http://www.codecogs.com/eqnedit.php?latex=\rho(\vec{x},\omega,{\omega}')=\frac{d}{\pi}" target="_blank"><img src="http://latex.codecogs.com/gif.latex?\rho(\vec{x},\omega,{\omega}')=\frac{d}{\pi}" title="\rho(\vec{x},\omega,{\omega}')=\frac{d}{\pi}" /></a></p>
<p>Also, as mentioned previously, the outgoing radiance in all directions is the same as the incoming radiance, so this reduces the rendering equation to:</p>
<p><a href="http://www.codecogs.com/eqnedit.php?latex=L=L_e@plus;\frac{d}{\pi}\int_{\Omega}L\cos\theta\,\delta{\omega}'" target="_blank"><img src="http://latex.codecogs.com/gif.latex?L=L_e+\frac{dL}{\pi}\int_{\Omega}\cos\theta\,\delta{\omega}'" title="L=L_e+\frac{dL}{\pi}\int_{\Omega}\cos\theta\,\delta{\omega}'" /></a></p>
<p>Solving this equation for L is now pretty easy. First we have to convert from an integral over solid angles, to a double-angle version. The important thing to remember when doing this is to introduce the new <i>sine</i> term:</p>
<p><a href="http://www.codecogs.com/eqnedit.php?latex=L=L_e@plus;\frac{dL}{\pi}\int_{\phi=0}^{2\pi}\int_{\theta=0}^{\frac{\pi}{2}}\cos\theta\sin\theta\,\delta{\theta}\,\delta{\phi}" target="_blank"><img src="http://latex.codecogs.com/gif.latex?L=L_e+\frac{dL}{\pi}\int_{\phi=0}^{2\pi}\int_{\theta=0}^{\frac{\pi}{2}}\cos\theta\sin\theta\,\delta{\theta}\,\delta{\phi}" title="L=L_e+\frac{dL}{\pi}\int_{\phi=0}^{2\pi}\int_{\theta=0}^{\frac{\pi}{2}}\cos\theta\sin\theta\,\delta{\theta}\,\delta{\phi}" /></a></p>
<p>There&#8217;s a double angle trigonometric identity that makes this easier:</p>
<p><a href="http://www.codecogs.com/eqnedit.php?latex=\cos{x}sin{x}=\frac{\sin{2x}}{2}" target="_blank"><img src="http://latex.codecogs.com/gif.latex?\cos{x}sin{x}=\frac{\sin{2x}}{2}" title="\cos{x}sin{x}=\frac{\sin{2x}}{2}" /></a></p>
<p>So we just need to integrate the following:</p>
<p><a href="http://www.codecogs.com/eqnedit.php?latex=L=L_e@plus;\frac{dL}{2\pi}\int_{\phi=0}^{2\pi}\int_{\theta=0}^{\frac{\pi}{2}}\sin2\theta\,\delta{\theta}\,\delta{\phi}" target="_blank"><img src="http://latex.codecogs.com/gif.latex?L=L_e+\frac{dL}{2\pi}\int_{\phi=0}^{2\pi}\int_{\theta=0}^{\frac{\pi}{2}}\sin2\theta\,\delta{\theta}\,\delta{\phi}" title="L=L_e+\frac{dL}{2\pi}\int_{\phi=0}^{2\pi}\int_{\theta=0}^{\frac{\pi}{2}}\sin2\theta\,\delta{\theta}\,\delta{\phi}" /></a></p>
<p>Integrate over theta:</p>
<p><a href="http://www.codecogs.com/eqnedit.php?latex=L=L_e@plus;\frac{dL}{2\pi}\int_{\phi=0}^{2\pi}\left[\frac{-\cos2\theta}{2}\right]_{0}^{\frac{\pi}{2}}\,\delta{\phi}" target="_blank"><img src="http://latex.codecogs.com/gif.latex?L=L_e+\frac{dL}{2\pi}\int_{\phi=0}^{2\pi}\left[\frac{-\cos2\theta}{2}\right]_{0}^{\frac{\pi}{2}}\,\delta{\phi}" title="L=L_e+\frac{dL}{2\pi}\int_{\phi=0}^{2\pi}\left[\frac{-\cos2\theta}{2}\right]_{0}^{\frac{\pi}{2}}\,\delta{\phi}" /></a></p>
<p>This integral over theta is just 1, so now integrate over phi:</p>
<p><a href="http://www.codecogs.com/eqnedit.php?latex=L=L_e@plus;\frac{dL}{2\pi}\int_{\phi=0}^{2\pi}\,\delta{\phi}" target="_blank"><img src="http://latex.codecogs.com/gif.latex?L=L_e+\frac{dL}{2\pi}\int_{\phi=0}^{2\pi}\,\delta{\phi}" title="L=L_e+\frac{dL}{2\pi}\int_{\phi=0}^{2\pi}\,\delta{\phi}" /></a></p>
<p>This integral is of course just 2 &Pi;. So we&#8217;re left with a very simple equation:</p>
<p><a href="http://www.codecogs.com/eqnedit.php?latex=L=L_e@plus;dL" target="_blank"><img src="http://latex.codecogs.com/gif.latex?L=L_e+dL" title="L=L_e+dL" /></a></p>
<p>Solving for L gives us the final expect radiance at every point in the sphere:</p>
<p><a href="http://www.codecogs.com/eqnedit.php?latex=L=\frac{L_e}{1-d}" target="_blank"><img src="http://latex.codecogs.com/gif.latex?L=\frac{L_e}{1-d}" title="L=\frac{L_e}{1-d}" /></a></p>
<p>This intuitively makes sense. As d grows, so does L. When d is 1, all hell breaks loose, and when d is 0 all we are left with is the emitted light. Obviously d can never be greater than 1 or the energy conservation rule would have been broken.</p>
<h2>The test application</h2>
<p>Alright, so all I need to do now is make a test application that fires a bunch of rays around the inside of a sphere and compare the results to the analytical solution. Well&#8230; So I thought. Due to the curvature of the inside of the sphere, I found that a good number of rays I fired near the horizon were escaping the sphere.</p>
<p>Currently I apply an epsilon to the minimum ray intersection to try and prevent self-intersections and this causes problems. For now (and just for these tests), instead I&#8217;m pushing the ray starting point away from the intersection a small amount in the direction of the intersection normal. I made the sphere really big too. I&#8217;d welcome any better ideas for alleviating this problem.</p>
<p>For the tests below, I set the emitted light value to 1, and the diffuse reflectance, d, to 0.5, meaning that the outgoing radiance, L, should be 2.</p>
<h2>Multi-bounce Monte Carlo results</h2>
<p>You can work out from the radiance equation how different numbers of bounces of light will affect the final solution. In this case, I just ran my Monte Carlo integrator from 0 to 16 bounces and produced the following graph showing the percentage (of the expected result) absolute error.</p>
<p><img class="aligncenter" src="http://spreadsheets.google.com/oimg?key=0AliVyEtgVru8dHJDZ2w3V0FBcmRSb0t4TElJbHhXNXc&amp;oid=5&amp;v=1264822032867" alt="" /></p>
<p>You can see that the error starts off really high, but drops off pretty quickly as more bounces are added. Each subsequent bounce has less and less effect on the error reduction, as expected.</p>
<p>What you have to remember though, is that each bounce adds exponentially to the number of rays that have to be cast to achieve that error. In real-world situations we need to cast a lot of rays over the hemisphere in order to get a correct solution. To get under 5% error you&#8217;d need four bounces with this method. Even using a very modest number of rays per estimation quickly becomes unruly: 256 rays with 4 bounces = over 4 billion rays needed! Per pixel! Without anti-aliasing!</p>
<h2>Path Tracer results</h2>
<p>Ok, so how about the path tracer? In theory the path tracer should average out to zero error, but can have potentially very high variance. Here&#8217;s the average error over 1000 runs with different survival probabilities:</p>
<p><img src="http://spreadsheets.google.com/oimg?key=0AliVyEtgVru8dFIyWlMtWVdMTGl4RUJFZkd5blRhRXc&amp;oid=1&amp;v=1264822087193" alt="" /></p>
<p>Well, it&#8217;s not zero everywhere, but it&#8217;s a pretty close in most cases. I think that the results obtained from this kind of method probably depend quite a bit on the quality of the random number generator used. I&#8217;m just using the stdlib version, so I&#8217;ll try switching it up at some point to see how it affects things. I haven&#8217;t shown it here, but the variance at low survival probabilities is incredibly high. This surely accounts for how far off the results are under about 10% survival.</p>
<p>The good thing about the path tracer is that it doesn&#8217;t require exorbitant numbers of rays. Yes, the variance can be high, but it can be reduced by tracing more paths. The net result is that it produces low error images much quicker than using standard Monte Carlo integration.</p>
<h2>The End</h2>
<p>Well, the point of this post really was: How do you know your integrator is correct? Comparing it to something simple that can be calculated analytically is not the be-all and end-all, but it&#8217;s a good start. There are many other things that could still be wrong even if your integrator gets good results against this simple test, like how you&#8217;re calculating sample directions, your PDF etc.</p>
<p> For me, being able to compare my integrators to a known solution definitely helped me to make sure that my path tracer was getting correct results.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.rorydriscoll.com/2010/01/30/whats-wrong-with-this-picture/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Irradiance Caching: Part 2</title>
		<link>http://www.rorydriscoll.com/2009/01/24/irradiance-caching-part-2/</link>
		<comments>http://www.rorydriscoll.com/2009/01/24/irradiance-caching-part-2/#comments</comments>
		<pubDate>Sat, 24 Jan 2009 22:45:17 +0000</pubDate>
		<dc:creator>Rory</dc:creator>
				<category><![CDATA[Global Illumination]]></category>
		<category><![CDATA[Graphics]]></category>
		<category><![CDATA[Irradiance Caching]]></category>

		<guid isPermaLink="false">http://www.rorydriscoll.com/?p=200</guid>
		<description><![CDATA[In my previous post, I wrote very briefly about an  important improvement to the irradiance caching algorithm &#8211; irradiance gradients &#8211; and I&#8217;m going to expand on rotational gradients this time. Gradients The gradient of a function represents both the direction and rate of change of that function as the inputs vary. For a one [...]]]></description>
			<content:encoded><![CDATA[<p>In my previous post, I wrote very briefly about an  important improvement to the irradiance caching algorithm &#8211; irradiance gradients &#8211; and I&#8217;m going to expand on rotational gradients this time.</p>
<h2>Gradients</h2>
<p>The gradient of a function represents both the direction and rate of change of that function as the inputs vary. For a one dimensional function this is simply the derivative of the function. As you move into higher dimensions, you need to consider which coordinate system the inputs for the function are specified in, as this will change how you need to calculate the gradient.</p>
<p>For now, I&#8217;m just going to focus on calculating the gradient of a function defined using normalized spherical coordinates. Unfortunately, there&#8217;s no real standard way to define spherical coordinates, and despite similar looking symbols, the values are often interchanged. I&#8217;m going to define the spherical coordinates on the unit sphere as azimuthal value φ [0, π), and polar value θ [0, 2π).</p>
<p style="text-align: center;"><img class="size-full wp-image-238 aligncenter" title="sphericalcoordinates" src="http://www.rorydriscoll.com/wp-content/uploads/2009/01/sphericalcoordinates.png" alt="sphericalcoordinates" width="290" height="264" /></p>
<p><span id="more-200"></span>When dealing with multiple dimensions, you can calculate the gradient by splitting the gradient calculation into multiple partial derivatives and summing them with appropriate vector weights. For normalized spherical coordinates the gradient is:</p>
<p><img src="http://latex.codecogs.com/gif.latex?\nabla&amp;space;f(\phi,&amp;space;\theta)&amp;space;=&amp;space;\frac{\delta&amp;space;f}{\delta&amp;space;\theta}&amp;space;\vec{v_\theta}&amp;space;+&amp;space;\frac{1}{\sin&amp;space;\theta}\frac{\delta&amp;space;f}{\delta&amp;space;\phi}\vec{v_\phi}" alt="" /></p>
<p>The function <em>f</em> represents a scalar field, so the gradient is a vector field. Each vector points in the direction of greatest increase. Much like the integrals of functions described using spherical coordinates, you have to take care to weight the azimuthal contribution by the sine of the polar angle.</p>
<p>As a real-world example of using gradients, let&#8217;s calculate the gradient for a simple function:</p>
<p><img src="http://latex.codecogs.com/gif.latex?f(\phi,&amp;space;\theta)&amp;space;=&amp;space;\cos&amp;space;\theta" border="0" alt="" /></p>
<p>The derivatives of the function for each argument are easy to calculate:</p>
<p><img src="http://latex.codecogs.com/gif.latex?\frac{\delta&amp;space;f}{\delta&amp;space;\theta}&amp;space;=&amp;space;-\sin&amp;space;\theta" border="0" alt="" /></p>
<p><img src="http://latex.codecogs.com/gif.latex?\frac{\delta&amp;space;f}{\delta&amp;space;\phi}&amp;space;=&amp;space;0" border="0" alt="" /></p>
<p>Combining these together with the previous definition, you can calculate the gradient vector at any point on the unit sphere for <em>f </em>using:</p>
<p><img src="http://latex.codecogs.com/gif.latex?\nabla&amp;space;f(\phi,&amp;space;\theta)&amp;space;=&amp;space;-\vec{v_\theta}&amp;space;\sin&amp;space;\theta" border="0" alt="" /></p>
<p>There&#8217;s lots more to be learned about gradients, and a good start would be <a href="http://en.wikipedia.org/wiki/Gradient">Wikipedia</a>, and also <a href="http://www-math.mit.edu/18.013A/HTML/chapter09/section04.html">this page</a> on the MIT website.</p>
<h2>Rotational Irradiance Gradient</h2>
<p>The irradiance contribution from a direction on the hemisphere about the surface normal, specified using spherical coordinates φ [0, 2π) and θ [0, π / 2) is:</p>
<p><img src="http://latex.codecogs.com/gif.latex?f&amp;space;(\phi,&amp;space;\theta)&amp;space;=&amp;space;L(\phi,&amp;space;\theta)&amp;space;\cos&amp;space;\theta" alt="" /></p>
<p>Where L is the incident radiance in the supplied direction. So to calculate the gradient vector at any point on the hemisphere, you just need to evaluate:</p>
<p><img src="http://latex.codecogs.com/gif.latex?\nabla&amp;space;f(\phi,&amp;space;\theta)&amp;space;=&amp;space;-\vec{v_\theta}L(\phi,&amp;space;\theta)&amp;space;\sin&amp;space;\theta" alt="" /></p>
<p>This just calculates the gradient in a specific direction, but for the irradiance gradient we need to calculate the average gradient over the entire hemisphere. We can do this at the same time as we calculate the irradiance by using a similar Monte Carlo estimator. We want to share the sampling strategy between the irradiance calculation and the rotational gradient calculation, so we&#8217;re stuck using the same pdf:</p>
<p><img src="http://latex.codecogs.com/gif.latex?p(x)&amp;space;=&amp;space;\frac&amp;space;{\cos&amp;space;\theta}{\pi}" alt="" /></p>
<p>So the estimator for the irradiance gradient becomes:</p>
<p><img src="http://latex.codecogs.com/gif.latex?\nabla&amp;space;E&amp;space;\approx&amp;space;\frac&amp;space;{1}{N}&amp;space;\sum_{i=1}^{N}{&amp;space;\frac&amp;space;{-\vec{v_\theta}L_i\sin&amp;space;\theta}{\frac{\cos&amp;space;\theta}{\pi}}&amp;space;}" alt="" /></p>
<p>Which collapses down to:</p>
<p><img src="http://latex.codecogs.com/gif.latex?\nabla&amp;space;E&amp;space;\approx&amp;space;\frac&amp;space;{\pi}{N}&amp;space;\sum_{i=1}^{N}{-\vec{v_\theta}L_i\tan&amp;space;\theta}" alt="" /></p>
<p>The vector <em>v</em> is a unit vector on the plane of the hemisphere pointing in the perpendicular direction to the angle φ.  There are two perpendicular vectors to φ, and which one you decide to use depends on the order you do the cross product on when evaluating the gradient. Using the left hand rule for rotation, I&#8217;m doing a clockwise rotation of φ by ninety degrees.</p>
<h2>Using the Rotational Irradiance Gradient</h2>
<p>The irradiance estimate at a point is defined by a weighted sum of irradiance cache entries:</p>
<p><img src="http://latex.codecogs.com/gif.latex?E&amp;space;=&amp;space;\frac{\sum_{1}^{N}{w_i&amp;space;E_i}}{\sum_{1}^{N}{w_i}}" alt="" /></p>
<p>Now we have the rotational gradient, we can use it to improve the estimate. The cross product of the surface normal and cache entry normal represents both a direction and magnitude of rotational difference. We then project this difference onto the irradiance gradient to calculate how much the irradiance is changing in that direction:</p>
<p><img src="http://latex.codecogs.com/gif.latex?E&amp;space;=&amp;space;\frac{\sum_{1}^{N}{w_i&amp;space;(E_i&amp;space;+&amp;space;\nabla&amp;space;E_i&amp;space;\cdot&amp;space;(\vec{n_i}&amp;space;\times&amp;space;\vec{n}))}}{\sum_{1}^{N}{w_i}}" alt="" /></p>
<p>Note that conceptually this calculation needs to be performed once for each color channel, since the gradient of the irradiance is really the gradient of three scalar fields &#8211; red, green and blue. In practice it&#8217;s easier to assume that the gradient is a three dimensional vector of colors, rather than scalars.</p>
<h2>Implementation</h2>
<p>The implementation is pretty straightforward, but there are a couple of optimizations that can be made because we are using a unit hemisphere for sampling. Assuming that we have the sample direction in Cartesian coordinates in local space, where the z-axis points in the polar direction, we can get the cos weighting from the z coordinate:</p>
<p><img title="z = \cos \theta" src="http://latex.codecogs.com/gif.latex?z&amp;space;=&amp;space;\cos&amp;space;\theta" alt="" /></p>
<p>Also, we can get the sine weighted projected unit vector by simply setting the z value to zero, since:</p>
<p><img src="http://latex.codecogs.com/gif.latex?\lvert&amp;space;(x,&amp;space;y,&amp;space;0)&amp;space;\rvert&amp;space;=&amp;space;\sin&amp;space;\theta" alt="" /></p>
<p>So, to get the local space tan weighted perpendicular vector, we just need to use the following:</p>
<p><img src="http://latex.codecogs.com/gif.latex?\vec{v_\theta}&amp;space;\tan&amp;space;\theta&amp;space;=&amp;space;(\frac{y}{z},&amp;space;-\frac{x}{z},&amp;space;0)" alt="" /></p>
<p>Note that the x and y values were swapped, and the x value negated to get the perpendicular vector.</p>
<h2>Results</h2>
<p>I&#8217;ve rendered out a before and after shot, showing just the indirect irradiance. There are no translational gradients being used at the moment, so there are still some artefacts. Here&#8217;s the before shot:</p>
<p><img class="aligncenter size-full wp-image-231" title="nogradient" src="http://www.rorydriscoll.com/wp-content/uploads/2009/01/nogradient.png" alt="nogradient" width="656" height="396" /></p>
<p>And here&#8217;s the same render, but with rotational gradients this time:</p>
<p><img class="aligncenter size-full wp-image-232" title="rotationalgradient" src="http://www.rorydriscoll.com/wp-content/uploads/2009/01/rotationalgradient.png" alt="rotationalgradient" width="656" height="396" /></p>
<p>Note that the time to render each frame is almost exactly the same, yet the rotational gradients provide a much smoother result. Next I&#8217;ll implement translational gradients and hopefully the image will look considerably better.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.rorydriscoll.com/2009/01/24/irradiance-caching-part-2/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>Irradiance Caching: Part 1</title>
		<link>http://www.rorydriscoll.com/2009/01/18/irradiance-caching-part-1/</link>
		<comments>http://www.rorydriscoll.com/2009/01/18/irradiance-caching-part-1/#comments</comments>
		<pubDate>Mon, 19 Jan 2009 01:35:35 +0000</pubDate>
		<dc:creator>Rory</dc:creator>
				<category><![CDATA[C++]]></category>
		<category><![CDATA[Global Illumination]]></category>
		<category><![CDATA[Graphics]]></category>
		<category><![CDATA[Irradiance Caching]]></category>

		<guid isPermaLink="false">http://www.rorydriscoll.com/?p=166</guid>
		<description><![CDATA[Solving the rendering equation with even just one bounce of indirect lighting can take a long time. The majority of time spent rendering a frame is in estimating the lighting integral. For example, rendering a single bounce of indirect lighting at 720p resolution with 256 sample rays for a Monte Carlo estimator requires about 237 [...]]]></description>
			<content:encoded><![CDATA[<p>Solving the rendering equation with even just one bounce of indirect lighting can take a long time. The majority of time spent rendering a frame is in estimating the lighting integral. For example, rendering a single bounce of indirect lighting at 720p resolution with 256 sample rays for a Monte Carlo estimator requires about 237 million rays to be cast. This doesn&#8217;t even include the rays needed for sampling the lights for direct lighting, so in practice, the total will be even higher.</p>
<p>One interesting observation made by Greg Ward in his <a href="http://radsite.lbl.gov/radiance/papers/sg88/paper.html">Siggraph &#8217;88 paper</a> is that contrary to direct lighting, where shadows and lights can cause harsh changes, the indirect lighting on a surface tends to vary relatively slowly. One way to picture why this is, is to imagine the computing average color from the what you can see from each of your eyes. Even though each eye has a slightly different view on the world, the images they see are nearly similar, and so the average color is also nearly the same.</p>
<p><span id="more-166"></span></p>
<p>The image below shows the same scene from my previous post with just the indirect irradiance, and it&#8217;s pretty clear that for each surface, the lighting varies in a very smooth fashion.</p>
<p><img class="aligncenter size-full wp-image-167" title="finalgatherindirectonly" src="http://www.rorydriscoll.com/wp-content/uploads/2009/01/finalgatherindirectonly.png" alt="finalgatherindirectonly" width="656" height="396" /></p>
<p>Ward proposed using this knowledge to reduce the number of times that the Monte Carlo estimator was evaluated by interpolating between nearby previously calculated values. At the time he just called it &#8216;lazy evaluation&#8217;, which I personally think is a good way to picture the idea. Later it became known as irradiance caching.</p>
<h2>Irradiance Caching</h2>
<p>The basic concept for irradiance caching is really simple: For each point on a surface at which you want to evaluate irradiance, if the cache contains any valid entries then interpolate between them. Otherwise, calculate a new irradiance entry, and add it to the cache.</p>
<p>A cache entry contains the position and normal for the point on the surface where the irradiance was evaluated as well as the irradiance value itself. One important additional piece of information that the cache requires is the range over which the entry is considered potentially valid. This range could be calculated in a number of ways, but the most common one is to use the <a href="http://en.wikipedia.org/wiki/Harmonic_mean">harmonic mean</a> of the hit distance of the rays used for the estimator. For n estimator samples, each with hit distance d, the harmonic mean is simply:</p>
<p><img src="http://latex.codecogs.com/gif.latex?H = \frac{N}{\sum_{1}^{N}{\frac{1}{d_i}}}" title="H = \frac{N}{\sum_{1}^{N}{\frac{1}{d_i}}}" /></p>
<p>Using the harmonic mean distance makes the cache entry distribution very dense in corners and crevices, and sparse in open spaces. This matches up very well with where the indirect irradiance is likely to be changing the fastest. To get an idea of how the cache entry distribution looks, here&#8217;s the scene above with the cache entry positions shown as red dots:</p>
<p><img class="aligncenter size-full wp-image-176" title="irradiancecachepoints" src="http://www.rorydriscoll.com/wp-content/uploads/2009/01/irradiancecachepoints.png" alt="irradiancecachepoints" width="656" height="396" /></p>
<p>Once you can add entries into the cache, you need to know how to find whether or not a particular cache entry can be used for interpolating the irradiance at a sample point. There are potentially quite a few ways that you can discard invalid cache entries depending on how fancy you want to get. For now, I&#8217;m using three simple tests.</p>
<p>Discard the entry if any of the following are true</p>
<ul>
<li>It is out of range of the sample point.</li>
<li>It has a normal that is too different than the sample normal.</li>
<li>It is in front of the sample point.</li>
</ul>
<p>Once you have a valid cache entry, you need to calculate a weight for that entry, then carry on looking for other entries that are potentially valid. As you come across each valid cache entry, you need to keep the sum of the weighted irradiance values, and the sum of the weights themselves. From these two sums, you can calculate the final interpolated irradiance:</p>
<p><img src="http://latex.codecogs.com/gif.latex?E = \frac{\sum_{1}^{N}{w_i E_i}}{\sum_{1}^{N}{w_i}}" title="E = \frac{\sum_{1}^{N}{w_i E_i}}{\sum_{1}^{N}{w_i}}" /></p>
<p>The weight for a particular cache entry is another part of the algorithm that can potentially be calculated in many different ways. For now, I&#8217;m using the weight that Ward proposes, but there&#8217;s some interesting information about the weights used at Dreamworks in <a href="http://www.graphics.cornell.edu/~jaroslav/papers/2008-irradiance_caching_class/10-EricSlides.pdf">this paper</a>. Here&#8217;s Ward&#8217;s initial weighting function:</p>
<p><img src="http://latex.codecogs.com/gif.latex?w_i = \frac{1}{\frac{\lvert \vec{P} - \vec{P_i} \rvert}{r_i} + \sqrt{1 + \vec{N} \cdot \vec{N_i}}}" title="w_i = \frac{1}{\frac{\lvert \vec{P} - \vec{P_i} \rvert}{r_i} + \sqrt{1 + \vec{N} \cdot \vec{N_i}}}" /></p>
<p>Note that you have to be a little bit wary of this function, since it is unbounded. When the sample point lies exactly at the same point as the cache entry then there you will get a divide by zero.</p>
<p>Typically, you would also discard cache entries that are below some weight threshold as specified by the user. This effectively scales the density of the cache entries and allows the user to make the trade off between speed and quality.</p>
<h2>Implementation</h2>
<p>I&#8217;ve made a very bare bones implementation of irradiance caching as outlined above. At the moment I&#8217;m not using a quad tree to store the cache entries, so each cache check requires iterating through an array of entries. Clearly this is a very slow way to process the cache entries, but for now it does a decent enough job to allow me to focus on the irradiance caching algorithm itself. Here are the results:</p>
<p><img class="aligncenter size-full wp-image-186" title="irradiancecacheindirectonly" src="http://www.rorydriscoll.com/wp-content/uploads/2009/01/irradiancecacheindirectonly.png" alt="irradiancecacheindirectonly" width="656" height="396" /></p>
<p>Not very impressive, or smooth, is it? I was hoping that the simple implementation I have made would provide better results than this, but apparently not. At the moment there&#8217;s one crucial improvement to the algorithm that my implementation is missing though &#8211; <a href="http://radsite.lbl.gov/radiance/papers/erw92/paper.pdf">Irradiance Gradients</a>. Irradiance Gradients basically give a better clue as to how to interpolate the irradiance cache entries, both positionally and rotationally. I&#8217;m hoping that they will significantly reduce the artefacts visible at the moment.</p>
<p>One problem that can occur when using an irradiance cache is that later cache entries don&#8217;t contribute to previously rendered pixels. When this happens, you can see blocky artefacts where the irradiance values have been interpolated differently. Something like this:</p>
<p><img class="aligncenter size-full wp-image-187" title="irradiancecachenopregather" src="http://www.rorydriscoll.com/wp-content/uploads/2009/01/irradiancecachenopregather.png" alt="irradiancecachenopregather" width="656" height="396" /></p>
<p>One thing you can do to avoid this situation is to perform an irradiance gathering pass before doing the final render. When you perform the final render, you should have no cache misses. In my case, I am using a progressive renderer, so the cache is actually fairly well primed before rendering the 1&#215;1 pixel size.</p>
<p><img class="aligncenter size-full wp-image-188" title="irradiancecacheprogressive" src="http://www.rorydriscoll.com/wp-content/uploads/2009/01/irradiancecacheprogressive.png" alt="irradiancecacheprogressive" width="656" height="396" /></p>
<h2>Improvements</h2>
<p>In addition to irradiance gradients, there have been a load of improvements made to irradiance caching since the inital paper. The <a href="http://www.graphics.cornell.edu/~jaroslav/papers/2008-irradiance_caching_class/index.htm">course notes </a>for the Siggraph 2008 course provide details of many of these. I&#8217;ll post up some screenshots when I&#8217;ve added the irradiance gradients.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.rorydriscoll.com/2009/01/18/irradiance-caching-part-1/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>Better Sampling</title>
		<link>http://www.rorydriscoll.com/2009/01/07/better-sampling/</link>
		<comments>http://www.rorydriscoll.com/2009/01/07/better-sampling/#comments</comments>
		<pubDate>Thu, 08 Jan 2009 07:33:51 +0000</pubDate>
		<dc:creator>Rory</dc:creator>
				<category><![CDATA[C++]]></category>
		<category><![CDATA[Global Illumination]]></category>
		<category><![CDATA[Graphics]]></category>

		<guid isPermaLink="false">http://www.rorydriscoll.com/?p=77</guid>
		<description><![CDATA[A couple of days ago, I compared the images my ambient occlusion integrator produced with those of Modo using similar settings. I noticed immediately how much &#8216;cleaner&#8217; the render from Modo was. Clearly there was an issue with the way I was picking my samples, so I set about improving things. My approach for generating [...]]]></description>
			<content:encoded><![CDATA[<p>A couple of days ago, I compared the images my ambient occlusion integrator produced with those of Modo using similar settings. I noticed immediately how much &#8216;cleaner&#8217; the render from Modo was. Clearly there was an issue with the way I was picking my samples, so I set about improving things.</p>
<p>My approach for generating the ambient occlusion rays was to generate uniform random samples over the hemisphere about the normal. Based on two random numbers in the range [0,1), I calculate the normalized sample direction using the following function:</p>
<pre class="brush: cpp; title: ; notranslate">
Vector3 Sample::UniformSampleHemisphere(float u1, float u2)
{
	const float r = Sqrt(1.0f - u1 * u1);
	const float phi = 2 * kPi * u2;

	return Vector3(Cos(phi) * r, Sin(phi) * r, u1);
}
</pre>
<p>This generates points on a hemisphere from uniform variables u1 and u2, where each point has equal probability of being selected. The following image was generated with 256 random uniform samples:</p>
<p><img class="aligncenter size-full wp-image-78" title="ao256samplesrandomuniform" src="http://www.rorydriscoll.com/wp-content/uploads/2009/01/ao256samplesrandomuniform.png" alt="ao256samplesrandomuniform" width="656" height="396" /></p>
<p><span id="more-77"></span>It looks pretty noisy, that&#8217;s for sure. Part of the trouble comes from the fact that there&#8217;s no way to ensure that there&#8217;s an even distribution of the rays. A common way to alleviate this problem is to do <a href="http://en.wikipedia.org/wiki/Stratified_sampling">stratified sampling</a> instead of fully random sampling. The idea of stratified sampling is to split up the domain into evenly sized segments, and then to pick a random point from within each of those segments. You still get some randomness, but the points are more evenly distributed, which in turn reduces the variance. Less variance means less noise. Here&#8217;s the scene again, using 256 rays, but this time using stratified sampling:</p>
<p><img class="aligncenter size-full wp-image-79" title="ao256samplesstratifieduniform" src="http://www.rorydriscoll.com/wp-content/uploads/2009/01/ao256samplesstratifieduniform.png" alt="ao256samplesstratifieduniform" width="656" height="396" /></p>
<p>As expected, it&#8217;s much less noisy, and for the same amount of computation!</p>
<h2>Sampling for Diffuse Monte Carlo Estimator</h2>
<p>The stratified sampler helps out with the indirect diffuse lighting calculation too, but one other thing you can do to reduce noise for the Monte Carlo estimator is to choose random values that have a similar &#8216;shape&#8217; to the integral you are estimating. Looking at the integral for diffuse reflections, you will see the familiar cosine term inside the integral:</p>
<p><img title="L_o = \int \frac{c}{\pi} L_i \cos \theta ,d \omega" src="http://latex.codecogs.com/gif.latex?L_o = \int \frac{c}{\pi} L_i \cos \theta ,d \omega" alt="" /></p>
<p>Where c is the diffuse material color, Li is the incoming radiance, and pi is the energy conservation constant.</p>
<p>Rather than wasting samples on areas of the integral where they will get mulitiplied out by the cosine term, why not just choose proportionally fewer samples in those areas?</p>
<p>Recall that the Monte Carlo estimator for an the integral of the function f(x), with probability density function p(x) is:</p>
<p><img title="F_N = \frac{1}{N} \sum_{i=1}^{N}{\frac{f(x_i)}{p(x_i)}}" src="http://latex.codecogs.com/gif.latex?F_N = \frac{1}{N} \sum_{i=1}^{N}{\frac{f(x_i)}{p(x_i)}}" alt="" /></p>
<p>The probability density function is just a function that returns the probability that a particular value will be chosen. For the uniform hemisphere sampling function above, the pdf is just a constant, (1 / (2 * pi)). This makes the Monte Carlo estimator for the diffuse integral:</p>
<p><img title="L_o \approx \frac{2c}{N} \sum_{i=1}^{N}{L_i \cos \theta}" src="http://latex.codecogs.com/gif.latex?L_o \approx \frac{2c}{N} \sum_{i=1}^{N}{L_i \cos \theta}" alt="" /></p>
<p>Rather than mutliply by the cosine term above, we just want to generate proportionally fewer rays at the bottom of the hemisphere. The integral of the pdf over the hemisphere must equal one, so by switching to a cosine-weighted sample distribution, the pdf becomes (cos(theta) / pi).</p>
<p>This makes the estimator:</p>
<p><img title="L_o \approx \frac{c}{\pi N} \sum_{i=1}^{N}{\frac{L_i \cos \theta}{\frac{\cos \theta}{\pi}}}" src="http://latex.codecogs.com/gif.latex?L_o \approx \frac{c}{\pi N} \sum_{i=1}^{N}{\frac{L_i \cos \theta}{\frac{\cos \theta}{\pi}}}" alt="" /></p>
<p>Which cleans up rather nicely to:</p>
<p><img title="L_o \approx \frac{c}{N} \sum_{i=1}^{N}{L_i}" src="http://latex.codecogs.com/gif.latex?L_o \approx \frac{c}{N} \sum_{i=1}^{N}{L_i}" alt="" /></p>
<p>Normally I would post a couple of images up for comparison&#8217;s sake, but in this case, the difference is pretty difficult to perceive without being able to compare one on top of the other. The difference is small, but it is definitely worth it!</p>
<p>The common way to generate a cosine weighted hemisphere sampler is to generate uniform points on a disk, and then project them up to the hemisphere. Here&#8217;s some code:</p>
<pre class="brush: cpp; title: ; notranslate">
Vector3 Sample::CosineSampleHemisphere(float u1, float u2)
{
	const float r = Sqrt(u1);
	const float theta = 2 * kPi * u2;

	const float x = r * Cos(theta);
	const float y = r * Sin(theta);

	return Vector3(x, y, Sqrt(Max(0.0f, 1 - u1)));
}
</pre>
<p>Just by doing these two small steps, I&#8217;ve been able to clean up my images significantly. Here&#8217;s the scene from above again, this time with single bounce final gather with 256 rays, stratified cosine-sampled:</p>
<p><img class="aligncenter size-full wp-image-93" title="finalgather256samplescosinesampled" src="http://www.rorydriscoll.com/wp-content/uploads/2009/01/finalgather256samplescosinesampled.png" alt="finalgather256samplescosinesampled" width="656" height="396" /></p>
<p>Next on my list is to take a look at path tracing, followed by irradiance caching (wasn&#8217;t that the point of all this?). This should allow me to get fairly cheap multi-bounce diffuse lighting.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.rorydriscoll.com/2009/01/07/better-sampling/feed/</wfw:commentRss>
		<slash:comments>10</slash:comments>
		</item>
		<item>
		<title>The Holidays: Time for fun work!</title>
		<link>http://www.rorydriscoll.com/2009/01/03/the-holidays-time-for-fun-work/</link>
		<comments>http://www.rorydriscoll.com/2009/01/03/the-holidays-time-for-fun-work/#comments</comments>
		<pubDate>Sun, 04 Jan 2009 01:48:52 +0000</pubDate>
		<dc:creator>Rory</dc:creator>
				<category><![CDATA[C++]]></category>
		<category><![CDATA[Global Illumination]]></category>
		<category><![CDATA[Graphics]]></category>

		<guid isPermaLink="false">http://www.rorydriscoll.com/?p=70</guid>
		<description><![CDATA[For the first time in about three years, I&#8217;ve had two weeks off work. I&#8217;ve spent a lot of time just relaxing and taking a break from things, but I&#8217;ve also been able to get back to doing some graphics work. Ever since Vivendi bought Activision, the project that I was leading has been &#8220;put [...]]]></description>
			<content:encoded><![CDATA[<p>For the first time in about three years, I&#8217;ve had two weeks off work. I&#8217;ve spent a lot of time just relaxing and taking a break from things, but I&#8217;ve also been able to get back to doing some graphics work. Ever since Vivendi bought Activision, the project that I was leading has been &#8220;put on hold&#8221;, so I&#8217;ve been back on the game team. It&#8217;s not as fun for me, that&#8217;s for sure, but luckily, I have my code at home to play with, so all is not lost! With the holidays, I&#8217;ve found some motivation to get back to it.</p>
<p>What have I been doing? Well, as I was approaching the break, I read through the course notes from the Practical Global Illumination with Irradiance Caching course at Siggraph last year. I thought the course itself was really good, and very clearly presented. After blitzing through the notes again, I thought I&#8217;d have a go at writing a ray tracer. It seemed simple enough at the time, but like most things, the devil is in the details.</p>
<p>The first thing I did was to set up a really simple single-threaded ray tracer that just displayed the color of the surface it hit. This was fairly quick to get up and running once I had written a few supporting classes for the cameras and shapes. It&#8217;s not very glamorous, but it&#8217;s a start:</p>
<p style="text-align: center;"><a href="http://www.rorydriscoll.com/wp-content/uploads/2009/01/basicintegrator.png"><img class="size-full wp-image-72 aligncenter" title="basicintegrator" src="http://www.rorydriscoll.com/wp-content/uploads/2009/01/basicintegrator.png" alt="Simple Integrator" width="500" height="301" /></a></p>
<p><span id="more-70"></span>Next, I added point lights and directional lights, and wrote a new integrator to calculate direct diffuse lighting. Once you have a function to trace rays around the scene, it&#8217;s really easy to add hard-edged shadows. It looks a lot better than the solid color integrator I first used, but it still not very impressive.</p>
<p>Here&#8217;s the scene with a single directional light and hard-edged shadows:</p>
<p style="text-align: center;"><a href="http://www.rorydriscoll.com/wp-content/uploads/2009/01/directonly.png"><img class="size-full wp-image-73 aligncenter" title="directonly" src="http://www.rorydriscoll.com/wp-content/uploads/2009/01/directonly.png" alt="" width="500" height="301" /></a></p>
<p>I wanted to flex the ray tracer a little bit, so and easy next step was to add an ambient occlusion integrator. Initially, I just used a function generate random uniform rays on the hemisphere around the hit normal, and used the ratio of misses to hits as the occlusion value. I found that this was really pretty noisy, so I tried using the length of the ray hits to weight the occlusion values. This definitely improved things, but it&#8217;s still pretty noisy. The obvious way to reduce the noise is to use more rays, but I&#8217;d like to find a cheaper way to do this if possible.</p>
<p>Here&#8217;s the scene rendered with the ambient occlusion integrator using 4096 rays per hit:</p>
<p style="text-align: center;"><a href="http://www.rorydriscoll.com/wp-content/uploads/2009/01/ao4096samples.png"><img class="size-full wp-image-71 aligncenter" title="ao4096samples" src="http://www.rorydriscoll.com/wp-content/uploads/2009/01/ao4096samples.png" alt="" width="500" height="301" /></a></p>
<p>The first time I tried to render this scene using 4096 ambient occlusion rays per pixel, it took about thirteen minutes. I&#8217;ve never really used release builds at home and the settings weren&#8217;t great, so I tweaked some of the project settings, and defined out asserts. This got the time down to about ten minutes. I&#8217;m running these renders on my Macbook Pro, so I have a whole other core just sitting there doing nothing. Switching to using a multithreaded renderer basically sped the renders up by a factor of two.</p>
<p>Combining some of the concepts of the ambient occlusion integrator, and the direct diffuse integrator, I created a multi-bounce diffuse integrator. Like the direct diffuse integrator, it calculates the direct diffuse lighting at the hit point. Additionally though, it uses a Monte Carlo estimator to approximate the diffuse lighting integral over the hemisphere about the normal of the hit point. It can handle any number of bounces of indirect light, but the render time increases exponentially with each bounce added. Like the ambient occlusion integrator, it requires a large number of sample rays to get an acceptable level of noise.</p>
<p>Here&#8217;s the scene again with one bounce of indirect light, and 4096 rays per hit:</p>
<p style="text-align: center;"><a href="http://www.rorydriscoll.com/wp-content/uploads/2009/01/onebounce4096samples.png"><img class="size-full wp-image-76 aligncenter" title="onebounce4096samples" src="http://www.rorydriscoll.com/wp-content/uploads/2009/01/onebounce4096samples.png" alt="" width="500" height="301" /></a></p>
<p>When a ray misses the scene, it looks up an environment color, which you can see in the background. Most of the indirect rays actually miss the scene, so this background color actually has a huge effect over the look of the scene. I should mention as well that I&#8217;m using a really simple tone mapping operator to map the HDR ray tracer values down to the 8 bit per channel texture.</p>
<p>While working on the ray tracer, I would often be playing around with the objects and lights in the scene. I quickly found out that it&#8217;s really not very fun to wait for the ray trace to complete before getting some feedback. I can reduce the number of indirect rays to make things quicker, but even at relatively low values, it can take a while to render the final scene.</p>
<p>I had already split the rendering of the scene into 32 by 32 blocks when I switched to a multi-threaded ray tracer, so it was a really simple extension to change the resolution in each of these blocks on the fly. I basically start things off by rendering with each ray covering 32 by 32 pixels, then when that completes, I immediately kick off another render at 16 by 16, and so on. Each successive render takes four times as long as the previous render, so if the 1 by 1 render takes about a minute, then you get the 8 by 8 render in about a second!</p>
<p>Here&#8217;s the scene rendered using 512 indirect samples, paused at the 4 by 4 resolution:</p>
<p style="text-align: center;"><a href="http://www.rorydriscoll.com/wp-content/uploads/2009/01/full512samples4by4.png"><img class="size-full wp-image-75 aligncenter" title="full512samples4by4" src="http://www.rorydriscoll.com/wp-content/uploads/2009/01/full512samples4by4.png" alt="" width="500" height="301" /></a></p>
<p>And here&#8217;s the scene at the conclusion of rendering (note that the time is cumulative of all the previous renders):</p>
<p style="text-align: center;"><a href="http://www.rorydriscoll.com/wp-content/uploads/2009/01/full512samples1by1.png"><img class="size-full wp-image-74 aligncenter" title="full512samples1by1" src="http://www.rorydriscoll.com/wp-content/uploads/2009/01/full512samples1by1.png" alt="" width="500" height="301" /></a></p>
<p>It&#8217;s pretty clear at the 4 by 4 resolution how the render is going to look, and it only took four seconds to get there, whereas the final scene took nearly a minute. The 1 by 1 resolution actually took only 40 seconds of that minute to render, but still, having the feedback within a tenth of the final render time seems worth the extra wait at the end.</p>
<p>That&#8217;s basically as far as I got over the past couple of weeks. Like many things I do, there seems to be more to do now than at the beginning. One of the things I&#8217;d really like to do is to be able to render out the lighting to radiosity normal maps. This would allow me to combine the static precomputed lighting in my DirectX10 engine. I could also output spherical harmonic coefficients for light probes which would allow me to render dynamic objects using the precomputed lighting.</p>
<p>Well, work starts back up in a couple of days, so the amount of time I can spend on this is going to be limited again, but I&#8217;ll post any significant updates. I have another article about the the lighting calculation on the the way, but it&#8217;s competing for my time!</p>
]]></content:encoded>
			<wfw:commentRss>http://www.rorydriscoll.com/2009/01/03/the-holidays-time-for-fun-work/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
	</channel>
</rss>

