Click here to Skip to main content
6,594,932 members and growing! (14,502 online)
Email Password   helpLost your password?
Multimedia » General Graphics » Graphics     Intermediate License: The GNU General Public License (GPL)

Simple Ray Tracing in C#

By andalmeida

Simple Ray Tracing in C#
C# 2.0, Windows, .NET 2.0VS2005, Dev
Posted:24 Jul 2007
Updated:10 Aug 2007
Views:35,327
Bookmarked:68 times
Announcements
Loading...
 
Search    
Advanced Search
Add to IE Search
printPrint   add Share
      Discuss Discuss   Broken Article?Report  
23 votes for this article.
Popularity: 6.59 Rating: 4.84 out of 5

1

2

3
2 votes, 8.7%
4
21 votes, 91.3%
5

See sample

Screenshot - rt.png

Introduction

This article demonstrates how to make a simple and very basic Ray Tracing with spheres; it can serve you as a base for implementing more complex algorithms. In a future article I will show how to improve this using image mapping.

Background

The Ray Tracing process is an approach to generate high quality computer graphics, as deeper the level of recursivity interaction with the 3D objects it has more photo realistic appearing.

The Ray Tracing Algorithm is implemented by calculating the intersection of 3D lines with the 3D objects in the model. The first point of the 3D line we define as the viewer position, the endpoint is located at the projection plane chosen. So for each discrete point at the projection plane an equation for the line is obtained and it is calculated all intersections with all objects selecting the intersection which are nearest to the viewer, so the color is plotted. Using this approach the implemented algorithm can handle opacity, light, shadows and so on.

3D lines equations can be represented in the form:

  • x = px + t*vx
  • y = py + t*vy
  • z = pz + t*vz

Where (px,py,pz) are all the points lying in the 3D line, t is a scalar parameter, and (vx,vy,vz) is a direction vector.

The above equation can be obtained by the definition where a line can be defined by 2 points, so given P1(x1,y1,z1) and P2(x2,y2,z2) we have:

  • v = (x2-x1,y2-y1,z2-z1)

replacing the found v and p we have:

  • x = x1 + t*(x2-x1)
  • y = y1 + t*(y2-y1)
  • z = z1 + t*(z2-z1)

So we can be sure that all (x,y,z) which satisfies the above equation belongs to the line defined by P1P2.

Spheres can be represented in the form:

  • r2 = (x-cx)2+(y-cy)2+(z-cz)2

where

  • r is the sphere radius
  • (cx,cy,cz) is the center of the sphere

So we can be sure that all x,y,x points lies on the sphere surface.

Our objective now is to determine the intersection equation between a given line and a sphere it must be a set of (x,y,z) points which satisfies both equations. It is simple to imagine that a line intersecting a sphere can result 0 intersections, 1 intersection (if tangent) or at most 2 intersections.

The Equations

  • r2 = (x-cx)2+(y-cy)2+(z-cz)2
  • x = x1 + t*(x2-x1)
  • y = y1 + t*(y2-y1)
  • z = z1 + t*(z2-z1)

Replacing x, y and z we have:

  • r2 = (x1 + t*(x2-x1)-cx)2 + (y1 + t*(y2-y1) -cy)2 + (z1+ t*(z2-z1)-cz)2

Let's create a variable for the vector:

  • vx = x2 - x1
  • vy = y2 - y1
  • vz = z2 - z1

So now we have:

  • r2 = (x1-cx+t*vx)2 + (y1-cy+t*vy)2 + (z1-cz+t*vz)2
  • (x1-cx+t*vx)2 + (y1-cy+t*vy)2 + (z1-cz+t*vz)2 - r2 = 0

Let's replace (x1,y1,z1) with (px,py,pz) just to simplify...

Now we have a perfect 2nd degree equation which can give us 0, 1 or 2 different solutions for 't':

double A = (vx * vx + vy * vy + vz * vz);
double B = 2.0 * (px * vx + py * vy + pz * vz - vx * cx - vy * cy - vz * cz);
double C = px * px - 2 * px * cx + cx * cx + py * py - 2 * py * cy + cy * cy +
           pz * pz - 2 * pz * cz    + cz * cz - radius * radius;
double D = B * B - 4 * A * C;
double t = -1.0;
if (D >= 0)
     {
     double t1 = (-B - System.Math.Sqrt(D)) / (2.0 * A);
     double t2 = (-B + System.Math.Sqrt(D)) / (2.0 * A);
     if (t1 > t2)
          t = t1;
     else
          t = t2;  // we choose the nearest t from the first point

     }

The Source Code

<script language="C#" runat="server">
private void Page_Load(object sender, System.EventArgs e)
    {
    Bitmap newBitmap = new Bitmap(200, 200, PixelFormat.Format32bppArgb);
    Graphics g = Graphics.FromImage(newBitmap);

    Color clrBackground = Color.Black;
    g.FillRectangle(new SolidBrush(clrBackground), new Rectangle(0, 0, 200,
                    200));
    Rectangle rect = new Rectangle(0, 0, 200, 200);

    System.Collections.ArrayList obj3dArrayList;
    obj3dArrayList = new System.Collections.ArrayList();
    obj3dArrayList.Add(new Sphere(0.0, 0.0, 90.0, 100.0, 0.0, 0.0, 255.0));
    obj3dArrayList.Add(new Sphere(-180.0, -130.0, -110.0, 15.0, 255.0, 0.0,
                       0.0));
    obj3dArrayList.Add(new Sphere(-140.0, -140.0, -150.0, 20.0, 255.0, 200.0,
                       0.0));
    Graphics graphics = g;
    // viewer position

    double px = (double)Session["eyex"],
    py = (double)Session["eyey"],
    pz = (double)Session["eyez"];
    // light position

    double lpx = (double)Session["lpx"],
    lpy = (double)Session["lpy"],
    lpz = (double)Session["lpz"];
    // light direction

    double lvx = (double)Session["lvx"],
    lvy = (double)Session["lvy"],
    lvz = (double)Session["lvz"];
    double fMax = 200.0;
    for (int i = rect.Left; i <= rect.Right; i++)
        {
        double x = Sphere.GetCoord(rect.Left, rect.Right, -fMax, fMax, i);
        for (int j = rect.Top; j <= rect.Bottom; j++)
        {
            double y = Sphere.GetCoord(rect.Top, rect.Bottom, fMax, -fMax, j);
            double t = 1.0E10;
            double vx = x - px, vy = y - py, vz = -pz;
            double mod_v = Sphere.modv(vx, vy, vz);
            vx = vx / mod_v;
            vy = vy / mod_v;
            vz = vz / mod_v;
            bool bShadow = false;
            Sphere spherehit = null;
            for (int k = 0; k < (int)obj3dArrayList.Count; k++)
                {
                Sphere sphn = (Sphere)obj3dArrayList[k];
                double taux = Sphere.GetSphereIntersec(sphn.cx, sphn.cy,
                              sphn.cz,
                              sphn.radius, px, py, pz, vx, vy, vz);
                if (taux < 0) continue;
                if (taux > 0 && taux < t)
                {
                    t = taux;
                    spherehit = sphn;
                }
            }
            Color color = Color.FromArgb(10, 20, 10);
            if (spherehit != null)
            {
                double itx = px + t * vx, ity = py + t * vy, itz = pz +
                t * vz;
                // shadow

                double tauxla = Sphere.GetSphereIntersec(spherehit.cx,
                                spherehit.cy, spherehit.cz, spherehit.radius,
                                lpx, lpy, lpz, itx - lpx,
                                ity - lpy, itz - lpz);

                for (int k = 0; k < (int)obj3dArrayList.Count; k++)
                {
                    Sphere sphnb = (Sphere)(obj3dArrayList[k]);
                    if (sphnb != spherehit)
                    {
                        double tauxlb = Sphere.GetSphereIntersec(sphnb.cx,
                                        sphnb.cy, sphnb.cz, sphnb.radius, lpx,
                                        lpy, lpz, itx - lpx, ity - lpy, itz -
                                        lpz);
                        if (tauxlb > 0 && tauxla < tauxlb)
                        {
                            bShadow = true;
                            break;
                        }
                    }
                }
                double cost = Sphere.GetCosAngleV1V2(lvx, lvy, lvz, itx -
                              spherehit.cx, ity - spherehit.cy, itz -
                              spherehit.cz);
                if (cost < 0) cost = 0;
                double fact = 1.0;
                if (bShadow == true) fact = 0.5; else fact = 1.0;
                double rgbR = spherehit.clR * cost * fact;
                double rgbG = spherehit.clG * cost * fact;
                double rgbB = spherehit.clB * cost * fact;
                color = Color.FromArgb((int)rgbR, (int)rgbG, (int)rgbB);
                pen = new Pen(color);
            }
            Brush brs = new SolidBrush(color);
            graphics.FillRectangle(brs, i, j, 1, 1);
            brs.Dispose();

        }// for pixels lines

    }// for pixels columns

    ///////////////////////////////////////

    MemoryStream tempStream = new MemoryStream();
    newBitmap.Save(tempStream, ImageFormat.Png);
    Response.ClearContent();
    Response.ContentType = "image/png";
    Response.BinaryWrite(tempStream.ToArray());
    Response.Flush();
    }
</script>

The Sphere Class

public class Sphere
{
    public Sphere(double x, double y, double z, double r, double clr,
                  double clg, double clb)
    {
        cx = x;
        cy = y;
        cz = z;
        radius = r;
        clR = clr;
        clG = clg;
        clB = clb;
    }
    public static double GetCoord(double i1, double i2, double w1, double w2,
        double p)
    {
        return ((p - i1) / (i2 - i1)) * (w2 - w1) + w1;
    }
    public static double modv(double vx, double vy, double vz)
    {
        return System.Math.Sqrt(vx * vx + vy * vy + vz * vz);
    }
    void Move(double vx, double vy, double vz)
    {
        cx += vx;
        cy += vy;
        cz += vz;
    }
    void MoveTo(double vx, double vy, double vz)
    {
        cx = vx;
        cy = vy;
        cz = vz;
    }
    void RotX(double angle)
    {
        double y = cy * System.Math.Cos(angle) - cz * System.Math.Sin(angle);
        double z = cy * System.Math.Sin(angle) + cz * System.Math.Cos(angle);
        cy = y;
        cz = z;
    }
    void RotY(double angle)
    {
        double x = cx * System.Math.Cos(angle) - cz * System.Math.Sin(angle);
        double z = cx * System.Math.Sin(angle) + cz * System.Math.Cos(angle);
        cx = x;
        cz = z;
    }
    public static double GetSphereIntersec(double cx, double cy, double cz,
                         double radius, double px, double py, double pz,
                         double vx, double vy, double vz)
    {
        // x-xo 2 + y-yo 2 + z-zo 2 = r 2

        // x,y,z = p+tv

        // At2 + Bt + C = 0

        double A = (vx * vx + vy * vy + vz * vz);
        double B = 2.0 * (px * vx + py * vy + pz * vz - vx * cx - vy *
                   cy - vz * cz);
        double C = px * px - 2 * px * cx + cx * cx + py * py - 2 * py *
                   cy + cy * cy + pz * pz - 2 * pz * cz + cz * cz -
                   radius * radius;
        double D = B * B - 4 * A * C;
        double t = -1.0;
        if (D >= 0)
        {
            double t1 = (-B - System.Math.Sqrt(D)) / (2.0 * A);
            double t2 = (-B + System.Math.Sqrt(D)) / (2.0 * A);
            if (t1 > t2) t = t1; else t = t2;
        }
        return t;
    }
    public static double GetCosAngleV1V2(double v1x, double v1y, double v1z,
                                         double v2x, double v2y, double v2z)
    {
        /* incident angle
         intersection pt (i)
        double ix, iy, iz;
        ix = px+t*vx;
        iy = py+t*vy;
        iz = pz+t*vz;
        normal at i
        double nx, ny, nz;
        nx = ix - cx;
        ny = iy - cy;
        nz = iz - cz;

        cos(t) = (v.w) / (|v|.|w|)
        */
        return (v1x * v2x + v1y * v2y + v1z * v2z) / (modv(v1x, v1y, v1z) *
               modv(v2x, v2y, v2z));
    }
    public double cx, cy, cz, radius, clR, clG, clB;
}

License

This article, along with any associated source code and files, is licensed under The GNU General Public License (GPL)

About the Author

andalmeida


Member
I have started programming in 1990 at College with GWBASIC, Fortran and Pascal.
In 1992 started programming in C and them didn't stop, passing through C++, C#.NET, Java, ASP, COM, DCOM, VRML, OPENGL and other.
Occupation: Software Developer (Senior)
Location: Netherlands Netherlands

Other popular General Graphics articles:

  • A flexible charting library for .NET
    Looking for a way to draw 2D line graphs with C#? Here's yet another charting class library with a high degree of configurability, that is also easy to use.
  • CxImage
    CxImage is a C++ class to load, save, display, transform BMP, JPEG, GIF, PNG, TIFF, MNG, ICO, PCX, TGA, WMF, WBMP, JBG, J2K images.
  • 3D Pie Chart
    A class library for drawing 3D pie charts.
  • Barcode Image Generation Library
    This library was designed to give an easy class for developers to use when they need to generate barcode images from a string of data.
  • ImageStone
    An article on a library for image manipulation.
Article Top
You must Sign In to use this message board.
FAQ FAQ 
 
Noise Tolerance  Layout  Per page   
 Msgs 1 to 10 of 10 (Total in Forum: 10) (Refresh)FirstPrevNext
QuestionCan you help me please????? Pinmemberbobbyn950:58 14 Jan '09  
AnswerRe: Can you help me please????? Pinmemberandalmeida1:27 14 Jan '09  
GeneralRe: Can you help me please????? Pinmemberbobbyn9515:30 15 Jan '09  
GeneralNice nice nice PinmemberWindmiller2:09 13 Sep '07  
GeneralRe: Nice nice nice Pinmemberandalmeida3:07 13 Sep '07  
JokeMy brain just exploded PinmemberBen Daniel13:54 24 Jul '07  
GeneralRe: My brain just exploded Pinmemberandalmeida14:07 24 Jul '07  
GeneralFormatting and Colors PinmemberJon Rista13:32 24 Jul '07  
QuestionRe: Formatting and Colors Pinmemberandalmeida13:53 24 Jul '07  
AnswerRe: Formatting and Colors Pinmemberandalmeida14:06 24 Jul '07  

General General    News News    Question Question    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

PermaLink | Privacy | Terms of Use
Last Updated: 10 Aug 2007
Editor: Sean Ewington
Copyright 2007 by andalmeida
Everything else Copyright © CodeProject, 1999-2009
Web15 | Advertise on the Code Project