Click here to Skip to main content
15,880,891 members
Articles / Programming Languages / C#

Simple Ray Tracing in C# Part VII (Shadows)

Rate me:
Please Sign up or sign in to vote.
4.84/5 (47 votes)
15 Jul 2016GPL31 min read 120.5K   295   119   29
Adding shadows to Simple Ray Tracing in C#

 

Screenshot - oneshot_rgb.png
Fig. 1 - Basic sample of shadows
Screenshot - shadow3.png
Fig. 2 - Basic sample of shadows

Introduction

In this article we will add shadows to our ray tracing. This will give a bit more of a realistic appearance to our final images, and at this point in our implementation if you have patience the results can be amazing.

Background

At first I recommend a previous reading in the last ray tracing articles.

The process of adding shadows used here is simple: for each object intersection point, which I call hitpoint, we trace a ray toward the light, and if this ray hits another object so we have shadow at hitpoint, and them we reduce the rgb color at the hitpoint by a percentage.

C#
...
   tRay ltShot = new tRay();
   ltShot.origin = (tPoint)lightsArrayList[0];
   ltShot.target = obj.hitpoint;
   tElementBase objS = get_first_intersection(ltShot);
   if (objS != null && objS != obj && objS.hitscalar>0)
     {
     clraux.x *= 0.9;
     clraux.y *= 0.9;
     clraux.z *= 0.9;
     }

 

Some Results

Here are some results of the current step of our ray tracing.

 

Screenshot - NY.png

Fig. - Hi-Res NY on Mirror sphere

Screenshot - oneshot_rgb3.png

Fig. 3 - Basic sample of shadows

Screenshot - shadow2.png

Fig. 4 - Basic sample of shadows

 

Screenshot - parallelsII.png

Fig. 5 - Parallel mirrors effect

Screenshot - earth2.png

Fig. 6 - After fixing sphere intersection for internal viewing

Screenshot - test1.png

Fig. 7 - Testing a glass wrapper

The classes

Algebra

C#
using System;
using System.Collections.Generic;
using System.Text;

namespace ssAlgebra
{
    public class tPoint
    {
        public tPoint(double x, double y, double z)
        {
            this.x = x;
            this.y = y;
            this.z = z;
        }

        public double x, y, z;
    }

    public class tAlgebra
    {
        public tAlgebra()
        {
        }
        public static double GetCoord(double i1, double i2, double w1, 
                                      double w2, double p)
        {
            double eps = 1.0E-10;
            if (Math.Abs(i2 - i1) < eps) return 0;
            return ((p - i1) / (i2 - i1)) * (w2 - w1) + w1;
        }
        public static void Cross3(double ax, double ay, double az,
            double bx, double by, double bz,
            ref double outx, ref double outy, ref double outz)
        {
            outx = ay * bz - az * by;
            outy = az * bx - ax * bz;
            outz = ax * by - ay * bx;
        }
        public static void Refract(double n1, double n2, double inx, 
                                   double iny, double inz,
            double mirrorx, double mirrory, double mirrorz,
            ref double outx, ref double outy, ref double outz)
        {
            double c1 = -Dot3(mirrorx, mirrory, mirrorz, inx, iny, inz);
            double n = n1 / n2;
            double c2 = Math.Sqrt(1.0 - n * n * (1.0 - c1 * c1));
            outx = (n * inx) + (n * c1 - c2) * mirrorx;
            outy = (n * iny) + (n * c1 - c2) * mirrory;
            outz = (n * inz) + (n * c1 - c2) * mirrorz;
        }
        public static void Reflect(double inx, double iny, double inz,
            double mirrorx, double mirrory, double mirrorz,
            ref double outx, ref double outy, ref double outz)
        {
            double c1 = -Dot3(mirrorx, mirrory, mirrorz, inx, iny, inz);
            //Rl = V + (2 * N * c1 )
            outx = -(inx + (2 * mirrorx * c1));
            outy = -(iny + (2 * mirrory * c1));
            outz = -(inz + (2 * mirrorz * c1));

        }
        public static double Dot3(double x1, double y1, double z1, 
                                  double x2, double y2, double z2)
        {
            return ((x1 * x2) + (y1 * y2) + (z1 * z2));
        }
        public static double GetCosAngleV1V2(double v1x, double v1y, 
                     double v1z, double v2x, double v2y, double v2z)
        {
            // cos(t) = (v.w) / (|v|.|w|) = (v.w) / 1
            return Dot3(v1x, v1y, v1z, v2x, v2y, v2z);
        }
        public static double modv(double vx, double vy, double vz)
        {
            return System.Math.Sqrt(vx * vx + vy * vy + vz * vz);
        }
        public static bool Normalize(ref double vx, ref double vy, 
                                  ref double vz)
        {
            double mod_v = tAlgebra.modv(vx, vy, vz);
            double eps = 1.0E-10;

            if (Math.Abs(mod_v) < eps)
                return true;

            vx = vx / mod_v;
            vy = vy / mod_v;
            vz = vz / mod_v;
            return false;
        }
        public static void RotX(double angle, ref double y, ref double z)
        {
            double y1 = y * System.Math.Cos(angle) - z * 
                          System.Math.Sin(angle);
            double z1 = y * System.Math.Sin(angle) + z * 
                          System.Math.Cos(angle);
            y = y1;
            z = z1;
        }
        public static void RotY(double angle, ref double x, ref double z)
        {
            double x1 = x * System.Math.Cos(angle) - z * 
                          System.Math.Sin(angle);
            double z1 = x * System.Math.Sin(angle) + z * 
                          System.Math.Cos(angle);
            x = x1;
            z = z1;
        }
        public static void RotZ(double angle, ref double x, ref double y)
        {
            double x1 = x * System.Math.Cos(angle) - y * 
                          System.Math.Sin(angle);
            double y1 = x * System.Math.Sin(angle) + y * 
                          System.Math.Cos(angle);
            x = x1;
            y = y1;
        }
    }
}

Geometry

C#
using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using ssAlgebra;
using System.Collections;

namespace ssGeometry
{

    public class tMaterial
    {
        public double ambientR, ambientG, ambientB, ambientA;
        public double diffuseR, diffuseG, diffuseB, diffuseA;
        public double specularR, specularG, specularB, specularA;
        public double emissionR, emissionG, emissionB, emissionA;
        public double shininess;
        public double reflectance;
        public double alpha;

        public tMaterial(double alpha, double reflectance,
            double ambientR, double ambientG, double ambientB,
            double specularR, double specularG, double specularB,
            double shininess,
            double diffuseR, double diffuseG, double diffuseB)
        {
            this.alpha = alpha;
            this.reflectance = reflectance;
            this.ambientR = ambientR;
            this.ambientG = ambientG;
            this.ambientB = ambientB;
            this.specularR = specularR;
            this.specularG = specularG;
            this.specularB = specularB;
            this.shininess = shininess;
            this.diffuseR = diffuseR;
            this.diffuseG = diffuseG;
            this.diffuseB = diffuseB;
        }

    }
    public abstract class tElementBase
    {
        public tElementBase()
        {
            tHitPoint = new tPoint(0, 0, 0);
        }

        public abstract tPoint getMapColor(double px, double py, double pz);
        public tPoint get_point_color(System.Collections.ArrayList lights, 
                                      tPoint rayV)
        {
            tPoint color = new tPoint(0, 0, 0);

            tPoint normal = new tPoint(0, 0, 0);

            //foreach light
            tPoint light = (tPoint)lights[0];

            getNormal(hitpoint.x, hitpoint.y, hitpoint.z,
                ref normal.x, ref normal.y, ref normal.z);

            double lvX = light.x - hitpoint.x,
                   lvY = light.y - hitpoint.y,
                   lvZ = light.z - hitpoint.z;

            tAlgebra.Normalize(ref normal.x, ref normal.y, ref normal.z);
            tAlgebra.Normalize(ref lvX, ref lvY, ref lvZ);

            double cost = tAlgebra.GetCosAngleV1V2(lvX, lvY, lvZ,
                normal.x, normal.y, normal.z);

            double cosf = 0;

            tAlgebra.Reflect(-lvX, -lvY, -lvZ,
                             normal.x, normal.y, normal.z,
                             ref vReflX, ref vReflY, ref vReflZ);

            tAlgebra.Normalize(ref vReflX, ref vReflY, ref vReflZ);
            tAlgebra.Normalize(ref rayV.x, ref rayV.y, ref rayV.z);

            cosf = tAlgebra.GetCosAngleV1V2(rayV.x, rayV.y, rayV.z, vReflX, 
                                vReflY, vReflZ);

            double result1 = Math.Max(0, cost) * 255.0;
            double result2 = Math.Pow(Math.Max(0, cosf), 
                                material.shininess) * 255.0;

            double rgbR = (material.ambientR * 255.0) +
                (material.diffuseR * result1) +
                (material.specularR * result2);
            double rgbG = (material.ambientG * 255.0) +
                (material.diffuseG * result1) +
                (material.specularG * result2);
            double rgbB = (material.ambientB * 255.0) +
                (material.diffuseB * result1) +
                (material.specularB * result2);

            if (imgBitmap != null)
            {
                tPoint colorPt = getMapColor(hitpoint.x, hitpoint.y, 
                                     hitpoint.z);

                double sz = tAlgebra.modv(colorPt.x, colorPt.y, colorPt.z);
                result1 = Math.Max(0, cost) * sz;
                result2 = Math.Pow(Math.Max(0, cosf), material.shininess) * 
                                    sz;

                rgbR = (material.ambientR * colorPt.x) +
                    (material.diffuseR * result1) +
                    (material.specularR * result2);
                rgbG = (material.ambientG * colorPt.y) +
                    (material.diffuseG * result1) +
                    (material.specularG * result2);
                rgbB = (material.ambientB * colorPt.z) +
                    (material.diffuseB * result1) +
                    (material.specularB * result2);

            }

            color.x = rgbR;
            color.y = rgbG;
            color.z = rgbB;

            return color;
        }

        public tRay get_reflected_ray(tRay shot)
        {
            tRay ray = new tRay();
            double inx = 0, iny = 0, inz = 0, 
                      nx = 0, ny = 0, nz = 0, rx = 0, 
                      ry = 0, rz = 0;

            getNormal(hitpoint.x, hitpoint.y, hitpoint.z, ref nx, ref ny, 
                      ref nz);

            inx = shot.getRay().x;
            iny = shot.getRay().y;
            inz = shot.getRay().z;

            tAlgebra.Normalize(ref inx, ref iny, ref inz);
            tAlgebra.Normalize(ref nx, ref ny, ref nz);

            tAlgebra.Reflect(-inx, -iny, -inz, nx, ny, nz, ref rx, ref ry, 
                      ref rz);
            tAlgebra.Normalize(ref rx, ref ry, ref rz);

            ray.origin.x = hitpoint.x;
            ray.origin.y = hitpoint.y;
            ray.origin.z = hitpoint.z;
            ray.target.x = (hitpoint.x + rx);
            ray.target.y = (hitpoint.y + ry);
            ray.target.z = (hitpoint.z + rz);

            return ray;

        }

        public tMaterial material
        {
            get
            {
                return tmaterial;
            }
            set
            {
                tmaterial = value;
            }
        }
        public tPoint hitpoint
        {
            get
            {
                return tHitPoint;
            }
            set
            {
                tHitPoint = value;
            }
        }
        public double hitscalar
        {
            get
            {
                return tHitScalar;
            }
            set
            {
                tHitScalar = value;
            }
        }
        protected Bitmap imgBitmap = null;
        private tMaterial tmaterial;
        private tPoint tHitPoint;
        private double tHitScalar;

        public bool interpolate = false;
        private double vReflX = 0, vReflY = 0, vReflZ = 0;
        public abstract void RotateXYZ(double rx, double ry, double rz);
        public abstract void RotateZYX(double rx, double ry, double rz);
        public abstract double GetIntersect(double p1x, double p1y, 
                                 double p1z,
                                 double p2x, double p2y, double p2z);

        public abstract void getNormal(double x, double y, double z,
            ref double nx, ref double ny, ref double nz);


        public tRay get_refracted_ray(tRay shot)
        {
            tRay ray = new tRay();
            double inx = 0, iny = 0, inz = 0, nx = 0, ny = 0, nz = 0, 
                                rx = 0, ry = 0, rz = 0;

            getNormal(hitpoint.x, hitpoint.y, hitpoint.z, ref nx, ref ny, 
                                ref nz);

            inx = shot.getRay().x;
            iny = shot.getRay().y;
            inz = shot.getRay().z;

            tAlgebra.Normalize(ref inx, ref iny, ref inz);
            tAlgebra.Normalize(ref nx, ref ny, ref nz);

            double n1 = 1.00;
            double n2 = 1.33;

            tAlgebra.Refract(n1, n2, -inx, -iny, -inz, nx, ny, nz, 
                               ref rx, ref ry, ref rz);
            tAlgebra.Normalize(ref rx, ref ry, ref rz);

            ray.origin.x = hitpoint.x;
            ray.origin.y = hitpoint.y;
            ray.origin.z = hitpoint.z;
            ray.target.x = (hitpoint.x + rx);
            ray.target.y = (hitpoint.y + ry);
            ray.target.z = (hitpoint.z + rz);

            return ray;
        }

    }
    public class tSphere : tElementBase
    {
        public tSphere(double x, double y, double z, double r, string txmap)
        {
            cx = x;
            cy = y;
            cz = z;
            radius = r;
            if (txmap != null)
            {
                System.Drawing.Image image1 = new Bitmap(txmap);
                imgBitmap = new Bitmap(image1);
            }
        }

        public override tPoint getMapColor(double px, double py, double pz)
        {
            tPoint color = new tPoint(0, 0, 0);

            //double rtx =0, rty = 0, rtz = 0;
            //getNormal(px,py,pz,ref rtx, ref rty, ref rtz);
            px = px - cx;
            py = py - cy;
            pz = pz - cz;

            tAlgebra.RotX(1.5, ref py, ref pz);
            tAlgebra.RotZ(-2.5, ref px, ref py);

            double phi = Math.Acos(pz / radius);
            double S = Math.Sqrt(px * px + py * py);
            double theta;

            if (px > 0)
                theta = Math.Asin(py / S);//Atan(py/px);
            else
                theta = Math.PI - Math.Asin(py / S);// Math.Atan(py / px);

            if (theta < 0) theta = 2.0 * Math.PI + theta;

            double x1 = tAlgebra.GetCoord(0.0, Math.PI * 2.0, 0.0, 
                          imgBitmap.Width - 1, theta);
            double y1 = tAlgebra.GetCoord(0.0, Math.PI, 0.0, 
                          imgBitmap.Height - 1, phi);
            int i1 = (int)x1, j1 = (int)y1;
            if (i1 >= 0 && j1 >= 0 && i1 < imgBitmap.Width && 
                               j1 < imgBitmap.Height)
            {
                Color clr = imgBitmap.GetPixel(i1, j1);
                color.x = clr.R;
                color.y = clr.G;
                color.z = clr.B;
            }

            return color;
        }

        public override void RotateXYZ(double rx, double ry, double rz)
        {
            tAlgebra.RotX(rx, ref cy, ref cz);
            tAlgebra.RotY(ry, ref cx, ref cz);
            tAlgebra.RotZ(rz, ref cx, ref cy);
        }

        public override void RotateZYX(double rx, double ry, double rz)
        {
            tAlgebra.RotZ(rz, ref cx, ref cy);
            tAlgebra.RotY(ry, ref cx, ref cz);
            tAlgebra.RotX(rx, ref cy, ref cz);
        }

        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;
        }
        public override double GetIntersect(double px, double py, double pz, 
                          double x, double y, double z)
        {
            // x-xo 2 + y-yo 2 + z-zo 2 = r 2
            // x,y,z = p+tv 
            // At2 + Bt + C = 0
            double vx = x - px;
            double vy = y - py;
            double vz = z - pz;


            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 && t1 > -1.0E-6) t = t1; else t = t2;
 
                hitpoint.x = px + t * vx;
                hitpoint.y = py + t * vy;
                hitpoint.z = pz + t * vz;

                hitscalar = t;
            }

            return t;
        }

        public override void getNormal(double x, double y, double z, 
                    ref double nx, ref double ny, ref double nz)
        {
            nx = x - cx;
            ny = y - cy;
            nz = z - cz;
        }
        public double cx, cy, cz, radius, clR, clG, clB;

    }
    public class tTriangle : tElementBase
    {
        private tObjectBase objbase;
        public tTriangle(tObjectBase objbase, int va, int vb, int vc, 
                     int ta, int tb, int tc, int na, int nb, int nc, 
                     tMaterial mat, string txmap, bool interpolate)
        {
            this.objbase = objbase;

            if (txmap != null)
            {
                System.Drawing.Image image1 = new Bitmap(txmap);
                imgBitmap = new Bitmap(image1);
            }
            this.va = va;
            this.vb = vb;
            this.vc = vc;
            this.ta = ta;
            this.tb = tb;
            this.tc = tc;
            this.na = na;
            this.nb = nb;
            this.nc = nc;
            this.interpolate = interpolate;

            this.material = mat;
        }

        public override void RotateXYZ(double rx, double ry, double rz)
        {
            Init();
        }
        public override void RotateZYX(double rx, double ry, double rz)
        {
            Init();
        }
        public void Init()
        {
            tnormal = new tPoint(0, 0, 0);
            GetNormal(ref tnormal.x, ref tnormal.y, ref tnormal.z);
        }
        public bool SameSide(double p1x, double p1y, double p1z,
                             double p2x, double p2y, double p2z,
                             double ax, double ay, double az,
                             double bx, double by, double bz)
        {
            double eps = -1.0E-10; // must be negative 
                            // for boundary merge on aliasing
            double cp1x = 0, cp1y = 0, cp1z = 0, cp2x = 0, cp2y = 0,
                   cp2z = 0;
            tAlgebra.Cross3(bx - ax, by - ay, bz - az, p1x - ax,
              p1y - ay, p1z - az, ref cp1x, ref cp1y, ref cp1z);
            tAlgebra.Cross3(bx - ax, by - ay, bz - az, p2x - ax,
              p2y - ay, p2z - az, ref cp2x, ref cp2y, ref cp2z);
            if (tAlgebra.Dot3(cp1x, cp1y, cp1z, cp2x, cp2y, cp2z) > eps)
                return true;
            else
                return false;
        }
        public bool PointInTriangle(double px, double py, double pz)
        {
            if (SameSide(px, py, pz, objbase.point(va).x, 
                    objbase.point(va).y, objbase.point(va).z,
                    objbase.point(vb).x, objbase.point(vb).y, 
                    objbase.point(vb).z, objbase.point(vc).x, 
                    objbase.point(vc).y, objbase.point(vc).z) &&
                SameSide(px, py, pz, objbase.point(vb).x, 
                    objbase.point(vb).y, objbase.point(vb).z,
                    objbase.point(va).x, objbase.point(va).y, 
                    objbase.point(va).z, objbase.point(vc).x, 
                    objbase.point(vc).y, objbase.point(vc).z) &&
                SameSide(px, py, pz, objbase.point(vc).x, 
                    objbase.point(vc).y, objbase.point(vc).z,
                    objbase.point(va).x, objbase.point(va).y, 
                    objbase.point(va).z, objbase.point(vb).x, 
                    objbase.point(vb).y, objbase.point(vb).z))
                return true;
            else
            return false;
        }
        // ray p1, ray p2
        public override double GetIntersect(double p1x, double p1y, 
                                 double p1z,
                                 double p2x, double p2y, double p2z)
        {
            double u = 0;
             double v1x = objbase.point(vc).x - p1x;
             double v1y = objbase.point(vc).y - p1y;
             double v1z = objbase.point(vc).z - p1z;
             double v2x = p2x - p1x;
             double v2y = p2y - p1y;
             double v2z = p2z - p1z;
             double dot1 = tAlgebra.Dot3(tnormal.x, tnormal.y, tnormal.z,
                                         v1x, v1y, v1z);
             double dot2 = tAlgebra.Dot3(tnormal.x, tnormal.y, tnormal.z,
                                         v2x, v2y, v2z);
             if (Math.Abs(dot2) < 1.0E-10)
                 return -1; // division by 0 means parallel
             u = dot1 / dot2;
             // point in triangle?
             if (!PointInTriangle(p1x + u * (p2x - p1x),
                             p1y + u * (p2y - p1y), p1z + u * (p2z - p1z)))
                 return -1;

             hitpoint.x = p1x + u * v2x;
             hitpoint.y = p1y + u * v2y;
             hitpoint.z = p1z + u * v2z;

             hitscalar = u;

            return u;
        }
        private void GetNormal(ref double nx, ref double ny, ref double nz)
        {
            double ux = objbase.point(vc).x - objbase.point(va).x, 
                   uy = objbase.point(vc).y - objbase.point(va).y, 
                   uz = objbase.point(vc).z - objbase.point(va).z;
            double wx = objbase.point(vb).x - objbase.point(va).x, 
                   wy = objbase.point(vb).y - objbase.point(va).y, 
                   wz = objbase.point(vb).z - objbase.point(va).z;

            // u x w
            nx = wz * uy - wy * uz;
            ny = wx * uz - wz * ux;
            nz = wy * ux - wx * uy;
            tAlgebra.Normalize(ref nx, ref ny, ref nz);


        }
        public override void getNormal(double x, double y, double z, 
               ref double nx, ref double ny, ref double nz)
        {
            if (!interpolate)
            {
                nx = -tnormal.x;
                ny = -tnormal.y;
                nz = -tnormal.z;
                return;
            }

             tPoint U = new tPoint(objbase.point(va).x - 
              objbase.point(vb).x, objbase.point(va).y - 
              objbase.point(vb).y, objbase.point(va).z - 
              objbase.point(vb).z);
             tPoint V = new tPoint(objbase.point(vc).x - 
                objbase.point(vb).x, 
                objbase.point(vc).y - objbase.point(vb).y, 
                objbase.point(vc).z - objbase.point(vb).z);
           
             tPoint N = new tPoint(x - objbase.point(vb).x,y - 
               objbase.point(vb).y,z - objbase.point(vb).z);

             double dU = tAlgebra.modv(U.x, U.y, U.z);
             double dV = tAlgebra.modv(V.x, V.y, V.z);
             double dN = tAlgebra.modv(N.x, N.y, N.z);

             tAlgebra.Normalize(ref N.x, ref N.y, ref N.z);
             tAlgebra.Normalize(ref U.x, ref U.y, ref U.z);

             double cost = tAlgebra.Dot3(N.x, N.y, N.z, U.x, U.y, U.z);
             if (cost < 0) cost = 0;
             if (cost > 1) cost = 1;

             double t = Math.Acos(cost);


             double distY = 0, distX = 0;
             distX = dN * Math.Cos(t);
             distY = dN * Math.Sin(t);

             double u = distX/ dU;
             double v = distY/ dV;

             tAlgebra.Normalize(ref objbase.normalVector(na).x, 
              ref objbase.normalVector(na).y, 
              ref objbase.normalVector(na).z);
             tAlgebra.Normalize(ref objbase.normalVector(nb).x, 
              ref objbase.normalVector(nb).y, 
              ref objbase.normalVector(nb).z);
             tAlgebra.Normalize(ref objbase.normalVector(nc).x, 
              ref objbase.normalVector(nc).y, 
              ref objbase.normalVector(nc).z);

             nx = -( (1.0 - (u + v)) * objbase.normalVector(nb).x + 
              objbase.normalVector(na).x * u + 
              objbase.normalVector(nc).x * v);
             ny = -( (1.0 - (u + v)) * objbase.normalVector(nb).y + 
              objbase.normalVector(na).y * u + 
              objbase.normalVector(nc).y * v);
             nz = -( (1.0 - (u + v)) * objbase.normalVector(nb).z + 
              objbase.normalVector(na).z * u + 
              objbase.normalVector(nc).z * v);
        }
        public override tPoint getMapColor(double px, double py, double pz)
        {
            tPoint color = new tPoint(0, 0, 0);

            double dx = imgBitmap.Width;
            double dy = imgBitmap.Height;

            double Ux = 0, Uy = 0, Uz = 0;
            double Vx = 0, Vy = 0, Vz = 0;

            double v2x = 0, v2y = 0, v2z = 0;
            double distp = 0;

            Ux = objbase.point(vc).x - objbase.point(vb).x;
            Uy = objbase.point(vc).y - objbase.point(vb).y;
            Uz = objbase.point(vc).z - objbase.point(vb).z;

            v2x = px - objbase.point(vb).x;
            v2y = py - objbase.point(vb).y;
            v2z = pz - objbase.point(vb).z;
            distp = tAlgebra.modv(objbase.point(vb).x - px, 
               objbase.point(vb).y - py, objbase.point(vb).z - pz);

            Vx = objbase.point(va).x - objbase.point(vb).x;
            Vy = objbase.point(va).y - objbase.point(vb).y;
            Vz = objbase.point(va).z - objbase.point(vb).z;

            double dU = tAlgebra.modv(Ux, Uy, Uz);
            double dV = tAlgebra.modv(Vx, Vy, Vz);


            tAlgebra.Normalize(ref Ux, ref Uy, ref Uz);
            tAlgebra.Normalize(ref v2x, ref v2y, ref v2z);
            double cost = tAlgebra.Dot3(Ux, Uy, Uz, v2x, v2y, v2z);
            double t = Math.Acos(cost);

            double distY = 0, distX = 0;
            distY = dU - distp * Math.Cos(t);
            distX = dV - distp * Math.Sin(t);

            double x1 = 0;
            double y1 = 0;
            y1 = tAlgebra.GetCoord(0, dU, objbase.texturePoint(tc).y * dy, 
                  objbase.texturePoint(tb).y * dy, distY);
            x1 = tAlgebra.GetCoord(0, dV, objbase.texturePoint(ta).x * dx, 
                  objbase.texturePoint(tb).x * dx, distX);

            int i1 = (int)x1, j1 = (int)y1;
            if (i1 >= 0 && j1 >= 0 && i1 < imgBitmap.Width && j1 < 
                  imgBitmap.Height)
            {
                Color clr = imgBitmap.GetPixel(i1, j1);
                color.x = clr.R;
                color.y = clr.G;
                color.z = clr.B;
            }

            return color;
        }

        // Points
        public int va, vb, vc;

        // Texture
        public int ta, tb, tc;  // only x,y counts (discard z)

        // Normals
        public int na, nb, nc;

        public tPoint tnormal;
    }
    public abstract class tObjectBase
    {
        public tObjectBase()
        {
        }

        public tPoint texturePoint(int i)
        {
            return (tPoint)textureArray[i];
        }

        public tPoint point(int i)
        {
            return (tPoint)vertexArray[i];
        }

        public tPoint normalVector(int i)
        {
            return (tPoint)normalArray[i];
        }

        public void normalAdd(int i, tPoint v)
        {
            ((tPoint)normalArray[i]).x += v.x;
            ((tPoint)normalArray[i]).y += v.y;
            ((tPoint)normalArray[i]).z += v.z;
        }

        public tTriangle AddTriangle(int a, int b, int c,
            int ta, int tb, int tc, int na, int nb, int nc,
            tMaterial mat, string txmap, bool interpolate)
        {
            tTriangle tri = new tTriangle(this, a, b, c, ta, tb, tc, 
                    na, nb, nc, mat, txmap, interpolate);
            tri.material = mat;
            tri.Init();
            elementArray.Add(tri);
            return tri;
        }
        public void AddTriangle(tTriangle tri)
        {
            elementArray.Add(tri);
        }
        public void AddSphere(double cx, double cy, double cz, 
                    double radius, tMaterial mat, string txmap)
        {
            tSphere sph = new tSphere(cx, cy, cz, radius, txmap);
            sph.material = mat;
            elementArray.Add(sph);
        }
       
        public void RotateAllVertices(double rx, double ry, double rz)
        {
            for (int i = 0; i < vertexArray.Count; i++)
            {
                tAlgebra.RotX(rx, ref ((tPoint)vertexArray[i]).y, 
                  ref ((tPoint)vertexArray[i]).z);
                tAlgebra.RotY(ry, ref ((tPoint)vertexArray[i]).x, 
                  ref ((tPoint)vertexArray[i]).z);
                tAlgebra.RotZ(rz, ref ((tPoint)vertexArray[i]).x, 
                  ref ((tPoint)vertexArray[i]).y);
            }
        }

        public ArrayList elementArray = new ArrayList();
        public ArrayList vertexArray = new ArrayList();
        public ArrayList normalArray = new ArrayList();
        public ArrayList textureArray = new ArrayList();
    }
    public class tObject : tObjectBase
    {
        // object files are usually formed by a set of triangles
        public void LoadOBJFile(string filename, string txfilename, 
               tMaterial mat, double scale, 
        double mx, double my, double mz, bool interpolate)
        {
            System.IO.FileStream file = new System.IO.FileStream(filename,
            System.IO.FileMode.Open, System.IO.FileAccess.Read);
            System.IO.StreamReader reader = 
                   new System.IO.StreamReader(file);
            string filebody = reader.ReadToEnd();
            file.Close();

            string[] lines = filebody.Split('\n');

            tPoint ta = new tPoint(1, 0, 0);
            textureArray.Add(ta);
            tPoint tb = new tPoint(0, 0, 0);
            textureArray.Add(tb);
            tPoint tc = new tPoint(1, 1, 0);
            textureArray.Add(tc);

            foreach (string line in lines)
            {
                if (line.Length < 1) continue;
                string auxline = line.Replace("  ", " ");
                auxline = auxline.Replace("\r", "");
                auxline = auxline.Replace("\n", "");
                auxline = auxline.TrimEnd();
                if (auxline.Length == 0) continue;
                if (auxline.IndexOf("mtllib") >= 0)
                {
                    auxline = auxline.Replace("mtllib", "");
                }
                else
                    //we do not read the vertex normals, 
                    //they are calculated below
                    if ((auxline[0] == 'v') && (auxline[1] == ' '))
                    {
                        auxline = auxline.Replace("v ", "");
                        string[] points = auxline.Split(' ');
                        tPoint pt = new tPoint(0, 0, 0);
                        pt.x = double.Parse(points[0]) * scale + mx;
                        pt.y = double.Parse(points[1]) * scale + my;
                        pt.z = double.Parse(points[2]) * scale + mz;
                        vertexArray.Add(pt);
                        tPoint nrm = new tPoint(0, 0, 0);
                        normalArray.Add(nrm);
                    }
                    else
                        if (auxline[0] == 'f')
                        {
                            auxline = auxline.Replace("f ", "");
                            string[] auxsub = auxline.Split(' ');


                            if (auxsub.Length == 3)
                            {
                                string[] vx = auxsub[0].Split('/');
                                string[] vy = auxsub[1].Split('/');
                                string[] vz = auxsub[2].Split('/');


                                int va = int.Parse(vx[0]) - 1;
                                int vb = int.Parse(vy[0]) - 1;
                                int vc = int.Parse(vz[0]) - 1;

                                tPoint A = (tPoint)vertexArray[va];
                                tPoint B = (tPoint)vertexArray[vb];
                                tPoint C = (tPoint)vertexArray[vc];


                                int na = va, nb = vb, nc = vc;
                                if (vx.Length == 3)
                                {
                                    na = int.Parse(vx[2]) - 1;
                                    nb = int.Parse(vy[2]) - 1;
                                    nc = int.Parse(vz[2]) - 1;
                                    tPoint nA = (tPoint)normalArray[na];
                                    tPoint nB = (tPoint)normalArray[nb];
                                    tPoint nC = (tPoint)normalArray[nc];
                                    AddTriangle(va, vb, vc, 0, 1, 2, 
                     na, nb, nc, mat, txfilename, interpolate);
                                }
                                else
                                {
                                    tTriangle tri = new tTriangle(this, 
                                        va, vb, vc, 0, 1, 2, 
                                        na, nb, nc, mat, 
                                        txfilename, interpolate);
                                    tri.Init();
                                    AddTriangle(tri);
                                    // update normals
                                    // vertex normals are initially 0,0,0
                                    // each reached vertex makes 
                                    // normal to sum its triangle normal
                                    normalAdd(na, tri.tnormal);
                                    normalAdd(nb, tri.tnormal);
                                    normalAdd(nc, tri.tnormal);
                                }
                            }
                        }
            }
        }


    }


    public class tRay
    {
        public tPoint origin;
        public tPoint target;

        public tRay()
        {
            origin = new tPoint(0, 0, 0);
            target = new tPoint(0, 0, 0);
        }

        public tPoint getRay()
        {
            return new tPoint((target.x - origin.x), (target.y - origin.y), 
                  (target.z - origin.z));
        }
    }

    public class tRayTracer
    {
        // rotation
        public int levels = 3;
        int level = 0;
        System.Collections.ArrayList objectList;
        System.Collections.ArrayList lightsArrayList;


        public tRayTracer(int levels)
        {
            this.levels = levels;
            objectList = new System.Collections.ArrayList();
            lightsArrayList = new System.Collections.ArrayList();
        }

        public void AddLight(ref tPoint light)
        {
            lightsArrayList.Add(light);
        }

        private tElementBase get_first_intersection(tRay shot)
        {
            double eps = 1.0E-10;
            double t = 1.0E10;

            tElementBase objhit = null;

            for (int p = 0; p < (int)objectList.Count; p++)
            {
                tObjectBase objBase = (tObjectBase)objectList[p];

                for (int k = 0; k < (int)objBase.elementArray.Count; k++)
                {
                    tElementBase objn = 
                    (tElementBase)objBase.elementArray[k];

                    double taux = objn.GetIntersect(shot.origin.x,
                                                    shot.origin.y,
                                                    shot.origin.z,
                                                    shot.target.x,
                                                    shot.target.y,
                                                    shot.target.z);

                    if (Math.Abs(taux) <= eps) continue;

                    if (taux > 0 && taux < t)
                    {
                        t = taux;
                        objhit = objn;
                    }
                }
            }
            return objhit;
        }
        public tPoint trace_ray(tRay shot)
        {
            level++;
            tPoint point_color = new tPoint(0, 0, 0),
                   reflect_color = new tPoint(0, 0, 0),
                   refract_color = new tPoint(0, 0, 0),
                   clraux = new tPoint(0, 0, 0);

            if (level > levels)
            {
                level--;
                point_color.x = 0;
                point_color.y = 0;
                point_color.z = 0; 
                return point_color;
            }

            tElementBase obj = get_first_intersection(shot);
          
            if (obj != null)
            {
                Type type = obj.GetType();
                if (type.Name.CompareTo("tSphere") == 0)
                {
                    int sphere = 0;
                    sphere++;
                }

                point_color = obj.get_point_color(lightsArrayList, 
                              shot.getRay());

                tRay rfl = null;
                tRay rfr = null;

                if (obj.material.reflectance > 0)
                {
                    rfl = obj.get_reflected_ray(shot);
                    reflect_color = trace_ray(rfl);
                }
                if (obj.material.alpha > 0)
                {
                    rfr = obj.get_refracted_ray(shot);
                    refract_color = trace_ray(rfr);
                }

                double refl = obj.material.reflectance;
                double refr = obj.material.alpha;
                double ownp = 1.0 - refl;

                clraux.x = (point_color.x * ownp + reflect_color.x * 
                          refl + refract_color.x * refr);
                clraux.y = (point_color.y * ownp + reflect_color.y *
                          refl + refract_color.y * refr);
                clraux.z = (point_color.z * ownp + reflect_color.z * 
                          refl + refract_color.z * refr);

                level--;

                // calculate shadow
                if (obj != null && level == 0)
                {
                    tRay ltShot = new tRay();
                    ltShot.origin = (tPoint)lightsArrayList[0];
                    ltShot.target = obj.hitpoint;
                    tElementBase objS = get_first_intersection(ltShot);

                    if (objS != null && objS != obj && objS.hitscalar>0)
                    {
                        clraux.x *= 0.9;
                        clraux.y *= 0.9;
                        clraux.z *= 0.9;
                    }
                }


                return clraux; // combine with other
            }
            level--;



            return point_color;
        }

        public void AddObject(tObject object_)
        {
            objectList.Add(object_);
        }

        public void RotateAllObjects(double ax, double ay, double az)
        {
            

            for (int p = 0; p < (int)objectList.Count; p++)
            {
                tObjectBase objBase = (tObjectBase)objectList[p];

                objBase.RotateAllVertices(ax, ay, az);

                for (int k = 0; k < (int)objBase.elementArray.Count; k++)
                {
                    tElementBase objn = 
                          (tElementBase)objBase.elementArray[k];
                    objn.RotateXYZ(ax, ay, az);
                }
            }
        }

    }

}

Using the code

Helper funtion (normalizeColor), used to normalize the colors within the valid 0-255 range:

C#
public void normalizeColor(ref tPoint color)
{
    color.x = Math.Min(color.x, 255);
    color.y = Math.Min(color.y, 255);
    color.z = Math.Min(color.z, 255);
    color.x = Math.Max(color.x, 0);
    color.y = Math.Max(color.y, 0);
    color.z = Math.Max(color.z, 0);
}

One sample:

C#
int scrsize = 200; // image size in pixels (for width and height)
Bitmap newBitmap = new Bitmap(scrsize, scrsize,
                   PixelFormat.Format32bppArgb);
Graphics g = Graphics.FromImage(newBitmap);
Rectangle rect = new Rectangle(0, 0, scrsize, scrsize);
double fMax = 4.9; // virtual model size
int rec = 3;  // recursivity
tRayTracer rayTracer = new tRayTracer(rec);
tMaterial mat1 = new tMaterial(0,0,
       0.329412,0.223529,0.027451,
       0.992157,0.941176,0.807843,27.8974,
       0.780392,0.568627,0.113725);

tObject teapot = new tObject();
teapot.LoadOBJFile("./objs/teapot.obj", null, mat1, 1,
              0, -1, -2, true);
rayTracer.AddObject(teapot);
tMaterial mat2 = new tMaterial(0, 0,
       0.029412, 0.023529, 0.327451,
       0.792157, 0.741176, 0.807843, 27.8974,
       0.580392, 0.568627, 0.713725);
tObject sphere1 = new tObject();
sphere1.AddSphere(3, 0, -3, 1, mat2, null);
rayTracer.AddObject(sphere1);
tMaterial mat3 = new tMaterial(0, 0.1,
       0.5, 0.5, 0.5,
       0.792157, 0.741176, 0.807843, 7.8974,
       0.580392, 0.568627, 0.583725);
tObject box = new tObject();
box.LoadOBJFile("./objs/cube.obj", "./images/chess.bmp",
               mat3, 5, 0, 0, 0, false);
rayTracer.AddObject(box);
tPoint light1 = new tPoint(0,0,4.5);
rayTracer.AddLight(ref light1);
rayTracer.RotateAllObjects(-0.1, -0.1, 0);
tPoint color = new tPoint(0, 0, 0);
double deltaP = Math.Abs(tAlgebra.GetCoord(rect.Left,
                        rect.Right,
                       -fMax, fMax, rect.Left + 1) + fMax) / 2.0;
tRay ray = new tRay();
ray.target.z = 0.0;
ray.origin.x = 0;
ray.origin.y = 0;
ray.origin.z = 4.9;

for (int i = rect.Left; i < rect.Right; i++)
    {
        double x = tAlgebra.GetCoord(rect.Left,
          rect.Right, -fMax, fMax, i);

        for (int j = rect.Top; j < rect.Bottom; j++)
        {
            double y = tAlgebra.GetCoord(rect.Top,
              rect.Bottom, fMax, -fMax, j);

            ray.target.x = x; ray.target.y = y;

            color.x = 0; color.y = 0; color.z = 0;
                ray.target.x = x - deltaP; ray.target.y =
                     y - deltaP; ray.target.z = 0.0;
                tPoint colorA = rayTracer.trace_ray(ray);
                normalizeColor(ref colorA);

                ray.target.x = x - deltaP; ray.target.y =
                     y + deltaP; ray.target.z = 0.0;
                tPoint colorB = rayTracer.trace_ray(ray);
                normalizeColor(ref colorB);

                ray.target.x = x + deltaP; ray.target.y =
                     y - deltaP; ray.target.z = 0.0;
                tPoint colorC = rayTracer.trace_ray(ray);
                normalizeColor(ref colorC);

                ray.target.x = x + deltaP; ray.target.y =
                     y + deltaP; ray.target.z = 0.0;
                tPoint colorD = rayTracer.trace_ray(ray);
                normalizeColor(ref colorD);

                ray.target.x = x; ray.target.y = y;
                     ray.target.z = 0.0;
                tPoint colorE = rayTracer.trace_ray(ray);
                normalizeColor(ref colorE);

                ray.target.x = x; ray.target.y = y - deltaP;
                    ray.target.z = 0.0;
                tPoint colorF = rayTracer.trace_ray(ray);
                normalizeColor(ref colorF);

                ray.target.x = x; ray.target.y = y + deltaP;
                    ray.target.z = 0.0;
                tPoint colorG = rayTracer.trace_ray(ray);
                normalizeColor(ref colorG);

                ray.target.x = x - deltaP; ray.target.y = y;
                    ray.target.z = 0.0;
                tPoint colorH = rayTracer.trace_ray(ray);
                normalizeColor(ref colorH);

                ray.target.x = x + deltaP; ray.target.y = y;
                    ray.target.z = 0.0;
                tPoint colorI = rayTracer.trace_ray(ray);
                normalizeColor(ref colorI);

                color.x = (colorA.x + colorB.x + colorC.x +
                           colorD.x + colorE.x + colorF.x +
                           colorG.x + colorH.x + colorI.x) / 9.0;
                color.y = (colorA.y + colorB.y + colorC.y +
                           colorD.y + colorE.y + colorF.y +
                           colorG.y + colorH.y + colorI.y) / 9.0;
                color.z = (colorA.z + colorB.z + colorC.z +
                           colorD.z + colorE.z + colorF.z +
                           colorG.z + colorH.z + colorI.z) / 9.0;

            normalizeColor(ref color);
            Color colorpx = Color.FromArgb((int)color.x,
                (int)color.y, (int)color.z);
            newBitmap.SetPixel(i, j, colorpx);
        }
    }
    newBitmap.Save("oneshot.bmp");

License

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


Written By
CEO
Brazil Brazil
"A well written code is self explanatory" - Anonymous Programmer
Founder @TIHUNTER.COM.BR
Linkedin Profile

Comments and Discussions

 
QuestionNo Source code. The download files are not latest. They are from your article 4. Pin
Member 1252384623-Apr-19 2:04
Member 1252384623-Apr-19 2:04 
GeneralMy vote of 4 Pin
fredatcodeproject9-Apr-13 1:57
professionalfredatcodeproject9-Apr-13 1:57 
GeneralMy vote of 5 Pin
Hassan Oumar Mahamat15-Mar-13 13:17
professionalHassan Oumar Mahamat15-Mar-13 13:17 
QuestionLatest source code and library Pin
andalmeida6-Jan-12 5:16
andalmeida6-Jan-12 5:16 
QuestionReally nice article! Pin
obazavil15-Dec-08 2:18
obazavil15-Dec-08 2:18 
AnswerRe: Really nice article! Pin
andalmeida15-Dec-08 2:29
andalmeida15-Dec-08 2:29 
AnswerRe: Really nice article! Pin
andalmeida15-Dec-08 2:38
andalmeida15-Dec-08 2:38 
GeneralRe: Really nice article! Pin
obazavil15-Dec-08 16:16
obazavil15-Dec-08 16:16 
Questionproject file? Pin
elwolv18-Sep-07 18:12
elwolv18-Sep-07 18:12 
AnswerRe: project file? Pin
andalmeida19-Sep-07 2:06
andalmeida19-Sep-07 2:06 
Question[Message Deleted] Pin
R_L_H13-Sep-07 9:46
R_L_H13-Sep-07 9:46 
AnswerRe: Unrelated to shadows... Pin
andalmeida13-Sep-07 9:53
andalmeida13-Sep-07 9:53 
AnswerRe: Unrelated to shadows... Pin
andalmeida16-Sep-07 5:37
andalmeida16-Sep-07 5:37 
GeneralGreat Articles but Pin
Dileep.M11-Sep-07 21:14
Dileep.M11-Sep-07 21:14 
GeneralRe: Great Articles but Pin
andalmeida12-Sep-07 2:16
andalmeida12-Sep-07 2:16 
GeneralProject files added (compiling) Pin
andalmeida11-Sep-07 8:09
andalmeida11-Sep-07 8:09 
GeneralOh, my, Pin
Muammar©10-Sep-07 20:01
Muammar©10-Sep-07 20:01 
God! OMG | :OMG:




Smile: A curve that can set a lot of things straight!
(\ /)
(O.o)
(><)

GeneralSource code Pin
andalmeida6-Sep-07 4:58
andalmeida6-Sep-07 4:58 
GeneralKhazbak asks... Pin
Genevieve Sovereign6-Sep-07 4:45
Genevieve Sovereign6-Sep-07 4:45 
GeneralRe: Khazbak asks... Pin
andalmeida6-Sep-07 4:50
andalmeida6-Sep-07 4:50 
GeneralImprovements Pin
andalmeida6-Sep-07 2:36
andalmeida6-Sep-07 2:36 
GeneralRe: Improvements Pin
Pete O'Hanlon6-Sep-07 2:55
mvePete O'Hanlon6-Sep-07 2:55 
GeneralRe: Improvements Pin
Geeky Monkey Russ11-Sep-07 2:56
Geeky Monkey Russ11-Sep-07 2:56 
GeneralRe: Improvements Pin
andalmeida11-Sep-07 6:59
andalmeida11-Sep-07 6:59 
GeneralRe: Improvements Pin
Geeky Monkey Russ11-Sep-07 22:32
Geeky Monkey Russ11-Sep-07 22:32 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.