のぐそんブログ

暗いおじさんがシコシコ書くブログです。

Three.jsでパーティクルを作ろうと思った際に調べたこと

Three.jsでパーティクルをつくるにはどうすればいいのか調べてみました。

THREE.Spriteでパーティクル

THREE.SpriteオブジェクトはTHREE.Object3Dオブジェクトを継承しており、THREE.Meshと同じように動作します。

Spriteは、何もプロパティをあたえないと、常に面をカメラの方に向けている2Dの平面です。

 let material = new THREE.SpriteMaterial();
    let sprite = new THREE.Sprite(material);
    scene.add(sprite);

See the Pen YQNZqg by nogson (@satofaction) on CodePen.

THREE.Spriteに渡せるマテリアルは、THREE.SpriteMaterialか、THREE.SpriteCanvasMaterialのいずれかです。

パーティクルを作成する際に、Spriteを大量に作成してもそれっぽいのができそう。 ただし、オブジェクト大量に作成するとパフォーマンス的によくありません。

See the Pen LLxWBv by nogson (@satofaction) on CodePen.

THREE.Points

THREE.Pointsは大量の頂点データを管理するのに便利です。 頂点座標と塗のデータをジオメトリに追加します。 パーティクルのサイズはマテリアルのプロパティで調整します。 頂点カラーを変更するためにはvertexColorsとtrueに設定します。

 let geometry = new THREE.Geometry();
 let material = new THREE.PointsMaterial({
     //pointsを使用する場合はMaterialのサイズで調整する
     size:0.1,
     vertexColors:true
 });

 let range = 1000;

 for (let i = 0; i < range; i++) {

      let pos = getPostion(
          Math.random() * width,
          Math.random() * height,
          Math.random() * width
      );

      //頂点データを生成
      let particle = new THREE.Vector3(pos.x,pos.y,pos.z);

      //頂点色を設定
      let color = new THREE.Color(0xffff);
                
      //頂点データをジオメトリに追加
      geometry.vertices.push(particle);

      //頂点色をジオメトリに追加
      geometry.colors.push(color);

 }

point = new THREE.Points(geometry,material);
scene.add(point);

See the Pen owBZKv by nogson (@satofaction) on CodePen.

THREE.PointsMaterialのプロパティ

プロパティ 説明
color Points内のすべてのパーティクルの色を一括指定できる。vertexColorsプロパティをtrueにして、ジオメトリに頂点カラーを設定した場合は、ジオメトリの頂点カラーが有効になる。| |map|マップを使うとパーティクルにテクスチャを設定できる
size パーティクルのサイズ
sizeAttenuation falseを設定すると、カメラからの距離にかかわらずパーティクルが同じ大きさになる
vertexColors パーティクルの頂点カラーの設定を有効にする
opacity 透明度を設定
transparent trueを設定すると、opacityプロパティが有効になる
blendind パーティクルのブレンドモードを設定
fog パーティクルがシーンのfogの影響を受けるかを設定。デフォルトはtrue
depthTes パーティクルがシーンのfogの影響を受けるかを設定。デフォルトはtrue

パーティクルにテクスチャを貼ってみる

画像を読み込んでmapプロパティに下のテクスチャを貼ってみます。

loader = new THREE.TextureLoader();
loader.load('images/img.png', function (texture) {
    createPoints(texture);
    render();
});


function createPoints(texture) {
    let geometry = new THREE.Geometry();
    let material = new THREE.PointsMaterial({
        //pointsを使用する場合はMaterialのサイズで調整する
        size: 0.2,
        color: 0xffffff,
        vertexColors: true,
        map: texture,
        transparent: true,
    });

    let maxLength = 2000;

    for (let i = 0; i < maxLength; i++) {

        let pos = getPostion(
            Math.random() * width,
            Math.random() * height,
            Math.random() * width
        );

        //頂点データを生成
        let particle = new THREE.Vector3(pos.x, pos.y, pos.z);

        //頂点色を設定
        let color = new THREE.Color(0xffffff);

        //頂点データをジオメトリに追加
        geometry.vertices.push(particle);


        //頂点色をジオメトリに追加
        geometry.colors.push(color);

    }

    point = new THREE.Points(geometry, material);
    scene.add(point);
}

こんな感じになりました。

Kobito.65GIWm.png

しかしいくつか問題がありました。 頂点の描画順で重なり順が決まる為、パーティクルが重なった場合に深度テストが影響して、画像の透明部分がうまく描画できませんでした。

震度テストを無効(depthTest: false )にしましたが、今度はサイズの小さいパーティクルがサイズの大きなオブジェクトより前に出てしまいました。

かと言って、z座標をいじるとそもそもの動きがおかしくなってしまいました。

まとめ

1万頂点ぐらいまでなら、PCが唸らずに描画できました。 どうも10万頂点以上いくと、パーフォーマンスなどに影響があるので気をつけたほうが良いようです。

実際今回つくったサンプルはこちらです。