Three.js: Funktionsanfrage: Erhöhen Sie die Leistung der Raycaster-Kollisionserkennung durch räumliche Suchbäume

Erstellt am 11. Dez. 2017  ·  36Kommentare  ·  Quelle: mrdoob/three.js

Ich habe eine VR-Anwendung ähnlich WebVR-Vive-Dragging gebaut, die es ermöglicht, mit zahlreichen 3D-Objekten über VR-Controller zu interagieren. Das bedeutet, dass ein Benutzer mit einem VR-Controller ein Objekt greifen und es verschieben oder skalieren kann.

Problem : Wenn es komplexe 3D-Objekte in der Szene gibt, dh THREE.Mesh Objekte mit Geometrien mit einer sehr großen Anzahl von Vertices, dann wird das Raycasting während der Kollisionserkennung sehr langsam. Daher ist das Problem die Komplexität der Geometrie eines Objekts.

Es gibt Baumdatenstrukturen für die schnelle räumliche Suche, wie Octree oder R-Tree . Ich habe Threeocttree gefunden, das es ermöglicht, eine Geometrie in kleinere Teile aufzuteilen, aber es scheint, dass es etwas veraltet ist (Three.js r60).

Soweit ich sehe, gibt es in der raycast -Methode des THREE.Mesh -Objekts bereits einige Leistungsoptimierungen (zunächst Überprüfung des Begrenzungsrahmens und der Kugel, bevor der eigentliche Raycast ausgeführt wird). Vielleicht wäre es sinnvoll, eine weitere solche Überprüfungsstufe mit räumlichen Suchbäumen zu haben?! Was denken Sie?

Mit freundlichen Grüßen

Enhancement

Hilfreichster Kommentar

Ich habe in der Vergangenheit vorgeschlagen, den räumlichen Index in threejs aufzunehmen. Es gab einige Arbeiten in dieser Richtung, und das vorhandene Oct-Tree-Beispiel ist eines davon. Letztendlich denke ich, dass es an Interesse seitens der Kerngemeinde fehlt, diese Funktionalität als erstklassigen Bürger in drei zu haben.

Ich würde gerne meinen BVH-Code für das Projekt spenden, wenn genügend Interesse und Absicht besteht, ihn in die Hauptverteilung aufzunehmen (keine Beispiele).
Mein Code konzentriert sich auf 2 Aspekte:

  • dynamische Szenen

    • schnell einfügen

    • schnelles Löschen

    • Umrüstung (Fähigkeit der Knoten, ihre Form zu ändern, ohne neu eingefügt werden zu müssen)

  • Abfragegeschwindigkeit

Ich habe 2 Implementierungen:

  • BinaryBVH

    • sehr kompakt, mit ByteBuffer

    • Mischung aus UintX- und FloatX-Typen über DataView

    • ideal für die Serialisierung

    • kann mit Standardwerkzeugen wie der lzma-Bibliothek komprimiert werden

    • unveränderlich

    • Build-Geschwindigkeit ist hochoptimiert

    • funktioniert nur für BufferGeometry

    • SAH-Optimierung

    • Strahlabfragen

  • BVH

    • Der Objektbaum ist folglich viel größer als die binäre Implementierung

    • veränderlich

    • schnelle Masseneinlage

    • schneller Einzelposteneinwurf

    • Umrüstung

    • Stack-, rekursive und Stack-lose Traversal-Implementierungen (unterschiedliche Leistungsmerkmale)

    • SAH-Optimierung

    • Frustum-Abfragen

    • Ray fragt

Ich persönlich kann ohne einen räumlichen Index nicht leben, es ist ein Unterschied zwischen n ^ 2- und log (n) -Leistung. In meinem aktuellen Projekt stützen sich beispielsweise die folgenden Teile auf den räumlichen Index:

  • Laubsystem (Bäume, Blumen, Büsche etc.)
  • alle Objektplatzierungen (Charaktere und Häuser)
  • Auswahl (wird bei Interaktionen wie Mausklicks verwendet)

http://server1.lazy-kitty.com/komrade

Das Gelände hat ungefähr 1 m große Polygone, und das Platzieren von Tausenden von Bäumen und das Ausführen von Echtzeit-Raycasts darauf ist einfach ein No-Go, insbesondere für Geräte der unteren Preisklasse.

Alle 36 Kommentare

Auf die Gefahr hin, das Offensichtliche zu sagen, aber haben Sie die Version von threeocttree ausprobiert, die sich bereits im Repo befindet ( examples/js/Octree.js )?

Ich stimme nicht für eine fest verdrahtete Verwendung von räumlichen Suchbäumen in three.js . Der Overhead/die Komplexität solcher Algorithmen überwiegt den Leistungsgewinn in vielen Anwendungen.

Überprüfen Sie zuerst den Begrenzungsrahmen und die Kugel, bevor Sie den eigentlichen Raycast durchführen

Das ist sinnvoll und sollte ausreichen. Wenn Benutzer im Kontext fortgeschrittenerer Anwendungsfälle mehr Leistung benötigen, können sie das erwähnte Beispiel als Ausgangspunkt verwenden. Octrees könnte eine gute Wahl sein. Aber ich habe noch nie eine R-Tree-Lösung in interaktiven 3D-Anwendungen gesehen, weil ihr Ansatz ziemlich ausgeklügelt ist (der Algorithmus führt Daten durch, anstatt Raumpartitionierung).

Hallo und danke für eure Antworten,
@ Mugen87 Ich stimme zu, dass viele Anwendungsfälle keine so schnellen Suchen erfordern. Da es jedoch einen Mechanismus zum Interagieren mit 3D-Objekten gibt (und ich denke, das ist der Fall, wofür es meistens verwendet wird), ist THREE.Raycaster Schritt von der Überprüfung des Begrenzungsrahmens / der Kugel zuerst (was ich völlig zustimme, um vernünftig zu sein ) in sagen wir mal konstante Zeit zu linearem Zeitaufwand beim eigentlichen Raycast ist ziemlich groß. Ich könnte mir statt eines globalen Suchbaums eine Art "pro-Geometrie-Suchbaum" vorstellen. Solange sich die Geometrie nicht ändert (häufig sind Transformationen viel wahrscheinlicher als Geometrieänderungen), muss der Suchbaum nicht aktualisiert werden.

Deshalb dachte ich, dass eine solche Optimierung mit räumlichen Suchbäumen ein festerer Bestandteil von three.js sein könnte. Aber ich verstehe auch, dass dies zusätzliche Komplexität und Overhead verursacht.

@moraxy Ich werde mir die Beispielversion ansehen. Ich wollte nur wissen, ob so ein Feature Sinn macht.

Nochmals vielen Dank und freundliche Grüße

Vielleicht könnte ein einfacherer Suchbaum in THREE.BufferGeometry integriert werden. Beispielsweise könnte ein Aufruf von computeBoundingBox auch eine Art schreibgeschützten Suchbaum aufbauen, der auf die entsprechenden Knoten verweist. Da THREE.BufferGeometry ist

am besten geeignet für statische Objekte, bei denen Sie die Geometrie nach der Instanziierung nicht viel manipulieren müssen

dieser Suchbaum muss nach der Initialisierung nicht geändert werden. Dies würde etwas Overhead für das Aktualisieren/Löschen reduzieren. Ein Octree wäre ein guter Ausgangspunkt ( ähnliches Konzept in BabylonJS ).

am besten geeignet für statische Objekte, bei denen Sie die Geometrie nach der Instanziierung nicht viel manipulieren müssen

FWIW, ich glaube nicht, dass das eine wahre Aussage ist.

@WestLangley Zitat aus der THREE.BufferGeometry -Dokumentation

THREE.BufferGeometry-Dokumentation

Es sollte wahrscheinlich entfernt werden - auf jeden Fall denke ich nicht, dass dies als technische Aussage gemeint ist, sondern bedeutet, dass es für den Benutzer schwieriger ist, die Geometrie zu manipulieren, nachdem sie erstellt wurde.

Es sollte wahrscheinlich entfernt werden

Ganz sicher.

Ich habe gerade eine bessere Methode für Raycasting auf PlaneBufferGeometry implementiert. Ich verwende den far-Parameter und finde die erste und letzte Indexposition. Dies funktioniert in Planebuffer, da das Indexarray in x/y-Reihenfolge ist. Außerhalb des x/y-Begrenzungsrahmens müssen alle Kollisionen außerhalb des Fernbereichs liegen. Besteht der Wunsch, das irgendwo zu begehen? Die aktuelle Implementierung ist spezifisch für meinen Anwendungsfall, aber ich wäre bereit, zu versuchen, sie auf Wunsch allgemeiner zu gestalten. Ich konnte meine Raycasting-Leistung auf einem großen PlaneBuffer (2,5 Sekunden auf 10 ms) erheblich reduzieren.

@kpetrow Ich denke, die Octree-Beispiele von three.js sind angemessen. Es steht Ihnen natürlich frei, Ihren Code auf GitHub zu teilen, wenn Sie der Meinung sind, dass er für andere nützlich wäre.

Ich habe in der Vergangenheit vorgeschlagen, den räumlichen Index in threejs aufzunehmen. Es gab einige Arbeiten in dieser Richtung, und das vorhandene Oct-Tree-Beispiel ist eines davon. Letztendlich denke ich, dass es an Interesse seitens der Kerngemeinde fehlt, diese Funktionalität als erstklassigen Bürger in drei zu haben.

Ich würde gerne meinen BVH-Code für das Projekt spenden, wenn genügend Interesse und Absicht besteht, ihn in die Hauptverteilung aufzunehmen (keine Beispiele).
Mein Code konzentriert sich auf 2 Aspekte:

  • dynamische Szenen

    • schnell einfügen

    • schnelles Löschen

    • Umrüstung (Fähigkeit der Knoten, ihre Form zu ändern, ohne neu eingefügt werden zu müssen)

  • Abfragegeschwindigkeit

Ich habe 2 Implementierungen:

  • BinaryBVH

    • sehr kompakt, mit ByteBuffer

    • Mischung aus UintX- und FloatX-Typen über DataView

    • ideal für die Serialisierung

    • kann mit Standardwerkzeugen wie der lzma-Bibliothek komprimiert werden

    • unveränderlich

    • Build-Geschwindigkeit ist hochoptimiert

    • funktioniert nur für BufferGeometry

    • SAH-Optimierung

    • Strahlabfragen

  • BVH

    • Der Objektbaum ist folglich viel größer als die binäre Implementierung

    • veränderlich

    • schnelle Masseneinlage

    • schneller Einzelposteneinwurf

    • Umrüstung

    • Stack-, rekursive und Stack-lose Traversal-Implementierungen (unterschiedliche Leistungsmerkmale)

    • SAH-Optimierung

    • Frustum-Abfragen

    • Ray fragt

Ich persönlich kann ohne einen räumlichen Index nicht leben, es ist ein Unterschied zwischen n ^ 2- und log (n) -Leistung. In meinem aktuellen Projekt stützen sich beispielsweise die folgenden Teile auf den räumlichen Index:

  • Laubsystem (Bäume, Blumen, Büsche etc.)
  • alle Objektplatzierungen (Charaktere und Häuser)
  • Auswahl (wird bei Interaktionen wie Mausklicks verwendet)

http://server1.lazy-kitty.com/komrade

Das Gelände hat ungefähr 1 m große Polygone, und das Platzieren von Tausenden von Bäumen und das Ausführen von Echtzeit-Raycasts darauf ist einfach ein No-Go, insbesondere für Geräte der unteren Preisklasse.

Ich würde gerne alles sehen, was Raycast optimiert. Gibt es Raum für Optimierung, ohne die Komplexität zu erhöhen? Octree erfordert eine komplett neue Bibliothek mit allen neuen Ergänzungen und Updates usw.

Eine Sache, die mir auffällt, ist, dass, sobald eine Geometrie in ein indiziertes Puffer-Array konvertiert wurde, die Eigenschaften der ursprünglichen Geometrie nicht mehr intelligent verwendet werden. Wie oben erwähnt, können Ebenenpuffergeometrien superoptimiert werden, wenn man weiß, dass der ArrayBuffer aus einer PlaneGeometry stammt. Vielleicht die Methode MESH.raycast (raycaster, intersects) überschreiben, um auf dem Geometrietyp zu basieren?

Ein anderer Ansatz besteht darin, Raycaster als separates Plug-in zu haben – ebenso wie die interaktiven Steuerelemente und die Loader. Dann wäre es großartig, einen leistungsstarken Raycaster hinzuzufügen, der dem entspricht, was @Usnul vorschlägt.

Ich mag die Idee, steckbare Raycaster zu haben, aber ich denke, es gibt ein bisschen Verwirrung. Was ich für nützlich halte, ist ein räumlicher Index, kein Raycaster an sich. Der räumliche Index ermöglicht Dinge wie:

  • Spatial Culling (denken Sie an Kegelstumpf-Culling)
  • Sichtbarkeitsabfragen durchführen
  • Erstellen eines Pfadverfolgungs-Renderers
  • schnelle Sortierung (Ausnutzung der Datenlokalität)

derzeit macht three.js 2 davon explizit (Sortieren und Culling) und 1 über Beispiele (Ray-Tracing-Renderer)

Das interne Beibehalten eines räumlichen Index würde das Sortieren und Aussortieren beschleunigen, was zunehmend mehr Nutzen für größere Szenen auf Kosten von zusätzlichem RAM bietet, der zum Speichern des Index erforderlich ist.

Warum nimmst du nicht einfach GPU-Picking? Es gibt einige Beispiele und es ist ziemlich einfach zu implementieren. Die Aufführung ist Tag und Nacht. In unseren Anwendungsfällen mit Modellen von über einer Million Polygonen ging das Picken von fast einer Sekunde bis zu einer Ausführung in Echtzeit mit 60 fps, während Dinge über das Modell gezogen wurden.

https://github.com/brianxu/GPUPicker

@hccampos
Die GPU-Auswahl ist nicht unbedingt schneller als auf der CPU. Für die GPU müssen Sie mit einer Auflösung rendern, die proportional zur gewünschten Präzision ist. Wenn Sie also Pixelgenauigkeit wünschen, müssen Sie mit einer Bildschirmauflösung von 1: 1 rendern. Denken Sie daran, dass dies ein separater Renderdurchgang ist, Sie rendern Objekte als separate unterschiedliche Farben. Ein zusätzlicher Renderdurchgang bedeutet Grafikspeicherverbrauch (typisch für verzögerte Pipeline).
Die Verwendung eines Binärraum-Partitionierungsindex gibt Ihnen ein log(n)-Timing-Profil für Ihre Auswahl, sodass Sie für 1.000.000 Polygone etwa 14 Operationen zum Auflösen einer Strahlabfrage betrachten würden. Abhängig von Ihrer Datenstruktur dauert es wahrscheinlich einige Mikrosekunden, was Ihnen Tausende von Abfragen ermöglicht, bevor Sie anfangen, Ihr Frame-Budget zu belasten.

Lassen Sie uns einen Vergleich anstellen, sagen wir, Sie haben ein 1-Meter-Poly-Modell und Sie möchten einen Strahl vom Bildschirmraum direkt entlang der Kamerarichtung in die Szene werfen (Anwendungsfall auswählen). Nehmen wir an, Sie verwenden die niedrigste Barrel-Auflösung, "Full HD" oder 1920 × 1080. Nehmen wir an, Sie rendern nur RGB (24 Bit pro Pixel), Sie benötigen 6220800 Byte (ca. 6 MB) oder Grafik-RAM zum Rendern das. Wenn Sie eine CPU-Lösung verwenden, sagen wir, Sie gehen mit AABB BVH mit 10 Polygonen pro Blatt, das heißt, Sie benötigen ungefähr 200.000 Knoten, sagen wir, jeder Knoten hat ungefähr 6 * 8 Bytes für Koordinaten, Zwischenknoten haben zusätzliche 2 * 4 Bytes für untergeordnete Zeiger und Blattknoten haben 10 * 4 Bytes für Polygonzeiger, das sind 14.400.000 Bytes (ca. 14 MB). Der Hauptunterschied kommt ins Spiel, wenn Sie bedenken, dass Ihre BVH-Abfragen im Vergleich zum GPU-Fall nur sehr wenig RAM-Bandbreite beanspruchen und nur eine Handvoll Operationen pro Abfrage erforderlich sind, verglichen mit dem Rendern der vollständigen 1-Meter-Poly-Geometrie.
Wenn Sie eine für Desktops typischere Auflösung wie 2560 x 1440 verwenden, erhalten Sie ein Renderziel von 11059200 Byte (11 MB).

Wenn Sie über ein großes Budget für Grafik-RAM-Bandbreite und eine ziemlich anständige Anzahl von Shader-Kernen verfügen, ist dies sicherlich eine einfache und unkomplizierte Methode zur Auswahl.

@Usnul absolut, aber es ist wahrscheinlich die am einfachsten zu implementierende Lösung. Die Implementierung und Pflege räumlicher Indexdatenstrukturen wird wahrscheinlich eine nicht unerhebliche Belastung für die Betreuer von threejs sein.

Abgesehen davon hätte ich absolut gerne eine gute, gut getestete und gepflegte räumliche Indexdatenstruktur als Teil von Three oder als separates npm-Paket.

Ich war neugierig, einen solchen räumlichen Index für meine verwendeten Geometrien zu implementieren, also habe ich ein recht einfaches Octree (nur Erstellen und Suchen) implementiert, das meine BufferGeomtry aufteilt. Mit dieser einfachen Lösung habe ich recht vielversprechende Ergebnisse erzielt: Angewandt auf eine Geometrie mit etwa 500K Vertices reduzierte sich die Raycast-Zeit von ~120ms auf ~2,3ms. Die Erstellung des Baums dauert derzeit etwa 500 ms, aber da die Geometrie- und Baumerstellung in einem Webworker nur einmal beim Start der Anwendung durchgeführt wird, ist dies kein solches Problem.

Ich denke, der Algorithmus könnte ganz einfach in THREE.BufferGeometry integriert und vielleicht mit einem Flag wie THREE.BufferAttribute 's dynamic ein- oder ausgeschaltet werden. Auf diese Weise könnte es nur verwendet werden, wenn sich BufferGeometry nicht ändern soll. Leider musste ich die raycast -Methode THREE.Mesh überschreiben, obwohl ich nur zwei Zeilen darin ändern musste (Octree-Suchaufruf und Iteration des Positionsarrays).

Wie auch immer, in meiner VR-Anwendung muss ich Objekt-Controller-Kollisionen während der Renderschleife erkennen und nicht nur einmal per Mausklick. Somit muss ich mich auf meine jetzige Lösung verlassen. Ich werde versuchen, ob ich es weiter verbessern kann.

UPDATE Ich habe einen Fehler bei der Messung der Raycast-Zeit gemacht. Der korrekte Wert beträgt ~2,3 ms (statt ~0,3 ms). Ich habe den obigen Wert entsprechend geändert.

Ich denke, dass es in den meisten komplexeren Anwendungen, die auf three.js aufbauen, notwendig ist, eine Opt-in-Methode für schnelle räumliche Suchen zu haben. Zum Beispiel ein 3D-Editor. Ich habe mit einigen Implementierungen da draußen herumgespielt, aber sie lassen sich entweder nicht gut integrieren oder zu eng in eine bestimmte Version von three.js einbinden. Wenn also jemand eine Möglichkeit hat, dies zu tun, ohne den Upgrade-Pfad auf neuere Versionen von three.js zu unterbrechen, wäre das großartig.

@matthias-w Ich habe den Raycaster für ein Obj-Modell ausprobiert, aber der Controller erkennt nichts. Die Strahlen gehen durch das Modell. Kannst du mir sagen, wie du das Problem behoben hast

Zum Thema optionaler räumlicher Index. Ich glaube, dass es sogar für den Renderer selbst (z. B. zum Culling) ausreichend nützlich ist, ein integraler Bestandteil der Engine zu sein. Hier ist eine verwandte Diskussion:

13909

Hey! Ich war auch ein bisschen daran interessiert (obwohl ich seitdem andere Lösungen für unsere Raycast-Anforderungen gefunden habe), aber ich dachte, ich würde auch einige meiner Experimente beitragen. Es ist ein bisschen grob, aber ich habe das vor ein paar Monaten zusammengestellt:

https://github.com/gkjohnson/threejs-fast-raycast

Es fügt den DREI Geometrieobjekten eine computeBoundsTree -Funktion hinzu und überschreibt die Raycast-Funktion, um diese zu verwenden (und gibt nur den ersten Treffer als zusätzliche Optimierung zurück). Es ist nur für statische, hochkomplexe Meshes wirklich vorteilhaft und trägt nicht dazu bei, die Szene räumlich zu segmentieren. Hier ist die Demo , in der Sie den Unterschied in der Raycast-Leistung auf einem 80.000-Dreieck-Mesh sehen können. Das Berechnen des Begrenzungsbaums ist etwas langsam, aber mit ein wenig Arbeit könnte es wahrscheinlich glatter sein.

Was meine Meinung dazu betrifft, bin ich etwas zwiegespalten. Das fühlt sich nach etwas an, das ziemlich gut als Erweiterung von THREE gebaut werden kann. Und letztendlich scheint es sowieso nicht optimal zu sein, Prüfungen / Kollisionen / Casts pro Dreieck für komplexe oder animierte Netze durchzuführen. In einfachen Fällen mag es in Ordnung sein, aber es scheint, als wäre die Verwendung der typischen Ebenen- / Würfel- / Kugel- / Kapseldarstellungen oder vereinfachter Netze besser geeignet, um in komplexen Fällen superschnelles Raycasting (oder Okklusions-Culling, Kollisionen usw.) zu ermöglichen. Wenn Sie nach pixelgenauen Casts suchen, funktioniert dies natürlich nicht ganz so gut, aber die richtige Lösung hängt wirklich von Ihrem Anwendungsfall ab.

@Usnul @matthias-w Sind Ihre Octree-/BVH-Implementierungen Open Source oder online verfügbar? Ich hätte auf jeden Fall Interesse, mal reinzuschauen!

@gkjohnson

Sind Ihre Octree-/BVH-Implementierungen Open Source oder online verfügbar? Ich hätte auf jeden Fall Interesse, mal reinzuschauen!

Der Code ist derzeit nicht Open Source. Ich könnte Ihnen die Quellen zur Verfügung stellen, wenn Sie mich privat kontaktieren.

Es gibt ein Beispiel, das ich für instanzierte Meshes gemacht habe:
http://server1.lazy-kitty.com/tests/instanced-foliage-1mil/
Das obige Beispiel beinhaltet Folgendes:

  • BVH wird dynamisch aktualisiert, wenn neue Instanzen in den Baum eingefügt werden
  • BVH wird inkrementell optimiert, indem Rotationen in der Tiefe 1-2 im laufenden Betrieb verwendet werden
  • BVH wird mit einer frustum-Abfrage abgetastet, welche Instanzen gerendert werden sollen

Ich habe eine Version davon in dem Spiel, an dem ich arbeite:
http://server1.lazy-kitty.com/komrade/

In Bezug auf Ihre Implementierung. Ich mag es, meins unterscheidet sich in zwei wesentlichen Punkten:

  • Ich konzentriere mich auf Leistung
  • Ich konzentriere mich auf den Speicherfußabdruck
  • Ich vermeide Müllabfuhr an vielen Stellen

Einige spezifischere Punkte:

  • Ich verwende auch transparent eine Mischung aus Splitting-Strategien, basierend darauf, was Sie mit dem BVH machen möchten
  • Mein BVH basiert auf AABB
  • Ich unterstütze Aktualisierungen von Knotengrenzen durch Umrüstung

@Usnul Ich kann Ihre E-Mail-Adresse oder ähnliches nicht sehen, aber ich bin sehr daran interessiert, Ihre Lösung für die räumliche Indizierung zu untersuchen.

Derzeit durchlaufe ich einfach alle relevanten Szenenobjekte und berechne die Entfernung von der Kamera. Nicht der optimale Ansatz.

@titansoftime
es ist
travnick at gmail com
Wusste nicht, dass es nicht öffentlich zugänglich ist. Mein Fehler.

@Usnul Ich interessiere mich auch für das 1-Millionen-Projekt. Vielleicht sogar 10 oder 20 Millionen, wenn möglich. Bitte mailen Sie mir: kaori.nakamoto. [email protected]

Vielen lieben Dank!

http://server1.lazy-kitty.com/tests/instanced-foliage-10mil/
Dies ist eine Version mit 10 Millionen

Entschuldigung für die späte Antwort.
@gkjohnson Der Code ist nicht Open Source. Außerdem klingt Ihre Lösung viel raffinierter als meine. Daher denke ich, dass es nicht so viel aus meiner Lösung zu lernen gibt.

@sid3007 Ich bin mir nicht sicher, ob ich dein Problem verstehe. Ich kann meine Vorgehensweise erklären. Vielleicht ist es hilfreich.

Mein Anwendungsfall ist recht einfach. Wenn meine Anwendung startet, werden die Geometrien für verschiedene Modelle geladen. Diese Modelle können vom Benutzer transformiert werden. Die Geometrien ändern sich nicht. Es gibt keine Geometrieverformungen. Daher habe ich ein sehr einfaches Octree implementiert, das beim Start der Anwendung einmal für jede Geometrie erstellt wird. Der Octree ist nicht dynamisch. Es wird basierend auf dem Begrenzungsrahmen der gegebenen Geometrie und dem Array von Scheitelpunkten erstellt. Während seiner Konstruktion prüft es, ob ein Oktant einen Scheitelpunkt enthält oder ob der Box3 -Begrenzungsrahmen des Oktanten ein Dreieck schneidet (drei aufeinanderfolgende Scheitelpunkte in nicht indizierter Geometrie) und speichert die Scheitelpunkt-Array-Referenzen mit dem Knoten des Octrees.
Der Per-Mesh-Octree wird dann zusammen mit dem Mesh gespeichert. Ich überschreibe auch die Methode raycast meiner Mesh-Instanzen THREE.Mesh (nur eine Zeile), um tatsächlich die Octree-Schnittpunkt-Testmethode aufzurufen. Dies gibt die Scheitelpunktindizes der Geometrieflächen zurück, die von der standardmäßigen Schnittpunktlogik des Netzes verwendet werden können.
Ich habe eine Optimierung vorgenommen: Die Octree-Erstellung erfolgt einmal beim App-Start in einem WebWorker (eigentlich ein Pool von Workern). Der Grund dafür ist, dass die Baumerstellung für große Geometrien ziemlich lange dauert (einige Sekunden). Dies würde die Browser-Benutzeroberfläche blockieren, also habe ich sie in einen anderen Thread verschoben.
Ich hoffe, mein Ansatz wird klar.

@matthias-w Klingt nach toller Arbeit! Ich habe unabhängig davon fast dasselbe getan, glaube aber nicht, dass ich eine annähernd so große Leistungssteigerung erzielt habe. https://discourse.threejs.org/t/octree-injection-for-faster-raytracing/8291/2 (objektorientierte Octree-Implementierung und weniger saubere Änderungen an Mesh.raycast)

Wäre es für Sie möglich, die Octree-Implementierung als Open Source/beizutragen, um mehr Experimente durch andere zu ermöglichen?

@EliasHasle Danke. Eigentlich ist der Leistungsgewinn nicht so gut, da ich bei meinen ersten Maßnahmen einen Fehler gemacht habe. Ich habe den Wert korrigiert (siehe meinen Beitrag oben). Die Timings machen jetzt im Hinblick auf die logarithmische Suchkomplexität von Octree mehr Sinn. Leider kann ich den Code im Moment nicht zur Verfügung stellen, aber vielleicht später in diesem Jahr. Wie auch immer, meine Implementierung ist ziemlich einfach (und übrigens nicht so sauber ;)). Ich vermute also, dass es keine besonderen Erkenntnisse geben würde.

@matthias-w Ich denke, 2,3 ms gegenüber 120 ms für einen Raycast auf einem 500k-Vertex-Mesh sind immer noch eine signifikante Verbesserung, die beispielsweise die Echtzeitauflösung von abgefeuerten Kugeln in Spielen ermöglicht (bei einem ziemlich großen Teil der Berechnung). Budget pro Rahmen).

Hast du es auch mit Raytracing versucht?

Basiert Ihre Implementierung auf einem Baum von JS-Objekten? Wörtliche Objekte oder Instanzen eines Prototyps? Meins ist vollständig selbstähnlich, sodass jeder Knoten ein Octree ist, mit Methoden und allem.

@EliasHasle

Wenn Sie an Raycasting gegen statische Geometrie interessiert sind, haben ich und andere erhebliche Anstrengungen in three-mesh-bvh gesteckt, das einen BVH zum Indizieren von Dreiecken verwendet und Hochleistungs-Raycasting sowie Schnittpunkterkennung gegen statische komplexe Geometrie ermöglicht. Der Baumaufbauprozess ist komplexer, daher dauert er etwas länger, aber das Raycasting dauert weniger als eine Millisekunde und oft weniger als 0,1 ms, wenn Raycasting gegen Geometrie mit Hunderttausenden von Dreiecken erfolgt.

Die Bauzeit könnte auf verschiedene Arten verbessert werden, aber sie war gut genug für das, was ich damit machen wollte – das zu verbessern, steht jedoch auf der Liste.

Ich würde gerne einen dynamischen szenenbasierten Octree erstellen, um besseres Raycasting und Kollisionserkennung für Szenen mit vielen Maschen zu ermöglichen, aber ich hatte selbst keinen guten Anwendungsfall. Vielleicht eines Tages!

@EliasHasle Meine Implementierung ist ein Baum von JS-Klassenobjekten (ich verwende ES6-Klassen, dh Prototypinstanzen). Grundsätzlich ist ein Knoten in meiner Implementierung ein Baum. Ich habe jedoch eine zusätzliche Octree-Klasse, die den Stammknoten enthält und Methoden zum Erstellen und Durchsuchen (und auch zum Debuggen und Visualisieren) des Baums bereitstellt.

Ich habe nicht viel mit den Werten für die maximale Baumknotenebene und die maximale Anzahl von Scheitelpunkten pro Blattknoten experimentiert. Vielleicht gibt es eine bessere Kombination.
Ich verwende auch eine Begrenzungsvolumenhierarchie, um Schnittmengentests zu beschleunigen. Also teste ich die Bounding Sphere und Bounding Box des Netzes, bevor ich tatsächlich den Octree der Netzgeometrie überprüfe.

Ich kann ein Buch empfehlen, das eine sehr schöne Sammlung von Kollisionserkennungsansätzen ist: C. Ericson, Real-Time Collision Detection, CRC Press, 2004

@gkjohnson Das sieht toll aus! Die Leistung ist beeindruckend. Welche Art von BVH verwendest du?

@matthias-w Danke!

Es gibt ein paar Split-Strategieoptionen, aber das Teilen der BVH-Knoten in der Mitte der längsten Seite baut den Baum am schnellsten auf. Die Dreiecke werden basierend auf der Mitte ihrer Grenzen in Seiten unterteilt, und die Baumknoten werden erweitert, um die untergeordneten Dreiecke vollständig zu enthalten (es gibt also eine kleine Überlappung der Baumknoten).

@gkjohnson Also ist die BVH-Implementierung eine Art unausgeglichener Kd-Baum mit Achsenauswahl abhängig von der Kastenform, richtig? Wenn ja, habe ich darüber nachgedacht, so etwas für Fälle zu tun, in denen die Wurzel BB zu weit von einem Würfel entfernt ist, und dann nach der bedingten Aufteilung Octrees zu verwenden. Dies würde vermutlich gut auf Fälle wie die Kartenwelt von @Usnul zutreffen , in der die anfängliche Aufteilung in den beiden "Karten" -Dimensionen erfolgen würde und die nachfolgende Aufteilung in 3D erfolgen würde. Ich denke, es ist eine viel bessere Lösung als meine, die Wurzel BB zu einem Begrenzungswürfel mit demselben Zentrum zu erweitern und dann die gesamte Octree-Aufteilung zu verwenden.

Ich habe gerade eine bessere Methode für Raycasting auf PlaneBufferGeometry implementiert.

@kpetrow Ich denke, dass bei der Optimierung von Raycasts auf PlaneBufferGeometry die Annahme, dass die Scheitelpunktpositionen nicht geändert werden, für hochauflösende Geometrien, bei denen eine lineare Suche nicht ausreicht, sehr wahrscheinlich nicht gültig ist. Soweit ich das beurteilen kann, besteht die Hauptverwendung von hochauflösendem PlaneBufferGeometry darin, es durch Verschieben der Scheitelpunkte neu zu formen, während die Topologie beibehalten wird, z. B. um Gelände zu bauen.

Schluss zugunsten von #13909.

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen