Empfehlungen zum Bau von Szenerie-Objekten
Gerade beim Bau von Szenerieobjekten sollte besonders auf die Performance (Framerate, Ladezeiten, Speicherbedarf) geachtet werden. In diesem Artikel werden dem Entwickler in dieser Hinsicht einige Empfehlungen mit auf den Weg gegeben.
Allgemeines
Zunächst sei festgehalten, dass sich der Artikel vor allem mit der Performance befasst und nur einen groben Rahmen und Hintergrundinformationen liefern. Präzise Richtlinien wie Texturauflösungen, Polygon-Zahlen u.Ä. können und wollen wir nicht vorgeben, da diese immer sehr stark davon abhängen, welche Prioritäten der Modellierer festlegt und natürlich auch, wie groß seine Zielgruppe sein soll bzw. wie sehr er auf schlechtere Rechenleistungen Rücksicht nehmen möchte.
Der Artikel stellt meinen aktuellen Kenntnisstand dar und ist mit bestem Wissen und Gewissen verfasst. Die von mir geschilderten Interaktionen in LOTUS und die qualitativen Fakten sind naturgemäß wesentlich verlässlicher zu verstehen als meine Einschätzungen und Abwägungen aufgrund derselben.
Der aktuelle Stand ist Februar 2018; zur Zeit fehlen insbesondere hinsichtlich der Darstellung entfernter Kacheln noch wesentliche Algorithmen; die Ideen sind zwar ausgereift, aber dennoch ist die Umsetzbarkeit noch nicht bekannt.
Polygonzahlen, Texturauflösungen und Detaillierungsgrad
Es gibt niemals einen Grund, mit Polygonzahlen und Auflösungen verschwenderisch zu sein, d.h. sich keine Gedanken zu machen und einfach die Stellschrauben blind oder nach simplen/sinnlosen Vorgaben ("Texturen mindestens 1024x1024" oder "Kreise mindestens mit 24 Eckpunkten") einzustellen. Bei der Wahl der jeweiligen Detaillierung sollten folgende Punkte stets beachtet werden:
Technisch:
- Heutige Grafikkarten rechnen wesentlich länger an den Materialeigenschaften, als an den Polygonen.
- Für die Framerate ist es unerheblich, wie groß Texturen sind. Der Renderer arbeitet ohnehin pro Bildschirm-Pixel und greift jeweils auf eine bestimmte Stelle im Textur-Speicherbereich zu. Ob dieser Speicherbereich (entsprechend der Auflösung) groß oder klein ist, ist dabei egal. Der Zugriff auf einer 3 TB Festplatte ist ja auch genauso schnell wie auf eine 1 TB-Festplatte - und nicht etwa dreimal so lange!
- Meshs und Texturen müssen im Grafikspeicher liegen, damit sie gerendert werden können! LOTUS erlaubt dem Nutzer aber das Laden der Texturen mit geringeren Auflösungen; daher ist es den Nutzern schlechterer Grafikkarten durchaus möglich, ursprünglich hochauflösende designte Texturen zu laden.
- Texturen sind im Grafikspeicher kritischer als Meshs - eine einzige 1024x1024-Textur enthält über eine Million Pixel – ein Vertex verbraucht so viel, wie nur ein paar Pixel - in diesen Größenordnungen bewegen wir uns also erst bei hundertausenden von Vertices.
- Nicht zu vergessen sind aber neben der Performance und dem Grafikspeicherbedarf auch die Ladezeiten! Der Speicherbedarf auf der Festplatte dagegen ist heutzutage schlichtweg irrelevant.
- Jeder Objekt-Typ ist mit seinem Mesh und seinen Texturen nur einmal im Grafikspeicher, selbst wenn sich dutzende Objekte dieses Typs in der Nähe des Users befinden. Gerendert werden aber natürlich dennoch alle dutzend Objekte, der Framerate ist es egal, ob es 20 Objekte desselben Typs oder unterschiedlicher Typen gerendert werden. Dem Grafikspeicher aber durchaus nicht!
Strategisch:
- Immer berücksichtigen, wie lange, wie oft und wie nah der Nutzer dem jeweiligen Modell-Bereich kommt! Einem Haltestellenhäuschen kommt der User deutlich näher als einem Springbrunnen, der in der naheliegenden Fußgängerzone steht. Auch wird sich eine Haltestelle oder (rote) Ampel wesentlich länger im Blickfeld des Users befinden, als ein Stromkasten oder eine Werbe-Uhr, die einfach irgendwo entlang der Strecke steht.
- Immer überlegen, aus welchen Blickwinkeln welche Polygone nötig sind! Häuser in Nebenstraßen werden fast nie gesehen, Eckhäuser vor allem frontal (nämlich der Teil, der Richtung Querstraße "zeigt") und Reihenhäuser wiederum schräg/seitlich. Dementsprechend sind von der Texturauflösung her Eckhäuser kritischer als Reihenhäuser, bei Reihenhäusern ist es aber durchaus interessant, ob sie mit Balkonen ausgestattet sind und wie diese von der Seite texturiert sind. Dass Rückseiten dementsprechend besonders sparsam ausgestaltet werden sollten, ist wohl selbstverständlich. Eigentlich könnte man sie auch weglassen - aber falls man sie dann mal aus einem ungewöhnlichen Winkel (F3er-Sicht oder von weitem durch eine andere Baulücke) sieht, dann würde eine geringe Detaillierung zwar kaum auffallen, sehr wohl aber ein "Loch"...
- Weitere Kriterien sind der Kontrast gegenüber dem Hintergrund und auch, wie sehr das Objekt die Aufmerksamkeit auf sich zieht. Ampeln werden besonders oft und intensiv betrachtet, Haltestellen und Verkehrszeichen schon weniger und Stromkästen, Parkbänke und Mülleimer dienen einfach der Gesamtatmosphäre, werden aber sicher nur sehr selten direkt betrachtet.
- Die (üblicherweise maximale) sichtbare Größe eines Objektes definiert außerdem eine absolute Obergrenze für eine sinnvolle Texturauflösung oder Objektdetaillierung: Wenn ein Polygon üblicherweise maximal 20% der Bildschirmhöhe einnimmt, dann wird es bei HD-Auflösung maximal 216 Pixel groß erscheinen, bei 4K das doppelte. Es wäre also Verschwendung, dieses Polygon mit mehr als 1024 Pixeln zu mappen - außer, wenn man die seltenen Fälle abfangen möchte, in denen das Objekt doch näher (und damit größer auf dem Bildschirm) ist. Aber dafür sollte es schon einen sehr guten Grund geben... ;-) Eine typische Ausnahme hierfür sind Gebäude-Texturen, an denen man dicht vorbei fährt - denn diese überschreiten dann den Bildschirmausschnitt erheblich. Mit Mesh-Detaillierung verhält es sich ähnlich: Erreicht ein kreisförmiges Gebilde später nur maximal eine Größe von 10 Pixeln, dann wird es kaum noch erkennbar sein, dass es eigentlich ein Achteck ist. Nebenbei sei angemerkt, dass der Map-Editor umgekehrt die Gleise extrem oft unterteilt, weil man aus der Fahrerperspektive sehr schnell durchschauen kann, dass Bögen eigentlich eckig sind...
Drawcalls
Ein Drawcall ist ein elementarer Rendervorgang, bei dem eine Gruppe von Dreiecken mit denselben Materialeigenschaften gerendert wird. Auch wenn nur ein einziges Objekt über verschiedene Materialien verfügt, sind auch entsprechend viele Drawcalls nötig. Es dauert wesentlich länger, wenn sich das Rendern von einer bestimmten Anzahl von Dreiecken über mehrere Drawcalls erstreckt, als wenn dieselbe Anzahl von Dreiecken im Rahmen eines einzelnen Drawcalls gerendert wird.
Da ein Drawcall aber immer nur mit einem Material und einer Textur arbeitet, gibt es immer mindestens soviele Drawcalls wie Texturen. Aus diesem Grunde lohnt es sich, möglichst wenige Einzeltexturen innerhalb eines Objektes zu verwenden.
Grundregel
Dies bedeutet im Allgemeinen, dass wenige große Texturen besser sind als viele kleine. Im Zweifelsfall lassen sich kleine Texturen auf große "Sammeltexturen" zusammenfassen.
Ausnahmen
Es gibt aber auch Ausnahmen, in denen die Vorteile anderer Strategien überwiegen:
Falls sich viele verschiedene Gebäude dieselbe Dachtextur teilen, dann hat das zwei Vorteile: Die Daten der Dachtextur müssen nur einmal im Speicher gehalten werden (und sind nicht Teil der jeweiligen individuellen Texturen, die dadurch mehr Speicher je Gebäudetyp benötigen). Wenn LOTUS die Gebäude intern zu einem Objekt zusammenfasst, dann stellen die (vielen) Dächer der Gebäude nur noch ein Drawcall dar. Wiederholungstexturen können günstiger sein und sind nicht ohne Kniffe mit Sammeltexturen vereinbar. Siehe hierzu das nächste Kapitel.
Splines
Splines werden in LOTUS nicht direkt gerendert, sondern immer pro Kachel und pro Spline-Klasse (Normal, Oberfläche, Markierung) zusammengefasst.
Da Splines direkt in Blender erstellt werden, muss - abgesehen von den Eigenschaften des Grundmodells - keine besondere Rücksicht auf die Texturaufteilung genommen werden. Wenn das Grundmodell in Blender "funktioniert", dann "funktioniert" es auch als Spline in LOTUS.
Daraus ergibt sich, das sich Splines derselben Klasse mit denselben Materialeigenschaften, die auch auf denselben Kacheln zum Einsatz kommen, dieselbe(n) Textur(en) teilen sollten. Beispielsweise ist es sinnvoll, die Texturen der zusammen auftretenden Bordstein- und Gehwegssplines auf einer Textur zusammenzufassen.
Vorteile und Probleme von Wiederholungstexturen
Mit "Wiederholungstexturen" sind jene Texturen gemeint, die "zu klein" gemappt werden und sich dadurch auf der gemappten Fläche wiederholen; ein Beispiel wäre eine Dachziegel-Textur, die selbst nur wenige Dachziegeln enthält und sich über das gesamte Dach mehrfach wiederholt. Hierdurch lassen sich bei vergleichsweise kleinem Textur-Speicherbedarf große Flächen hochauflösend mappen. Das Konzept lässt sich aber nicht ohne weiteres mit den Sammeltexturen vereinen.
Eine effektive Lösung ist das Unterteilen des Daches in kleinere Kacheln, die dann jeweils nur einfach gemappt werden. Zwar nimmt die Komplexität des Daches dann deutlich zu, heutzutage ist aber - wie oben erwähnt - der Renderaufwand vor allem abhängig von den Materialeigenschaften, den zu rendernden Bildschirmpixeln und den Drawcalls, aber fast gar nicht mehr von der Komplexität des Meshs, zumal wir uns bei Gebäuden auf extrem niedrigem Niveau bewegen.
Diese Herangehensweise erlaubt uns nun also die "Pseudo-Wiederholung" von Sammeltexturen und damit gleichzeitig eine Reduzierung des Texturspeicherbedarfs (oder Erhöhung der Auflösung) und eine Reduktion der Drawcalls.
Beispielsweise benutzen wir für unsere Stadt-Reihenhäuser eine große Sammeltextur, worauf sich zwei verschiedene Rückseitentexturen, zwei verschiedene fensterlose Seitenwände, vier Dachziegeltexturen, eine Dachpappentextur und mehrere Dachobjekt-Texturen (Schornsteine, Dachluken usw.) befinden. Jedes Standardhaus besteht also nur aus zwei Drawcalls: Einmal die individuelle Textur für die Front und dann die Sammeltextur für alles andere.
Aber Achtung:
Möchte man Fensterfronten auf dieselbe Weise texturieren, dann darf man nicht die Nachttexturen außer Acht lassen! Extrembeispiel wäre, wenn man einen Plattenbau (mit seiner sich extrem wiederholender Fassade) mit nur einem Fenstersegment texturiert. Nachts würden dann entweder alle Fenster leuchten oder gar keins! Auch wenn es nur wenige Fenster sind (2 x 2) kommt es dennoch schnell zu Musterbildungen.
Eine Lösung ist, dass das sich wiederholende Segment deutlich größer gewählt wird (z.B. vier Stockwerke und acht Fenster nebeneinander), sodass sich zwar immer noch Muster bilden, diese aber eher verschmerzbar sind.
Sofern die Fenster jedoch ohnehin auf separaten Polygonen/Texturen liegen, dann kann man je eine nachtleuchtende und eine nicht-nachtleuchtende Version des Fensters nebeneinander auf einer gemeinsamen Textur platzieren. Und je nach "Zufall" kann man dann mal die linke Variante oder die rechte Variante verwenden und somit nachts ein unregelmäßiges Bild erzeugen.
Ganz wichtig
Damit die hier vorgestellten Optimierung hinsichtlich der Drawcalls funktionieren, müssen die Materialeigenschaften auch identisch sein! Innerhalb des Objektes verwendet man idealerweise direkt dasselbe Material, objektübergreifend (z.B. Splines) muss man gezielt darauf achten, dass Materialtyp und dessen Parameter exakt gleich eingestellt sind. Nur dann gelingt das Zusammenfassen zu einem Drawcall seitens LOTUS!
Wahl des Texturformates
LOTUS kann sowohl Bitmaps-Texturen (*.bmp) als auch DXT-komprimierte Texturen (*.dds) importieren.
DXT-komprimierte Texturen haben den Vorteil, dass sie nicht nur auf der Festplatte, sondern auch im Grafikspeicher in komprimierter Form vorliegen und somit weit weniger Platz verbrauchen. Nachteil ist jedoch einerseits, dass die Komprimierung verlustbehaftet und andererseits nur 16 Alphakanal-Stufen möglich sind (gegenüber 256 bei Bitmaps).
Bitmap-Texturen werden vom Content-Tool verlustfrei komprimiert, müssen aber im Grafikspeicher dekomprimiert werden.
JPG-Dateien werden nicht unterstützt, was aber insofern verschmerzt werden kann, weil sie für das Kopieren in den Grafikspeicher ebenfalls dekomprimiert werden müssen und somit nur einen Vorteil hinsichtlich des Festplattenspeichers bieten (der im Allgemeinen unkritisch ist). Der Grafikspeicherbedarf ist hingegen identisch.
Im Zuge der Vorbereitung der Texturen des zu entwickelnden 3D-Objektes ist daher im Hinterkopf zu behalten, dass JPG-Dateien zwar allgemein einfacher zu handhaben sind (schneller zu kopieren, öffnen, speichern...), sie aber nach dem Import nach LOTUS keinerlei Vorteile haben.
LODs
LOD bedeutet "Level of Detail" und steht für ein Verfahren zur Optimierung der Ablaufgeschwindigkeit bei weiter entfernteren Objekten. Hierzu wird zunächst das Objekt normal erstellt. Dann werden reduzierte Varianten erstellt und zusätzlich abgespeichert. Hierbei können
- die Anzahl der Texturen, ggf. die Auflösung (erfordert zusätzlichen Texturspeicher),
- die Komplexität / die Anzahl der Meshs und/oder
- die Komplexität / die Anzahl der Materialien reduziert werden.
Aufgrund der Art des Rendervorgangs sollte in allererster Linie über die Reduzierung der Anzahl der Texturen und Materialien nachgedacht werden. Erst dann sollte die Komplexität des Meshs geprüft werden. Handelt es sich um ein Objekt, was aufgrund der Nähe ohnehin nicht in entfernten Kacheln enthalten sein wird und reduziert sich das Mesh dadurch nicht signifikant (> 70%), dann dürfte der Effekt nur sehr gering ausfallen (wie oben erklärt, hat die Anzahl der Vertices weit weniger Einfluss auf die Performance, als zu vermuten wäre).
Handelt es sich allerdings um ein Objekt, was auf den mittel-weiten oder gar ganz entfernten Kacheln zu einem Gesamtmesh zusammengefasst wird, dann lohnt sich der Aufwand eher: Der als kritisch anzusehende Speicherbedarf der entfernten Kachel-Meshs wird dann u.U. erheblich reduziert, insbesondere, wenn es sich um Gebäude handelt, die oft verwendet werden (z.B. Einfamilien-, Reihen- oder Hochhäuser).