<pragma:references('System.Drawing.dll')>
<pragma:target('exe')>
using System, System.Reflection;
using System.Drawing;
namespace Freya.Demos.RayTracing;
public
// GEOMETRY ----------------------------------------
Tolerance = static class
public
const Epsilon: Double = 0.000001;
end;
Vector = record
public
X, Y, Z: Double;
constructor(X, Y, Z: Double);
property Length: Double =>
(X.Sqr + Y.Sqr + Z.Sqr).Sqrt;
property Sqr: Double =>
X.Sqr + Y.Sqr + Z.Sqr;
method Dot(V: Vector): Double;
method Mirror(Axis: Vector): Vector;
method Normalize: Vector;
method ToString: String; override;
public
static Null: Vector = new Vector(0, 0, 0); readonly;
static XRay: Vector = new Vector(1, 0, 0); readonly;
static YRay: Vector = new Vector(0, 1, 0); readonly;
static ZRay: Vector = new Vector(0, 0, 1); readonly;
method+(V1, V2: Vector): Vector;
method-(V1, V2: Vector): Vector;
method*(V1, V2: Vector): Double;
method^(V1, V2: Vector): Vector;
method*(Factor: Double; V: Vector): Vector;
end;
Matrix = record
public
A11, A12, A13: Double;
A21, A22, A23: Double;
A31, A32, A33: Double;
constructor(A11, A12, A13, A21, A22, A23, A31, A32, A33: Double);
constructor(Eye, LookAt, Sky: Vector);
method Rotate(V: Vector): Vector;
method Rotate(X, Y, Z: Double): Vector;
method RotateNormalize(X, Y, Z: Double): Vector;
method Transpose;
end;
<DefaultMember('Items')>
Ray = sealed class
public
Origin, Direction: Vector;
property Items[Time: Double]: Vector;
begin
Result.X := Origin.X + Time * Direction.X;
Result.Y := Origin.Y + Time * Direction.Y;
Result.Z := Origin.Z + Time * Direction.Z;
end;
end;
HitInfo = record
public
HitPoint, Normal: Vector;
Material: IMaterial;
Time: Double;
end;
// COLOR MODEL -------------------------------------
Pixel = record
public
R, G, B: Single;
constructor(R, G, B: Single);
constructor(R, G, B: Double);
constructor(Color: System.Drawing.Color);
constructor(Intensity: Double);
method Add(F: Single; P: Pixel);
method Clip;
method Interpolate(Amount: Single; Other: Pixel): Pixel;
method Multiply(F: Single; P: Pixel): Pixel;
method ToColor: System.Drawing.Color;
method ToString: String; override;
public
static Black: Pixel = new Pixel; readonly;
static White: Pixel = new Pixel(1, 1, 1); readonly;
static Red: Pixel = new Pixel(1, 0, 0); readonly;
static Green: Pixel = new Pixel(0, 1, 0); readonly;
static Blue: Pixel = new Pixel(0, 0, 1); readonly;
method+(P1, P2: Pixel): Pixel;
method*(P1, P2: Pixel): Pixel;
method*(Factor: Single; P: Pixel): Pixel;
end;
<DefaultMember('Items')>
PixelMap = sealed class
public
constructor(Height, Width: Integer);
constructor(Colors: Array@2[Pixel]);
property Width, Height: Integer; readonly;
property Items[Row, Col: Integer]: Pixel;
method ToBitmap: System.Drawing.Bitmap;
end;
// BASIC INTERFACES --------------------------------
IShape = interface
method ShadowTest(R: Ray): Boolean;
method HitTest(R: Ray; MaxTime: Double; var Info: HitInfo): Boolean;
end;
ISampler = interface
method Render(Scene: Scene): PixelMap;
method Render(Scene: Scene; Row, Col: Integer): Pixel;
end;
ICamera = interface
property PrimaryRay: Ray; readonly;
property Location, Target, Up: Vector;
property Height, Width: Integer;
method GetRay(Row, Col: Integer);
end;
ILight = interface
property Color: Pixel; readonly;
method GetDirection(L: Vector): Vector;
method Initialize(Scene: Scene);
method Intensity(L: Vector): Single;
end;
IMaterial = interface
method GetColor(Location: Vector): Pixel;
method Radiance(
Location, Normal, Reflection: Vector;
Light: ILight; Color: Pixel): Pixel;
method Reflection(
var Location: Vector; Cosine: Double; out Filter: Pixel): Single;
end;
Scene = sealed class
public
constructor(
Sampler: ISampler; Camera: ICamera; Root: IShape;
Lights: Array[ILight]);
property Sampler: ISampler; readonly;
property Camera: ICamera; readonly;
property Root: IShape; readonly;
property Lights: Array[ILight]; readonly;
method Render: PixelMap;
method Render(Row, Col: Integer): Pixel;
static method Render(Sampler: ISampler; Camera: ICamera; Root: IShape;
Lights: Array[ILight]): PixelMap;
end;
// SCENE IMPLEMENTATIONS ----------------------------
BasicSampler = sealed class(ISampler)
public
constructor(Bounces: Integer; MinWeight: Double);
property Bounces: Integer; readonly;
property MinWeight: Double; readonly;
method Render(Scene: Scene): PixelMap;
method Render(Scene: Scene; Row, Col: Integer): Pixel;
protected
method Trace(R: Ray): Pixel; virtual;
end;
PerspectiveCamera = sealed class(ICamera)
public
constructor(
Location, Target, Up: Vector;
Angle: Double; Height, Width: Integer);
constructor(
Location, Target: Vector;
Angle: Double; Height, Width: Integer);
constructor(
Location, Target: Vector;
Height, Width: Integer);
property PrimaryRay: Ray; readonly;
property Location, Target, Up: Vector;
property Height, Width: Integer;
method GetRay(Row, Col: Integer);
end;
PointLight = sealed class(ILight)
public
constructor(Location: Vector; C: Pixel);
property Color: Pixel; readonly;
method GetDirection(L: Vector): Vector;
method Initialize(Scene: Scene);
method Intensity(L: Vector): Single;
end;
// SHAPES IMPLEMENTATIONS ---------------------------
Sphere = sealed class(IShape)
public
constructor(Center: Vector; Radius: Double; Material: IMaterial);
end;
Cylinder = sealed class(IShape)
public
constructor(Bottom, Top: Vector; Radius: Double; Material: IMaterial);
end;
Union = sealed class(IShape)
public
constructor(Shapes: Array[IShape]);
constructor(Shape1, Shape2: IShape);
constructor(Shape1, Shape2, Shape3: IShape);
constructor(Shape1, Shape2, Shape3, Shape4: IShape);
end;
// MATERIALS ----------------------------------
Plastic = sealed class(IMaterial)
public
constructor(Color: Pixel; Reflection, PhongAmount, PhongSize: Double);
constructor(Color: Pixel; Reflection: Double);
constructor(Color: Pixel);
end;
Metal = sealed class(IMaterial)
public
constructor(Color: Pixel;
MinReflection, MaxReflection, Diffuse: Double;
PhongAmount, PhongSize: Double);
constructor(Color: Pixel;
MinReflection, MaxReflection, Diffuse: Double);
constructor(Color: Pixel;
MinReflection, MaxReflection: Double);
constructor(Color: Pixel; MinReflection: Double);
end;
implementation for Vector is
constructor(X, Y, Z: Double);
begin
Self.X := X;
Self.Y := Y;
Self.Z := Z;
end;
method Dot(V: Vector): Double =>
X * V.X + Y * V.Y + Z * V.Z;
method Mirror(Axis: Vector): Vector =>
(Self - (2.0 * Dot(Axis)) * Axis).Normalize;
method Normalize: Vector =>
using len := (X.Sqr + Y.Sqr + Z.Sqr).Sqrt do
if len = 0 then Self
else new Vector(X / len, Y / len, Z / len);
method ToString: String =>
String.Format('[{0},{1},{2}]', X, Y, Z);
method+(V1, V2: Vector): Vector;
begin
Result.X := V1.X + V2.X;
Result.Y := V1.Y + V2.Y;
Result.Z := V1.Z + V2.Z;
end;
method-(V1, V2: Vector): Vector;
begin
Result.X := V1.X - V2.X;
Result.Y := V1.Y - V2.Y;
Result.Z := V1.Z - V2.Z;
end;
method*(V1, V2: Vector): Double =>
V1.X * V2.X + V1.Y * V2.Y + V1.Z * V2.Z;
method^(V1, V2: Vector): Vector =>
new Vector(
V1.Y * V2.Z - V1.Z * V2.Y,
V1.Z * V2.X - V1.X * V2.Z,
V1.X * V2.Y - V1.Y * V2.X);
method*(Factor: Double; V: Vector): Vector;
begin
Result.X := Factor * V.X;
Result.Y := Factor * V.Y;
Result.Z := Factor * V.Z;
end;
implementation for Matrix is
constructor(A11, A12, A13, A21, A22, A23, A31, A32, A33: Double);
begin
Self.A11 := A11; Self.A12 := A12; Self.A13 := A13;
Self.A21 := A21; Self.A22 := A22; Self.A23 := A23;
Self.A31 := A31; Self.A32 := A32; Self.A33 := A33;
end;
constructor(Eye, LookAt, Sky: Vector);
begin
A13 := LookAt.X - Eye.X;
A23 := LookAt.Y - Eye.Y;
A33 := LookAt.Z - Eye.Z;
var dist := 1.0 / (A13.Sqr + A23.Sqr + A33.Sqr).Sqrt;
A13 *= dist; A23 *= dist; A33 *= dist;
A11 := Sky.Y * A33 - Sky.Z * A23;
A21 := Sky.Z * A13 - Sky.X * A33;
A31 := Sky.X * A23 - Sky.Y * A13;
dist := 1.0 / (A11.Sqr + A21.Sqr + A31.Sqr).Sqrt;
A11 *= dist; A21 *= dist; A31 *= dist;
A12 := A23 * A31 - A33 * A21;
A22 := A33 * A11 - A13 * A31;
A32 := A13 * A21 - A23 * A11;
end;
method Rotate(V: Vector): Vector =>
new Vector(
A11 * V.X + A12 * V.Y + A13 * V.Z,
A21 * V.X + A22 * V.Y + A23 * V.Z,
A31 * V.X + A32 * V.Y + A33 * V.Z);
method Rotate(X, Y, Z: Double): Vector =>
new Vector(
A11 * X + A12 * Y + A13 * Z,
A21 * X + A22 * Y + A23 * Z,
A31 * X + A32 * Y + A33 * Z);
method RotateNormalize(X, Y, Z: Double): Vector =>
(new Vector(
A11 * X + A12 * Y + A13 * Z,
A21 * X + A22 * Y + A23 * Z,
A31 * X + A32 * Y + A33 * Z)).Normalize;
method Transpose;
begin
var T := A12; A12 := A21; A21 := T;
T := A13; A13 := A31; A31 := T;
T := A23; A23 := A32; A32 := T;
end;
implementation for Pixel is
constructor(R, G, B: Single);
begin
Self.R := Math.Min(R, 1.0F);
Self.G := Math.Min(G, 1.0F);
Self.B := Math.Min(B, 1.0F);
end;
constructor(R, G, B: Double);
begin
Self.R := Single(Math.Min(R, 1.0));
Self.G := Single(Math.Min(G, 1.0));
Self.B := Single(Math.Min(B, 1.0));
end;
constructor(Color: System.Drawing.Color);
begin
Self.R := Color.R / 255F;
Self.G := Color.G / 255F;
Self.B := Color.B / 255F;
end;
constructor(Intensity: Double);
begin
Self.R := Single(Math.Min(Intensity, 1.0));
Self.G := Self.R;
Self.B := Self.R;
end;
method Add(F: Single; P: Pixel);
begin
R += F * P.R;
G += F * P.G;
B += F * P.B;
end;
method Clip;
begin
if R < 0.0F then R := 0.0F else if R > 1.0F then R := 1.0F;
if G < 0.0F then G := 0.0F else if G > 1.0F then G := 1.0F;
if B < 0.0F then B := 0.0F else if B > 1.0F then B := 1.0F;
end;
method Interpolate(Amount: Single; Other: Pixel): Pixel =>
new Pixel(
R + (Other.R - R) * Amount,
G + (Other.G - G) * Amount,
B + (Other.B - B) * Amount);
method Multiply(F: Single; P: Pixel): Pixel;
begin
Result.R := F * R * P.R;
Result.G := F * G * P.G;
Result.B := F * B * P.B;
end;
method ToColor: System.Drawing.Color =>
System.Drawing.Color.FromArgb(255,
Integer(R * 255F), Integer(G * 255F), Integer(B * 255F));
method ToString: String =>
String.Format('<{0}/{1}/{2}>', R, G, B);
method+(P1, P2: Pixel): Pixel;
begin
Result.R := P1.R + P2.R;
Result.G := P1.G + P2.G;
Result.B := P1.B + P2.B;
end;
method*(P1, P2: Pixel): Pixel;
begin
Result.R := P1.R * P2.R;
Result.G := P1.G * P2.G;
Result.B := P1.B * P2.B;
end;
method*(Factor: Single; P: Pixel): Pixel;
begin
Result.R := Factor * P.R;
Result.G := Factor * P.G;
Result.B := Factor * P.B;
end;
implementation for PixelMap is
Colors: Array@2[Pixel];
constructor(Height, Width: Integer);
begin
Self.Height := Height;
Self.Width := Width;
Self.Colors := new Array@2[Pixel](Height, Width);
end;
constructor(Colors: Array@2[Pixel]);
begin
Self.Height := Colors.GetUpperBound(0) + 1;
Self.Width := Colors.GetUpperBound(1) + 1;
Self.Colors := Colors;
end;
property Items(Row, Col: Integer): Pixel;
begin
Result := Colors[Row, Col];
end;
property Items(Row, Col: Integer; Value: Pixel);
begin
Colors[Row, Col] := Value;
end;
method ToBitmap: System.Drawing.Bitmap;
begin
Result := new System.Drawing.Bitmap(Width, Height);
try
for row := 0 to Height - 1 do
for col := 0 to Width - 1 do
Result.SetPixel(col, row,
Colors[Height - row - 1, col].ToColor);
except
Result.Dispose;
raise;
end;
end;
implementation for Scene is
constructor(
Sampler: ISampler; Camera: ICamera; Root: IShape;
Lights: Array[ILight]);
begin
Self.Sampler := Sampler;
Self.Camera := Camera;
Self.Root := Root;
Self.Lights := Lights ?? new Array[ILight](0);
end;
method Render: PixelMap => Sampler.Render(Self);
method Render(Sampler: ISampler; Camera: ICamera; Root: IShape;
Lights: Array[ILight]): PixelMap =>
(new Scene(Sampler, Camera, Root, Lights)).Render;
method Render(Row, Col: Integer): Pixel =>
Sampler.Render(Self, Row, Col);
implementation for BasicSampler is
Root: IShape;
Lights: Array[ILight];
constructor(Bounces: Integer; MinWeight: Double);
begin
Self.Bounces := Bounces;
Self.MinWeight := MinWeight;
end;
method Initialize(Scene: Scene);
begin
Self.Root := Scene.Root;
Self.Lights := Scene.Lights;
for light in Self.Lights do
light.Initialize(Scene);
end;
method Render(Scene: Scene): PixelMap;
begin
Initialize(Scene);
var Camera := Scene.Camera;
var Wdth := Camera.Width;
var Pixels := new Array@2[Pixel](Camera.Height, Wdth);
for Row: Integer := 0 to Camera.Height - 1 do
begin
for Col: Integer := Wdth - 1 downto 0 do
begin
Camera.GetRay(Row, Col);
Pixels[Row, Col] := Trace(Camera.PrimaryRay);
end;
end;
Result := new PixelMap(Pixels);
end;
method Render(Scene: Scene; Row, Col: Integer): Pixel;
begin
Initialize(Scene);
Scene.Camera.GetRay(Row, Col);
Result := Trace(Scene.Camera.PrimaryRay);
end;
method Trace(R: Ray): Pixel;
var
info: HitInfo;
lightFilter: Pixel;
reflection: Vector;
begin
var bncs := Self.Bounces;
var weight := 1.0F;
var filter := Pixel.White;
while Root.HitTest(R, Double.MaxValue, var info) do
begin
var location := R[info.Time];
var cosine := R.Direction.Dot(info.Normal);
reflection.X := R.Direction.X - 2 * cosine * info.Normal.X;
reflection.Y := R.Direction.Y - 2 * cosine * info.Normal.Y;
reflection.Z := R.Direction.Z - 2 * cosine * info.Normal.Z;
var mat := info.Material;
var surface := mat.GetColor(location);
// Multiply the surface color by the ambient light factor.
var color := 0.2F * surface;
for light in Self.Lights do
begin
var factor: Single := light.Intensity(location);
if factor > 0.0F then
color.Add(factor, mat.Radiance(
location, info.Normal, reflection, light, surface));
end;
Result += color.Multiply(weight, filter);
bncs--;
if bncs <= 0 then Break;
weight *= mat.Reflection(var location, cosine, out lightFilter);
if weight < MinWeight then Break;
filter *= lightFilter;
R.Origin := location;
R.Direction := reflection;
end;
Result.Clip;
end;
implementation for PerspectiveCamera is
Transform: Matrix;
Angle, Distance: Double;
A, BRow, BCol: Double;
constructor(
Location, Target, Up: Vector;
Angle: Double; Height, Width: Integer);
begin
Self.Location := Location;
Self.Target := Target;
var diff := Target - Location;
if (Up ^ Diff).Length < Tolerance.Epsilon then
if (Vector.ZRay ^ Diff).Length < Tolerance.Epsilon then
Up := Vector.YRay
else
Up := Vector.ZRay;
Self.Up := Up;
Self.Angle := Angle;
Self.Height := Height;
Self.Width := Width;
Self.PrimaryRay := new Ray;
Self.Transform := new Matrix(Location, Target, Up);
Self.Distance := (Location - Target).Length;
var pixelSize := Distance * (Angle * Math.PI / 360).Tan;
if Width >= Height then
pixelSize /= Width
else
pixelSize /= Height;
Self.A := 2 * pixelSize;
Self.BRow := pixelSize * (1 - Height);
Self.BCol := pixelSize * (1 - Width);
end;
constructor(Location, Target: Vector;
Angle: Double; Height, Width: Integer)
: Self(Location, Target, Vector.YRay, Angle, Height, Width);
constructor(Location, Target: Vector; Height, Width: Integer)
: Self(Location, Target, Vector.YRay, 60.0, Height, Width);
method GetRay(Row, Col: Integer);
begin
PrimaryRay.Origin := Location;
PrimaryRay.Direction := Transform.RotateNormalize(
A * Col + BCol, A * Row + BRow, Distance);
end;
implementation for PointLight is
Location: Vector;
TestRay: Ray;
Root: IShape;
constructor(Location: Vector; C: Pixel);
begin
Self.Location := Location;
Self.Color := C;
Self.TestRay := new Ray;
end;
method GetDirection(L: Vector): Vector =>
(Location - L).Normalize;
method Initialize(Scene: Scene);
begin
Self.Root := Scene.Root;
end;
method Intensity(L: Vector): Single;
begin
TestRay.Origin := Self.Location;
TestRay.Direction := Self.Location - L;
Result := if Root.ShadowTest(TestRay) then 0.0F else 1.0F;
end;
implementation for Union is
Shapes: Array[IShape];
constructor(Shapes: Array[IShape]);
begin
Self.Shapes := Shapes;
end;
constructor(Shape1, Shape2: IShape);
begin
Self.Shapes := [Shape1, Shape2];
end;
constructor(Shape1, Shape2, Shape3: IShape);
begin
Self.Shapes := [Shape1, Shape2, Shape3];
end;
constructor(Shape1, Shape2, Shape3, Shape4: IShape);
begin
Self.Shapes := [Shape1, Shape2, Shape3, Shape4];
end;
method IShape.ShadowTest(R: Ray): Boolean;
begin
for shape in Shapes do
if shape.ShadowTest(R) then
begin
Result := true;
Exit;
end;
Result := false;
end;
method IShape.HitTest(
R: Ray; MaxTime: Double; var Info: HitInfo): Boolean;
begin
for I: Integer := 0 to Shapes.Length - 1 do
if Shapes[I].HitTest(R, MaxTime, var Info) then
begin
for J: Integer := I + 1 to Shapes.Length - 1 do
Shapes[J].HitTest(R, Info.Time, var Info);
Result := true;
Exit;
end;
Result := false;
end;
implementation for Sphere is
Center: Vector;
Radius, R2, RInv: Double;
Material: IMaterial;
constructor(Center: Vector; Radius: Double; Material: IMaterial);
begin
Self.Center := Center;
Self.Radius := Radius;
Self.R2 := Radius.Sqr;
Self.RInv := 1.0 / Radius;
Self.Material := Material;
end;
method IShape.ShadowTest(R: Ray): Boolean;
begin
var delta := Center - R.Origin;
var ray2 := 1.0 / R.Direction.Sqr;
var beta := ray2 * R.Direction.Dot(delta);
var discr := beta.Sqr - (delta.Sqr - R2) * ray2;
if discr < 0.0 then
Result := false
else
begin
discr := discr.Sqrt;
var t := beta - discr;
if t < Tolerance.Epsilon then
begin
t := beta + discr;
Result := t >= Tolerance.Epsilon and t <= 1.0;
end
else
Result := t <= 1.0;
end;
end;
method IShape.HitTest(
R: Ray; MaxTime: Double; var Info: HitInfo): Boolean;
var
delta: Vector;
beta, discr, t: Double;
begin
delta := Self.Center - R.Origin;
beta := R.Direction.Dot(delta);
discr := beta.Sqr - (delta.Sqr - R2);
if discr < 0.0 then
Result := false
else
begin
discr := discr.Sqrt;
t := beta - discr;
if t < Tolerance.Epsilon then
begin
t := beta + discr;
if t < Tolerance.Epsilon then
begin
Result := false;
Exit;
end;
end;
if t > MaxTime then
begin
Result := false;
Exit;
end;
Info.Time := t;
Info.HitPoint := R[t];
Info.Normal := RInv * (Info.HitPoint - Center);
Info.Material := Material;
Result := true;
end;
end;
implementation for Cylinder is
Bottom, Top, BNormal, TNormal: Vector;
Radius, Height, R2, RInv: Double;
Transform, Inverse: Matrix;
Material: IMaterial;
constructor(Bottom, Top: Vector; Radius: Double; Material: IMaterial);
begin
Self.Bottom := Bottom;
Self.Top := Top;
Self.Radius := Radius;
Self.Material := Material;
Self.R2 := Radius * Radius;
Self.RInv := 1.0 / Radius;
Self.Height := (Top - Bottom).Length;
var v := (Top - Bottom).Normalize;
var proj := (1.0 - v.Y.Sqr).Sqrt;
if proj.Abs > Tolerance.Epsilon then
Self.Transform := new Matrix(
v.Z / proj, v.X, v.X * v.Y / proj,
0.0, v.Y, -proj,
-v.X / proj, v.Z, v.Y * v.Z / proj)
else if v.Y > 0.0 then
Self.Transform := new Matrix(+1, 0, 0, 0, +1, 0, 0, 0, +1)
else
Self.Transform := new Matrix(+1, 0, 0, 0, -1, 0, 0, 0, +1);
Inverse := Transform;
Self.BNormal := new Vector(-Inverse.A12, -Inverse.A22, -Inverse.A32);
Self.TNormal := new Vector(Inverse.A12, Inverse.A22, Inverse.A32);
Inverse.Transpose;
end;
method IShape.ShadowTest(R: Ray): Boolean;
begin
var Org := Inverse.Rotate(R.Origin - Bottom);
var Dir := Inverse.Rotate(R.Direction);
var tt0 := Tolerance.Epsilon;
var tt1 := 1.0;
var temp := 1.0 / Dir.Y;
var t0 := -Org.Y * temp;
var t1 := (Height - Org.Y) * temp;
if temp >= 0.0 then
begin
if t0 > tt0 then tt0 := t0;
if t1 < 1.0 then tt1 := t1;
end
else
begin
if t1 > tt0 then tt0 := t1;
if t0 < 1.0 then tt1 := t0;
end;
if tt0 > tt1 then
Exit;
temp := 1.0 / (Dir.X.Sqr + Dir.Z.Sqr);
var beta := -(Dir.X.Sqr + Dir.Z.Sqr) * temp;
temp := beta.Sqr - (Org.X.Sqr + Org.Z.Sqr - R2) * temp;
if temp < 0.0 then
Exit;
temp := temp.Sqrt;
t0 := beta - temp;
t1 := beta + temp;
if t0 > tt0 then tt0 := t0;
if t1 < tt1 then tt1 := t1;
Result := tt0 <= tt1;
end;
method IShape.HitTest(
R: Ray; MaxTime: Double; var Info: HitInfo): Boolean;
var
tt0, tt1: Double;
begin
var Org := Inverse.Rotate(R.Origin - Bottom);
var Dir := Inverse.Rotate(R.Direction);
// Compute time bounds for the Y axis.
var temp := 1.0 / Dir.Y;
if temp >= 0 then
begin
tt0 := -Org.Y * temp;
tt1 := (Height - Org.Y) * temp;
end
else
begin
tt1 := -Org.Y * temp;
tt0 := (Height - Org.Y) * temp;
end;
// Compute time bounds for the XZ plane.
temp := 1.0 / (1.0 - Dir.Y.Sqr);
var beta := -(Dir.X * Org.X + Dir.Z * Org.Z) * temp;
temp := beta * beta - (Org.X.Sqr + Org.Z.Sqr - R2) * temp;
if temp < 0 then
Exit;
temp := temp.Sqrt;
var t0 := beta - temp;
var t1 := beta + temp;
if t0 > tt0 then tt0 := t0;
if t1 < tt1 then tt1 := t1;
if tt0 > tt1 then
Exit;
// Check the first intersection.
if tt0 < Tolerance.Epsilon then
begin
tt0 := tt1;
if tt0 < Tolerance.Epsilon then
Exit;
end;
if tt0 > MaxTime then
Exit;
// Fill the hit information record.
Info.Time := tt0;
beta := Org.Y + tt0 * Dir.Y;
if beta < Tolerance.Epsilon then
Info.Normal := BNormal
else if Height - beta < Tolerance.Epsilon then
Info.Normal := TNormal
else
Info.Normal := Transform.Rotate(
(Org.X + tt0 * Dir.X) * RInv,
0.0,
(Org.Z + tt0 * Dir.Z) * RInv);
Info.HitPoint := R[tt0];
Info.Material := material;
Result := true;
end;
implementation for Plastic is
Color: Pixel;
Reflection: Single;
PhongAmount, PhongSize: Double;
constructor(Color: Pixel; Reflection, PhongAmount, PhongSize: Double);
begin
Self.Color := Color;
Self.Reflection := Math.Max(Math.Min(1.0F, Single(Reflection)), 0.0F);
Self.PhongAmount := PhongAmount;
Self.PhongSize := PhongSize + 1.0;
end;
constructor(Color: Pixel; Reflection: Double)
: Self(Color, Reflection, 0.0, 1.0);
constructor(Color: Pixel) : Self(Color, 0.0, 0.0, 1.0);
method IMaterial.GetColor(Location: Vector): Pixel => Color;
method IMaterial.Radiance(
Location, Normal, Reflection: Vector;
Light: ILight; Color: Pixel): Pixel;
begin
var lightRay := Light.GetDirection(Location);
var cosine := Single(lightRay.Dot(Normal));
if cosine > 0.0F then
Result := Light.Color.Multiply(cosine, Color);
if PhongAmount > 0.0 then
begin
var f := lightRay.Dot(Reflection);
if f > 0.0 then
Result.Add(
Single(PhongAmount * Math.Pow(f, PhongSize)),
Light.Color);
end;
end;
method IMaterial.Reflection(
var Location: Vector; Cosine: Double; out Filter: Pixel): Single;
begin
Filter := Pixel.White;
Result := Reflection;
end;
implementation for Metal is
Color, LightFilter: Pixel;
MinReflection, MaxReflection, Delta: Single;
Diffuse, PhongAmount, PhongSize: Double;
constructor(Color: Pixel;
MinReflection, MaxReflection, Diffuse: Double;
PhongAmount, PhongSize: Double);
begin
Self.Color := Color;
Self.MinReflection :=
Math.Max(Math.Min(1.0F, Single(MinReflection)), 0.0F);
Self.MaxReflection :=
Math.Max(Math.Min(1.0F, Single(MaxReflection)), 0.0F);
Self.Diffuse := Diffuse;
Self.PhongAmount := PhongAmount;
Self.PhongSize := PhongSize + 1.0;
Self.Delta := Self.MaxReflection - Self.MinReflection;
var maxClr := Math.Max(Color.R, Math.Max(Color.G, Color.B));
if maxClr < 1.0 / 255.0 then
Self.LightFilter := Pixel.White
else
Self.LightFilter := new Pixel(
1.0F - Color.R / maxClr,
1.0F - Color.G / maxClr,
1.0F - Color.B / maxClr);
end;
constructor(Color: Pixel; MinReflection, MaxReflection, Diffuse: Double)
: Self(Color, MinReflection, MaxReflection, Diffuse, 0.0, 1.0);
constructor(Color: Pixel; MinReflection, MaxReflection: Double)
: Self(Color, MinReflection, MaxReflection, 1.0, 0.0, 1.0);
constructor(Color: Pixel; MinReflection: Double)
: Self(Color, MinReflection, 1.0, 1.0, 0.0, 1.0);
method IMaterial.GetColor(Location: Vector): Pixel => Color;
method IMaterial.Radiance(
Location, Normal, Reflection: Vector;
Light: ILight; Color: Pixel): Pixel;
begin
var lightRay := Light.GetDirection(Location);
var cosine := Single(Diffuse * lightRay.Dot(Normal));
if cosine > 0.0F then
Result := cosine * Color;
if PhongAmount > 0.0 then
begin
var f := lightRay.Dot(Reflection);
if f > 0.0 then
Result += Single(PhongAmount * Math.Pow(f, PhongSize)) *
Color.Interpolate(cosine * cosine.Sqr.Sqr, Light.Color);
end;
end;
method IMaterial.Reflection(
var Location: Vector; Cosine: Double; out Filter: Pixel): Single;
begin
if Cosine < 0.0 then
begin
var c5 := 1.0F + Single(Cosine);
c5 *= c5.Sqr.Sqr;
// Interpolate between lightFilter and Pixel.White.
Filter := new Pixel(
1.0F - c5 * LightFilter.R,
1.0F - c5 * LightFilter.G,
1.0F - c5 * LightFilter.B);
// Schlick's aproximation for Fresnel coefficient.
Result := MinReflection + c5 * Delta;
end
else
begin
Filter := new Pixel(
1.0F - LightFilter.R, 1.0F - LightFilter.G, 1.0F - LightFilter.B);
Result := MaxReflection;
end;
end;
implementation
method CreateMaterial1(AColor: System.Drawing.Color): IMaterial =>
new Metal(new Pixel(AColor), 0.2, 1.0, 1.0, 0.4, 10.0);
method CreateMaterial2(AColor: System.Drawing.Color): IMaterial =>
new Plastic(new Pixel(AColor), 0.6);
method CreateScene1: Scene =>
new Scene(
new BasicSampler(10, 0.001),
new PerspectiveCamera(
new Vector(0, 0, -10), Vector.Null, 200, 320),
new Union(
[
new Sphere(new Vector( 0.0,0,0), 1.0,
CreateMaterial1(System.Drawing.Color.Orchid)),
new Sphere(new Vector(-1.7,0,0), 0.7,
CreateMaterial1(System.Drawing.Color.RoyalBlue)),
new Sphere(new Vector(+1.7,0,0), 0.7,
CreateMaterial1(System.Drawing.Color.RoyalBlue)),
new Sphere(new Vector(-2.9,0,0), 0.5,
CreateMaterial1(System.Drawing.Color.ForestGreen)),
new Sphere(new Vector(+2.9,0,0), 0.5,
CreateMaterial1(System.Drawing.Color.ForestGreen))
]),
[new PointLight(new Vector(10, 10, -10), Pixel.White)]);
method CreateScene2: Scene =>
new Scene(
new BasicSampler(12, 0.001),
new PerspectiveCamera(
new Vector(0, 0, -5), Vector.Null, 300, 300),
new Union(
new Sphere(new Vector(+1.0,-1.0,0.0), 1.41,
CreateMaterial2(System.Drawing.Color.Blue)),
new Sphere(new Vector(-1.0,+1.0,0.0), 1.41,
CreateMaterial2(System.Drawing.Color.Green))),
[new PointLight(new Vector(-2, 3, -10), new Pixel(0.6))]);
method Main;
begin
try
var time := Environment.TickCount;
var map := CreateScene1.Render;
time := Environment.TickCount - time;
Console.WriteLine(String.Format(
'Time ellapsed: {0} milliseconds.', time));
if map <> nil then
map.ToBitmap.Save('c:\xsbench.bmp');
except on e: Exception do
Console.WriteLine(e.Message);
Console.WriteLine(e.StackTrace);
end;
end;
end.