65.9K
CodeProject is changing. Read more.
Home

Simple Ray Tracing in C# Part VII (Shadows)

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.84/5 (45 votes)

Sep 5, 2007

GPL3

1 min read

viewsIcon

124543

downloadIcon

295

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.

...
   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

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

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:

        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:

            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");