ここで説明する「インスタンス化」は、たとえば200頂点モデルのすべての頂点/インデックスに対して1つの属性を持つ方法を提供すると思います。
つまり、これにより、モデルの200個の頂点すべてに適用される平行移動または方向属性配列を1つだけ持つことができます。したがって、これらのモデルの10Kのシーンを「インスタンス化」するには、2000Kではなく10Kの属性のみが必要になります。
どうやらThreeのInstancedBufferGeometryオブジェクトとInstancedBufferAttributeオブジェクトがこれを提供しているようですが、オブジェクトのまばらな説明以外のドキュメントは見つかりませんでした。 「Vanilla」Three.jsでGLSLを使用する以外の方法もあるかもしれませんが、ShaderMaterialを使用していると思います。
誰かがそれらがどのように機能し、Three.jsでそれらを使用する方法を説明できますか?
この答えを自分で探しているときに、あなたの質問に出くわしました。インスタンス化を使用する2つの例( threejs.org/examples から直接)を次に示します。
簡単な説明:
THREE.InstancedBufferGeometry
とTHREE.BufferGeometry
の主な違いは、前者は使用される特別な属性(THREE.InstancedBufferAttributes
)を使用できることですインスタンスごとに。
複数のインスタンスが必要なボックスを作成していると想像してください。頂点、法線、およびUVバッファはすべて、基本形状を記述するため、標準のTHREE.BufferAttribute
オブジェクトになります。ただし、各インスタンスを独自の位置に移動するには、場所を保持するためにTHREE.InstancedBufferAttribute
を定義する必要があります(例では通常、この属性に「offset
」という名前を付けています)。
THREE.InstancedBufferAttributes
内の頂点参照の数は、インスタンスの数を表します。たとえば、offset
に9つの値を入力すると、3つのインスタンスが存在することを示します(これには元の形状が含まれます)。 THREE.InstancedBuferGeometry.maxInstancedCount
値を設定することにより、これらの描画数を制御することもできます。
最後に、willインスタンス化された属性の制御に役立つシェーダーが必要です。
小さな例:
var cubeGeo = new THREE.InstancedBufferGeometry().copy(new THREE.BoxBufferGeometry(10, 10, 10));
//cubeGeo.maxInstancedCount = 8;
cubeGeo.addAttribute("cubePos", new THREE.InstancedBufferAttribute(new Float32Array([
25, 25, 25,
25, 25, -25, -25, 25, 25, -25, 25, -25,
25, -25, 25,
25, -25, -25, -25, -25, 25, -25, -25, -25
]), 3, 1));
var vertexShader = [
"precision highp float;",
"",
"uniform mat4 modelViewMatrix;",
"uniform mat4 projectionMatrix;",
"",
"attribute vec3 position;",
"attribute vec3 cubePos;",
"",
"void main() {",
"",
" gl_Position = projectionMatrix * modelViewMatrix * vec4( cubePos + position, 1.0 );",
"",
"}"
].join("\n");
var fragmentShader = [
"precision highp float;",
"",
"void main() {",
"",
" gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);",
"",
"}"
].join("\n");
var mat = new THREE.RawShaderMaterial({
uniforms: {},
vertexShader: vertexShader,
fragmentShader: fragmentShader,
side: THREE.DoubleSide,
transparent: false
});
var mesh = new THREE.Mesh(cubeGeo, mat);
scene.add(mesh);
html * {
padding: 0;
margin: 0;
width: 100%;
overflow: hidden;
}
#Host {
width: 100%;
height: 100%;
}
<script src="https://threejs.org/build/three.js"></script>
<script src="https://threejs.org/examples/js/controls/TrackballControls.js"></script>
<script src="https://threejs.org/examples/js/libs/stats.min.js"></script>
<div id="Host"></div>
<script>
var WIDTH = window.innerWidth,
HEIGHT = window.innerHeight,
FOV = 35,
NEAR = 1,
FAR = 1000;
var renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setSize(WIDTH, HEIGHT);
document.getElementById('Host').appendChild(renderer.domElement);
var stats = new Stats();
stats.domElement.style.position = 'absolute';
stats.domElement.style.top = '0';
document.body.appendChild(stats.domElement);
var camera = new THREE.PerspectiveCamera(FOV, WIDTH / HEIGHT, NEAR, FAR);
camera.position.z = 250;
var trackballControl = new THREE.TrackballControls(camera, renderer.domElement);
trackballControl.rotateSpeed = 5.0; // need to speed it up a little
var scene = new THREE.Scene();
var light = new THREE.PointLight(0xffffff, 1, Infinity);
camera.add(light);
scene.add(light);
function render() {
if (typeof updateVertices !== "undefined") {
updateVertices();
}
renderer.render(scene, camera);
stats.update();
}
function animate() {
requestAnimationFrame(animate);
trackballControl.update();
render();
}
animate();
</script>
質問は少し紛らわしいですが、最善を尽くします。
あなたはいくつかのことを混乱させていると思います、そして私はこれをthree.jsユーザーの間でよく見ます。まず第一に、属性の用語が間違っていると思います。何千もの属性を作成しているのではなく、メッシュなどのエンティティが1つの属性(position
、または複数のuv
、normal
、aMyAttribute
など。
実際、webglが対処できる属性の最大数はありますが、それは異なりますが、ボールパークは数千ではなく16です。
属性には、200個の「頂点」を定義するデータを含めることができますが、これも相対的なものであり、2つのコンポーネントを持つことができ、4つのコンポーネントを持つことができます。
「200頂点モデル」から複数の「オブジェクト」を作成する場合、ジオメトリを乗算することはありません。属性の数は増えません。ユニフォームで何が起こるかは実際にはわかりませんが、GPUに関する限り、200個の頂点を保持しています。 Javascriptには、まだいくつかの属性と統一された場所、およびいくつかのGeometry
クラスの1つのインスタンスが含まれています。
乗算するのは「ノード/オブジェクト」です。JavaScriptでは、複数のsay Mesh
オブジェクトがあります。これは、他のさまざまなタイプ、行列、ベクトル、クォータニオンなどを保持します。
レンダー関数を呼び出すと、レンダラーはこれらの各ノードに対して描画呼び出しを発行します。それを行うたびに、その特定の描画呼び出しを処理するためにwebglの状態を設定する必要があります。これがシーンの周りに散らばっている同じオブジェクトである場合、異なるのは位置/回転/スケールのユニフォームだけです。それらが材料を持っている場合、それは色の均一、または異なるテクスチャである可能性があります。いずれにせよ、これはオーバーヘッドを生み出し、物事を遅くします。
モデルが木だとしましょう。それからフォレストを作成し、a forest
の代わりにmany trees
をレンダリングすると、このオーバーヘッドが排除されますが、同じ量のシェーダー処理が実行されます。各ツリーの各頂点には、シェーダーを実行する必要があります。
もちろん、これにより、属性が保持するデータが増加します。ここで、200個の頂点を保持する代わりに、いくつかの便利なオブジェクト空間(「ツリー空間」と呼ぶ方がよい)では、「フォレスト空間」に200 xN個の頂点を保持する必要があります。つまり、 tree 0
の頂点はフォレストのどこかに存在し、tree N
の同じ頂点はフォレストの別の場所に存在します。これは、ツリーごとに新しいジオメトリを作成し、変換をその頂点にベイク処理してから、別のツリーとマージする場合などに発生します。
インスタンス化により、このケースについてより賢くなります。共通のプロパティ(同じツリー)を共有する個々の頂点をすべて保持する代わりに、元のツリーの200個の頂点と、それらがN回描画される場所を説明する属性を保持できます。したがって、ジオメトリをマージして個別に変換をベイク処理するのではなく、変換のみを含む属性を作成します。
バニラマテリアルではカスタム属性をどう処理するかわからないため、これを使用できない可能性があります。ただし、three.jsがシェーダーを処理する方法を使用すると、ロジックを挿入して既存のマテリアルを拡張することはそれほど難しくありません。