Simple Ray Tracing in C# Part VII (Shadows)






4.84/5 (45 votes)
Adding shadows to Simple Ray Tracing in C#
![]() Fig. 1 - Basic sample of shadows
|
![]() 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.
- Simple Ray Tracing in C#
- Simple Ray Tracing in C# Part II (Triangles intersection)
- Simple Ray Tracing in C# Part III (Reflection)
- Simple Ray Tracing in C# Part IV (Anti-Aliasing)
- Simple Ray Tracing in C# Part V (Texture Mapping)
- Simple Ray Tracing in C# Part VI (Vertex Normal Interpolation)
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.
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");