cover_photo

ThreeJS粒子特效筆記02 - Data Texture & Geometry Buffer

從零開始的GPGPU Particle System

table of content

  1. intro
  2. section 1 Import Test Texture
  3. section 2 Data Texture
  4. section 3 Use Geometry Buffer
  5. full code

Intro


初始專案

今天這篇文章將延續上一篇專案模板設置 並往下介紹GPGPU Particle Sytem的最重要的核心觀念「資料結構」,有了資料結構的概念以後我們就可以繼續往我們的終極目標前進,將簡易物理引擎混合到shader並作出粒子特效了~

Folder structure

資料夾結構如下(僅列出本期有使用到的檔案) 這裡附上 template git 連結有需要可以自行下載

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

Section 1 Import Test Texture


這個階段我們有兩個重要的目標

  1. 學會如何將貼圖載入 : TextureLoader
  2. 學會如何將貼圖傳入shader : uniform sampler2D

1. import texture into your project

test.jpg

2. import texture via vite

把檔案丟到public資料夾,由於vite的特性所以不需要額外註記public

import texture from '/test.jpg'

3. load texture by using Texture Loader

main.js

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,
    })

4. use texture in shader

在shader中使用貼圖的類別聲明是sampler2D 順帶一提uniform則代表全域變數通常是由cpu傳進來得參數 varying則是需要由vertex shader傳進 fragment shader中

varying vec2 vUv;
uniform sampler2D uTexture;


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

Section 2 Data Texture


在這個階段中我們將學習

  1. buffer的資料格式
  2. DataTexture的創建方式

p.s. 為了方便解釋,所以階段三與階段二的成果會是相同的( ~~絕對不是我偷懶只想放一張圖~~)

what is Data Texture?

在 Three.js 中,「Data Texture」是指基於數據動態生成的一種紋理。這是一種將數據編碼到圖像像素中以可視化的方法。這些紋理可用於表示各種類型的數據,例如高度圖、顏色圖或任何其他可視化為圖像的數據類型。 官方文件傳送門

1. declare buffer size

由於data texture是將資料寫入貼圖的方式,而貼圖一般都是2的倍數以及正方形的結構所以這邊我先聲明size表示貼圖的長跟寬、number則表示粒子的數量

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

2. create positon buffer and Init

本次的示例中將會使用貼圖來儲存每個particle的位置資訊,使用Float32Array是因為位置資訊是浮點數,其他能用得資料結構還有這些

而之所以要 * 4是因為我們寫進的貼圖有RGBA四個通道,雖然我們只使用前面三個通道,但第四個通道不能為空,雖然我們不使用但還是要幫他填上一個值 截圖 2024-03-14 上午11.01.02.png

//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;

      }
    }

3. create Data Texture

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

4. transfer to fragment shader

將我們創建好的positionTexture傳進shader

//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,
    })

5. sample texture

//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;

}

Section 3 Use Buffer Geometry


截圖 2024-03-14 上午11.01.02.png

What is Buffer Geometry 在Three.js中,"BufferGeometry"(緩衝幾何體)是一種用於儲存3D模型幾何信息的高性能數據結構。與傳統的Geometry(幾何體)相比,BufferGeometry具有更高的性能和更低的內存占用。

BufferGeometry將模型的幾何信息儲存在GPU的緩衝區中,而不是在CPU的內存中。這意味著當你使用BufferGeometry時,模型的幾何信息可以直接在GPU上進行渲染,而不需要從CPU傳輸到GPU,這大大提高了渲染性能。

白話文就是一個你需要從頭建立的幾何體,你需要提供至少vertex(模型頂點)、uv(模型的貼圖座標)

1. create buffer geometry

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

2 . create position buffer & uv buffer

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

3. init position and uv

//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
      }
    }

4. transfer buffer data to shader

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

5. remove positionTexture in your code

既然我們不需要仰賴貼圖將座標傳進shader裡了,所以還需要額外將section2有使用到positionTexture的地方著解掉

6. Use in shader

//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;

}

full code

git 連結