Three.jsの多くのオブジェクトにはNeedsUpdateプロパティがあり、ドキュメントに記述されることはめったにありません(ただし、Three.jsには多くのドキュメントはありません。多くの問題は、GitHubの問題に依存する必要があります)。シンプルな導入プログラムの場合、このプロパティを使用できないため、さまざまなチュートリアルでこれをオンラインで書く方法を知りません。
では、この属性は何に使用されていますか?一言で言えば、このフレームのキャッシュを更新する必要があることをレンダラーに伝えます。フラグビットとして使用するのは非常に簡単ですが、なぜキャッシュを更新する必要があるのか、どのキャッシュを更新するかを知る必要があるため、慎重に理解する必要があります。
なぜNeedsUpDateまず、キャッシュが必要な理由を見てみましょう。キャッシュの存在は、一般に、データ送信時間の数を減らすことであり、それによりデータ送信に費やされる時間を短縮します。ここでは、オブジェクト(メッシュ)が最終的に画面に正常に表示されることは容易ではないことも事実です。戦場に3回転送する必要があります。
1つ目は、プログラムを通じてローカルディスクからメモリにすべての頂点データとテクスチャデータを読み取ることです。
次に、プログラムがメモリで適切な処理を行った後、画面に描画する必要があるオブジェクトの頂点データとテクスチャデータをビデオメモリに転送する必要があります。
最後に、各フレームをレンダリングすると、ビデオメモリの頂点データとテクスチャデータがアセンブリと描画のためにGPUに流されます。
ピラミッド様のデータ送信モデルによると、最初のステップは明らかに最も遅いです。 WebGLのような環境でネットワークを介して送信されると、さらに遅くなります。 2つ目は、メモリからビデオメモリまでの時間です。これは、後で簡単なデータテストになります。
次に、これらの3つのステップの使用頻度があります。小さなシナリオの場合、最初のステップは1回限りです。つまり、プログラムが初期化されるたびに、シナリオのすべてのデータがメモリにロードされます。大規模なシナリオの場合、一部の非同期荷重が実行される場合がありますが、現在、私たちが検討している問題ではありません。 2番目のステップの頻度は、この時間について話す最も重要なことである必要があります。まず、この送信ステップを実行することで引き起こされる消費をテストする簡単なプログラムを作成します。
var canvas = document.createelement( 'canvas');
var _gl = canvas.getContext( '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');
bindbuffer();
console.profileend( 'buffer_test');
関数bindbuffer(){
for(var i = 0; i <1000; i ++){
_gl.bindbuffer(_gl.array_buffer、buffer);
_gl.BufferData(_Gl.Array_Buffer、New Float32Array(Vertices)、_Gl.Static_Draw);
}
}
最初にこのプログラムを簡単に説明しましょう。頂点は、頂点を保存する配列です。ここでは、1,000個の頂点がランダムに生成されます。各頂点には3つの座標x、y、zがあるため、3000サイズの配列が必要です。 _gl.CreateBufferコマンドは、ビデオメモリに頂点データを保存するためのキャッシュを開き、_gl.BufferDataを使用して、生成された頂点データをメモリからビデオメモリに転送します。ここでは、シーンに1000の頂点を持つ1000のオブジェクトがあり、各頂点はフロートデータの3 32ビット4バイトであると仮定します。ほぼ1000 x 1000 x 12 = 11mのデータを計算します。プロファイルには約15msかかります。ここでは、15msがほんの少しの時間であることがわかります。ただし、リアルタイムプログラムの場合、30FPSのフレームレートを確保する場合、各フレームに必要な時間は約30ミリ秒で制御する必要があります。データ送信だけを実行するのに半分の時間がかかるにはどうすればよいですか?大きな頭はGPUの描画操作であり、CPUのさまざまな処理である必要があり、レンダリングプロセス全体の操作のあらゆるステップでけちなことをする必要があります。
したがって、このステップでの送信の数を最小限に抑える必要があります。実際、すべての頂点データとテクスチャデータをメモリからビデオメモリにロードしたときに転送するために使用できます。これがThree.JSが今行っていることです。描画する必要があるオブジェクトの頂点データは、初めてビデオメモリに転送され、バッファをジオメトリにキャッシュします。__webglvertexbuffer。その後、描画するたびに、ジオメトリのVerticesNeedUpdateプロパティを判断します。更新する必要がない場合は、現在のキャッシュを直接使用してください。 VerticesNeedupateが真であることがわかった場合、ジオメトリ内の頂点データはジオメトリに転送されます。__WebGlvertExbuffer。一般的に、静的オブジェクトにはこのステップは必要ありません。ただし、パーティクルシステムとして頂点を使用したり、スケルトンアニメーションを使用するメッシュなど、頻繁に変更されるオブジェクトが遭遇した場合、これらのオブジェクトは各フレームで頂点を変更するため、各フレームは頂点を再送信する必要があることをレンダリングする必要があることをレンダリングするために頂点を頂点に設定する必要があります。
実際、WebGLプログラムでは、頂点シェーダーでより多くの頂点位置が変更され、粒子効果とスケルトンアニメーションが完了します。 JavaScriptのコンピューティング能力の制限により、CPU側に配置すると計算すると拡張が容易ですが、これらの計算操作の多くがGPU側に配置されます。この場合、頂点データを再送信する必要はないため、上記のケースは実際のプログラムではあまり使用されておらず、テクスチャと材料キャッシュの更新に関するものです。
上記のケースでは、主に頂点データが送信されるシナリオを説明しています。頂点データに加えて、テクスチャである大きな頭もあります。 1024*1024サイズのR8G8B8A8形式のテクスチャは、最大4mのメモリサイズを占有する必要があるため、次の例を見てください。
var canvas = document.createelement( 'canvas');
var _gl = canvas.getContext( 'Experimental-Webgl');
var texture = _gl.createTexture();
var img = new Image;
img.onload = function(){
console.profile( 'テクスチャテスト');
bindTexture();
console.profileend( 'テクスチャテスト');
}
img.src = 'test_tex.jpg';
関数bindTexture(){
_gl.bindtexture(_gl.texture_2d、texture);
_gl.teximage2d(_gl.texture_2d、0、_gl.rgba、_gl.rgba、_gl.unsigned_byte、img);
}
ここで倒錯した1000回を繰り返す必要はありません。 10241024のテクスチャを一度に送信するには30msかかり、256256の画像はほぼ2msです。したがって、Three.jsでは、テクスチャは最初に1回のみ送信する必要があります。その後、Texture.NeedSupDateプロパティが手動でTrueに設定されていない場合、ビデオメモリに転送されたテクスチャが直接使用されます。
どのキャッシュを更新する必要があるか上記では、Three.jsが2つのケースを通じてそのようなニーズアップデート属性を追加する必要がある理由について説明しています。次に、これらのキャッシュを手動で更新するために必要な状況で知るためのいくつかのシナリオをリストします。
テクスチャの非同期負荷フロントエンドの画像は非同期にロードされているため、これは小さなピットです。 texture.needsupdate = trueを作成した場合、IMGを作成した後にtrueを真に書いた場合、Three.js Rendererは_gl.teximage2dを使用してこのフレームのビデオメモリに空のテクスチャデータを転送し、このフラグをfalseに設定します。次に、画像が読み込まれると、ビデオメモリデータが更新されません。したがって、テクスチャを書く前に、オンロードイベントに画像全体がロードされるのを待つ必要があります。
ビデオテクスチャほとんどのテクスチャは、上記のケースと同じように写真を直接読み込み、送信しますが、ビデオテクスチャではありません。ビデオは画像ストリームであり、各フレームに表示される画像が異なるため、グラフィックカードのテクスチャデータを更新するには、各フレームのneedupdateをTrueに設定する必要があります。
レンダリングバッファーを使用しますレンダリングバッファーは比較的特別なオブジェクトです。一般に、プログラムはシーン全体が描かれた後、画面に直接洗い流されます。ただし、ポストプロセッシングまたはスクリーンベースのXXX(画面ベースの周囲の発生など)が増えている場合は、最初にレンダリングバッファに描画する必要があります。このバッファーは実際にはテクスチャですが、ディスクからロードされていない以前の図面によって生成されます。 RenderBufferを初期化および保存するために、3.jsに特別なテクスチャオブジェクトWebGlrenderTargetがあります。このテクスチャは、各フレームでTrueに設定する必要があります。
マテリアルのニーズアップデートこの材料は、3.JSから3.materialで説明されています。実際、この資料には送信されるデータはあまりありませんが、なぜNeedUpDateを作成する必要があるのですか?ここで私はシェーダーについて話します。シェーダーはシェーダーとして翻訳されており、GPUで頂点とピクセルをプログラミングする可能性を提供します。絵画には、明るく暗い絵画方法を表すための日陰の用語があります。 GPUのシェーディングは似ています。光の光と闇は、オブジェクトの素材を表現するためにプログラムによって計算されます。 OK、ShaderはGPUで実行されているプログラムであるため、すべてのプログラムと同様に、コンパイルとリンク操作を実行する必要があります。 WebGLでは、シェーダープログラムは実行時にコンパイルされます。これにはもちろん時間がかかるため、プログラムの終了までコンパイルして実行するのが最善です。したがって、材料が3.jsで初期化されると、シェーダープログラムがコンパイルされリンクされ、コンパイルリンクがキャッシュされた後に取得されたプログラムオブジェクトがリンクされます。一般的に、素材はシェーダー全体を再コンパイルする必要はありません。材料を調整するには、シェーダーの均一なパラメーターを変更するだけです。ただし、元のポンシェーダーをランバートシェーダーに置き換えるなど、素材全体を交換する場合は、材料を設定する必要があります。ただし、この状況はまれであり、より一般的な状況は以下に言及しているものです。
ライトを追加して削除しますこれはシーンでより一般的であるはずです。たぶん、Three.jsを使用し始めたばかりの多くの人々がこのピットに落ちるでしょう。シーンに動的にライトを追加した後、ライトが機能しないことがわかります。ただし、Three.js、たとえばPhong、Lambertなどのシェーダーを使用して、レンダラーのソースコードを見ている場合、3つのjsがビルトインシェーダーコードで#defineを使用してシーンのライト数を設定することがわかります。この#defineの値は、素材が更新されるたびに、弦のスプライシングシェーダーによって取得されます。コードは次のとおりです。
"#define max_dir_lights" + parameters.maxdirlights、
"#define max_point_lights" + parameters.maxpointlights、
"#define max_spot_lights" + parameters.maxspotlights、
"#define max_hemi_lights" + parameters.maxhemilights、
実際、この執筆方法は、GPUレジスタの使用を効果的に削減できます。ライトが1つしかない場合は、1つのライトに必要な均一な変数のみを宣言できます。ただし、特に追加するときは、ライトの数が変化した場合、シェーダーを再ステッチおよびコンパイルしてリンクする必要があります。現時点では、すべての材料の材料を真に設定する必要があります。
テクスチャを変更しますここでのテクスチャの変更は、テクスチャデータを更新することを意味するのではなく、元の素材がテクスチャを使用したが、後で使用されなかったか、元の素材がテクスチャを使用してから追加したことを意味します。素材が手動で更新されていない場合、最終効果はあなたの考えとは異なります。この問題の理由は、上記の照明に似ており、テクスチャが使用されたかどうかを判断するためにマクロをシェーダーに追加したためでもあります。
parameters.map? "#define use_map": ""
parameters.envmap? "#define use_envmap": ""
parameters.lightmap? "#define use_lightmap": ""
parameters.bumpMap? "#define use_bumpmap": ""
パラメーター。ノーマルマップ? "#define use_normalmap": ""
parameters.specularMap? "#define use_spcularmap": ""
したがって、マップ、envmap、またはlightmapが真の値を変更するたびに、素材を更新する必要があります
他の頂点データの変更実際、上記のテクスチャの変更は問題を引き起こします。これは主に、初期化中にテクスチャがないためです。ただし、この環境が動的に追加された場合、材料を設定するだけでは不十分です。また、geometry.uvsneedsupdateをtrueに設定する必要があります。なぜそんな問題があるのですか?これは、Three.jsによるプログラムの最適化のためです。レンダラーで初めてジオメトリと素材を初期化する場合、テクスチャがないと判断された場合、メモリ内のデータに各頂点UVデータがありますが、3.JSはこのデータをビデオメモリにコピーしません。当初の意図は、貴重なビデオメモリスペースを保存することです。ただし、テクスチャを追加した後、ジオメトリはこれらのUVデータをインテリジェントに転送してテクスチャ使用しません。 UVSNeedSupdateを手動で設定して、UVを更新する時が来たことを知らせなければなりません。この質問は、最初は長い間私をチートしました。
いくつかのタイプの頂点データのneedupdate属性については、この問題を見ることができます
https://github.com/mrdoob/three.js/wiki/updates
やっとThree.jsの最適化は良い仕事をしていますが、さまざまな最適化によって触れることができるさまざまな落とし穴をもたらします。これを行う最良の方法は、ソースコードを調べるか、問題に言及するためにgithubにアクセスすることです