33
29

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

これだけ覚えればできる!Three.jsのGPU Instancing

Last updated at Posted at 2019-04-10

#概要
WebGLには拡張機能でインスタンシングなるものがあります。GPUに一つだけ3Dモデルのデータをロードし、GPU側でまとめて描画させることで高速に大量のモデルを描画する手法です。
Three.jsにはそれをラップしたInstancedBufferGeometryがあるのでそちらの使用法を解説したいと思います。

#作るもの
image.png
Gitはこちらです。
デモはこちらです。

#どうやって作るの?
手順は以下のようになります。

  1. ベースとなるモデルのジオメトリを生成
  2. InstancedBufferGeometryを用意
  3. InstancedBufferGeometryに全てのモデルに共通のAttribute(頂点座標やノーマル、インデックスなど)を設定
  4. モデルごとで異なる値のAttributeを生成、InstancedBufferGeometryに設定
  5. シェーダーを作成。
  6. メッシュ(ラインなどでも)を生成

#コード

let originBox = new THREE.BoxBufferGeometry(0.3,0.3,0.3);
let geo = new THREE.InstancedBufferGeometry();

let vertice = originBox.attributes.position.clone();
geo.addAttribute('position', vertice);

let normal = originBox.attributes.normal.clone();
geo.addAttribute('normals', normal);

let uv = originBox.attributes.normal.clone();
geo.addAttribute('uv', uv);

let indices = originBox.index.clone();
geo.setIndex(indices);

let offsetPos = new THREE.InstancedBufferAttribute(new Float32Array(this.num * 3), 3 );
let num = new THREE.InstancedBufferAttribute(new Float32Array(this.num * 1), 1 );

for (let i = 0; i < this.num; i++) {
    let range = 5;
    let x = Math.random() * range - range / 2;
    let y = Math.random() * range - range / 2;
    let z = Math.random() * range - range / 2;
    offsetPos.setXYZ(i,x,y,z);
    num.setX(i,i);
}

geo.addAttribute('offsetPos', offsetPos);
geo.addAttribute('num', num);

let cUni = {
    time: {
        value: 0
    }
}

this.uni = THREE.UniformsUtils.merge([THREE.ShaderLib.standard.uniforms,cUni]);
this.uni.diffuse.value = new THREE.Vector3(1.0,1.0,1.0);
this.uni.roughness.value = 0.1;

let mat = new THREE.ShaderMaterial({
    vertexShader: vert,
    fragmentShader: THREE.ShaderLib.standard.fragmentShader,
    uniforms: this.uni,
    flatShading: true,
    lights: true
})

this.obj = new THREE.Mesh(geo, mat);

##コード解説
順番に解説します。

1.ベースとなるモデルのジオメトリを生成

let originBox = new THREE.BoxBufferGeometry(0.3,0.3,0.3);

Three.jsのBoxBufferGeometryです

2. InstancedBufferGeometryを用意

let geo = new THREE.InstancedBufferGeometry();

3. InstancedBufferGeometryに全てのモデルに共通のAttributeを設定

let vertice = originBox.attributes.position.clone();
geo.addAttribute('position', vertice);

let normal = originBox.attributes.normal.clone();
geo.addAttribute('normals', normal);

let uv = originBox.attributes.normal.clone();
geo.addAttribute('uv', uv);

let indices = originBox.index.clone();
geo.setIndex(indices);

originBoxからpositionnormalUVのattributeをコピーしてそのまま設定してます。

4. モデルごとで異なる値のAttributeを生成、設定

let offsetPos = new THREE.InstancedBufferAttribute(new Float32Array(this.num * 3), 3 );
let num = new THREE.InstancedBufferAttribute(new Float32Array(this.num * 1), 1 );

for (let i = 0; i < this.num; i++) {
    let range = 5;
    let x = Math.random() * range - range / 2;
    let y = Math.random() * range - range / 2;
    let z = Math.random() * range - range / 2;
    offsetPos.setXYZ(i,x,y,z);
    num.setX(i,i);
}

geo.addAttribute('offsetPos', offsetPos);
geo.addAttribute('num', num);

ここが肝です。
一つのBoxごとに位置を変えたいので、ワールド座標を示すoffsetPosのAttributeを生成します。
こちらは個々で別の値を入れるため、InstancedBufferAttributeを使います。


### 5. シェーダー、マテリアルを作成

```javascript
let cUni = {
    time: {
        value: 0
    }
}
this.uni = THREE.UniformsUtils.merge([THREE.ShaderLib.standard.uniforms,cUni]);
this.uni.diffuse.value = new THREE.Vector3(1.0,1.0,1.0);
this.uni.roughness.value = 0.1;

let mat = new THREE.ShaderMaterial({
    vertexShader: vert,
    fragmentShader: THREE.ShaderLib.standard.fragmentShader,
    uniforms: this.uni,
    flatShading: true,
    lights: true
})

フラグメントシェーダーはThreeのStandardを使ってます。

attribute vec3 offsetPos;
varying vec3 vViewPosition;
uniform float time;

float PI = 3.141592653589793;

highp mat2 rotate(float rad){
    return mat2(cos(rad),sin(rad),-sin(rad),cos(rad));
}

void main() {
    vec3 pos = position;
    float s = max(0.0,sin(-time * 4.0 + length(offsetPos)));
    pos *= s;
    pos.xz *= rotate(s * 4.0);
    pos.xy *= rotate(s * 4.0);
    vec4 mvPosition = modelViewMatrix * vec4(pos + offsetPos, 1.0);
    gl_Position = projectionMatrix * mvPosition;
    vViewPosition = -mvPosition.xyz;
}

boxを回転してから位置を移動してます。

6. メッシュ(ラインなどでも)を生成

this.obj = new THREE.Mesh(geo, mat);

#できました
image.png

今回はInstancedBufferGeomtryを使ってジオメトリを効率的に使用することできました。ComputationRendererとも相性がとても良さそうですね!

33
29
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
33
29

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?