Language
cover_photo

Three.js Particle Notes 02: Data Texture and Geometry Buffer

Using texture data to drive particle positions on the GPU

Quick answer

Three.js Particle Notes 02: Data Texture and Geometry Buffer: This note continues the GPGPU particle series by focusing on data textures and geometry buffers. These two pieces explain how information moves between simulation and rendering.

This note continues the GPGPU particle series by focusing on data textures and geometry buffers. These two pieces explain how information moves between simulation and rendering.

A data texture stores numerical values in a texture-like format. Instead of using it as an image, the shader reads it as structured data, such as particle position, velocity, or custom attributes.

The geometry buffer defines what will be drawn. In many particle systems, the visible geometry is simple; the important information comes from textures sampled in the shader.

This separation is powerful because the CPU does not need to update every particle manually. The GPU can read texture data and compute positions in parallel.

Before publishing, I would verify the code snippets and add a small diagram showing how data texture, simulation shader, render target, and visible geometry connect.

The following source media, links, code, and MDX components are kept as technical references.

Media

  • test.jpg
  • 截圖 2024-03-14 上午11.01.02.png

Code and Configuration Snippets

Snippet 1

root
L public
     L test.jpg
|- index.html
|- main.js
L style.js

Snippet 2

import texture from '/test.jpg'

Snippet 3

this.geometry = new THREE.PlaneGeometry(1,1,this.size,this.size);
this.material = new THREE.MeshNormalMaterial();
//透過uniform變數將texture傳遞給shader
this.material = new THREE.ShaderMaterial({
        uniforms: {
            time: {value: this.time},
            uTexture: {value: new THREE.TextureLoader().load(texture)}
        },
        vertexShader: vertexShader,
        fragmentShader: fragmentShader,
    })

Snippet 4

varying vec2 vUv;
uniform sampler2D uTexture;


void main() {
    vec4 color = texture2D(uTexture, vUv);
    gl_FragColor = vec4( color.xyz, 1.0 );
}

Snippet 5

//main.js
this.size = 32;
this.number = this.size * this.size;
    

Snippet 6

//main.js
this.positions = new Float32Array(this.number * 4);
for (let i=0; i<this.size; i++){
      for (let j=0; j<this.size; j++){
        let index = i*this.size + j;
        this.positions[4 * index] = i/(this.size-1) - 0.5;//x
        this.positions[4 * index+1] = j/(this.size-1) - 0.5;//y
        this.positions[4 * index+2] = Math.random() * 0.05;//z
        this.positions[4 * index+2] = 1;

      }
    }

Snippet 7

//main.js
this.positionsTexture = new THREE.DataTexture(this.positions, this.size, this.size,THREE.RGBAFormat, THREE.FloatType);
this.positionsTexture.needsUpdate = true;

Snippet 8

//main.js
this.material = new THREE.ShaderMaterial({
        uniforms: {
            time: {value: this.time},
            positionTexture: {value: new THREE.TextureLoader().load(texture)},
            uTexture: {value: this.positionsTexture}
        },
        vertexShader: vertexShader,
        fragmentShader: fragmentShader,
    })

Snippet 9

//vertex shader
uniform float time;
uniform sampler2D positionTexture;
varying vec2 vUv;


void main() {

    vUv = uv;
    vec4 color = texture2D(positionTexture, uv);
    vec3 newpos = color.xyz;

    vec4 mvPosition = modelViewMatrix * vec4( newpos, 1.0 );

    gl_PointSize =  5.0;

    gl_Position = projectionMatrix * mvPosition;

}

Snippet 10

//main.js
this.geometry = new THREE.BufferGeometry();

Snippet 11

//main.js
this.positions = new Float32Array(this.number * 3);
this.uvs = new Float32Array(this.number * 2);

Snippet 12

//main.js
    for (let i=0; i<this.size; i++){
      for (let j=0; j<this.size; j++){
        let index = i*this.size + j;
        this.positions[3 * index] = i/(this.size-1) - 0.5;//x
        this.positions[3 * index+1] = j/(this.size-1) - 0.5;//y
        this.positions[3 * index+2] = Math.random() * 0.05;//z

        this.uvs[2* index] = i/(this.size-1);//u
        this.uvs[2 * index+1] = j/(this.size-1);//v
      }
    }

Snippet 13

//main.js
    this.geometry.setAttribute('position', new THREE.BufferAttribute( this.positions , 3));
    this.geometry.setAttribute('uv',new THREE.BufferAttribute( this.uvs, 2) );

Snippet 14

//vertex shader
uniform float time;
uniform sampler2D uTexture;
varying vec2 vUv;


void main() {

    vUv = uv;
    vec3 newpos = position;

    vec4 mvPosition = modelViewMatrix * vec4( newpos, 1.0 );

    gl_PointSize =  5.0;

    gl_Position = projectionMatrix * mvPosition;

}

FAQ

What is this article about?

This note continues the GPGPU particle series by focusing on data textures and geometry buffers. These two pieces explain how information moves between simulation and rendering.

Who is this article for?

It is for readers who want to understand the implementation, design tradeoffs, and learning context behind Three.js Particle Notes 02: Data Texture and Geometry Buffer.

Buy me a coffee