Im ersten Teil dieser Artikelreihe haben wir das Interface „IRayTraceable“, welches die Schnittstelle der Szene zur RayTraceCamera aus Teil 2 darstellt, definiert. Jedes Objekt, welches „IRayTraceable“ implemetiert, kann von der RayTraceCamera als Szene verwendet werden.
public interface IRayTraceable { Vector3D DirectionToSun { get; set; } Color Background { get; set; } double MaxLightening { get; set; } // Gültige Werte von 0 (Originalfarbe) bis 1 (jede Farbe wird bis auf Weiss aufgehellt) double MaxDarkening { get; set; } // Gültige Werte von 0 (Originalfarbe) bis -1 (jede Farbe wird bis auf Schwarz abgedunkelt) PointOnPrimitive RayTrace(Ray ray); bool RayTraceHits(Ray ray); // Liefert true, wenn der Ray auf ein beliebiges Primitive trifft. }
Wir werden uns nun mit der Klasse „Scene“ beschäftigen, welche eine Implementierung dieses Interfaces darstellt:
public class Scene : IRayTraceable { public Color Background { get; set; } public Vector3D DirectionToSun { get; set; } public double MaxLightening { get; set; } // Gültige Werte von 0 (Originalfarbe) bis 1 (jede Farbe wird bis auf Weiss aufgehellt) public double MaxDarkening { get; set; } // Gültige Werte von 0 (Originalfarbe) bis -1 (jede Farbe wird bis auf Schwarz abgedunkelt) public List<Primitive>; Primitives = new List<Primitive>(); public Scene() { Background = Color.SkyBlue; DirectionToSun = new Vector3D(1, 1, 1).normalize(); MaxLightening = 0.8; MaxDarkening = -0.8; } }
Der Konstruktor setzt Standardwerte für alle Properties aus dem Interface. Weiterhin bekommt die Klasse die Liste „Primitives“, in der alle Primitive der Szene abgelegt werden sollen. Nun fehlen noch die Methoden „RayTrace“ und „RayTraceHits“ aus dem Interface. Die Methode „RayTrace“ soll ermitteln ob und auf welches Primitiv ein Ray trifft. Zurückgegeben wird ein PointOnPrimitive. Sollte der Ray auf mehrere Primitive treffen, so wird nur der PointOnPrimitive zurückgegeben, welcher der Kamera am nächsten ist, denn alle dahinterliegenden Primitive sind für die Kamera nicht sichtbar. Es gibt mehrere Möglichkeiten das entsprechende Primitiv zu ermitteln. Leider sind effiziente Algorithmen für diese Aufgabe nicht einfach zu implementieren. Daher wollen wir uns mit diesem sehr einfachen, aber auch sehr langsamen Algorithmus begnügen: Wir überprüfen einfach immer alle Primitive auf Schnittpunkte mit dem Ray. Dies hat den Nachteil, dass auch Primitive aus weit entfernten Bildbereichen immer mit geprüft werden. Zum Beschleunigen des Raytracings gibt es in dieser Funktion ein großes Potential.
public PointOnPrimitive RayTrace(Ray ray) { PointOnPrimitive nearestPoint = null; foreach (var p in Primitives) { PointOnPrimitive q = p.RayTrace(ray); if (nearestPoint == null || (q != null && q.distanceToRayStart < nearestPoint.distanceToRayStart)) nearestPoint = q; } return nearestPoint; }
Die Methode „RayTraceHits“ soll nur ein true oder false zurück liefern, abhängig davon ob ein beliebiges Primitiv von dem Ray getroffen wird, oder nicht. Wir können sie also ähnlich der Methode „RayTrace“ implementieren, jedoch reicht es, wenn wir die Schleife über alle Primitive abbrechen, sobald wir den ersten Schnittpunkt mit einem Primitiv gefunden haben:
public bool RayTraceHits(Ray ray) { foreach (var p in Primitives) { PointOnPrimitive pop = p.RayTrace(ray); if (pop != null) return true; // Schnittpunkt mit Primitiv gefunden } return false; }
Nachdem wir diese beiden Methoden zur Klasse „Scene“ hinzugefügt haben, ist sie einsatzbereit.
Pingback: Raytracing Teil 2: Die Kamera | Volkers Blog