Частицы FBO с накопительным движением - PullRequest
0 голосов
/ 24 апреля 2019

Ссылка на дискурс темы Threejs: https://discourse.threejs.org/t/fbo-particles-with-cumulative-movement/7221

Это трудно объяснить из-за моих ограниченных знаний по этому вопросу, но я сделаю все возможное ..

На данный момент у меня есть базовая система частиц FBO, которая работает.Ниже описано, как он настроен:

var FBO = function( exports ){

    var scene, orthoCamera, rtt;
    exports.init = function( width, height, renderer, simulationMaterial, renderMaterial ){

        var gl = renderer.getContext();

        //1 we need FLOAT Textures to store positions
        //https://github.com/KhronosGroup/WebGL/blob/master/sdk/tests/conformance/extensions/oes-texture-float.html
        if (!gl.getExtension("OES_texture_float")){
            throw new Error( "float textures not supported" );
        }

        //2 we need to access textures from within the vertex shader
        //https://github.com/KhronosGroup/WebGL/blob/90ceaac0c4546b1aad634a6a5c4d2dfae9f4d124/conformance-suites/1.0.0/extra/webgl-info.html
        if( gl.getParameter(gl.MAX_VERTEX_TEXTURE_IMAGE_UNITS) == 0 ) {
            throw new Error( "vertex shader cannot read textures" );
        }

        //3 rtt setup
        scene = new THREE.Scene();
        orthoCamera = new THREE.OrthographicCamera(-1,1,1,-1,1/Math.pow( 2, 53 ),1 );

        //4 create a target texture
        var options = {
            minFilter: THREE.NearestFilter,//important as we want to sample square pixels
            magFilter: THREE.NearestFilter,//
            format: THREE.RGBAFormat,//180407 changed to RGBAFormat
            type:THREE.FloatType//important as we need precise coordinates (not ints)
        };
        rtt = new THREE.WebGLRenderTarget( width,height, options);


        //5 the simulation:
        //create a bi-unit quadrilateral and uses the simulation material to update the Float Texture
        var geom = new THREE.BufferGeometry();
        geom.addAttribute( 'position', new THREE.BufferAttribute( new Float32Array([   -1,-1,0, 1,-1,0, 1,1,0, -1,-1, 0, 1, 1, 0, -1,1,0 ]), 3 ) );
        geom.addAttribute( 'uv', new THREE.BufferAttribute( new Float32Array([   0,1, 1,1, 1,0,     0,1, 1,0, 0,0 ]), 2 ) );
        scene.add( new THREE.Mesh( geom, simulationMaterial ) );


        //6 the particles:
        //create a vertex buffer of size width * height with normalized coordinates
        var l = (width * height );
        var vertices = new Float32Array( l * 3 );
        for ( var i = 0; i < l; i++ ) {

            var i3 = i * 3;
            vertices[ i3 ] = ( i % width ) / width ;
            vertices[ i3 + 1 ] = ( i / width ) / height;
        }

        //create the particles geometry
        var geometry = new THREE.BufferGeometry();
        geometry.addAttribute( 'position',  new THREE.BufferAttribute( vertices, 3 ) );

        //the rendermaterial is used to render the particles
        exports.particles = new THREE.Points( geometry, renderMaterial );
        exports.particles.frustumCulled = false;
        exports.renderer = renderer;

    };

    //7 update loop
    exports.update = function(){

        //1 update the simulation and render the result in a target texture
        // exports.renderer.render( scene, orthoCamera, rtt, true );
        exports.renderer.setRenderTarget( rtt );
        exports.renderer.render( scene, orthoCamera );
        exports.renderer.setRenderTarget( null );

        //2 use the result of the swap as the new position for the particles' renderer
        // had to add .texture on the end of rtt for r103
        exports.particles.material.uniforms.positions.value = rtt.texture;

    };
    return exports;
}({});

Ниже перечислены шейдеры, которые он использует:

    <script type="x-shader/x-vertex" id="simulation_vs">
    //vertex shader
    varying vec2 vUv;
    void main() {
        vUv = vec2(uv.x, uv.y);

        gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
    }
    </script>

    <script type="x-shader/x-fragment" id="simulation_fs">
    //fragment Shader
    uniform sampler2D positions;//DATA Texture containing original positions
    varying vec2 vUv;
    void main() {

        //basic simulation: displays the particles in place.
        vec3 pos = texture2D( positions, vUv ).rgb;

        // we can move the particle here 


        gl_FragColor = vec4( pos,1.0 );
    }
    </script>

    <script type="x-shader/x-vertex" id="render_vs">
    //vertex shader
    uniform sampler2D positions;//RenderTarget containing the transformed positions
    uniform float pointSize;//size
    void main() {

        //the mesh is a nomrliazed square so the uvs = the xy positions of the vertices
        vec3 pos = texture2D( positions, position.xy ).xyz;
        //pos now contains a 3D position in space, we can use it as a regular vertex

        //regular projection of our position
        gl_Position = projectionMatrix * modelViewMatrix * vec4( pos, 1.0 );

        //sets the point size
        gl_PointSize = pointSize;
    }
    </script>

    <script type="x-shader/x-fragment" id="render_fs">
    //fragment shader
    void main()
    {
        gl_FragColor = vec4( vec3( 1. ), .25 );
    }
    </script>

Я понимаю, что переместил бы частицы в "simulation_fs", но еслиЯ перемещаю частицу в этом шейдере, и если я попытаюсь сделать что-то вроде этого,

pos.x += 1.0;

она все равно будет сдвигать ее только на одну единицу от исходного положения текстуры.Я хочу, чтобы движение было кумулятивным.

Позволит ли использование второго набора симуляционных шейдеров перемещать частицы совокупным образом?Это практическое решение?

Ответы [ 2 ]

1 голос
/ 24 апреля 2019

Для кумулятивного движения необходимо использовать uniforms:

Посмотрите на передачу униформы с именем time вашему вершинному шейдеру. Затем вы можете обновлять время один раз за кадр и использовать его для анимации ваших позиций вершин. Например:

position.x = 2.0 * time; // Increment linearly

position.x = sin(time); // Sin wave back-forth animation

Без изменения переменной ваши анимации вершин будут статичными от одного кадра к следующему.

0 голосов
/ 16 мая 2019

Мне нужно было сделать что-то подобное, и создать абсолютно минимальный пример, который я могу настроить в будущем. Вы увидите, что позиционные изменения накапливаются.

Следующее было упрощено из замечательного обсуждения FBO Николя Баррадо (мастера webgl):

// specify the container where we'll render the scene
var elem = document.querySelector('body'),
    elemW = elem.clientWidth,
    elemH = elem.clientHeight

// generate a scene object
var scene = new THREE.Scene();

// generate a camera
var camera = new THREE.PerspectiveCamera(75, elemW/elemH, 0.001, 100);

// generate a renderer
var renderer = new THREE.WebGLRenderer({antialias: true, alpha: true});
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(elemW, elemH);
elem.appendChild(renderer.domElement);

// generate controls
var controls = new THREE.TrackballControls(camera, renderer.domElement);

// position camera and controls
camera.position.set(0.5, 0.5, -5);
controls.target = new THREE.Vector3(0.5, 0.5, 0);

/**
* FBO
**/

// verify browser agent supports "frame buffer object" features
gl = renderer.getContext();
if (!gl.getExtension('OES_texture_float') ||
     gl.getParameter(gl.MAX_VERTEX_TEXTURE_IMAGE_UNITS) == 0) {
  alert(' * Cannot create FBO :(');
}

// set initial positions of `w*h` particles
var w = h = 256,
    i = 0,
    data = new Float32Array(w*h*3);
for (var x=0; x<w; x++) {
  for (var y=0; y<h; y++) {
    data[i++] = x/w;
    data[i++] = y/h;
    data[i++] = 0;
  }
}

// feed those positions into a data texture
var dataTex = new THREE.DataTexture(data, w, h, THREE.RGBFormat, THREE.FloatType);
dataTex.minFilter = THREE.NearestFilter;
dataTex.magFilter = THREE.NearestFilter;
dataTex.needsUpdate = true;

// add the data texture with positions to a material for the simulation
var simMaterial = new THREE.RawShaderMaterial({
  uniforms: { posTex: { type: 't', value: dataTex }, },
  vertexShader: document.querySelector('#sim-vs').textContent,
  fragmentShader: document.querySelector('#sim-fs').textContent,
});

// delete dataTex; it isn't used after initializing point positions
delete dataTex;

THREE.FBO = function(w, simMat) {
  this.scene = new THREE.Scene();
  this.camera = new THREE.OrthographicCamera(-w/2, w/2, w/2, -w/2, -1, 1);
  this.scene.add(new THREE.Mesh(new THREE.PlaneGeometry(w, w), simMat));
};

// create a scene where we'll render the positional attributes
var fbo = new THREE.FBO(w, simMaterial);

// create render targets a + b to which the simulation will be rendered
var renderTargetA = new THREE.WebGLRenderTarget(w, h, {
  wrapS: THREE.RepeatWrapping,
  wrapT: THREE.RepeatWrapping,
  minFilter: THREE.NearestFilter,
  magFilter: THREE.NearestFilter,
  format: THREE.RGBFormat,
  type: THREE.FloatType,
  stencilBuffer: false,
});

// a second render target lets us store input + output positional states
renderTargetB = renderTargetA.clone();

// render the positions to the render targets
renderer.render(fbo.scene, fbo.camera, renderTargetA, false);
renderer.render(fbo.scene, fbo.camera, renderTargetB, false);

// store the uv attrs; each is x,y and identifies a given point's
// position data within the positional texture; must be scaled 0:1!
var geo = new THREE.BufferGeometry(),
    arr = new Float32Array(w*h*3);
for (var i=0; i<arr.length; i++) {
  arr[i++] = (i%w)/w;
  arr[i++] = Math.floor(i/w)/h;
  arr[i++] = 0;
}
geo.addAttribute('position', new THREE.BufferAttribute(arr, 3, true))

// create material the user sees
var material = new THREE.RawShaderMaterial({
  uniforms: {
    posMap: { type: 't', value: null }, // `posMap` is set each render
  },
  vertexShader: document.querySelector('#ui-vert').textContent,
  fragmentShader: document.querySelector('#ui-frag').textContent,
  transparent: true,
});

// add the points the user sees to the scene
var mesh = new THREE.Points(geo, material);
scene.add(mesh);

function render() {
  // at the start of the render block, A is one frame behind B
  var oldA = renderTargetA; // store A, the penultimate state
  renderTargetA = renderTargetB; // advance A to the updated state
  renderTargetB = oldA; // set B to the penultimate state

  // pass the updated positional values to the simulation
  simMaterial.uniforms.posTex.value = renderTargetA.texture;

  // run a frame and store the new positional values in renderTargetB
  renderer.render(fbo.scene, fbo.camera, renderTargetB, false);

  // pass the new positional values to the scene users see
  material.uniforms.posMap.value = renderTargetB.texture;

  // render the scene users see as normal
  renderer.render(scene, camera);
  controls.update();
  requestAnimationFrame(render);
};

render();
  html, body { width: 100%; height: 100%; background: #000; }
  body { margin: 0; overflow: hidden; }
  canvas { width: 100%; height: 100%; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/101/three.min.js"></script>
<script src="https://threejs.org/examples/js/controls/TrackballControls.js"></script>

<!-- The simulation shaders update positional attributes -->
<script id='sim-vs' type='x-shader/x-vert'>
  precision mediump float;

  uniform mat4 projectionMatrix;
  uniform mat4 modelViewMatrix;

  attribute vec2 uv; // x,y offsets of each point in texture
  attribute vec3 position;

  varying vec2 vUv;

  void main() {
    vUv = vec2(uv.x, 1.0 - uv.y);
    gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
  }
</script>

<script id='sim-fs' type='x-shader/x-frag'>
  precision mediump float;

  uniform sampler2D posTex;

  varying vec2 vUv;

  void main() {

    // read the supplied x,y,z vert positions
    vec3 pos = texture2D(posTex, vUv).xyz;

    // update the positional attributes here!
    pos.x += cos(pos.y) / 100.0;
    pos.y += tan(pos.x) / 100.0;

    // render the new positional attributes
    gl_FragColor = vec4(pos, 1.0);
  }
</script>

<!-- The ui shaders render what the user sees -->
<script id='ui-vert' type='x-shader/x-vert'>
  precision mediump float;

  uniform sampler2D posMap; // contains positional data read from sim-fs
  uniform mat4 projectionMatrix;
  uniform mat4 modelViewMatrix;

  attribute vec2 position;

  void main() {

    // read this particle's position, which is stored as a pixel color
    vec3 pos = texture2D(posMap, position.xy).xyz;

    // project this particle
    vec4 mvPosition = modelViewMatrix * vec4(pos, 1.0);
    gl_Position = projectionMatrix * mvPosition;

    // set the size of each particle
    gl_PointSize = 0.3 / -mvPosition.z;
  }

</script>

<script id='ui-frag' type='x-shader/x-frag'>
  precision mediump float;

  void main() {
    gl_FragColor = vec4(0.0, 0.5, 1.5, 1.0);
  }
</script>
...