How to dynamically update geometry attributes in Three.js with RawShaderMaterial

In my Three.js scene, I am passing attributes to a RawShaderMaterial. However, I am struggling to update these attributes after the initial render.

You can find an example of my scene here:

/**
* Generating a scene with a background color
**/

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

// More code snippets...

/**
* Render!
**/
function render() {
  requestAnimationFrame(render); // rendering loop
  renderer.render(scene, camera); // render the scene with the camera perspective
  controls.update(); // updates user controls
};

var scene = getScene();
var camera = getCamera();
var renderer = getRenderer();
var controls = getControls(camera, renderer);
addPoints(scene);
render();
// More code snippets...

Despite my efforts, I have not been able to change all points to red in the scene by updating the texIdx attribute values as shown below:

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;

Any suggestions on how I can successfully update the texIdx attribute and see the changes reflected in the rendered scene would be greatly appreciated. Thank you!

Answer №1

To update a buffer attribute (or indexed buffer attribute), you can follow these steps: first, set the .dynamic property of the buffer to true, then manually modify the buffer, and finally, set the .needsUpdate attribute of the buffer to true.

In this new demonstration, upon clicking the scene, the buffer attribute texIdx is adjusted so that all points have texIdx equal to 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() {
      // turn all points to 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>

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Utilizing Angular's locale feature for currency formats

Exploring Angular (1.6.4) is a new adventure for me, and I have a task at hand to customize currency symbols across my webpage by configuring $locale in this Angular application. Currently, the code snippet looks like this: {{myCtrl.getPrice() | currency ...

"Why is it that the keypress event doesn't function properly when using the on() method

My goal is to capture the enter event for an input field $("input[name='search']").on("keypress", function(e){ if (e.which == '13') { alert('code'); } }); This is the HTML code snippet: <input name="searc ...

What is the best way to eliminate the time component from an object in JavaScript?

I have a task to strip the time information from a specific property in my object. To achieve this, I am checking if any timestamps are present in the property value by using an index. Here is the initial input: input [ { "S": "Charge Interest Agai ...

Updating the $_GET parameter using JQuery when a button is clicked

I'm working on implementing a feature where a series of buttons can update the $_GET['year'] variable on my website. These buttons should function across different pages within my website, such as: example.com example.com/?search=foo exa ...

Improving data in a table using ajax and JQuery

I am currently working on updating a table utilizing data from AJAX/JSON. Below is my JQuery code: Adjusted to simplify execution with functions. $(document).ready(function() { //var userid = $( ".migrating" ).data( "userid" ); function ajaxUpda ...

What is the best way to attach an event listener to detect the coordinates where a click occurs inside a div element?

Imagine a situation where you have a div container measuring 200px by 500px. The goal is to implement an event listener that can identify the x and y coordinates within this div when it is clicked. What would be the approach to achieve this in React? ...

What are some ways to divide drop targets with React DnD?

In my React Dnd v16.0.1 project, I am dynamically rendering containers and images from JSON data using map function. The issue I'm facing is with two droppable containers where only one should accept dropped images. However, when I drop an image on th ...

React is throwing an error: Uncaught SyntaxError stating that import declarations are only allowed at the top level of a module. This issue is coming

I encountered an issue where I received the error message: Uncaught SyntaxError: import declarations may only appear at top level of a module on index.js sometimes en index.css or bundle.js Despite this error, my App is not rendering and the div with id=& ...

Confirm that a collection is empty using node.js

const http = require('http'); const url = require('url'); const route = require('router')(); ... route.get('/{betNameType}', function(req, res) { const query = url.parse(req.url, true).query; if (!Object.keys ...

Has Next.js incorporated a maximum cache size feature along with an invalidation algorithm like LRU?

Currently, I have a Next.js site that utilizes getServerSideProps for data fetching. However, I am interested in switching to getStaticProps and implementing incremental static regeneration (ISR) for improved performance. Currently, my memory usage is ap ...

What is the best way to determine if a user is logged in on one tab but not on another tab within the same browser session?

GitHub has recently introduced a new functionality where if you are browsing a page as a guest in one tab and then log in from another tab, the tab where you are still a guest will show a specific message. This feature also functions in incognito or privat ...

Solution for repairing the display location button on Android within a React Native Map

I am facing an issue with the Location Button in my React Native Maps app. When the app is opened for the first time and the map is rendered, the button disappears. However, if I close the app but leave it running in the background, upon reopening the app, ...

Clicking the button in Angular should trigger a series of functions to be

It seems like I'm struggling with a simple question due to my lack of experience in this area. Any guidance or help would be greatly appreciated. I have a button that should trigger multiple functions when clicked, all defined in the same ts file. Wh ...

The raycaster is experiencing issues when used with multiple cameras in the Three.js library

I am currently developing an application similar to the threeJs editor. In this project, I have implemented four different cameras, each with unique names and positions. Here is an example of one of the cameras: cameras['home'] = new THREE.Combi ...

Issues with navigating routes in AngularJS

I'm currently learning Angular and having trouble with my routing setup in my application. I have a complex structure of nested modules, which makes me believe the issue lies within the injections. If anyone could provide some guidance, it would great ...

Trouble with executing jquery window.open during ajax success due to blocking

Encountering an issue when trying to open a new browser window in my ajax success call, as it is being blocked as a popup. After some investigation, I discovered that a user event must be associated with the window.open method to prevent this from happenin ...

Preventing page reload in Supabase with NextJS when the website loses focus and regains it

I am working with the structure in _app.js of my Next.js app. // _app.js // import ... import { createBrowserSupabaseClient } from '@supabase/auth-helpers-nextjs' import { SessionContextProvider } from '@supabase/auth-helpers-react' // ...

Why isn't the front-end loading properly upon reloading, and instead displaying JSON data?

Encountering an odd issue post deployment on Heroku today. The setup includes a React front-end and an Express back-end for a social media application. Specifically, there is an issue with the profile page where data is fetched from the back-end route. Eve ...

What is the best way to combine the elements of two arrays within a v-for loop?

Within my code, I have defined two arrays: users and projects. Each array contains unique numbers as IDs. A project can be owned by multiple users, so in the projects array, there is an array of user IDs named ownersId that corresponds to the id of users i ...

Nested component does not have draggable functionality enabled

When I use the following template located at https://codesandbox.io/s/vskdf, why is it not rendering inside my vuedraggable? Any suggestions? InventorySectionGroup.vue: <template> <!-- this div renders --> <div class="inventory-sec ...