Viele Objekte in drei.Js haben eine Bedarfs -Eigenschaft, und sie sind selten in den Dokumenten geschrieben (es gibt jedoch nicht viele Dokumente in drei.Js, und viele Probleme müssen sich immer noch auf Probleme auf Github verlassen). Sie wissen nicht, wie man dies in verschiedenen Tutorials online schreibt, da diese Eigenschaft für einfache Einführungsprogramme nicht verwendet werden kann.
Wofür wird dieses Attribut verwendet? Kurz gesagt, ich sage dem Renderer, dass ich den Cache in diesem Frame aktualisieren sollte. Obwohl es sehr einfach als Flag -Bit verwendet wird, weil Sie wissen müssen, warum Sie den Cache aktualisieren müssen und welche Caches aktualisiert werden müssen, ist es immer noch erforderlich, ihn sorgfältig zu verstehen.
Warum braucht es, um zu tunLassen Sie uns zunächst einen Blick darauf werfen, warum Cache erforderlich ist. Das Vorhandensein von Cache besteht im Allgemeinen darin, die Anzahl der Datenübertragungszeiten zu verringern und so die für die Datenübertragung aufgewendete Zeit zu verringern. Hier ist es auch wahr, dass es nicht einfach ist, dass ein Objekt (Mesh) am Ende erfolgreich auf dem Bildschirm angezeigt wird. Es muss dreimal auf das Schlachtfeld gebracht werden.
Das erste besteht darin, alle Scheitelpunktdaten und Texturdaten von der lokalen Festplatte durch das Programm in den Speicher zu lesen.
Nachdem das Programm eine geeignete Verarbeitung im Speicher durchgeführt hat, muss es die Vertex -Daten und Texturdaten der Objekte übertragen, die auf den Bildschirm zum Videospeicher gezogen werden müssen.
Schließlich werden beim Rendern jedes Frame die Scheitelpunktdaten und Texturdaten im Videospeicher zur Montage und Zeichnung in die GPU gespült.
Nach dem pyramidenähnlichen Datenübertragungsmodell ist der erste Schritt offensichtlich der langsamste. Wenn es in einer Umgebung wie WebGL über das Netzwerk übertragen wird, ist es noch langsamer. Die zweite ist die Zeit vom Speicher zum Videospeicher, der später ein einfacher Datentest ist.
Dann gibt es die Häufigkeit der Verwendung dieser drei Schritte. Für kleine Szenarien ist der erste Schritt einmalig, dh jedes Mal, wenn das Programm initialisiert wird, werden alle Daten eines Szenarios in den Speicher geladen. Bei großen Szenarien können einige asynchrone Beladungen durchgeführt werden, aber es ist derzeit nicht das Problem, das wir in Betracht ziehen. Die Häufigkeit des zweiten Schritts sollte das Wichtigste sein, um über diese Zeit zu sprechen. Schreiben Sie zunächst ein einfaches Programm, um den durch diese Übertragungsschritt verursachten Verbrauch zu testen.
var canvas = document.createelement ('canvas');
var _gl = canvas.getContext ('Experimental-Webgl');
var vertices = [];
für (var i = 0; i <1000*3; i ++) {
Eckics.push (i * math.random ());
}
var buffer = _gl.createBuffer ();
console.profile ('buffer_test');
bindbuffer ();
console.profileend ('buffer_test');
Funktion Bindbuffer () {
für (var i = 0; i <1000; i ++) {
_gl.bindbuffer (_gl.array_buffer, buffer);
_gl.BufferData (_gl.array_buffer, New Float32Array (Scheitelpunkte), _gl.static_draw);
}
}
Lassen Sie uns dieses Programm kurz kurz erklären. Scheitelpunkte sind ein Array, das Eckpunkte spart. Hier werden zufällig 1.000 Eckpunkte generiert. Da jeder Scheitelpunkt drei Koordinaten x, y und z hat, ist eine Array mit einer Größe von 3000 Größe erforderlich. Der Befehl _gl.createBuffer öffnet einen Cache zum Speichern von Vertex -Daten im Videospeicher und verwendet dann _gl.bufferdata, um die generierten Vertex -Daten vom Speicher auf Videospeicher zu übertragen. Hier gehen wir davon aus, dass 1000 Objekte mit 1000 Eckpunkten in einer Szene enthalten sind. Jeder Scheitelpunkt beträgt 3 32-Bit 4 Bytes von Float-Daten. Berechnen Sie die Daten von fast 1000 x 1000 x 12 = 11 m. Das Profil dauert ungefähr 15 ms. Hier können wir sehen, wie 15 ms nur ein wenig Zeit ist. Für ein Echtzeitprogramm muss jedoch die für jeden Frame erforderliche Zeitrate von 30 fps bei etwa 30 ms die Zeit gewährleistet werden. Wie kann es die Hälfte der Zeit dauern, nur die Datenübertragung durchzuführen? Sie sollten wissen, dass der große Kopf die Zeichenvorgänge in der GPU und in verschiedenen Verarbeitung in der CPU sein sollte, und Sie sollten bei jedem Schritt der Operation im gesamten Rendering -Prozess geizig sein.
Daher sollte die Anzahl der Übertragungen in diesem Schritt minimiert werden. Tatsächlich kann es verwendet werden, um alle Scheitelpunktdaten und Texturdaten vom Speicher auf Videospeicher zu übertragen, wenn sie geladen werden. Das macht drei.js jetzt. Die Scheitelpunktdaten des Objekts, das gezogen werden muss, werden zum ersten Mal in den Videospeicher übertragen und zwischen dem Puffer in Geometrie .__ WebGlvertexBuffer zwischengespeichert. Danach beurteilen Sie jedes Mal, wenn Sie zeichnen, die EckriceNeedupdate -Eigenschaft der Geometrie. Wenn Sie nicht aktualisieren müssen, verwenden Sie den aktuellen Cache direkt. Wenn Sie sehen, dass Scheitelpunkte trugen sind, werden die Scheitelpunktdaten in der Geometrie in die Geometrie übertragen .__ WebglvertexBuffer. Im Allgemeinen benötigen wir diesen Schritt nicht für statische Objekte. Wenn wir jedoch Objekten begegnen, die sich häufig ändern, z. B. die Verwendung von Scheitelpunkten als Partikelsysteme, und Mesh, das Skelettanimationen verwendet, ändern diese Objekte ihre Scheitelpunkte in jedem Frame, so
Tatsächlich werden in WebGL -Programmen mehr Scheitelpunktpositionen im Scheitelpunkt -Shader geändert, um Partikeleffekte und Skelettanimationen zu vervollständigen. Obwohl es einfacher ist, sich zu erweitern, wenn es auf der CPU -Seite zur Berechnung platziert wird, werden aufgrund der Einschränkungen der Rechenleistung von JavaScript mehr dieser Rechenvorgänge auf der GPU -Seite platziert. In diesem Fall müssen keine Scheitelpunktdaten übertragen werden, sodass der obige Fall im tatsächlichen Programm nicht viel verwendet wird, und es geht mehr um die Aktualisierung des Textur- und des materiellen Cache.
Der obige Fall beschreibt hauptsächlich ein Szenario, in dem Scheitelpunktdaten übertragen werden. Neben Scheitelpunktdaten gibt es auch einen großen Kopf, der die Textur ist. Eine 1024*1024 R8G8B8A8-Formattextur muss eine Speichergröße von bis zu 4 m einnehmen. Sehen Sie sich also das folgende Beispiel an.
var canvas = document.createelement ('canvas');
var _gl = canvas.getContext ('Experimental-Webgl');
var texture = _gl.createTexture ();
var img = neues Bild;
img.onload = function () {
console.profile ('Texturtest');
BindTexture ();
console.profileend ('Texturtest');
}
img.src = 'test_tex.jpg';
Funktion BindTexture () {
_gl.bindTexture (_gl.texture_2d, texture);
_gl.teximage2d (_gl.texture_2d, 0, _gl.rgba, _gl.rgba, _gl.unsigned_byte, img);
}
Hier müssen die perversen 1000 -mal nicht wiederholt. Es dauert 30 ms, um die Textur von 10241024 gleichzeitig zu übertragen, und ein 256256 -Bild beträgt fast 2 ms. Daher sollte in drei.Js die Textur zu Beginn nur einmal übertragen werden. Danach wird die Textur, die in den Videospeicher übertragen wurde, direkt an die Textur.Needsupdate -Eigenschaft eingestellt, die direkt auf TRUE übertragen wurde.
Welche Caches müssen aktualisiert werdenDas obige beschreibt, warum drei.js ein solches Bedarfs -Attribut in zwei Fällen hinzufügen müssen. Listen Sie anschließend mehrere Szenarien auf, um zu wissen, unter welchen Umständen Sie diese Caches manuell aktualisieren müssen.
Asynchrone Laden von TexturenDies ist eine kleine Grube, da das Front-End-Bild asynchron geladen ist. Wenn Sie direkt nach dem Erstellen von iMG texture.needsUpdate = true schreiben, verwendet der drei.js -Renderer _gl.teximage2d, um leere Texturdaten in dieses Frame in den Videospeicher zu übertragen und dieses Flag dann auf false zu setzen. Wenn das Bild geladen wird, werden die Videospeicherdaten nicht aktualisiert. Daher müssen Sie warten, bis das gesamte Bild in das Onload -Ereignis geladen wird, bevor Sie Textur schreiben.
VideostrukturDie meisten Texturen sind genau wie der oben genannte Fall, um Bilder direkt zu laden und zu übertragen, aber nicht für Videokexturen, da das Video ein Bildstrom ist und das in jedem Frame angezeigte Bild unterschiedlich ist. Daher müssen Sie BedarfSupdate für jeden Frame für jeden Frame einstellen, um die Texturdaten in der Grafikkarte zu aktualisieren.
Verwenden Sie RenderpufferDer Renderpuffer ist ein relativ spezielles Objekt. Im Allgemeinen spüle das Programm nach dem Ziehen der gesamten Szene direkt auf den Bildschirm. Wenn es jedoch mehr nach der Verarbeitung oder im Bildschirmbasis von XXX (z. B. Bildschirmbasierter Umgebungsaufkommen) gibt, muss die Szene zuerst auf einem Renderpuffer gezogen werden. Dieser Puffer ist eigentlich eine Textur, wird jedoch durch die vorherige Zeichnung erzeugt, die nicht von der Festplatte geladen wird. Es gibt ein spezielles Texturobjekt -Webglrendertarget in drei.js, um RenderBuffer zu initialisieren und zu speichern. Diese Textur muss auch in jedem Frame auf true eingestellt werden.
Materials benötigtDas Material wird in drei.Js bis drei.Material beschrieben. In der Tat hat das Material nicht viele Daten zu übertragen, aber warum müssen Sie ein Bedürfnissen erstellen? Hier werde ich über den Shader sprechen. Shader wird als Shader übersetzt, der die Möglichkeit bietet, Scheitelpunkte und Pixel in der GPU zu programmieren. Es gibt einen schattigen Begriff in Malerei, um die helle und dunkle Malmethode darzustellen. Die Schattierung in der GPU ist ähnlich. Das Licht und die Dunkelheit des Lichts werden vom Programm berechnet, um das Material eines Objekts auszudrücken. OK, da Shader wie alle Programme ein Programm ist, das auf der GPU ausgeführt wird, ist es erforderlich, einen Zusammenstellung und eine Verknüpfungsoperation durchzuführen. In WebGL wird das Shader -Programm zur Laufzeit kompiliert, was natürlich Zeit braucht, sodass es am besten zusammengestellt und bis zum Ende des Programms ausgeführt werden kann. Wenn das Material in drei.Js initialisiert wird, wird das Shader -Programm kompiliert und verknüpft und das Programmobjekt erhalten, das nach dem Abschnitt der Kompilierverbindung erhalten wird. Im Allgemeinen muss ein Material den gesamten Shader nicht mehr neu kompilieren. Um das Material anzupassen, müssen Sie nur die gleichmäßigen Parameter des Shaders ändern. Wenn Sie jedoch das gesamte Material ersetzen, z. B. das Ersetzen des ursprünglichen Pong -Shaders durch einen Lambert -Shader, müssen Sie Material einstellen. Diese Situation ist jedoch selten und desto häufiger ist die unten erwähnte.
Lichter hinzufügen und entfernenDies sollte in der Szene häufiger sein. Vielleicht werden viele Leute, die gerade mit drei verwendet haben. Js werden in diese Grube fallen. Nachdem sie dynamisch ein Licht zur Szene hinzugefügt haben, stellen sie fest, dass das Licht nicht funktioniert. Wenn Sie jedoch den in drei integrierten Shader verwenden. Der Wert dieser #Define wird jedes Mal, wenn das Material aktualisiert wird, durch String -Spleiß -Shader erhalten. Der Code ist wie folgt.
"#define max_dir_lights" + parameter.maxDirlights,
"#define max_point_lights" + parameter.maxpointsLights,
"#define max_spot_lights" + parameter.maxspotlights,
"#define max_hemi_lights" + parameter.maxhemilights,
In der Tat kann diese Schreibmethode die Verwendung von GPU -Registern effektiv verringern. Wenn es nur ein Licht gibt, können Sie nur die für ein Licht erforderliche einheitliche Variable deklarieren. Wenn sich jedoch die Anzahl der Lichter ändert, insbesondere beim Hinzufügen, müssen Sie den Shader neu einstellen und kompilieren und verknüpfen. Zu diesem Zeitpunkt müssen Sie auch das Material einstellen.
Textur ändernDie Texturänderung hier bedeutet nicht, die Texturdaten zu aktualisieren, sondern dass das Originalmaterial die Textur verwendet, sondern später nicht verwendet wurde oder das Originalmaterial die Textur nicht verwendet und dann hinzugefügt wurde. Wenn das Material nicht manuell aktualisiert wird, unterscheidet sich der endgültige Effekt von dem, was Sie denken. Der Grund für dieses Problem ist der oben genannten Beleuchtung ähnlich, und es liegt auch daran, dass dem Shader ein Makro hinzugefügt wurde, um festzustellen, ob die Textur verwendet wurde.
parameter.map? "#define use_map": "",
Parameter.Envmap? "#define use_envmap": "",
Parameter.LightMap? "#define use_lightmap": "",
parameter.bumpmap? "#define use_bumpmap": "",
Parameter.Normalmap? "#define use_normalmap": "",
Parameter.SpecularMap? "#define use_specularmap": "",
Jedes Mal, wenn MAP, EnvMap oder Lightmap den wahren Wert ändern, müssen Sie das Material aktualisieren
Änderungen in anderen ScheitelpunktdatenTatsächlich wird die oben genannte Änderung der Textur ein Problem erzeugen. Dies liegt hauptsächlich daran, dass während der Initialisierung keine Textur vorhanden ist. In dieser Umgebung reicht es jedoch nicht aus, Material einzustellen. Es muss auch Geometrie einstellen. Warum gibt es ein solches Problem? Es liegt an der Optimierung des Programms um drei.js. Bei der ersten Initialisierung von Geometrie und Material zum ersten Mal in Renderer, wenn beurteilt wird, dass es keine Textur gibt, obwohl in den Daten im Speicher jede Scheitelpunkt -UV -Daten vorhanden sind, kopieren drei.js diese Daten nicht in den Videospeicher. Die ursprüngliche Absicht sollte darin bestehen, einen wertvollen Video -Speicherraum zu sparen. Nach dem Hinzufügen von Textur überträgt die Geometrie diese UV -Daten jedoch nicht intelligent für die Verwendung von Textur. Wir müssen UVSneedsupdate manuell einstellen, um darüber zu informieren, dass es Zeit ist, UV zu aktualisieren. Diese Frage hat mich zu Beginn lange Zeit betrügen lassen.
Für die Notwendigkeitsattribute mehrerer Arten von Scheitelpunktdaten können Sie dieses Problem sehen
https://github.com/mrdoob/three.js/wiki/updates
endlichDie Optimierung von drei.js leistet gute Arbeit, bringt jedoch verschiedene Fallstricke mit, die möglicherweise von verschiedenen Optimierungen berührt werden. Der beste Weg, dies zu tun, besteht darin, den Quellcode zu betrachten oder zu Github zu gehen, um Probleme zu erwähnen