Raytracing in Spielen III: Quake Wars mit Raytracing

 3/5
Daniel Pohl
99 Kommentare

Transparenzen

Anstatt überall richtige 3D-Geometrie zu benutzen, findet man in Spielen häufig Annäherungen an reale Geometrie mittels 2D-Vierecken (bzw. zwei Dreiecken), auf die eine Textur mit Transparenzwerten angewendet wird. Bei deren Berechnung gibt es in beiden Renderingverfahren Vorteile, aber auch Herausforderungen.

Beispiel einer teilweise transparenten Textur eines Astes auf einer Oberfläche bestehend aus zwei Dreiecken
Beispiel einer teilweise transparenten Textur eines Astes auf einer Oberfläche bestehend aus zwei Dreiecken

Es ist nicht gerade einfach, die korrekten Schatten von teilweise transparenten Dreiecken in einem Rasterisierer zu erzeugen. Das hierfür am häufigsten benutzte Verfahren in der Rasterisierung names „Shadow Mapping“ liefert keine zusätzliche Information, die beim Erzeugen von unterschiedlich intensiven Schatten aus Transparenzen helfen würde. Daher werden solche Schatten oft fest in die Textur hinein gebacken. Deswegen können sie sich auch nicht ändern, wenn man die Lichtposition verändert (wenn man die Beleuchtung einer Szene zum Beispiel von Sonnenaufgang auf Sonnenuntergang umstellt).

Mit Raytracing geht's jedoch einfach. Wenn der Schattenstrahl ein Objekt trifft, so liest man an dieser Stelle den Transparenzwert der Textur aus und verfolgt den Strahl weiter, falls das Textursample an der Stelle transparent war. Dies ermöglicht interessante Special-Effects, erzeugt aber auch neue Herausforderungen. Das folgende Bild zeigt beispielsweise einen animierten „Kraftfeld-Shader“, der verschieden intensive Schatten am Boden wirft – abhängig davon, wie transparent das Kraftfeld an der Stelle ist.

Verschiedene Schatten, abhängig von den Transparenzen des Kraftfelds (1)
Verschiedene Schatten, abhängig von den Transparenzen des Kraftfelds (1)
Verschiedene Schatten, abhängig von den Transparenzen des Kraftfelds (2)
Verschiedene Schatten, abhängig von den Transparenzen des Kraftfelds (2)

Ein weiterer Vorteil im Falle von teilweise transparenten Objekten in einem Raytracer ist, dass sie nicht wie bei einem Rasterisierer nach ihrer Tiefe sortiert werden müssen, bevor man sie rendern kann. Das macht es für den Entwickler einfacher, kommt im Gegenzug jedoch mit erhöhten Kosten beim Rendern daher.

Wenn ein Strahl solch' eine transparente Oberfläche trifft, dann muss ein weiter Strahl von diesem Auftreffpunkt weiter in dieselbe Richtung geschossen werden (der ursprüngliche Strahl wird fortgesetzt). Wenn dies einmal passiert, dann ist der Einfluss auf die rechnerische Komplexität der Szene vernachlässigbar. Aber was passiert, wenn man zehn oder noch mehr dieser Flächen hintereinander aufgereiht hat? Dies kann zum Beispiel bei einem Baum passieren, der in der Computerwelt oft aus einem Mix vieler teilweise transparenter Vierecke besteht.

Modell eines Baums bestehend aus vielen teilweise transparenten Vierecken. (Bild erzeugt in Right Hemisphere Deep Exploration)
Modell eines Baums bestehend aus vielen teilweise transparenten Vierecken. (Bild erzeugt in Right Hemisphere Deep Exploration)
Dasselbe Modell des Baums mit Texturen und den Transparenzwerten. (Bild erzeugt in Right Hemisphere Deep Exploration)
Dasselbe Modell des Baums mit Texturen und den Transparenzwerten. (Bild erzeugt in Right Hemisphere Deep Exploration)

Als wir eine Vielzahl dieser Bäume in der Außenwelt renderten, wurde dies schnell zu unserem größten Flaschenhals. Durch mehrere Optimierungszyklen kamen wir auf einige Verbesserungen. Folgende Maßnahmen waren dabei am wichtigsten für die Performance:

  • Vermeide es, jedes Mal einen neuen Strahl nach dem Auslesen des Transparenzwertes zu schießen. Stattdessen benutze den alten Strahl, dieses mal mit dem Ursprung am vorherigen Auftreffpunkt und folge ihm einfach weiter in dieselbe Richtung.
  • Signalisiere mit einem „Flag“, ob eine Textur Transparenzen benutzt oder nicht. Falls nicht, dann braucht man hier keine weiteren Strahlen durch die potentiellen Transparenzen schießen – es gibt auf der ganzen Textur keine. Der Baumstamm ist ein Beispiel einer opaken Textur, die sich inmitten vieler anderer teilweise transparenter Texturen befindet.
  • Verringere die Anzahl der Strahlen, die zusammen gebündelt werden. In vielen Fällen kann man mit gebündelten Strahlen, die nahezu denselben Pfad haben, große Performancegewinne erreichen. Wenn allerdings ein Teil des Bündels eine andere Oberfläche trifft als der andere Teil, so führt dies zu einem Overhead beim Reorganisieren und Aufteilen dieser Bündel. Beim Rendern dieser Bäume wurde dieser Overhead so groß, dass er am Ende die Performance ausgebremst hat.

Trotz vieler Optimierungen und Tweaks ist das Rendern der Bäume immer noch aufwendig. Wir haben die Kosten, um einen einzelnen Pixel zu rendern, in einem Farbschema visualisiert. Ein blauer Pixel repräsentiert dabei einen schnell zu berechnenden Pixel, ein roter einen langsamen (d.h. mit viel Rechenaufwand). Ein intensives Rot ist bedeutet mehr Aufwand als ein leichtes Rot. Wie man in folgendem Bild sehen kann, ist das Rendern der Bäume immer noch teurer als zum Beispiel das Rendern der spiegelnden Wasseroberfläche oder anderer Teile der Szene. Es ist also klar notwendig, dass in diesem Bereich des Renderns mit Raytracing noch weitere Forschungsarbeit geleistet wird, um noch mehr Verbesserungen zu finden.

Performance kodiert in Farben. Blau benötigt weniger Zeit als Rot.
Performance kodiert in Farben. Blau benötigt weniger Zeit als Rot.