Heim  >  Artikel  >  Web-Frontend  >  Eine kurze Diskussion über die Anwendung von NeedsUpdate in three.js_html5 Tutorial-Fähigkeiten

Eine kurze Diskussion über die Anwendung von NeedsUpdate in three.js_html5 Tutorial-Fähigkeiten

WBOY
WBOYOriginal
2016-05-16 15:50:572638Durchsuche

Viele Objekte in Three.js verfügen über ein NeedsUpdate-Attribut, das in der Dokumentation selten erwähnt wird (aber es gibt nicht viele Dokumente für Three.js und viele Probleme müssen durch Issues auf Github in verschiedenen Tutorials im Internet gelöst werden). Ich bin nicht sehr gut darin, das zu schreiben, da dieses Attribut für ein einfaches Einstiegsprogramm nicht verwendet wird.
Wofür wird dieses Attribut also verwendet? Kurz gesagt, es sagt dem Renderer, dass ich den Cache für diesen Frame aktualisieren soll. Obwohl es als Flag sehr einfach zu verwenden ist, möchten Sie wissen, warum der Cache dies tun muss Um aktualisiert zu werden, müssen Sie wissen, welche Caches aktualisiert werden. Daher ist es notwendig, dies sorgfältig zu verstehen.
Warum NeedsUpdate benötigt wird
Lassen Sie uns zunächst einen Blick darauf werfen, warum Cache benötigt wird. Der Cache dient im Allgemeinen dazu, die Anzahl der Datenübertragungen zu reduzieren und dadurch die Zeit zu reduzieren, die das Programm benötigt Hier gilt das Gleiche: Im Allgemeinen ist es nicht einfach, ein Objekt (Mesh) erfolgreich auf dem Bildschirm anzuzeigen.
Zuerst müssen alle Scheitelpunktdaten und Texturdaten übertragen werden Durch das Programm von der lokalen Festplatte in den Speicher lesen.
Nachdem das Programm die entsprechende Verarbeitung im Speicher durchgeführt hat, überträgt es die Scheitelpunktdaten und Texturdaten der Objekte, die auf den Bildschirm gezeichnet werden müssen, in den Videospeicher.
Abschließend werden beim Rendern jedes Frames die Vertex- und Texturdaten im Videospeicher zur Montage und Zeichnung auf die GPU übertragen.
Nach dem Pyramiden-Datenübertragungsmodell ist der erste Schritt offensichtlich der langsamste. Wenn er in einer Umgebung wie WebGL über das Netzwerk übertragen wird, ist er noch langsamer. Der zweite Schritt ist die Zeit der Übertragung vom Speicher zum Video Ein einfacher Datentest wird später durchgeführt.
Dann gibt es noch die Häufigkeit, mit der diese drei Schritte verwendet werden. Bei kleinen Szenen ist der erste Schritt ein einmaliger Vorgang, das heißt, bei jeder Initialisierung des Programms werden alle Daten einer Szene in den Speicher geladen . Für groß angelegte Szenarien kann ein gewisses asynchrones Laden durchgeführt werden, dies wird jedoch derzeit nicht in Betracht gezogen. Die Häufigkeit des zweiten Schritts sollte dieses Mal das Wichtigste sein, worüber man sprechen sollte. Schreiben Sie zunächst ein einfaches Programm, um den durch diesen Übertragungsschritt verursachten Verbrauch zu testen

Code kopieren
Der Code lautet wie folgt:

var canvas = document.createElement('canvas'); 'experimental -webgl');
var vertices = [];
for(var i = 0; i < 1000*3; i ){
vertices.push(i * Math.random() );
}
var buffer = _gl.createBuffer();
console.profile('buffer_test');
console.profileEnd('buffer_test'); 🎜> function bindBuffer(){
for(var i = 0; i < 1000; i ){
_gl.bindBuffer(_gl.ARRAY_BUFFER, buffer);
_gl.bufferData(_gl.ARRAY_BUFFER, neues Float32Array (Vertices), _gl.STATIC_DRAW);
}
}


Lassen Sie mich zunächst kurz erklären, dass es sich bei Vertices um ein Array handelt, das Scheitelpunkte speichert. Da jeder Scheitelpunkt drei Koordinaten hat: x, y und z, wird ein Array mit einer Größe von 3000 benötigt Der Befehl _gl.createBuffer öffnet einen Puffer im Videospeicher, um Scheitelpunktdaten zu speichern, und verwendet dann _gl.bufferData, um eine Kopie der generierten Scheitelpunktdaten vom Speicher in den Videospeicher zu übertragen. Es wird hier davon ausgegangen, dass es 1000 Objekte mit 1000 Scheitelpunkten in einer Szene gibt. Jeder Scheitelpunkt besteht aus 3 Float-Daten mit 32 Bits und 4 Bytes. Nach der Berechnung sind es fast 1000 x 1000 x 12 = 11 MB. 15 ms Zeit, hier sehen Sie vielleicht, dass 15 ms nur eine so kleine Zeitspanne sind, aber wenn Sie für ein Echtzeitprogramm eine Bildrate von 30 fps sicherstellen möchten, sollte die für jedes Bild erforderliche Zeit auf etwa 30 ms eingestellt werden. Das bedeutet, dass die Übertragung der Daten nur die Hälfte der Zeit in Anspruch nimmt. Sie müssen wissen, dass die Zeichenvorgänge in der GPU und die verschiedenen Verarbeitungsvorgänge in der CPU geizig sein sollten.
Daher sollte die Anzahl der Übertragungen in diesem Schritt minimiert werden. Tatsächlich können alle Vertex- und Texturdaten vom Speicher in den Videospeicher übertragen werden. Dies ist, was three.js jetzt tut . Zu diesem Zeitpunkt werden die Scheitelpunktdaten des zu zeichnenden Objekts (Geometrie) in den Anzeigespeicher übertragen und der Puffer wird in der Geometrie.__webglVertexBuffer zwischengespeichert. Anschließend wird jedes Mal das verticesNeedUpdate-Attribut der Geometrie beurteilt Wenn keine Aktualisierung erforderlich ist, wird der aktuelle Cache verwendet. Wenn Sie sehen, dass verticesNeedUpate wahr ist, werden die Scheitelpunktdaten in Geometry erneut übertragen für statische Objekte, aber wenn wir auf Objekte stoßen, deren Scheitelpunkte sich häufig ändern, verwenden Sie beispielsweise Partikelsysteme, die Scheitelpunkte als Partikel verwenden, und Mesh, das Skelettanimationen verwendet. Diese Objekte ändern ihre Scheitelpunkte in jedem Frame, daher müssen sie ihre verticesNeedUpdate-Eigenschaft festlegen um jeden Frame wahr zu machen, um dem Renderer mitzuteilen, dass ich die Daten erneut übertragen muss.
Tatsächlich wird die Position der Scheitelpunkte in WebGL-Programmen meist im Vertex-Shader geändert, um Partikeleffekte und Skelettanimationen zu vervollständigen. Aufgrund der begrenzten Rechenleistung von JavaScript ist die Erweiterung jedoch einfacher, wenn sie auf der CPU-Seite berechnet wird , Die meisten dieser rechenintensiven Vorgänge werden auf der GPU-Seite ausgeführt. In diesem Fall besteht keine Notwendigkeit, die Scheitelpunktdaten erneut zu übertragen, sodass der obige Fall in tatsächlichen Programmen tatsächlich nicht häufig verwendet wird. Der Cache von Texturen und Materialien wird häufiger aktualisiert.
Der obige Fall beschreibt hauptsächlich eine Szene, in der Vertexdaten übertragen werden. Zusätzlich zu den Vertexdaten gibt es auch einen großen Teil der Textur. Eine Textur im R8G8B8A8-Format belegt bis zu 4 MB Speicher folgendes Beispiel

Kopieren Sie den Code
Der Code lautet wie folgt:

var canvas = document.createElement('canvas');
var _gl = canvas.getContext('experimental-webgl');
var img = new Image; img. onload = function(){
console.profile('texture test');
console.profileEnd('texture test'); src = 'test_tex.jpg';
function bindTexture(){
_gl.bindTexture(_gl.TEXTURE_2D, texture);
_gl.texImage2D(_gl.TEXTURE_2D, 0, _gl.RGBA, _gl.RGBA , _gl .UNSIGNED_BYTE, img);
}


Es ist nicht nötig, es 1000 Mal zu wiederholen. Es dauert 30 ms, um eine 10241024-Textur einmal zu übertragen, und fast 2 ms für eine 256256-Textur. Daher wird die Textur zu Beginn nur einmal übertragen. Wenn dann die Eigenschaft „texture.needsUpdate“ nicht manuell auf „true“ gesetzt ist, wird die Textur, die in den Videospeicher übertragen wurde, direkt verwendet.
Welche Caches müssen aktualisiert werden?
Das Obige beschreibt anhand von zwei Fällen, warum three.js ein solches NeedsUpdate-Attribut hinzufügen muss. Als Nächstes listen wir einige Szenarien auf, um zu erfahren, unter welchen Umständen es benötigt wird Diese Caches müssen manuell aktualisiert werden.
Asynchrones Laden von Texturen
Dies ist eine kleine Gefahr, da die Frontend-Bilder asynchron geladen werden. Wenn Sie Texture.needsUpdate=true direkt nach dem Erstellen des IMG schreiben, wird three.js verwendet Der Renderer verwendet _gl.texImage2D, um die leeren Texturdaten in diesem Frame in den Videospeicher zu übertragen, und setzt dieses Flag dann auf „false“. Danach werden die Videospeicherdaten nicht aktualisiert, bis das Bild geladen ist damit das gesamte Bild im Onload-Ereignis geladen wird, bevor Texture.needsUpdate = true
Video Texture
Die meisten Texturen laden und übertragen das Bild nur einmal wie im obigen Fall, aber nicht für Videotexturen, da es sich bei dem Video um einen Bildstream handelt und das anzuzeigende Bild in jedem Frame unterschiedlich ist. Daher muss NeedsUpdate in jedem Frame auf „True“ gesetzt werden, um die Texturdaten in der Grafikkarte zu aktualisieren.
Renderpuffer verwenden
Renderpuffer ist ein spezielles Objekt. Im Allgemeinen überträgt das Programm die gesamte Szene direkt auf den Bildschirm, aber wenn mehr Nachbearbeitung oder dieser bildschirmbasierte xxx erfolgt (z Bei einer bildschirmbasierten Umgebungsokklusion müssen Sie die Szene zunächst in einen Renderpuffer zeichnen. Dieser Puffer ist eigentlich eine Textur, wird jedoch durch die vorherige Zeichnung generiert und nicht von der Festplatte geladen. Es gibt ein spezielles Texturobjekt WebGLRenderTarget in three.js, um den Renderbuffer zu initialisieren und zu speichern. Diese Textur muss außerdem in jedem Frame auf true gesetzt werden. js wird durch THREE.Material beschrieben. Tatsächlich müssen keine Daten übertragen werden, aber warum müssen wir hier auch über den Shader sprechen? Die in der GPU vorgesehene Möglichkeit zur Verarbeitung von Scheitelpunkten und Pixeln ist der Begriff „Schattierung“, der die Schattierung in der GPU darstellt Drücken Sie das Material des Objekts aus, da der Shader wie alle Programme einmal zur Laufzeit kompiliert und verknüpft werden muss. Daher ist es am besten, das Programm einmal zu kompilieren und auszuführen. Daher wird in three.js das Shader-Programm kompiliert und verknüpft, wenn das Material initialisiert wird, und das nach der Kompilierung und Verknüpfung erhaltene Programmobjekt wird zwischengespeichert. Im Allgemeinen muss ein Material nicht den gesamten Shader neu kompilieren. Um das Material anzupassen, müssen Sie nur die einheitlichen Parameter des Shaders ändern. Wenn Sie jedoch das gesamte Material ersetzen, z. B. den ursprünglichen Phong-Shader durch einen Lambert-Shader ersetzen, müssen Sie material.needsUpdate auf true setzen und neu kompilieren. Diese Situation kommt jedoch selten vor, und die häufigere Situation ist die unten beschriebene.
Hinzufügen und Löschen von Lichtern
Dies sollte in Szenen relativ häufig vorkommen. Vielleicht fallen viele Leute, die gerade erst mit der Verwendung von three.js begonnen haben, in diese Grube und fügen der Szene nach der Einstellung dynamisch Lichter hinzu Ich habe festgestellt, dass das Licht nicht funktioniert, wenn der integrierte Shader von Three.js verwendet wird, z. B. Phong und Lambert. Wenn Sie sich den Quellcode im Renderer ansehen three.js befindet sich im integrierten Shader-Code. Verwenden Sie #define, um die Anzahl der Lichter in der Szene festzulegen. Der Wert dieses #define wird bei jeder Aktualisierung des Materials durch String-Splicing-Shader ermittelt



Code kopieren
Der Code lautet wie folgt: "#define MAX_DIR_LIGHTS " Parameter .maxDirLights,
"#define MAX_POINT_LIGHTS "parameters.maxPointLights,
"#define MAX_SPOT_LIGHTS "parameters.maxSpotLights,
"#define MAX_HEMI_LIGHTS "parameters.maxHemiLights,


Es stimmt, dass diese Schreibweise die Verwendung von GPU-Registern effektiv reduzieren kann. Wenn nur ein Licht vorhanden ist, können Sie nur eine einheitliche Variable deklarieren, die für ein Licht erforderlich ist, aber jedes Mal, wenn sich die Anzahl der Lichter ändert, insbesondere wenn Hinzufügen Sie müssen den Shader neu zusammenfügen, kompilieren und verknüpfen. Zu diesem Zeitpunkt müssen Sie auch material.needsUpdate aller Materialien auf true setzen.
Textur ändern
Die Textur hier ändern Dies bedeutet nicht, dass die Texturdaten aktualisiert werden, sondern weil das Originalmaterial Texturen verwendet und diese dann nicht mehr verwendet, oder weil das Originalmaterial keine Texturen verwendet und diese später hinzugefügt hat. Wenn Sie die Materialien nicht zwangsweise manuell aktualisieren, wird dies der endgültige Effekt sein Die Gründe für dieses Problem sind wie folgt: Das Hinzufügen von Lichtern oben ist fast dasselbe, da dem Shader ein Makro hinzugefügt wird, um zu bestimmen, ob Texturen verwendet werden:

Code kopieren
Code wie folgt:

parameters.map ? "#define USE_MAP" : ",
parameters.envMap ? "#define USE_ENVMAP" : ",
parameters.lightMap ? "#define USE_LIGHTMAP" : ",
parameters.bumpMap ? "#define USE_BUMPMAP" : ",
parameters.normalMap ? define USE_NORMALMAP" : "",
parameters.specularMap ? "#define USE_SPECULARMAP" : "",

Jedes Mal, wenn Map, EnvMap oder LightMap den wahren Wert ändern, muss das Material geändert werden aktualisiert werden
Änderungen in anderen Scheitelpunktdaten
Eigentlich verursacht die obige Texturänderung auch ein Problem. Der Hauptgrund ist, dass während der Initialisierung keine Textur vorhanden ist, diese jedoch später dynamisch hinzugefügt wird In dieser Umgebung reicht es nicht aus, material.needsUpdate auf true zu setzen. Warum tritt dieses Problem immer noch auf, wenn das Programm optimiert wird? Geometrie und Material werden zum ersten Mal im Renderer initialisiert. Wenn festgestellt wird, dass keine Textur vorhanden ist, enthalten die Daten im Speicher zwar UV-Daten für jeden Scheitelpunkt, aber three.js kopiert diese Daten immer noch nicht in das Video Speicher Die ursprüngliche Absicht sollte darin bestehen, wertvollen Videospeicherplatz zu sparen, aber nach dem Hinzufügen der Textur überträgt die Geometrie die UV-Daten nicht intelligent zur Texturverwendung. Wir müssen uvsNeedsUpdate manuell festlegen, um ihm mitzuteilen, dass es Zeit für eine Aktualisierung ist Das UV. Dieses Problem hat mich am Anfang wirklich lange beschäftigt.
Informationen zum Attribut „needUpdate“ mehrerer Vertexdaten finden Sie in dieser Ausgabe
https://github.com/mrdoob/ three.js/wiki/Updates
Finally
drei Die Optimierung von .js ist gut, aber verschiedene Optimierungen bringen verschiedene Fallstricke mit sich. In diesem Fall ist es am besten, sich den Quellcode oder die Dateiprobleme auf Github anzusehen.
Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn