<?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-4788717377176914087</id><updated>2011-09-03T04:32:16.053-07:00</updated><title type='text'>Undernones</title><subtitle type='html'>They're a lot like overalls.</subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://undernones.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4788717377176914087/posts/default?max-results=100'/><link rel='alternate' type='text/html' href='http://undernones.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><author><name>Stephen Ward</name><uri>http://www.blogger.com/profile/03805583345764878908</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://wardfam.com/~stephen/warwis-blog/images/buggy.gif'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>2</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>100</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-4788717377176914087.post-5293850422167064648</id><published>2010-12-06T21:58:00.000-08:00</published><updated>2010-12-13T21:27:59.724-08:00</updated><title type='text'>GPU Ray Tracing With GLSL</title><content type='html'>&lt;table cellpadding="0" cellspacing="0" class="tr-caption-container" style="float: right; margin-left: 1em; text-align: right;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://upload.wikimedia.org/wikipedia/commons/thumb/8/83/Ray_trace_diagram.svg/300px-Ray_trace_diagram.svg.png" imageanchor="1" style="clear: right; margin-bottom: 1em; margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" src="http://upload.wikimedia.org/wikipedia/commons/thumb/8/83/Ray_trace_diagram.svg/300px-Ray_trace_diagram.svg.png" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Tracing light rays from a virtual camera into a 3D scene&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;Ray tracing, at its core, is the process of tracing beams of light through some physical 3D space. &amp;nbsp;Light beams (or rays) typically travel in straight lines until they bump into some object. &amp;nbsp;At that point, the rays can either reflect off the surface, refract through it, or be absorbed by it. &amp;nbsp;Though the ray tracing technique can serve other purposes, it is generally used as a means of rendering &lt;a href="http://upload.wikimedia.org/wikipedia/commons/thumb/e/ec/Glasses_800_edit.png/320px-Glasses_800_edit.png"&gt;photorealistic&lt;/a&gt; &lt;a href="http://www.google.com/images?q=ray+tracing"&gt;images&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;One of the graphical features that are easy to obtain using ray tracing is physically accurate and pixel-perfect shadows. &amp;nbsp;As is apparent in the figure to the right, rendering a shadow is as simple as casting a ray from the surface of an object toward the center of a light source. &amp;nbsp;If that ray intersects some object before it reaches the light source, the originating surface point is in shadow.&lt;br /&gt;&lt;br /&gt;Simple!&lt;br /&gt;&lt;br /&gt;Ever since I was introduced to &lt;a href="http://www.opengl.org/documentation/glsl/"&gt;GLSL&lt;/a&gt;, I wondered if it would be feasible to implement ray tracing on the GPU. &amp;nbsp;The GPU, after all, is well-suited to geometrically-oriented computations. &amp;nbsp;I decided to take the opportunity to find out by attempting to implement it as a final project for &lt;a href="http://www.cs.utah.edu/~hansen/"&gt;Chuck Hansen&lt;/a&gt;'s course on &lt;a href="http://www.eng.utah.edu/~cs5610/"&gt;Interactive Computer Graphics&lt;/a&gt; at the &lt;a href="http://www.cs.utah.edu/"&gt;University of Utah&lt;/a&gt;. &amp;nbsp;As a first pass, I decided to use ray tracing simply as a means of computing the shadows of an interactive OpenGL scene.&lt;br /&gt;&lt;br /&gt;Read on for how I did it.&lt;br /&gt;&lt;br /&gt;&lt;a name='more'&gt;&lt;/a&gt;&lt;span class="Apple-style-span" style="font-size: x-large;"&gt;Inspiration&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Most of the ideas I used for implementing ray tracing on the GPU came from &lt;a href="http://graphics.stanford.edu/papers/rtongfx/"&gt;Ray Tracing on Programmable Graphics Hardware&lt;/a&gt;&amp;nbsp;(RTOPGH) by T. Purcell, I. Buck, W. Mark, and P. Hanrahan. &amp;nbsp;Their approach is to implement a complete ray tracer to generate the color of every pixel in a frame. &amp;nbsp;My goals differed in that my intent was to utilize ray tracing only to generate shadows in an otherwise conventionally-rendered OpenGL scene. &amp;nbsp;Nonetheless, the core work of the task is the same.&lt;br /&gt;&lt;br /&gt;Before I discovered the aforementioned paper, the biggest question I had about this project was how to load the scene data into the memory of the graphics card. &amp;nbsp;There is potentially quite a bit of geometry in a 3D scene of any human interest, and loading it into a shader program by means of uniform variables would be tedious to say the least, not to mention highly limiting. &amp;nbsp;The authors of RTOPGH realized that there is already a built-in mechanism for loading large amounts of data into graphics memory: texture units.&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-size: x-large;"&gt;Acceleration&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Before describing how the scene data is laid out in texture memory, it is worth noting that one of ray tracing's drawbacks is running time. &amp;nbsp;A naive implementation of ray tracing tests a ray for intersection with every polygon (or primitive) in the scene. &amp;nbsp;Significant speed ups can be gained by spatially partitioning the scene into large and/or small chunks containing references to all of the geometry contained within them. &amp;nbsp;Typically these chunks, which I will call voxels, are axis-aligned cuboids that can very quickly be checked for intersection with a ray. &amp;nbsp;There are many approaches that can be taken to partition the space. &amp;nbsp;The simple yet effective method I chose (based largely on familiarity) is called 3D-DDA.&lt;br /&gt;&lt;br /&gt;&lt;table cellpadding="0" cellspacing="0" class="tr-caption-container" style="float: left; margin-right: 1em; text-align: left;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/_YTdiWDGKdlE/TP28xOPC5UI/AAAAAAAAATk/dF-dAg-vUts/s1600/Data+Structures.png" imageanchor="1" style="clear: left; margin-bottom: 1em; margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="257" src="http://4.bp.blogspot.com/_YTdiWDGKdlE/TP28xOPC5UI/AAAAAAAAATk/dF-dAg-vUts/s400/Data+Structures.png" width="400" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Data structures laid out in texture memory.&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;The image to the left is a diagram from RTOPGH. &amp;nbsp;At the top is a representation of the texture containing the 3D-DDA voxel structure. &amp;nbsp;Each voxel contains a single value index into the triangle list texture. &amp;nbsp;In this way, a voxel simply represents a pointer to the first triangle in the list of triangles geometrically contained within that voxel.&lt;br /&gt;&lt;br /&gt;Every entry in the triangle list texture is an index into the global vertex list texture.&lt;br /&gt;&lt;br /&gt;Finally, the vertex list texture contains 3D points stored as 32-bit floating point RGB values.&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-size: x-large;"&gt;Implementation Details&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Because it can be difficult to keep track of coordinate spaces in OpenGL, I took care to specify all vertices both in the textures and in the calls to glVertex() in world coordinates. &amp;nbsp;I also explicitly set a uniform variable containing the location of the light source in world coordinates.&lt;br /&gt;&lt;br /&gt;Tracking points along the surface of each polygon is trivial thanks to built-in features of GLSL. &amp;nbsp;One can simply take advantage of the "varying" variable types in GLSL which simply interpolate data across vertices. &amp;nbsp;My vertex shader program consists of little more than setting a varying vector, which I called surfacePoint, to the value of gl_Vertex. &amp;nbsp;Thus, for every fragment at which I want to compute a shadow (which really is every fragment in the frame), I have a location from which to generate a ray toward the light.&lt;br /&gt;&lt;br /&gt;One of the tricker implementation details was that of accounting for limited texture space. &amp;nbsp;Specifically, when building a texture, OpenGL will throw an error if the client program attempts to create a texture larger than a specific platform-dependent size. &amp;nbsp;The implication of this is that loading scenes of any "interesting" size requires splitting the scene data into multiple textures. &amp;nbsp;To me, this felt akin to the good old days of spanning a ZIP file across multiple floppy disks. &amp;nbsp;There was a significant amount of extra book-keeping I had not anticipated at the start.&lt;br /&gt;&lt;br /&gt;At the heart of every good ray tracer is a fast triangle intersection function. &amp;nbsp;I found a great one at&amp;nbsp;&lt;a href="http://softsurfer.com/Archive/algorithm_0105/algorithm_0105.htm"&gt;Intersections of Rays, Segments, Planes and Triangles&lt;/a&gt; in 3D by Dan Sunday. &amp;nbsp;My slightly modified version is listed here:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;float intersectTriangle(vec3 orig, vec3 dir, vec3 vertices[3], float lastHitT)&lt;br /&gt;{&lt;br /&gt;    const float INFINITY = 1e10;&lt;br /&gt;    vec3 u, v, n; // triangle vectors&lt;br /&gt;    vec3 w0, w;  // ray vectors&lt;br /&gt;    float r, a, b; // params to calc ray-plane intersect&lt;br /&gt;&lt;br /&gt;    // get triangle edge vectors and plane normal&lt;br /&gt;    u = vertices[1] - vertices[0];&lt;br /&gt;    v = vertices[2] - vertices[0];&lt;br /&gt;    n = cross(u, v);&lt;br /&gt;&lt;br /&gt;    w0 = orig - vertices[0];&lt;br /&gt;    a = -dot(n, w0);&lt;br /&gt;    b = dot(n, dir);&lt;br /&gt;    if (abs(b) &amp;lt; 1e-5)&lt;br /&gt;    {&lt;br /&gt;        // ray is parallel to triangle plane, and thus can never intersect.&lt;br /&gt;        return INFINITY;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    // get intersect point of ray with triangle plane&lt;br /&gt;    r = a / b;&lt;br /&gt;    if (r &amp;lt; 0.0)&lt;br /&gt;        return INFINITY; // ray goes away from triangle.&lt;br /&gt;&lt;br /&gt;    vec3 I = orig + r * dir;&lt;br /&gt;    float uu, uv, vv, wu, wv, D;&lt;br /&gt;    uu = dot(u, u);&lt;br /&gt;    uv = dot(u, v);&lt;br /&gt;    vv = dot(v, v);&lt;br /&gt;    w = I - vertices[0];&lt;br /&gt;    wu = dot(w, u);&lt;br /&gt;    wv = dot(w, v);&lt;br /&gt;    D = uv * uv - uu * vv;&lt;br /&gt;&lt;br /&gt;    // get and test parametric coords&lt;br /&gt;    float s, t;&lt;br /&gt;    s = (uv * wv - vv * wu) / D;&lt;br /&gt;    if (s &amp;lt; 0.0 || s &amp;gt; 1.0)&lt;br /&gt;        return INFINITY;&lt;br /&gt;    t = (uv * wu - uu * wv) / D;&lt;br /&gt;    if (t &amp;lt; 0.0 || (s + t) &amp;gt; 1.0)&lt;br /&gt;        return INFINITY;&lt;br /&gt;&lt;br /&gt;    return (r &amp;gt; 1e-5) ? r : INFINITY;&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-size: x-large;"&gt;Results&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Here is what it looks like. &amp;nbsp;This scene is simple enough that it was easily rendered interactively without the need for any acceleration structure such as 3D-DDA.&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/_YTdiWDGKdlE/TP3MgBztLHI/AAAAAAAAATo/e1-4BKc5910/s1600/results.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://1.bp.blogspot.com/_YTdiWDGKdlE/TP3MgBztLHI/AAAAAAAAATo/e1-4BKc5910/s1600/results.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: left;"&gt;&lt;span class="Apple-style-span" style="font-size: x-large;"&gt;Stuff that I'm lying about in this post&lt;/span&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: left;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: left;"&gt;The two major components of this project that I haven't actually completed as of the preliminary presentation date are:&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: left;"&gt;&lt;/div&gt;&lt;ol&gt;&lt;li&gt;The 3D-DDA acceleration algorithm&lt;/li&gt;&lt;li&gt;Splitting scene data to span multiple texture units&lt;/li&gt;&lt;/ol&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="font-size: x-large;"&gt;Updated Results&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;After working on the two items above, I have some new results to share. &amp;nbsp;Unfortunately, the results are not too great. &amp;nbsp;First of all, I gave up on the 3D-DDA acceleration grid. &amp;nbsp;I wrote code to voxelize the shadow-casting geometry in C++. &amp;nbsp;However, encoding it as a texture and then making use of it within the shader program was turning out to be much more difficult than I had originally imagined.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Finally it occurred to me to wonder how much of a speedup, if any, would be gained by the grid. &amp;nbsp;Initially, my simple scene consisted of a single cube (consisting of 12 triangles) casting a shadow. &amp;nbsp;I added four more cubes, taking the triangle count up to 60. &amp;nbsp;At that point, there was a very noticeable slow down. &amp;nbsp;So in order for the 3D-DDA to be worth doing on the teapot model (which was my target), I would need the grid to be partitioned such that no single voxel contained more than about 30 or 40 triangles. &amp;nbsp;I queried the grid I had already written in C++ and discovered the following numbers:&lt;/div&gt;&lt;div&gt;&lt;ul&gt;&lt;li&gt;Maximum triangles in a voxel: 1,258&lt;/li&gt;&lt;li&gt;Minimum triangles in a voxel: 0&lt;/li&gt;&lt;li&gt;Average triangles in a voxel: 330&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;I could improve those numbers by refining the grid to a higher resolution. &amp;nbsp;Increasing the resolution of the grid for the teapot to 50x50x50, I was able to reduce those numbers to the following:&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;ul&gt;&lt;li&gt;Maximum triangles in a voxel: 50&lt;/li&gt;&lt;li&gt;Minimum triangles in a voxel: 0&lt;/li&gt;&lt;li&gt;Average triangles in a voxel: 0.14&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;Clearly those numbers are better. &amp;nbsp;They nonetheless come with a cost: 125,000 total voxels. &amp;nbsp;The higher voxel count leads to issues in the voxel-to-texture encoding, similar to spanning the triangles over multiple texture units. &amp;nbsp;With a limited number of available texture units, this quickly becomes infeasible.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;On the topic of spanning texture units, I was able to split the triangles across up to 6 texture units. &amp;nbsp;However, an unexpected problem arose, related, I believe, to the platform I am working on. &amp;nbsp;Visually, the result I saw was that the teapot's shadow had holes in it. &amp;nbsp;Each of the holes appeared to be in the shape of a triangle.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;The only hypothesis I have for why it might have been this way is what could be characterized as a sort of aliasing. &amp;nbsp;To look up any given triangle and vertex in the set of triangle textures, I need to map the index to some number between 0 and 1. &amp;nbsp;As the number of triangles I stuff into a single texture goes up, so does the likelihood that some pair of discrete floating values between 0 and 1 would map to the same triangle. &amp;nbsp;Without some sort of speed up in the ray tracing algorithm, it is difficult to test this (or any other) hypothesis, but it seems at least plausible to me.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Overall, this was a very enlightening project for me. &amp;nbsp;I was forced to think in creative ways about how to debug a GLSL program, and I feel as though I became quite familiar with the platform. &amp;nbsp;Finally, here is one more screenshot.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/_YTdiWDGKdlE/TQcAT751U6I/AAAAAAAAAUA/3ZhqPDa6rLE/s1600/Screen+shot+2010-12-13+at+10.26.46+PM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://1.bp.blogspot.com/_YTdiWDGKdlE/TQcAT751U6I/AAAAAAAAAUA/3ZhqPDa6rLE/s1600/Screen+shot+2010-12-13+at+10.26.46+PM.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4788717377176914087-5293850422167064648?l=undernones.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://undernones.blogspot.com/feeds/5293850422167064648/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://undernones.blogspot.com/2010/12/gpu-ray-tracing-with-glsl.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4788717377176914087/posts/default/5293850422167064648'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4788717377176914087/posts/default/5293850422167064648'/><link rel='alternate' type='text/html' href='http://undernones.blogspot.com/2010/12/gpu-ray-tracing-with-glsl.html' title='GPU Ray Tracing With GLSL'/><author><name>Stephen Ward</name><uri>http://www.blogger.com/profile/03805583345764878908</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://wardfam.com/~stephen/warwis-blog/images/buggy.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/_YTdiWDGKdlE/TP28xOPC5UI/AAAAAAAAATk/dF-dAg-vUts/s72-c/Data+Structures.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4788717377176914087.post-2973310015664774472</id><published>2009-03-23T19:56:00.000-07:00</published><updated>2009-03-23T20:16:05.548-07:00</updated><title type='text'>Good Intentions</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_YTdiWDGKdlE/SchQZI6SStI/AAAAAAAAAHs/Jvq1bgIMxb4/s1600-h/programmer.gif"&gt;&lt;img style="float:right; margin:0 0 10px 10px;cursor:pointer; cursor:hand;width: 200px; height: 200px;" src="http://1.bp.blogspot.com/_YTdiWDGKdlE/SchQZI6SStI/AAAAAAAAAHs/Jvq1bgIMxb4/s320/programmer.gif" border="0" alt="" id="BLOGGER_PHOTO_ID_5316587752929774290" /&gt;&lt;/a&gt;&lt;br /&gt;Today I intended to create a Unit Test project for my primary project at work (SeboAPI).  It was going to be grand.  It's going to wait until another day.  The problem is I have too many bites to take before it can really be said to be a worthwhile portion of the whale that is the SeboAPI.  There are too many decisions to make and not enough motivation at this point.&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;What I did actually make progress on was resurrecting my old CruiseControl .NET server.  Righteous!&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Tonight's FHE lesson was delivered by the ever-so-wiggly Mallory.  She taught us things we already knew about fish.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Tonight I skimmed the research pages of a few Computer Science departments to which I'm interested in applying.  In the process I came across a paper about a research project that intends to apply a native accent to a person's recorded speech of a foreign language.  It didn't take me long to get lost in all kinds of stuff I don't understand.  I feel good about giving it a shot, though.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;My plan for tomorrow is to whittle down my list of FogBugz tasks at work to less than or equal to 2 (not counting the one I created for unit testing).  I also intend to attend a PhD student's presentation at BYU tomorrow afternoon.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4788717377176914087-2973310015664774472?l=undernones.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://undernones.blogspot.com/feeds/2973310015664774472/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://undernones.blogspot.com/2009/03/good-intentions.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4788717377176914087/posts/default/2973310015664774472'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4788717377176914087/posts/default/2973310015664774472'/><link rel='alternate' type='text/html' href='http://undernones.blogspot.com/2009/03/good-intentions.html' title='Good Intentions'/><author><name>Stephen Ward</name><uri>http://www.blogger.com/profile/03805583345764878908</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://wardfam.com/~stephen/warwis-blog/images/buggy.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/_YTdiWDGKdlE/SchQZI6SStI/AAAAAAAAAHs/Jvq1bgIMxb4/s72-c/programmer.gif' height='72' width='72'/><thr:total>0</thr:total></entry></feed>
