cover_photo

ThreeJS粒子特效筆記01 - 專案模板設置

從零開始的GPGPU Particle System

table of content

Loading preview...
.

Loading preview...
.

Loading preview...
.

Loading preview...
.

Loading preview...
.

Loading preview...

what is GPGPU


在電腦領域,術語 GPGPU 代表通用圖形處理器 (用到圖形處理器的通用計算)。 圖形處理器 (圖形處理單元 GPU) 最初是專為加速圖形處理任務而設計的,例如在視訊遊戲中繪製影像。 然而,隨著技術的進步,人們發現 GPU 擁有大量平行處理核心,非常適合用於其他類型的運算,而不僅限於圖形處理。

講白話文就是讓gpu做渲染以外的計算,至於為什麼用gpu而不用cpu可以參考我之前寫的

Loading preview...

有了這門技術,我們就可以用它來處理複雜粒子特效的計算 17101429819761710142981976.gif 17101429475221710142947522.gif

Intro


這個系列我會紀錄我在

Loading preview...
中的筆記,分享如何從零到一的打造自己的GPU particle system 以及每一次的實作成果,歡迎對粒子特效有興趣的朋友一起學習喔~

p.s. 這系列文章會需要要求你有基本的知識背景包括以下

  1. html, css, js:基礎門檻bj4
  2. threejs : 不需要也太多的理解,至少知道他在幹嘛就行
  3. shader : 包含fragment shader, vertex shader, threejs 中使用的是glsl
  4. 基礎的3d渲染概念:攝影機、燈光、mesh、選染器等概念

Project Setup


使用vite初始化專案

bun create vite <app-name>

加入container到你的html

<body>
  <div id="container"></div>
</body>

為你的container加入一點style好讓他可以佔全視窗

#container{
  width: 100%;
  height: 100vh;
  background: black;
  margin: 0;
  padding: 0;
}

Install Dependencies


既然我們要使用threejs那當然要安裝他

bun i three

Create Sketch Class


1. basic sketch

//edit in main.js
import * as THREE from 'three'
export default class Sketch{
  construtor({dom}){
  
  }
}
new Sketch({
 dom: document.queryElementById("container")
})

2. add scene

constructor({dom}){
    ...
    this.container = dom;
    this.scene = new THREE.Scene();
     this.width = this.container.offsetWidth;
     this.height = this.container.offsetHeight;
    ...
  }

3. Init render

this.renderer = new THREE.WebGLRenderer( { antialias: true, alpha: true } );
  this.renderer.setSize( this.width, this.height );
  this.container.appendChild(this.renderer.domElement);

4. Init Camera (with control)

import {OrbitControls} from 'three/examples/jsm/controls/OrbitControls.js'

//in constructor
this.camera = new THREE.PerspectiveCamera( 70, width/height, 0.01, 10 );
camera.position.z = 1;
this.controls = new OrbitControls( this.camera, this.renderer.domElement );

5. Deal with window resize

resize(){
  this.width = this.container.offsetWidth;
   this.height = this.container.offsetHeight;
     renderer.setSize( this.width, this.height );
  this.camera.aspect = this.width / this.height;
  this.camera.updateProjectMatrix();
}

6. animate scene

class Sketch{
...
constructor({dom}){
...
this.renderer.setAnimationLoop( this.render );
...
}
render(){
  ... 
  renderer.render( this.scene, this.camera );
}
}

add object


在threejs 中如果想要渲染一個3d物件需要經果以下的流程

  1. 創建幾何體(產生頂點)
  2. 創建材質(決定物體要如何被渲染)
  3. 創建網格渲染器(MeshRender)
  4. 加入場景中

1. Geometry

addObjects(){
//創建一個1:1 50 * 50的平面幾何體
this.geometry = new THREE.PlaneGeometry(1,1,50,50);
}

2. vite plugin

安裝以下套件讓你接下來可以無痛匯入shader

bun i vite-plugin-glsl --save-dev

新增vite plugin到vite.config.js(如果根目錄沒有需要手動創建)

import glsl from 'vite-plugin-glsl';
import { defineConfig } from 'vite';
export default defineConfig({
    plugins: [glsl()]
  });

3. Create your first shader

如果你用vs code 建議可以安裝這個

Loading preview...
可以讓你的shader code看起來更好看喔~

vertex shader (./src/shader/vertex.glsl)

varying vec2 vUv;

void main() {

    vUv = uv;

    gl_PointSize =  10.0;

    gl_Position = projectionMatrix * mvPosition;

}

fragment shader (./src/shader/fragment.glsl)

varying vec2 vUv;

void main() {
    gl_FragColor = vec4( vUv,0., 1.0 );
}

add shader to your main.js

import vertexShader from './shaders/vertex.glsl';
import fragmentShader from './shaders/fragment.glsl';

4. Shader Material

addObjects(){
...
//創建shader material
this.material = new THREE.ShaderMaterial({
            vertexShader: vertexShader,
            fragmentShader: fragmentShader,
        })
       }

5. add to scene

this.mesh = new THRE.Points(this.geometry, this.material);
this.scene.add(this.mesh)

完成!!!

截圖 2024-03-11 下午5.10.16.png

同場加映


如果想要讓頂點按照sin上下擺動該如何做呢?

step 1 新增一個time的變數從,並透過uniform宣告到shader

//add object function
this.time = 0;
this.material = new THREE.ShaderMaterial({
            uniforms: {
                time: {value: this.time}
            },
            vertexShader: vertexShader,
            fragmentShader: fragmentShader,
        })
       }

step 2 每禎更新時間參數,並把該時間參數傳遞到shader中

this.time += 0.05;
this.material.uniforms.time.value = this.time;

step 3 更新vertex shader


varying vec2 vUv;
uniform float time;

void main() {

    vUv = uv;
    vec3 newpos = position;
    //這行是將時間參數傳進sin函數裡,目的是為了在不同時間點得到不同的高度
    newpos.z += sin( time + position.x*10. ) * 0.5;

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

    gl_PointSize =  ( 10.0 / -mvPosition.z );

    gl_Position = projectionMatrix * mvPosition;

}

step 4 完成~ 17101500300791710150030079.gif

Buy me a coffee