Click here to Skip to main content
15,881,938 members
Articles / Programming Languages / C#

Yet Another RayTracer for .NET

Rate me:
Please Sign up or sign in to vote.
4.87/5 (56 votes)
29 Mar 2007CPOL15 min read 129.3K   3.9K   124  
This article is meant as an introduction to raytracing and explains the basic techniques to raytrace a scene.
// Copyright 2006 Herre Kuijpers - <herre@xs4all.nl>
//
// This source file(s) may be redistributed, altered and customized
// by any means PROVIDING the authors name and all copyright
// notices remain intact.
// THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED. USE IT AT YOUR OWN RISK. THE AUTHOR ACCEPTS NO
// LIABILITY FOR ANY DATA DAMAGE/LOSS THAT THIS PRODUCT MAY CAUSE.
//-----------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using Drawing=System.Drawing;
using System.Text;
using System.Windows.Forms;
using RayTracer;

namespace RayTracerApp
{
    public partial class Form1 : Form
    {
        private bool IsTracing;
        private Scene scene = new Scene();
        private AntiAliasing anti_aliasing = AntiAliasing.Medium;
        private int sceneId = 0;
        Texture marbleTexture;
        Texture woodTexture;
        Texture wallTexture;
        Drawing.Bitmap bitmap;

        public Form1()
        {
            InitializeComponent();
            IsTracing = false;
        }

        #region Setup scene

        private void SetupScene2()
        {
            scene = new Scene();
            scene.Background = new Background(new Color(.2, .3, .4), 0.5);
            Vector campos = new Vector(0, 0, -5);
            scene.Camera = new Camera(campos, campos / -2, new Vector(0, 1, 0).Normalize());

            Random rnd = new Random();
            for (int i = 0; i < 40; i++)
            {

                // setup a solid reflecting sphere
                scene.Shapes.Add(new SphereShape(new Vector(rnd.Next(-100, 100) / 50.0, rnd.Next(-100, 100) / 50.0, rnd.Next(0, 200) / 50.0), .2,
                                   new SolidMaterial(new Color(rnd.Next(0, 100) / 100.0, rnd.Next(0, 100) / 100.0, rnd.Next(0, 100) / 100.0), 0.4, 0.0, 2.0)));

            }

            scene.Lights.Add(new Light(new Vector(5, 10, -1), new Color(0.8, 0.8, 0.8)));
            scene.Lights.Add(new Light(new Vector(-3, 5, -15), new Color(0.8, 0.8, 0.8)));

        }

        
        private void SetupScene1()
        {

            TextureMaterial woodMaterial = new TextureMaterial(woodTexture, 0.2, 0.0, 2, .5);
            TextureMaterial marbleMaterial = new TextureMaterial(marbleTexture, 0.0, 0.0, 2, .5);
            TextureMaterial wallMaterial = new TextureMaterial(wallTexture, 0.0, 0.0, 2, .4);

            
            scene = new Scene();
            scene.Background = new Background(new Color(.8, .8, .8), 0.8);
            Vector campos = new Vector(5, 1.8, -15);
            scene.Camera = new Camera(campos, campos / -3, new Vector(0, 1, 0).Normalize());
            
            // marble
            scene.Shapes.Add(new SphereShape(new Vector(1, 1, -5), 1,
                               marbleMaterial));

            //floor
            scene.Shapes.Add(new PlaneShape(new Vector(0, 1, 0).Normalize(), 0, woodMaterial));
            //wall
            scene.Shapes.Add(new PlaneShape(new Vector(0, 0, 1).Normalize(), 0, wallMaterial));

            scene.Lights.Add(new Light(new Vector(25, 20, -20), new Color(0.5, 0.5, 0.5)));
            scene.Lights.Add(new Light(new Vector(-3, 5, -15), new Color(0.5, 0.5, 0.5)));
        }

        // metallic box with marble on stone floor
        private void SetupScene4()
        {

            TextureMaterial woodMaterial = new TextureMaterial(woodTexture, 0.0, 0.0, 2, .5);
            TextureMaterial marbleMaterial = new TextureMaterial(marbleTexture, 0.3, 0.0, 2, .5);
            TextureMaterial wallMaterial = new TextureMaterial(wallTexture, 0.0, 0.0, 2, .4);


            scene = new Scene();
            scene.Background = new Background(new Color(.3, .8, .8), 0.8);
            Vector campos = new Vector(14, 2, -6);
            scene.Camera = new Camera(campos, campos / -2.5, new Vector(-0, 1, 0.1).Normalize());

            // marble
            scene.Shapes.Add(new SphereShape(new Vector(-3, 1, 5), 2,
                               marbleMaterial));

            // box
            scene.Shapes.Add(new BoxShape(new Vector(0, 1, -1), new Vector(1, 0, 0),
                               woodMaterial));

            //floor
            scene.Shapes.Add(new PlaneShape(new Vector(0, 1, 0).Normalize(), 0, wallMaterial));
            
            //wall
            //scene.Shapes.Add(new PlaneShape(new Vector(0, 0, 1).Normalize(), 0, wallMaterial));

            scene.Lights.Add(new Light(new Vector(25, 20, -20), new Color(0.5, 0.5, 0.5)));
            scene.Lights.Add(new Light(new Vector(-23, 25, -15), new Color(0.5, 0.5, 0.5)));
        }

        // single reflective shere on chessboard scene
        private void SetupScene3()
        {

            scene = new Scene();
            scene.Camera = new Camera(new Vector(0, 0, -15), new Vector(-.2, 0, 5), new Vector(0, 1, 0));
            scene.Background = new Background(new Color(0.5, .5, .5), 0.4);

            // setup a solid reflecting sphere
            scene.Shapes.Add(new SphereShape(new Vector(-0.5, 0.5, -2), 1.5,
                               new SolidMaterial(new Color(0, .5, .5), 0.3, 0.0, 2.0)));

            // setup the chessboard floor
            scene.Shapes.Add(new PlaneShape(new Vector(0.1, 0.9, -0.5).Normalize(), 1.2,
                               new ChessboardMaterial(new Color(1, 1, 1), new Color(0, 0, 0), 0.2, 0, 1, 0.7)));

            //add two lights for better lighting effects
            scene.Lights.Add(new Light(new Vector(5, 10, -1), new Color(0.8, 0.8, 0.8)));
            scene.Lights.Add(new Light(new Vector(-3, 5, -15), new Color(0.8, 0.8, 0.8)));

        }

        // marble balls scene
        private void SetupScene0()
        {
            TextureMaterial texture = new TextureMaterial(marbleTexture, 0.0, 0.0, 2, .5);

            scene = new Scene();
            scene.Camera = new Camera(new Vector(0, 0, -15), new Vector(-.2, 0, 5), new Vector(0, 1, 0));

            // setup a solid reflecting sphere
            scene.Shapes.Add(new SphereShape(new Vector(-1.5, 0.5, 0), .5,
                               new SolidMaterial(new Color(0, .5, .5), 0.2, 0.0, 2.0)));

            // setup sphere with a marble texture from an image
            scene.Shapes.Add(new SphereShape(new Vector(0, 0, 0), 1, texture));

            // setup the chessboard floor
            scene.Shapes.Add(new PlaneShape(new Vector(0.1, 0.9, -0.5).Normalize(), 1.2,
                               new ChessboardMaterial(new Color(1, 1, 1), new Color(0, 0, 0), 0.2, 0, 1, 0.7)));

            //add two lights for better lighting effects
            scene.Lights.Add(new Light(new Vector(5, 10, -1), new Color(0.8, 0.8, 0.8)));
            scene.Lights.Add(new Light(new Vector(-3, 5, -15), new Color(0.8, 0.8, 0.8)));

        }

        #endregion Setup scene

        private void button1_Click(object sender, EventArgs e)
        {
        }

        void tracer_RenderUpdate(int progress, double duration, double ETA, int scanline)
        {
            //only invalidate part of the picturebox that needs to be redrawn
            pbScene.Invalidate(new Drawing.Rectangle(0, scanline - 1, pbScene.Image.Width, 2));
            statusETA.Text = "Eta: " + (ETA/1000).ToString("0.0") + "s";
            statusDuration.Text = "Duration:" + (duration/1000).ToString("0.0") + "s";
            Application.DoEvents(); // some time to redraw the screen
        }

        private void pbScene_MouseDown(object sender, MouseEventArgs e)
        {
            if (IsTracing) return;
            if (bitmap == null) return;
            // this implementation is used for debugging purposes.
            // click on the pixel on the image to start the raytracing
            // for that particular ray through the clicked pixel.
            RayTracer.RayTracer tracer = new RayTracer.RayTracer(anti_aliasing,
                                                                    true,
                                                                    showPhongToolStripMenuItem.Checked,
                                                                    castShadowsToolStripMenuItem.Checked,
                                                                    showReflectionsToolStripMenuItem.Checked,
                                                                    showRefractionsToolStripMenuItem.Checked);

            Drawing.Rectangle rect = new Drawing.Rectangle(0, 0, 300, 300);

            DateTime t = DateTime.Now;

            Vector screen = scene.Camera.Position + scene.Camera.LookAt;
            //e.Graphics.FillRectangle(Brushes.Black, bitmap);

            double yp = (e.Y - rect.Top) * 1.0f / bitmap.Height * 2 - 1;
            double xp = (e.X - rect.Left) * 1.0f / bitmap.Width * 2 - 1;

            Vector pos = new Vector(screen.x + xp, screen.y - yp, screen.z);
            Vector dir = pos - scene.Camera.Position;

            Ray ray = new Ray(pos, dir.Normalize());
            Color color = tracer.CalculateColor(ray, scene);
            pictureBox1.BackColor = color.ToArgb();
        }

        private void renderNowToolStripMenuItem_Click(object sender, EventArgs e)
        {
            label1.Visible = false;
            if (IsTracing) return;

            IsTracing = true;

            switch (sceneId)
            {
                case 0:
                    SetupScene0();
                    break;
                case 1:
                    SetupScene1();
                    break;
                case 2:
                    SetupScene2();
                    break;
                case 3:
                    SetupScene3();
                    break;
                default:
                    SetupScene4();
                    break;
            }
            

            statusETA.Text = "Eta:";
            statusDuration.Text = "Duration:";

            RayTracer.RayTracer raytracer = new RayTracer.RayTracer(anti_aliasing,
                                                                    true,
                                                                    showPhongToolStripMenuItem.Checked,
                                                                    castShadowsToolStripMenuItem.Checked,
                                                                    showReflectionsToolStripMenuItem.Checked,
                                                                    showRefractionsToolStripMenuItem.Checked);
            raytracer.RenderUpdate += new RenderUpdateDelegate(tracer_RenderUpdate);

            Drawing.Rectangle rect = new Drawing.Rectangle(0, 0, 300, 300);
            bitmap = new Drawing.Bitmap(rect.Width, rect.Height);

            Drawing.Graphics g = Drawing.Graphics.FromImage(bitmap);
            DateTime t = DateTime.Now;

            pbScene.Image = bitmap;

            raytracer.RayTraceScene(g, rect, scene);

            IsTracing = false;
            TimeSpan s = DateTime.Now.Subtract(t);
            Console.WriteLine("total render time: " + s.TotalMilliseconds.ToString("0.00"));
            statusETA.Text = "";

        }

        private void CheckMenuAntiAliasing(object menuitem)
        {
            noneToolStripMenuItem.Checked = noneToolStripMenuItem.Equals(menuitem);
            quickToolStripMenuItem.Checked = quickToolStripMenuItem.Equals(menuitem);
            lowToolStripMenuItem.Checked = lowToolStripMenuItem.Equals(menuitem);
            mediumToolStripMenuItem.Checked = mediumToolStripMenuItem.Equals(menuitem);
            highToolStripMenuItem.Checked = highToolStripMenuItem.Equals(menuitem);
            veryHighToolStripMenuItem.Checked = veryHighToolStripMenuItem.Equals(menuitem);
        }

        private void noneToolStripMenuItem_Click(object sender, EventArgs e)
        {
            CheckMenuAntiAliasing(sender);
            anti_aliasing = AntiAliasing.None;
        }

        private void quickToolStripMenuItem_Click(object sender, EventArgs e)
        {
            CheckMenuAntiAliasing(sender);
            anti_aliasing = AntiAliasing.Quick;
        }

        private void lowToolStripMenuItem_Click(object sender, EventArgs e)
        {
            CheckMenuAntiAliasing(sender);
            anti_aliasing = AntiAliasing.Low;
        }

        private void mediumToolStripMenuItem_Click(object sender, EventArgs e)
        {
            CheckMenuAntiAliasing(sender);
            anti_aliasing = AntiAliasing.Medium;
        }

        private void highToolStripMenuItem_Click(object sender, EventArgs e)
        {
            CheckMenuAntiAliasing(sender);
            anti_aliasing = AntiAliasing.High;
        }

        private void veryHighToolStripMenuItem_Click(object sender, EventArgs e)
        {
            CheckMenuAntiAliasing(sender);
            anti_aliasing = AntiAliasing.VeryHigh;
        }

        private void CheckMenuScene(object sender)
        {
            marbleSpheresToolStripMenuItem.Checked = marbleSpheresToolStripMenuItem.Equals(sender);
            woodenFloorAndStoneWallToolStripMenuItem.Checked = woodenFloorAndStoneWallToolStripMenuItem.Equals(sender);
            randomMarblesToolStripMenuItem.Checked = randomMarblesToolStripMenuItem.Equals(sender);
        }

        private void marbleSpheresToolStripMenuItem_Click(object sender, EventArgs e)
        {
            CheckMenuScene(sender);
            sceneId = 0;
        }

        private void woodenFloorAndStoneWallToolStripMenuItem_Click(object sender, EventArgs e)
        {
            CheckMenuScene(sender);
            sceneId = 1;
        }

        private void randomMarblesToolStripMenuItem_Click(object sender, EventArgs e)
        {
            CheckMenuScene(sender);
            sceneId = 2;
        }

        private void reflectiveShereOnChessboardToolStripMenuItem_Click(object sender, EventArgs e)
        {
            CheckMenuScene(sender);
            sceneId = 3;
        }

        private void woodenBoxAndMarbleToolStripMenuItem_Click(object sender, EventArgs e)
        {
            CheckMenuScene(sender);
            sceneId = 4;
        }

        private void copyToolStripMenuItem_Click(object sender, EventArgs e)
        {
            Clipboard.SetImage(bitmap);
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            label1.Text = "Loading textures...";
            this.Visible = true;
            Application.DoEvents();

            string path = Application.StartupPath;

            // pre-load the textures here, so it only needs to be done once
            woodTexture = Texture.FromFile(path + @"\wood2.png");
            marbleTexture = Texture.FromFile(path + @"\marble1.png");
            wallTexture = Texture.FromFile(path + @"\wall1.png");

            label1.Text = "Press F5 to start the RayTracer!";
        }

    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Architect Rubicon
Netherlands Netherlands
Currently Herre Kuijpers is employed at Rubicon. During his career he developed skills with all kinds of technologies, methodologies and programming languages such as c#, ASP.Net, .Net Core, VC++, Javascript, SQL, Agile, Scrum, DevOps, ALM. Currently he fulfills the role of software architect in various projects.

Herre Kuijpers is a very experienced software architect with deep knowledge of software design and development on the Microsoft .Net platform. He has a broad knowledge of Microsoft products and knows how these, in combination with custom software, can be optimally implemented in the often complex environment of the customer.

Comments and Discussions