Eine offene Welt im Browser erschaffen, Teil 30: Eine Kamera, die Wände respektiert
Von Oleg Sidorkin, CTO und Mitgründer von Cinevva
Neu hier? Nutze den Serienleitfaden. Er erklärt, was ein Spike ist, und verlinkt alle Teile.
Teil 29 brachte uns einen einzigen Controller, der jeden Körper steuern kann. Der Körper bewegt sich jetzt korrekt. Er läuft, rutscht, schwimmt, gleitet, klettert in Höhlen und duckt sich unter Überhängen hindurch. Das Problem ist, was ihn dabei beobachtet. Neunundzwanzig Teile lang war die Kamera ein gewöhnliches Orbit-Rig, das dem Spieler folgte und genau eine clevere Sache tat, um peinliche Situationen zu vermeiden: Es weigerte sich, sich unter den Horizont zu neigen, damit es nicht unter flachen Boden rutschen konnte. Diese Begrenzung verrät das eigentliche Problem. Sie existierte, weil die Kamera keine Ahnung hatte, wo sich die Geometrie der Welt tatsächlich befand. Die einzige Verteidigung gegen Clipping bestand daher darin, jene Winkel zu verbieten, bei denen Clipping am wahrscheinlichsten war. Lief man an einen Hügel heran, saß die Kamera im Hügel. Betrat man eine der Marching-Cubes-Höhlen aus Teil 7, blickte man auf die Innenseite eines Felsens. Baute man mit den Autorenwerkzeugen aus Teil 16 ein Haus und stellte sich in einen Raum, schwebte die Kamera außerhalb der Wand und blickte auf die Fassade. Dieser Teil sorgt dafür, dass die Kamera die Geometrie genauso respektiert wie der Körper.
Die Form des Problems und die Form der Lösung
Eine Third-Person-Kamera hat eine schwierige Aufgabe und ein Dutzend einfache. Zu den einfachen gehören das Folgen, die Glättung und die Orbit-Eingabe – und all das hatten wir bereits. Die schwierige Aufgabe wird in der Fachliteratur als Sichtbarkeitsbedingung bezeichnet. Die von allen zitierte Übersicht Camera Control in Computer Graphics von Christie und Olivier ordnet das gesamte Fachgebiet darum herum an: Halte das Motiv im Bild und unverdeckt, während du die Welt respektierst. Zur Laufzeit reduziert sich das auf eine trügerisch einfache Frage, die in jedem Frame gestellt wird. Der Spieler ist der Drehpunkt. Der Benutzer hat die Kamera gedreht und gezoomt, sodass ihre gewünschte Position in einiger Entfernung hinter dem Spieler liegt. Wie weit kann die Kamera entlang dieser Linie tatsächlich zurückfahren, bevor sie in etwas Festes eindringt? Beantwortet man diese Frage ehrlich, zieht sich die Kamera vor dem Hügel ein, gleitet am Ausleger nach vorn, wenn man in eine Ecke zurückweicht, und stoppt an der Höhlendecke, statt sie zu durchstoßen.
Das Muster, das diese Frage beantwortet, ist alt und bewährt. Unreal nennt es einen Federarm, Godot liefert einen SpringArm3D-Knoten mit und Unitys Cinemachine teilt es auf ein Third-Person-Follow-Rig und eine Deoccluder-Erweiterung auf. Die Idee ist immer dieselbe. Die Kamera hängt am Ende eines Auslegers, der am Drehpunkt verankert ist. Ist der Weg frei, bleibt sie auf der gewünschten Länge. Ist etwas im Weg, wird sie zum Drehpunkt hin eingezogen. Wird der Weg wieder frei, federt sie zurück nach außen. Mark Haigh-Hutchinsons Real-Time Cameras, verfasst vom leitenden Kameraentwickler von Metroid Prime, widmet ganze Kapitel den Fehlermustern, durch die eine naive Umsetzung Spielern Übelkeit bereitet. Wir haben das Muster übernommen und eine eigene, kompakte Variante gebaut, die man in einer Sitzung lesen kann: public/world/src/camera-rig.mjs.
Ein Ausleger, der nur seine Länge verwaltet
Die Designregel, die das Rig klein hielt, ist aus der Controller-Arbeit in Teil 29 übernommen: Verwalte eine Sache vollständig und lehne alles andere ab. Das Rig verwaltet die Länge des Auslegers und sonst nichts. Gierwinkel, Neigungswinkel, Dämpfung der Orbitbewegung sowie Touch- und Mausradgesten verbleiben vollständig bei OrbitControls, das all dies bereits gut löst und das wir keinesfalls neu schreiben wollen. Das Rig ist also kein Kamera-Controller. Es ist ein Nachbearbeitungsschritt, der nach der Orbit-Berechnung ausgeführt wird und genau eine Zahl korrigiert: den Abstand zwischen Drehpunkt und Kamera.
Diese Entscheidung klingt sauber, wäre aber aufgrund der Arbeitsweise von OrbitControls beinahe sofort gescheitert. Zu Beginn jedes Updates liest OrbitControls die aktuelle Position der Kamera und leitet daraus ihren Orbitradius ab. Normalerweise bleibt das unsichtbar. Doch sobald unser Rig die Kamera verkürzt, um einer Wand auszuweichen, liest OrbitControls im nächsten Frame diese verkürzte Position, schließt daraus, der Benutzer müsse herangezoomt haben, und übernimmt die Verkürzung in den gewünschten Zoom des Benutzers. Nach wenigen Frames ist die Kamera auf den Kopf des Spielers zusammengeschrumpft und fährt nicht mehr zurück. Die Lösung besteht aus zwei Aufrufen, die das Orbit-Update umschließen – und das ist bereits die gesamte Integration. Bevor OrbitControls ausgeführt wird, stellt beforeControls() die Kamera auf den vollständigen, unverkürzten Abstand des letzten Frames zurück, sodass die Orbit-Berechnung stets den tatsächlichen Zoomwunsch des Benutzers liest. Nachdem OrbitControls ausgeführt wurde, liest afterControls(dt) diese frisch berechnete Sollposition im Orbit, löst die Kollision auf, dämpft die Länge und setzt die Kamera an die Position, an der sie tatsächlich gerendert werden soll. Die Absicht des Benutzers und die Kollisionskorrektur beeinflussen sich nie gegenseitig, und das Dolly bleibt genauso reaktionsschnell wie vor der Einführung des Rigs. Der Headless-Test, den wir für das Rig geschrieben haben, prüft genau das: Fahre den Ausleger sechzig Frames lang gegen eine Wand, entferne die Wand und die Länge federt auf den vollständigen Benutzerzoom von zehn Metern zurück, statt am Kollisionsabstand hängen zu bleiben.
Eine Schnittstelle für jeden Collider
Das Rig fragt nie, woraus die Welt besteht. Ein Collider ist lediglich ein Objekt mit einer probe-Methode. Das Rig übergibt ihr einen Strahl, eine maximale Distanz und den Sondenradius der Kamera und erhält eine Zahl zurück: die größte Entfernung, die die Kamera zurücklegen darf, bevor dieser Collider sie blockiert. Das Rig fragt jeden registrierten Collider ab und verwendet den nächstgelegenen Treffer. Das ist die gesamte Schnittstelle – derselbe Ansatz, den der Charakter-Controller verfolgte, als er die Fortbewegung in austauschbare Verhaltensweisen aufteilte. Gelände wird eingebunden, Requisiten werden eingebunden, Gebäudehüllen werden eingebunden, jeweils hinter demselben probe, und das Rig weiß nichts über sie. Eine neue Art von Hindernis hinzuzufügen bedeutet, der Liste einen Collider hinzuzufügen, nicht die Kamera zu bearbeiten.
Ein Detail dieser Schnittstelle macht sich besonders bezahlt: der Sondenradius. Wir senden keinen dünnen Strahl vom Drehpunkt zur Kamera aus, sondern bewegen eine Kugel, die groß genug ist, um die Near-Plane der Kamera zu umschließen. Ein einzelner Strahl stoppt das Zentrum der Kamera an der Wand. Die Near-Plane besitzt jedoch eine Breite, sodass ihre Ecken bereits in der Wand stecken würden, bevor der zentrale Strahl überhaupt einen Treffer meldet. Statt eines Strahls eine kleine Kugel zu bewegen, ist der Ansatz aller ausgelieferten Implementierungen. Unreal stellt ihn als Sondengröße bereit. Genau darin liegt der Unterschied zwischen einer Kamera, die sauber an einer Oberfläche anliegt, und einer, bei der man an den Bildschirmrändern durch die Oberfläche hindurchsehen kann.
Wir schätzen diesen Radius nicht, sondern leiten ihn her. Der am weitesten von der Kamera entfernte Punkt der Near-Plane ist eine ihrer Ecken, und seine Entfernung ergibt sich direkt aus der Projektion. Bei einer Near-Plane
Der Sondenradius entspricht dieser Eckdistanz multipliziert mit einem kleinen Sicherheitsfaktor. Zusätzlich gilt eine feste Untergrenze, damit er bei einem sehr schmalen Frustum nie unter ein sinnvolles Minimum fällt. Wann immer sich Sichtfeld, Seitenverhältnis oder Near-Plane ändern, wird der Radius neu berechnet. So kann eine Größenänderung des Fensters oder eine Zoomänderung, die sich auf die Projektion auswirkt, die Sonde nicht unbemerkt so klein werden lassen, dass sie die zu schützenden Ecken nicht mehr abdeckt.
Der Collider, der Höhlen kennt
Beim Gelände-Collider wird es interessant, denn das Gelände unserer Engine ist kein Höhenfeld. Seit Teil 7 ist es ein vorzeichenbehaftetes Distanzfeld – eine Funktion, die für jeden Punkt im Raum zurückgibt, wie weit er von der nächsten festen Oberfläche entfernt ist und ob er sich innerhalb oder außerhalb des Felsens befindet. Positive Werte bedeuten Luft, negative Werte Fels. Allein diese Tatsache ermöglicht unserer Kamera etwas, wozu eine Höhenfeldkamera prinzipbedingt nicht in der Lage ist. Ein Höhenfeld kennt die Bodenhöhe an einer x- und z-Position. Es besitzt kein Konzept einer Decke, weil es über jedem Punkt immer nur eine einzige Oberfläche gibt. Eine Höhenfeldkamera kann also verhindern, dass man in einen Hügel läuft, hat aber keine Ahnung, dass die Kante eines Überhangs oder eine Höhlendecke über dem Drehpunkt hängt, und fährt direkt durch beides hindurch. Ein Distanzfeld kennt jede Oberfläche in drei Dimensionen. Daher stoppt dieselbe Sonde, die die Kamera an einem Hang anhält, sie auch an einer Höhlendecke – ganz ohne Sonderfall.
Die Sonde entlang des Feldes zu bewegen ist eine Technik mit eigenem Namen und einer wissenschaftlichen Veröffentlichung als Grundlage. John Harts Sphere Tracing aus dem Jahr 1996 ist das Standardverfahren, um einen Strahl schrittweise gegen ein Distanzfeld zu bewegen. Der Trick besteht darin, dass das Feld nicht nur angibt, ob man etwas getroffen hat, sondern auch, welche sichere Distanz man zurücklegen kann, ohne etwas zu treffen. Statt sich also in winzigen festen Schritten vorwärtszutasten, tastet man das Feld ab, bewegt sich um den gemeldeten freien Abstand vorwärts und wiederholt den Vorgang. So macht die Sonde große Schritte durch freie Luft und kurze, vorsichtige Schritte, wenn sie sich einer Oberfläche nähert. Allerdings gibt es einen Haken, den uns unser Terrain aufzwingt. Ein echtes Distanzfeld gibt die tatsächliche euklidische Entfernung zur nächsten Oberfläche an, und ein voller Schritt um diesen freien Abstand ist immer sicher. Wo das Terrain jedoch noch eine Heightmap statt ausgehöhlter Voxel ist, liefert das Feld, das wir kostengünstig abtasten können, keine echte Distanz, sondern den vertikalen Freiraum, also den Abstand senkrecht nach unten zum Boden. An einem Hang überschätzt dieser Wert, wie weit sich die Kamera tatsächlich bewegen kann, denn der nächste Fels liegt seitlich und nicht direkt unter ihr. Die korrekte Distanz ist um einen Faktor kleiner, der mit dem Gradienten wächst,
Ausgeschrieben ist der Ausleger ein Strahl
Dabei ist
Der Collider bringt noch eine weitere Methode als Sicherheitsnetz mit: die Entpenetration. Die Kollisionserkennung sollte die Kamera von vornherein aus festen Körpern heraushalten, doch einige Situationen umgehen sie: ein Drehpunkt, der eine so dünne Wand schneidet, dass der Ausleger in ihr beginnt; eine Höhle, die gerade mit den Terrain-Werkzeugen ausgehoben wurde, während sich die Kamera in dem nun entfernten Fels befand; oder ein Bauteil, das um die Kamera herum platziert wurde. In solchen Fällen prüft der Collider, ob die Kamera den Frame innerhalb eines Festkörpers beendet hat, also ob
Einige wenige solcher Iterationen konvergieren gegen die
Hineinschnappen, sanftes Ausfahren und kein Zucken bei Zaunpfählen
Ein Ausleger, der in jedem Frame einfach auf die Kollisionsdistanz springt, ist schlimmer als gar kein Ausleger. Die Welt ist voller dünner Objekte, hinter denen die Kamera nur für einen einzigen Frame vorbeizieht: ein Zaunpfahl, eine Lampe, ein Baumstamm. Eine Kamera, die jedem davon ruckartig ausweicht und ebenso ruckartig zurückspringt, verursacht Übelkeit. Sowohl Haigh-Hutchinsons Buch als auch Itay Kerens beliebter Vortrag über Kamerabewegung gelangen zur selben Intuition: Die Kamera sollte auf Bedrohungen und Gefahren schneller reagieren, als sie sich anschließend von ihnen erholt. Deshalb ist die Dämpfung bewusst asymmetrisch. Wenn ein Verdecker auftaucht und der Ausleger kürzer werden muss, fährt er nahezu sofort ein, denn schon ein einzelner Frame mit Clipping sieht hässlich aus, während Spielende ein schnelles Einziehen verzeihen. Verschwindet der Verdecker und soll der Ausleger wieder länger werden, fährt er langsam aus – und erst, nachdem eine kurze Verweilzeit mit durchgehend freier Sicht verstrichen ist. Diese Verweilzeit bildet die Hysterese, die das Zucken beseitigt. Schwenkt man die Kamera schnell an einem dünnen Pfosten vorbei, bleibt der Pfosten nie lange genug aus dem Weg, um das langsame Ausfahren auszulösen. Die Kamera gleitet daher an ihm vorbei, als wäre er gar nicht da – genau so, wie das Auge es erwartet. Cinemachine stellt dieselbe Idee als getrennte Dämpfungswerte für das Hinein- und Herausfahren bei Kollisionen bereit. Diese Asymmetrie sorgt dafür, dass sich das Verhalten wie das eines Kameramanns anfühlt und nicht wie das einer Feder.
Im Code ist das eine einzige Zeile exponentieller Glättung, deren Rate je nach Vorzeichen der Änderung umgeschaltet wird. Wenn
wobei der Zweig für das sanfte Ausfahren erst ausgeführt wird, nachdem der Freiraum über die Verweilzeit
Das letzte Verhalten ist für die engen Innenräume gedacht, mit denen dieser gesamte Teil begann. Wenn der Ausleger so weit zusammenfährt, dass die Kamera direkt am Spieler sitzt, blenden wir den eigenen Avatar aus und lassen die Ansicht in der Nähe der Ego-Perspektive ruhen. Genau das macht Breath of the Wild in einem beengten Schrein, und darauf greifen die meisten Third-Person-Spiele in einer Ecke zurück. Die Alternative – eine gegen die Wand gedrückte Kamera, die auf einen Hinterkopf starrt – ist unbrauchbar. Das Rig stellt dafür ein einzelnes Flag bereit, und die Welt-Schleife liest es aus und schaltet die Sichtbarkeit des lokalen Avatars um. Bei einer Auslegerlänge unter einem Meter befindet man sich praktisch in der Ego-Perspektive, die Wände werden respektiert, und sobald man sich rückwärts in einen geräumigeren Raum bewegt, wird der Avatar wieder eingeblendet und der Ausleger fährt aus.
Was als Nächstes angeschlossen wird
Der Terrain-Collider wird heute ausgeliefert, und er ist die schwierigere Hälfte, denn Terrain ist überall, und ein Distanzfeld lässt sich nur umständlich abtasten. Die Props und Gebäudehüllen aus den Authoring-Experimenten bilden die einfache Hälfte, und der Vertrag wartet bereits auf sie. Ein zweiter Collider, ein Raycast-Collider, sendet vom Drehpunkt in Richtung Kamera einen Strahl gegen eine Liste von Meshes aus und meldet den nächsten Treffer auf dieselbe Weise wie der Terrain-Collider. Die günstige Version verwendet einen einzelnen Strahl, was ausreicht, bis die Anzahl der Props steigt. Die Erweiterung ersetzt den Strahl dann durch eine bewegte Kugel unter Verwendung von three-mesh-bvh, Garrett Johnsons Bibliothek, die ein Mesh in eine Bounding-Volume-Hierarchie einbettet, sodass räumliche Abfragen in logarithmischer Zeit statt per Brute Force ausgeführt werden. In beiden Fällen bleibt das Rig unverändert. Es fragt eine längere Liste von Collidern ab und übernimmt den nächsten Treffer. Genau deshalb haben wir zuerst den Vertrag und danach die Collider entwickelt.
In diesem Kapitel erwähnte Technologien
Ein Ausleger, der genau einen Wert verwaltet. Das Kamera-Rig ist eine Nachverarbeitung über OrbitControls und kein Ersatz dafür. Es verwaltet die Länge des Auslegers und überlässt Gierwinkel, Neigung, Zoom und Gestensteuerung dem bereits bewährten Orbit-Controller. Die Integration besteht aus zwei umschließenden Aufrufen: beforeControls() stellt die vollständige Distanz des vorherigen Frames wieder her, damit die Orbit-Berechnung den tatsächlichen Zoom des Benutzers ausliest, statt eine kollisionsbedingte Verkürzung mit einem Heranzoomen zu verwechseln; afterControls(dt) löst anschließend die Kollision auf und schreibt die gerenderte Position. Ohne dieses Paar fällt die Kamera innerhalb weniger Frames auf den Spieler zusammen.
Ein austauschbarer Collider-Vertrag. Ein Collider ist jedes Objekt mit einer probe, die beantwortet: „Wie weit kann sich die Kamera vom Drehpunkt entlang dieses Strahls bewegen, bevor du sie blockierst?“ Das Rig fragt jeden Collider ab und übernimmt den nächsten Treffer, ohne zu wissen, ob das Hindernis Terrain, ein Prop oder eine Wand ist. Das folgt derselben Disziplin, nur für eine Sache zuständig zu sein, die bereits der austauschbare Charakter-Controller für die Fortbewegung verwendete. Der Prüfkörper ist eine bewegte Kugel, die groß genug ist, um die Near Plane zu umfassen, und kein dünner Strahl. Dadurch schneiden die Ecken des Sichtstumpfs niemals eine Oberfläche, die der zentrale Strahl verfehlen würde. Ihr Radius wird aus der Projektion abgeleitet: die Entfernung zu einer Ecke der Near Plane
Ein vorzeichenbehafteter Terrain-Distanzprüfer, der Überhänge und Höhlen berücksichtigt. Da das Terrain ein vorzeichenbehaftetes Distanzfeld statt einer Heightmap ist, stoppt derselbe Prüfkörper, der die Kamera an einem Hang anhält, sie auch an einer Höhlendecke – ein Fall, den ein Heightmap-Raycast strukturell nicht erfassen kann. Das Marching ist Sphere Tracing nach Hart: Es schreitet um einen unterrelaxierten Anteil des vom Feld gemeldeten Freiraums voran und wird an beiden Enden begrenzt. Das ist nötig, weil die Heightmap-Regionen den vertikalen Freiraum statt der echten Distanz melden und dadurch überschätzen, wie weit sich die Kamera an einem Hang bewegen kann; ein voller Schritt würde daher über einen Grat springen. Ein gradientengesteuerter Entpenetrationsdurchlauf bildet das Sicherheitsnetz, das die Ansicht wiederherstellt, wenn der Boden unter der Kamera wegmodelliert wird.
Asymmetrische Dämpfung mit Verweilzeit. Der Ausleger schnappt schnell hinein, wenn ein Verdecker erscheint, und fährt nach dessen Verschwinden langsam wieder aus – allerdings erst nach einem kurzen Zeitfenster mit durchgehend freier Sicht. Dadurch lässt ein schneller Schwenk an einem Zaunpfahl vorbei die Kamera niemals ruckartig vorspringen. Unterhalb eines Schwellenwerts für die Verkürzung markiert das Rig die Ansicht als nahezu in der Ego-Perspektive, und die Welt-Schleife blendet den lokalen Avatar aus. Das ist der übliche Rückfallmodus für enge Innenräume und verhindert eine in der Wand steckende Kamera.
Quellen
Die Betrachtung der Kamerasteuerung als Sichtbarkeitsbedingung stammt von Marc Christie und Patrick Olivier, Camera Control in Computer Graphics (Computer Graphics Forum, 2008). Das Distanzfeld-Marching stammt von John C. Hart, Sphere Tracing: A Geometric Method for the Antialiased Ray Tracing of Implicit Surfaces (The Visual Computer, 1996). Das Spring-Arm-Muster und seine Kollision mit einer Prüfkugel sind in Epics Spring Arm Component sowie Unitys Cinemachine Deoccluder und Third Person Follow dokumentiert. Die Intuition hinter Bewegung und Dämpfung stammt von Mark Haigh-Hutchinson, Real-Time Cameras (Morgan Kaufmann, 2009), und Itay Keren, Scroll Back: The Theory and Practice of Cameras in Side-Scrollers (GDC 2015). Der Erweiterungspfad für Mesh-Collider verwendet Garrett Johnsons three-mesh-bvh.
Teil 30 von 30. Zurück: Teil 29 – Ein Controller für jeden Körper Serienübersicht: /de/blog/2026-02-25-open-world-browser-series-guide