A Ray Tracer in C#3.0


Ray tracers are a lot of fun.  When I was in middle school, I discovered POV-Ray and was so excited about the cool graphics it could create that I would often leave my 286 on overnight rendering ray-traced scenes and movies.  When I was in high school, I discovered Computer Graphics: Principles and Practice and spent weeks working through it’s ray tracing algorithms trying to implement my own ray tracer in C++.  When I was in college, taking an introductory course which used Scheme, I re-wrote the raytracer in Scheme to show some friends that you really could write programs longer than 20 lines in Scheme!  And then when I joined the C# team 3 years ago, I of course had to re-write my raytracer in C#. 


More recently, I took a pass through the code to update it to use C#3.0.  It’s not a particularly fancy or efficient ray tracer – but then again, it’s only 400 lines of code.  Below are a few of the interesting uses of C#3.0 from the ray tracer code.



C#3.0 Without the Databases and XML


Although we often demo C#3.0 using databases and XML to show off LINQ – it turns out that the new language features really are also great for applications which have little to do with querying.  Ray tracing, for example, is certainly not one of the prototypical scenario for query and transformation of data.  Nonetheless, I found quite a few places in the code where C#3.0 and LINQ to Objects really improved the code – making it easier to express what the program was doing.


LINQ to Objects

Here’s one example of the method which computes the intersections of a ray with the objects in a scene.   


        private IEnumerable<ISect> Intersections(Ray ray, Scene scene)
{
return scene.Things
.Select(obj => obj.Intersect(ray))
.Where(inter => inter != null)
.OrderBy(inter => inter.Dist);
}

The method intersects each object in the scene with the ray, then throws away those that didn’t intersect, and finally orders the rest by the distance from the source of the ray. The first object in this result is the closest object hit by the ray.  If there are no elements in the result, there were no intersections.  The LINQ to Objects query methods provide a nice way to describe this, and lambdas make it easy to write the code which processes the objects and intersections.


Object and Collection Initializers 



Here’s the code that describes the scene rendered in the image above.

        internal readonly Scene DefaultScene =
new Scene() {
Things = new SceneObject[] {
new Plane() {
Norm = Vector.Make(0,1,0),
Offset = 0,
Surface = Surfaces.CheckerBoard
},
new Sphere() {
Center = Vector.Make(0,1,0),
Radius = 1,
Surface = Surfaces.Shiny
},
new Sphere() {
Center = Vector.Make(-1,.5,1.5),
Radius = .5,
Surface = Surfaces.Shiny
}},
Lights = new Light[] {
new Light() {
Pos = Vector.Make(-2,2.5,0),
Color = Color.Make(.49,.07,.07)
},
new Light() {
Pos = Vector.Make(1.5,2.5,1.5),
Color = Color.Make(.07,.07,.49)
},
new Light() {
Pos = Vector.Make(1.5,2.5,-1.5),
Color = Color.Make(.07,.49,.071)
},
new Light() {
Pos = Vector.Make(0,3.5,0),
Color = Color.Make(.21,.21,.35)
}},
Camera = Camera.Create(Vector.Make(3,2,4), Vector.Make(-1,.5,0))
};

The scene consists of a collection of things, a collection of lights, and a camera, all of which are initialized in one statement using object and collection initializers.  This makes it quite a bit easier to describe the scene.  This format also helps to make the structure of the scene clear.


Lambdas

Here’s the code describing the two surface textures used in the image above:


    static class Surfaces {
// Only works with X-Z plane.
public static readonly Surface CheckerBoard =
new Surface() {
Diffuse = pos => ((Math.Floor(pos.Z) + Math.Floor(pos.X)) % 2 != 0)
? Color.Make(1,1,1)
: Color.Make(0,0,0),
Specular = pos => Color.Make(1,1,1),
Reflect = pos => ((Math.Floor(pos.Z) + Math.Floor(pos.X)) % 2 != 0)
? .1
: .7,
Roughness = 150
};

public static readonly Surface Shiny =
new Surface() {
Diffuse = pos => Color.Make(1,1,1),
Specular = pos => Color.Make(.5,.5,.5),
Reflect = pos => .6,
Roughness = 50
};
}


These two static fields are initialized with the surface styles for the checkerboard and shiny textures on the objects in the scene above.  The surfaces are created with object initializers, and lambda expressions are used to provide the functions to compute diffuse, specular and reflection values based on the position on the surface.  The combination makes it possible to do something similar to prototype objects.


Try Out the Code


Want to try it out yourself?  Just compile RayTracer.cs with the Orcas C# compiler.  Then run.


File iconRayTracer.cs


kick it on DotNetKicks.com

Comments (33)

  1. You’ve been kicked (a good thing) – Trackback from DotNetKicks.com

  2. Recently my time has been taken up with a series of internal issues involving Beta1, C# Samples and various

  3. ekampf 2.0 says:

    C# PM Luke Hoban has posted a simple C# Ray Tracer code that utilizes the new C# 3.0 language capabilities.

  4. I’m really impressed. Another msdn.com blogger just showed up a really nice example of how to use LINQ

  5. paulsta says:

    I have been wanting this kind of thing in C++ for ages (well, at least a year). I think it will be useful in terms of writing AI programs and games where you don’t keep your game characters in a database. An AI program could first load an entire common-sense database into memory. This is what I will use it for when LINQ comes out.

    e.g.

    print "Watch out! There are "+

    (select X from gameObjects where X.type==enemyTroll && X.pos.isNearTo(GamePlayer1.pos) ).length

    + "trolls near to you!"

    which is really easy to read.

    Here is another example that prints prime numbers less than 1000:

    foreach n in range(2,1000){

     if( (select d from range(2,sqrt(n)) where n%d==0).length ==0 ) print n+" is a primen"  

    }

    How about using LINQ with OpenGL or DirectX where you could use it with vertex lists and things. Or how about using it with pixels in bitmaps:

    foreach PIXEL in BITMAP

      if(PIXEL.colour.red<128)

          PIXEL.color=Color(0,128,0)

    is this possible? This could be accelerated by DirectX10 shaders.

    P.S.

    What are the ideas for c# 4.0? With the idea that the computer should work out the best way to do things it sounds like it is turning into something like Maple, or Mathematica. Or how about using ideas from PROLOG?

  6. On connaissait LinQ et ses possibilits pour du requtage type base de donnes, mais voici une application de RayTracing qui met en vidence les possibilits de LinQ dans un contexte compltement inhabituel :

  7. naikrovek@gmail.com says:

    how about a C# 2.0 version?  I’m not allowed to run beta applications at my employer.  Thanks!

  8. Coding4Fun says:

    Luke, a member of the c# team, created a Ray Tracer in c#. What is ray tracing exactly? Well, Wikipedia

  9. wout says:

    Neat syntax, but… I’d rather see MS finally invest some effort in building decent .NET compilers, making use of the specific processor capabilities so we can do fast vector/matrix math *sigh*.

  10. Paulsta – In principle there are places where LINQ could make sense with OpenGL or DirectX.  There was one specific topic related to leveraging pixel shaders from C# discussed on the forums recently http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=1533657&SiteID=1 – which may be relevent here.

  11. inv says:

    Hi, I have a little different question:

    Diffuse = pos => ((Math.Floor(pos.Z) + Math.Floor(pos.X)) % 2 != 0)                                        ? Color.Make(1,1,1)                                        : Color.Make(0,0,0)

    Is it possible to generate functions / lambdas at runtime other way than emitting IL instructions? I mean a simple way to let user specify a string describing a material(per-pixel basis) and then

    render that material without parsing the string?

    Btw, in

    return scene.Things

           .Select(obj => obj.Intersect(ray))

           .Where(inter => inter != null)

           .OrderBy(inter => inter.Dist);

    I don’t think the final OrderBy clause is a good approach since you only need the nearest intersection from the camera, so a linear loop over filtered Things would be quicker.

  12. inv –

    It is in fact possible to generate functions at runtime.  In .NET2.0, the CLR added a feature called Lightweight Code Generation to REflection.Emit which allows defining DynamicMethods by providing an IL stream.  The API then allows you to capture a delegate to this newly created method.  This would enable the kind of thing you are suggesting – but it would be a lot of work.

    In .NET3.5 and C#3.0 we’ve added Expression Trees, which actually make this a lot easier.  You can use static factory methods on the Expression class to create the body of the expression and then use the ".Compile()" method to get a delegate back.  Under the hood this also uses LCG – but you can avoid mucking around with IL.  

    If I can find some time I’ll write a post with a few examples of what this looks like.

    Also – you are right – the OrderBy is a bad idea – I should have used .Min().  Luckily there are only three objects in my scene – so the algorithmic complexity doesn’t rear its head here 🙂

  13. Not too long ago I blogged about a C# raytracer which took advantage of a lot of C#3.0 language constructs.

  14. mycall et al says:

    Not too long ago I blogged about a C# raytracer which took advantage of a lot of C#3.0 language constructs

  15. Multi-cpu systems and multicore processors are becoming ever more common, but writing code that actually

  16. Through Don Syme&#39;s blog I read about Luke Hoban moving from the C# team at Microsoft to the F# team

  17. Jamie says:

    Thanks for this, nice to see a non-typical usage for LINQ!

  18. thank you for this excellent program

  19. bryan says:

    hi, I’m a beginner at C#. When I try to Building your code in VS 2008 RTM, I got those error messages:

    Error 1 The type or namespace name ‘Linq’ does not exist in the namespace ‘System’ (are you missing an assembly reference?)

    Error 2 The type or namespace name ‘Func’ could not be found (are you missing a using directive or an assembly reference?)

    I find the ‘System.Data.Linq’ and ‘System.Xml.Linq’, which one I need?

    And how can I fix the error 2?

    If you have free time, please tell me how do.

    My email is bryan.chou@gmail.com.

    Thanks.

  20. Bryan – Sounds like you probably don’t have "System.Core.dll" referenced in your project.  This is where much of the core functionality in .NET 3.5 (including parts of LINQ) is provided.

  21. Khoorsandi says:

    i think using Assmebly instruction is very better and has extermly better Performance!

  22. dave^2=-1 says:

    Nice post on using new language features in C# 3.0 for ray tracing: LukeH’s WebLog : A Ray Tracer in

  23. mantas says:

    Hello there,its so nice to see people who can actualy do something (unlike me).

    i am very interested in raytracing curiently, but i have no programing skills whatsoever. so i cannot coment on your engine too much, apart from the image looks raytraced 🙂

    anyways, i have this idea about raytracing, and have no clue would it work. basicaly its, in raytracing u cast a ray, which is a straight line, and what if it wouldnt be strait line?

    here is about my idea a bit more:

    http://3dideas.wordpress.com/

    would be nice to hear a word from people who actualy understand more then me 🙂

    thanks, m.

  24. I&#39;ve been thinking about ranges again, particularly after catching a book error just in time, and

  25. The June 2008 Community Technology Preview (CTP) of Parallel Extensions to the .NET Framework was released

  26. I had some free time today and did this: I’d like to note that LukeH made the core raytracing engine

  27. nikola says:

    Hi Luke!

    This is pretty nice! I decided to use it, and made a Silverlight application out of it:

    http://blogs.msdn.com/nikola/archive/2008/06/10/raytracing-in-silverlight.aspx

    You’re in the credits – thanks for the great sample!

  28. Hi,

    I have made a port of this sample on Mac OS X, by using Mono and the Monobjc bridge.

    For more details: http://laurent.etiemble.free.fr/dotclear/tb.php?id=123

    Laurent Etiemble.

  29. I&#39;ve been thinking about ranges again, particularly after catching a book error just in time, and

  30. Adam says:

    Just found a simple way to speed this up significantly. Change:

    if (x == 0) pictureBox.Refresh();

    To:

    if (x == 0 && (y & 15) == 0) pictureBox.Refresh();

    On my PC that cut the rendering time down from around 25 seconds to about 7. Removing that line completely only saved an extra second.

  31. davetiyeci says:

    düğün davetiyesi ve davetiye sözleri

  32. bruce says:

    hi, the code has some errors, can you mail the whole code to me ? my email is afterburn888@gmail.com

  33. urih says:

    Inspired from you – silverlight raytracing benchmark: http://silver.urih.com/

Skip to main content