Three.js:使用RawShaderMaterial时更新几何属性

时间:2018-07-25 22:24:58

标签: javascript three.js

我有一个Three.js场景,该场景将一些属性传递给RawShaderMaterial。初始渲染后,我想更新一些属性,但是还无法弄清楚该怎么做。

这是示例场景(fiddle):

/**
* Generate a scene object with a background color
**/

function getScene() {
  var scene = new THREE.Scene();
  scene.background = new THREE.Color(0xaaaaaa);
  return scene;
}

/**
* Generate the camera to be used in the scene
**/

function getCamera() {
  var aspectRatio = window.innerWidth / window.innerHeight;
  var camera = new THREE.PerspectiveCamera(75, aspectRatio, 0.1, 100000);
  camera.position.set(0, 1, -6000);
  return camera;
}

/**
* Generate the renderer to be used in the scene
**/

function getRenderer() {
  // Create the canvas with a renderer
  var renderer = new THREE.WebGLRenderer({antialias: true});
  // Add support for retina displays
  renderer.setPixelRatio(window.devicePixelRatio);
  // Specify the size of the canvas
  renderer.setSize(window.innerWidth, window.innerHeight);
  // Add the canvas to the DOM
  document.body.appendChild(renderer.domElement);
  return renderer;
}

/**
* Generate the controls to be used in the scene
**/

function getControls(camera, renderer) {
  var controls = new THREE.TrackballControls(camera, renderer.domElement);
  controls.zoomSpeed = 0.4;
  controls.panSpeed = 0.4;
  return controls;
}

/**
* Generate the points for the scene
**/

function addPoints(scene) {
  var geometry  = new THREE.InstancedBufferGeometry();
  geometry.addAttribute( 'position',
    new THREE.BufferAttribute( new Float32Array( [0, 0, 0] ), 3));

  // add data for each observation
  var n = 10000; // number of observations
  var rootN = n**(1/2);
  var cellSize = 20;
  var translation = new Float32Array( n * 3 );
  var texIdx = new Float32Array( n );
  var translationIterator = 0;
  var texIterator = 0;
  for (var i=0; i<n*3; i++) {
    var x = Math.random() * n - (n/2);
    var y = Math.random() * n - (n/2);
    translation[translationIterator++] = x;
    translation[translationIterator++] = y;
    translation[translationIterator++] = Math.random() * n - (n/2);
    texIdx[texIterator++] = (x + y) > (n/8) ? 1 : 0;
  }

  var IBA = THREE.InstancedBufferAttribute;
  geometry.addAttribute('translation', new IBA(translation, 3, 1));
  geometry.addAttribute('texIdx', new IBA(texIdx, 1, 1));

  var canvases = [
    getElem('canvas', { width: 16384, height: 16384, }),
    getElem('canvas', { width: 16384, height: 16384, }),
  ];

  for (var i=0; i<canvases.length; i++) {
    var canvas = canvases[i];
    var ctx = canvas.getContext('2d');
    ctx.fillStyle = i == 0 ? 'red' : 'blue';
    ctx.rect(0, 0, 16384, 16384);
    ctx.fill();
  }

  window.canvases = canvases;

  var material = new THREE.RawShaderMaterial({
    uniforms: {
      a: {
        type: 't',
        value: getTexture(canvases[0]),
      },
      b: {
        type: 't',
        value: getTexture(canvases[1]),
      }
    },
    vertexShader: document.getElementById('vertex-shader').textContent,
    fragmentShader: document.getElementById('fragment-shader').textContent,
  });
  var mesh = new THREE.Points(geometry, material);
  mesh.frustumCulled = false; // prevent the mesh from being clipped on drag
  scene.add(mesh);
}

function getTexture(canvas) {
  var tex = new THREE.Texture(canvas);
  tex.needsUpdate = true;
  tex.flipY = false;
  return tex;
}

/**
* Create an element
**/

function getElem(tag, obj) {
  var obj = obj || {};
  var elem = document.createElement(tag);
  Object.keys(obj).forEach(function(attr) {
    elem[attr] = obj[attr];
  })
  return elem;
}

/**
* Render!
**/

function render() {
  requestAnimationFrame(render);
  renderer.render(scene, camera);
  controls.update();
};

/**
* Main
**/

var scene = getScene();
var camera = getCamera();
var renderer = getRenderer();
var controls = getControls(camera, renderer);
addPoints(scene);
render();
<html>
<head>
  <style>
  html, body { width: 100%; height: 100%; background: #000; }
  body { margin: 0; overflow: hidden; }
  canvas { width: 100%; height: 100%; }
  </style>
</head>
<body>
  <script src='https://cdnjs.cloudflare.com/ajax/libs/three.js/88/three.min.js'></script>
  <script src='https://rawgit.com/YaleDHLab/pix-plot/master/assets/js/trackball-controls.js'></script>

    <script type='x-shader/x-vertex' id='vertex-shader'>
    precision highp float;

    uniform mat4 modelViewMatrix;
    uniform mat4 projectionMatrix;

    uniform vec3 cameraPosition;

    attribute vec3 position; // sets the blueprint's vertex positions
    attribute vec3 translation; // x y translation offsets for an instance
    attribute float texIdx; // the texture index to access

    varying float vTexIdx;

    void main() {
      // set point position
      vec3 pos = position + translation;
      vec4 projected = projectionMatrix * modelViewMatrix * vec4(pos, 1.0);
      gl_Position = projected;

      // assign the varyings
      vTexIdx = texIdx;

      // use the delta between the point position and camera position to size point
      float xDelta = pow(projected[0] - cameraPosition[0], 2.0);
      float yDelta = pow(projected[1] - cameraPosition[1], 2.0);
      float zDelta = pow(projected[2] - cameraPosition[2], 2.0);
      float delta  = pow(xDelta + yDelta + zDelta, 0.5);
      gl_PointSize = 40000.0 / delta;
    }
    </script>

    <script type='x-shader/x-fragment' id='fragment-shader'>
    precision highp float;

    uniform sampler2D a;
    uniform sampler2D b;

    varying float vTexIdx;

    void main() {
      int textureIndex = int(vTexIdx);
      vec2 uv = vec2(gl_PointCoord.x, gl_PointCoord.y);
      if (textureIndex == 0) {
        gl_FragColor = texture2D(a, uv);
      } else if (textureIndex == 1) {
        gl_FragColor = texture2D(b, uv);
      }
    }
    </script>
</body>
</html>

构建此场景后,我尝试通过在控制台中运行以下几行来使所有点变为红色:

var texIdxAttr = scene.children[0].geometry.attributes.texIdx.array;
for (var i=0; i<texIdxAttr.length; i++) {
  scene.children[0].geometry.attributes.texIdx.array[i] = 0;
}
scene.children[0].geometry.verticesNeedUpdate = true;
scene.children[0].geometry.elementsNeedUpdate = true;
scene.children[0].geometry.uvsNeedUpdate = true;

但这似乎并没有改变。有谁知道我该怎么做才能更新texIdx属性值并在初始渲染后重新渲染场景?对于其他人可以提供的有关此问题的帮助,我将不胜感激。

1 个答案:

答案 0 :(得分:0)

似乎更新了一个缓冲区属性(或带索引的缓冲区属性),可以将该缓冲区的.dynamic属性设置为true,手动更改该缓冲区,然后设置.needsUpdate属性缓冲区到true的位置。

在此更新的演示中,单击场景后,缓冲区属性texIdx进行了更新,以使所有点的texIdx == 0:

<html>
<head>
  <style>
  html, body { width: 100%; height: 100%; background: #000; }
  body { margin: 0; overflow: hidden; }
  canvas { width: 100%; height: 100%; }
  </style>
</head>
<body>
  <script src='https://cdnjs.cloudflare.com/ajax/libs/three.js/88/three.min.js'></script>
  <script src='https://rawgit.com/YaleDHLab/pix-plot/master/assets/js/trackball-controls.js'></script>

    <script type='x-shader/x-vertex' id='vertex-shader'>
    precision highp float;

    uniform mat4 modelViewMatrix;
    uniform mat4 projectionMatrix;

    uniform vec3 cameraPosition;

    attribute vec3 position; // sets the blueprint's vertex positions
    attribute vec3 translation; // x y translation offsets for an instance
    attribute float texIdx; // the texture index to access

    varying float vTexIdx;

    void main() {
      // set point position
      vec3 pos = position + translation;
      vec4 projected = projectionMatrix * modelViewMatrix * vec4(pos, 1.0);
      gl_Position = projected;

      // assign the varyings
      vTexIdx = texIdx;

      // use the delta between the point position and camera position to size point
      float xDelta = pow(projected[0] - cameraPosition[0], 2.0);
      float yDelta = pow(projected[1] - cameraPosition[1], 2.0);
      float zDelta = pow(projected[2] - cameraPosition[2], 2.0);
      float delta  = pow(xDelta + yDelta + zDelta, 0.5);
      gl_PointSize = 40000.0 / delta;
    }
    </script>

    <script type='x-shader/x-fragment' id='fragment-shader'>
    precision highp float;

    uniform sampler2D a;
    uniform sampler2D b;

    varying float vTexIdx;

    void main() {
      int textureIndex = int(vTexIdx);
      vec2 uv = vec2(gl_PointCoord.x, gl_PointCoord.y);
      if (textureIndex == 0) {
        gl_FragColor = texture2D(a, uv);
      } else if (textureIndex == 1) {
        gl_FragColor = texture2D(b, uv);
      }
    }
    </script>

  <script>

  /**
  * Generate a scene object with a background color
  **/

  function getScene() {
    var scene = new THREE.Scene();
    scene.background = new THREE.Color(0xaaaaaa);
    return scene;
  }

  /**
  * Generate the camera to be used in the scene
  **/

  function getCamera() {
    var aspectRatio = window.innerWidth / window.innerHeight;
    var camera = new THREE.PerspectiveCamera(75, aspectRatio, 0.1, 100000);
    camera.position.set(0, 1, -6000);
    return camera;
  }

  /**
  * Generate the renderer to be used in the scene
  **/

  function getRenderer() {
    // Create the canvas with a renderer
    var renderer = new THREE.WebGLRenderer({antialias: true});
    // Add support for retina displays
    renderer.setPixelRatio(window.devicePixelRatio);
    // Specify the size of the canvas
    renderer.setSize(window.innerWidth, window.innerHeight);
    // Add the canvas to the DOM
    document.body.appendChild(renderer.domElement);
    return renderer;
  }

  /**
  * Generate the controls to be used in the scene
  **/

  function getControls(camera, renderer) {
    var controls = new THREE.TrackballControls(camera, renderer.domElement);
    controls.zoomSpeed = 0.4;
    controls.panSpeed = 0.4;
    return controls;
  }

  /**
  * Generate the points for the scene
  **/

  function addPoints(scene) {
    var BA = THREE.BufferAttribute;
    var IBA = THREE.InstancedBufferAttribute;
    var geometry  = new THREE.InstancedBufferGeometry();

    // add data for each observation
    var n = 10000; // number of observations
    var rootN = n**(1/2);
    var cellSize = 20;
    var translation = new Float32Array( n * 3 );
    var texIdx = new Float32Array( n );
    var translationIterator = 0;
    var texIterator = 0;
    for (var i=0; i<n*3; i++) {
      var x = Math.random() * n - (n/2);
      var y = Math.random() * n - (n/2);
      translation[translationIterator++] = x;
      translation[translationIterator++] = y;
      translation[translationIterator++] = Math.random() * n - (n/2);
      texIdx[texIterator++] = (x + y) > (n/8) ? 1 : 0;
    }

    var positionAttr = new BA(new Float32Array( [0, 0, 0] ), 3);
    var translationAttr = new IBA(translation, 3, 1);
    var texIdxAttr = new IBA(texIdx, 1, 1);
    positionAttr.dynamic = true;
    translationAttr.dynamic = true;
    texIdxAttr.dynamic = true;
    geometry.addAttribute('position', positionAttr);
    geometry.addAttribute('translation', translationAttr);
    geometry.addAttribute('texIdx', texIdxAttr);

    var canvases = [
      getElem('canvas', { width: 16384, height: 16384, }),
      getElem('canvas', { width: 16384, height: 16384, }),
    ];

    for (var i=0; i<canvases.length; i++) {
      var canvas = canvases[i];
      var ctx = canvas.getContext('2d');
      ctx.fillStyle = i == 0 ? 'red' : 'blue';
      ctx.rect(0, 0, 16384, 16384);
      ctx.fill();
    }

    var material = new THREE.RawShaderMaterial({
      uniforms: {
        a: {
          type: 't',
          value: getTexture(canvases[0]),
        },
        b: {
          type: 't',
          value: getTexture(canvases[1]),
        }
      },
      vertexShader: document.getElementById('vertex-shader').textContent,
      fragmentShader: document.getElementById('fragment-shader').textContent,
    });
    var mesh = new THREE.Points(geometry, material);
    mesh.frustumCulled = false; // prevent the mesh from being clipped on drag
    scene.add(mesh);

    window.onclick = function() {
      // paint it red
      for (var i=0; i<texIdxAttr.count; i++) {
        scene.children[0].geometry.attributes.texIdx.array[i] = 0;
      }
      scene.children[0].geometry.attributes.texIdx.needsUpdate = true;
    
    }
  }

  function getTexture(canvas) {
    var tex = new THREE.Texture(canvas);
    tex.needsUpdate = true;
    tex.flipY = false;
    return tex;
  }

  /**
  * Create an element
  **/

  function getElem(tag, obj) {
    var obj = obj || {};
    var elem = document.createElement(tag);
    Object.keys(obj).forEach(function(attr) {
      elem[attr] = obj[attr];
    })
    return elem;
  }

  /**
  * Render!
  **/

  function render() {
    requestAnimationFrame(render);
    renderer.render(scene, camera);
    controls.update();
  };

  /**
  * Main
  **/

  var scene = getScene();
  var camera = getCamera();
  var renderer = getRenderer();
  var controls = getControls(camera, renderer);
  addPoints(scene);
  render();

  </script>
</body>
</html>