Im ersten Teil dieser Artikelserie haben wir die beiden abstrakten Klassen Primitive und PointOnPrimitive definiert. Ein Primitiv ist allgemein eine elementare geometrische Form, welche vom Raytracer direkt verarbeitet werden kann. Alle komplexeren Formen und Objekte müssen aus mehreren Primitiven zusammengesetzt werden. Schreiben wir nun unsere erste Primitiv-Klasse namens „Plane“, welche von „Primitive“ erbt. Ein Plane ist eine unendlich ausgedehnte Ebene, welche ausgehend von dem Stützpunkt „Origin“ von den beiden Vektoren V und W aufgespannt wird:
public class Plane : Primitive // Eine unendliche Ebene, die von V und W aufgespannt wird { protected Vector3D Origin { get; private set; } protected Vector3D W { get; private set; } // Vektor in W-Richtung (lokales Koordinatensystem auf der Ebene) [Daumen rechte Hand] protected Vector3D V { get; private set; } // Vektor in V-Richtung (lokales Koordinatensystem auf der Ebene) [Mittelfinger rechte Hand]. Entspricht der unteren Kante des TexturBitmap protected Vector3D Normal { get; private set; } // Einheitsvektor Senkrecht zu V und W [Zeigefinger Rechte-Hand-Regel] protected double CosAlpha { get; private set; } // Cosinus des von V und W eingeschlossenen Winkels. protected double Vlength { get; private set; } protected double Wlength { get; private set; } public Plane(Vector3D Origin, Vector3D V, Vector3D W, Color color) : base(color) { this.Origin = Origin; this.V = V; this.W = W; Normal = V.cross(W).normalize(); CosAlpha = (V * W) / (V.length() * W.length()); Vlength = V.length(); Wlength = W.length(); }
Dem Konstruktor wird neben den drei Vektoren Origin, V und W auch noch eine Farbe (Color) übergeben. Dies ist die Farbe des Primitivs. Im Konstruktor werdennoch einige Werte berechnet, welche des Öfteren benötigt werden: Der Normalenvektor „Normal“, welcher senkrecht auf der Ebne steht, der Kosinus CosAlpha des zwischen V und W eingeschlossenen Winkels und die Längen der Vektoren V und W.
Bevor wir die Methode „RayTrace“ aus der abstrakten Klasse „Primitive“ in die Klasse „Plane“ implementieren, erstellen wir noch die Klasse „PointOnPlane“, welche eine Implementierung der abstrakten Klasse „PointOnPrimitive“ darstellt. Neben dem Konstruktor gibt es in dieser Klasse noch die Methoden „GetColor“, welche die Farbe des PointOnPrimitive liefert, „GetNormal“, welche den Normalenvektor der Ebene zurück gibt und „GetPrimitive“, welche das Primitiv selbst, auf dem sich der PointOnPrimitive befindet, zurück liefert. Weiterhin können wir einen PoitOnPrimitive mit der Methode ToPointInSpace in XYZ-Koordinaten umwandeln. Die Klassenvariablen v und w sind dabei die Vielfachen der Vektoren V und W der Ebene. Die XYZ-Koordinaten lassen sich also so berechnen: PointInSpace = Origin + V*v + W*w .
public class PointOnPlane : PointOnPrimitive { private double v; // in Vielfachen von V.length() private double w; // in Vielfachen von W.length() private Plane plane; public PointOnPlane(double v, double w, Plane plane, double distanceToRayStart) : base(distanceToRayStart) { this.v = v; this.w = w; this.plane = plane; } public override Color GetColor() { return plane.color; } public override Vector3D GetNormal() { return plane.Normal; } public override Vector3D ToPointInSpace() { return plane.Origin + (plane.V * v) + (plane.W * w); } public override Primitive GetPrimitive() { return plane; } } // class PointOnPlane
Um den Schnittpunkt einer Ebene mit einem Ray zu ermitteln müssen wir das folgende lineare Gleichungssystem nach den skalaren Variablen v, w und d lösen:
V*v + W*w + rayDirection*d = rayStart – Origin
Die Klasse Vector3D stellt die Methode „cramer“ bereit, welche ein lineares Gleichungssystem mit Hilfe der Cramerschen Regel löst. Wenn das Gleichungssystem lösbar ist, gibt „cramer“ true zurück. Wenn sich der Punkt dann noch vor der Kamera (und nicht dahinter) befindet, erstellt „Raytrace“ einen neuen PointOnPlane als Rückgabewert.
public override PointOnPrimitive RayTrace(Ray ray) { double v; double w; double d; if (Vector3D.cramer(V, W, ray.Direction, ray.Start - Origin, out v, out w, out d)) { if (-d > 0) return new PointOnPlane(v, w, this, -d); } return null; // Ray und Plane sind parallel, oder der Schnittpunkt liegt in negative Richtung }
Mit der Methode „RayTrace“ können wir ein Plane-Primitiv also fragen, ob und an welcher Stelle es von einem Ray getroffen wird. Damit ist auch diese Klasse fertig.
Pingback: RayTracing Teil 5: Dreiecke, Trapeze und Rauten als weitere ebene Primitive | Volkers Blog
Pingback: Raytracing Teil 3: Die Szene | Volkers Blog