Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
42
Help us understand the problem. What is going on with this article?
@ukonpower

Three.jsでGPUTrails

概要

パーティクル飛ばすと次に、やってみたくなりますよね。Trail。
今回はThree.js & GPUComputationRendererでGPUを使ったヌルヌルトレイルを実装して見たいと思います。

Gitはこちら
デモはこちら

方針

GPUComputationRendererでは計算結果の保存にテクスチャを使用します。
トレイルではトレイルの個数とそれぞれの長さ分の頂点座標を保持する必要があります。
そのためGPUComputationRendererは以下の図のようにトレイル一本の長さxトレイルの本数で初期化します。

image.png

また、計算自体は最初の頂点一つだけ行い、他の頂点は一つ隣の座標をそのままコピーすれば良いでしょう。

実装

GPUComputationRendererの作成

Trails.js
initComputeRenderer(){        
        this.computeRenderer = new GPUComputationRenderer(this.length,this.num,this.renderer);

        let initPositionTex = this.computeRenderer.createTexture();
        let initVelocityTex = this.computeRenderer.createTexture();

        this.initPosition(initPositionTex);

        this.comTexs.position.texture = this.computeRenderer.addVariable("texturePosition",comShaderPosition,initPositionTex);
        this.comTexs.velocity.texture = this.computeRenderer.addVariable("textureVelocity",comShaderVelocity,initVelocityTex);

        this.computeRenderer.setVariableDependencies( this.comTexs.position.texture, [ this.comTexs.position.texture, this.comTexs.velocity.texture] );
        this.comTexs.position.uniforms = this.comTexs.position.texture.material.uniforms;

        this.computeRenderer.setVariableDependencies( this.comTexs.velocity.texture, [ this.comTexs.position.texture, this.comTexs.velocity.texture] );  
        this.comTexs.velocity.uniforms = this.comTexs.velocity.texture.material.uniforms;
        this.comTexs.velocity.uniforms.time =  { type:"f", value : 0};

        this.computeRenderer.init();
    }

位置保存テクスチャと速度保存テクスチャを作成します。

Trails.js
initPosition(tex){
        var texArray = tex.image.data;
        let range = new THREE.Vector3(10,10,10);
        for(var i = 0; i < texArray.length; i += this.length * 4){
            let x = Math.random() * range.x - range.x / 2;
            let y = Math.random() * range.y - range.y / 2;
            let z = Math.random() * range.z - range.z / 2;
            for(let j = 0; j < this.length * 4; j += 4){
                texArray[i + j + 0] = x;
                texArray[i + j + 1] = y;
                texArray[i + j + 2] = z;
                texArray[i + j + 3] = 0.0;
            }
        }
    }

位置テクスチャをランダムな位置で初期化します。
テクスチャの初期値は0なので速度は初期化しなくて大丈夫です。

追記(2019/05/30)
初期値は0ではありませんでした!!
申し訳ありません...

今回のGPUTrailsでは必要ありませんが、テクスチャの使い道によっては以下のように初期化をします。

initVelocity(tex) {
    var texArray = tex.image.data;
    let range = new THREE.Vector3(10,10,10);
    for (var i = 0; i < texArray.length; i += 4) {
        let x = Math.random() * range.x - range.x / 2;
        let y = Math.random() * range.y - range.y / 2;
        let z = Math.random() * range.z - range.z / 2;
        texArray[i + 0] = x;
        texArray[i + 1] = y;
        texArray[i + 2] = z;
        texArray[i + 3] = 0.0;
    }
}

Trailジオメトリの作成

Trails.js
createTrails(){
        let geo = new THREE.BufferGeometry();

        let pArray = new Float32Array(this.num * this.length * 3);
        let indices = new Uint32Array((this.num * this.length - 1) * 3);
        let uv = new Float32Array(this.num * this.length * 2);

        let max = this.length * this.n;

        for(let i = 0; i < this.num; i++){
            for(let j = 0; j < this.length; j++){
                let c = i * this.length + j;
                let n = (c) * 3;
                pArray[n] = 0;
                pArray[n + 1] = 0;
                pArray[n + 2] = 0;

                uv[c * 2] = j / this.length;
                uv[c * 2 + 1] = i / this.num;

                indices[n] = c;
                indices[n + 1] = Math.min(c + 1,i * this.length + this.length - 1);
                indices[n + 2] = Math.min(c + 1,i * this.length + this.length - 1);
            }
        }

        geo.addAttribute('position', new THREE.BufferAttribute( pArray, 3 ) );
        geo.addAttribute('uv', new THREE.BufferAttribute( uv, 2 ) );
        geo.setIndex(new THREE.BufferAttribute(indices,1));

        this.uni = {
            texturePosition : {value: null},
            textureVelocity : {value: null},
        }

        let mat = new THREE.ShaderMaterial({
            uniforms: this.uni,
            vertexShader: vert,
            fragmentShader: frag,
        });
        mat.wireframe = true;

        this.obj = new THREE.Mesh(geo,mat);
        this.obj.matrixAutoUpdate = false;
        this.obj.updateMatrix();
    }

Trailのジオメトリを作成します。
THREE.Lineだと線が切り離せなかったため、indexを使ってMeshをつぎはぎしてLineに見せています。(多分、ダメ。絶対。)

trails.vs
#include <common>
uniform sampler2D texturePosition;
varying vec4 vColor;

void main() {
    vec3 pos = texture2D( texturePosition, uv ).xyz;

    vec3 c = vec3(uv.y,sin(uv.y * 3.0),1.0); 
    vColor = vec4(c,1.0);

    vec4 mvPosition = modelViewMatrix * vec4( pos + position, 1.0 );
    gl_Position = projectionMatrix * mvPosition;
}
trails.fs
varying vec4 vColor;
void main() {
    gl_FragColor = vColor;
}

トレイルのシェーダーです。

コンピュートシェーダーを作成

computeVelocity.glsl
void main() {
    if(gl_FragCoord.x >= 1.0) return;    

    vec2 uv = gl_FragCoord.xy / resolution.xy;
    vec3 pos = texture2D( texturePosition, uv ).xyz;
    vec3 vel = texture2D( textureVelocity, uv ).xyz;
    float idParticle = uv.y * resolution.x + uv.x;

    vel.xyz += 40.0 * vec3(
      snoise( vec4( 0.1 * pos.xyz, 7.225 + 0.5 * time ) ),
      snoise( vec4( 0.1 * pos.xyz, 3.553 + 0.5 * time ) ),
      snoise( vec4( 0.1 * pos.xyz, 1.259 + 0.5 * time ) )
    ) * 0.2;
    vel += -pos * length(pos) * 0.1;
    vel.xyz *= 0.9 + abs(sin(uv.y * 9.0)) * 0.03;

    gl_FragColor = vec4( vel.xyz, 1.0 );
}

速度の計算です。一番左のピクセルの時のみ、新たな速度を計算しています。
ノイズは毎度ながらこちらのnoise4Dを利用させていただきました。

computePosition.glsl
void main() {
    if(gl_FragCoord.x <= 1.0){
        vec2 uv = gl_FragCoord.xy / resolution.xy;
        vec3 pos = texture2D( texturePosition, uv ).xyz;
        vec3 vel = texture2D( textureVelocity, uv ).xyz;

        pos += vel * 0.01;
        gl_FragColor = vec4(pos,1.0);

    }else{
        vec2 bUV = (gl_FragCoord.xy - vec2(1.0,0.0)) / resolution.xy;
        vec3 bPos = texture2D( texturePosition, bUV ).xyz;  
        gl_FragColor = vec4(bPos,1.0);
    }
}

位置更新のシェーダーです。
一番左のピクセルの時は速度テクスチャから速度を取得して座標に加算しています。
それ以外は一つ左のピクセルの座標をそのまま上書きしています。

コンピュート!!

Trails.js
    update(){
        this.time += this.clock.getDelta();

        this.comTexs.velocity.uniforms.time.value = this.time;
        this.uni.texturePosition.value = this.computeRenderer.getCurrentRenderTarget(this.comTexs.position.texture).texture;
        this.uni.textureVelocity.value = this.computeRenderer.getCurrentRenderTarget(this.comTexs.velocity.texture).texture;
        this.computeRenderer.compute();
    }

uniformsに値を入れてコンピュートレンダラーを実行してします。

いざ生成

MainScene.js
        this.trails = new Trails(this.renderer,2000,30);
        this.scene.add(this.trails.obj);

sceneに長さ30頂点のトレイルを2000本生成します。

まとめ

今度はMesh貼ってリッチなトレイル作りたいです。

42
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ukonpower
シェーダー!ブレンダー!
junni
デジタル領域における広告デザインの企画・制作を行っています。アソビゴコロで、世界をハッピーに変えていく。それがわたしたちの理念です。

Comments

No comments
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account Login
42
Help us understand the problem. What is going on with this article?