瞭解了基本的粒子系統如何建立後,今天要來練習實作粒子系統中常見的應用:雪花。以下將會透過建立粒子系統、載入貼圖、雪花飄落動畫來一步步做出華麗的粒子特效。
Photo by Aaron Burden on Unsplash
這是本系列第 14 篇,如果還沒看過第 13 篇可以點以下連結前往:
按照慣例,今天的成果圖與 demo,下面就直接開始實作吧:
// points
const particleCount = 15000
let points
// 建立粒子系統
function createPoints() {
const geometry = new THREE.Geometry()
const material = new THREE.PointsMaterial({
size: 3
})
const range = 250
for (let i = 0; i < particleCount; i++) {
const x = THREE.Math.randInt(-range, range)
const y = THREE.Math.randInt(-range, range)
const z = THREE.Math.randInt(-range, range)
const point = new THREE.Vector3(x, y, z)
geometry.vertices.push(point)
}
points = new THREE.Points(geometry, material)
scene.add(points)
}
首先一開始先建立粒子系統,這裡我們建立 15000 個頂點,並且將每一個頂點的(x, y, z)
值分別設定在介於(-250, 250)
的隨機值,並利用自訂的頂點群建立粒子系統,將相機拉遠後看到的成果會是一個類似邊長為 500 的正立方體粒子群。
THREE.Math.randInt(min, max)
是 Three.js 中提供一個類似 Math.random() 的包裝,可直接指定最小值與最大值,會算出這區間隨機數。
const texture = new THREE.TextureLoader().load('./snowflake.png')
let material = new THREE.PointsMaterial({
size: 5,
map: texture,
blending: THREE.AdditiveBlending,
depthTest: false,
transparent: true,
opacity: 0.7
})
這邊稍微筆記一下關於 THREE.PointsMaterial
中幾個在粒子材質中需注意的屬性:
屬性 | 說明 |
---|---|
color | 粒子系統中所有粒子的材質顏色。若 vertexColors 設為 true ,則會將此值乘以頂點顏色得到最終呈現的顏色。預設為 0xffffff 白色。 |
vertexColors | 設為 THREE.VertexColors ,則會使用頂點的顏色。預設為 THREE.NoColors 。 |
blending | 渲染材質時的融合模式。用來調整載入的材質如何與背景融合。 |
depthTest | 深度緩衝。設為 true 時, 粒子會有深度緩衝效果,根據 z 座標決定物體間的遮擋效果,此屬性預設為 true 。 |
sizeAttenuation | 尺寸衰減。設為 true 時, 粒子的大小會隨著與相機的距離而不同;設為 false 時,則無論粒子距離相機遠近大小皆相同,可見官方範例。此屬性預設為 true 。 |
首先是載入雪花貼圖: 。
並且將融合模式設為 THREE.AdditiveBlending
,就是在渲染粒子時背景的顏色會被添加到粒子的背景上,簡單的說就是可以幫載入的貼圖去背,如果沒設這個屬性,會看到每個雪花都帶著醜醜的黑色背景。
而 depthTest
有關掉的話,會將融合模式中吃到的背景色,在兩片雪花疊加時有透明效果而不會被遮擋住。
scene.fog = new THREE.FogExp2(0x000000, 0.0008)
// camera
camera = new THREE.PerspectiveCamera(
70,
window.innerWidth / window.innerHeight,
1,
1000
)
camera.position.set(0, 0, 100)
camera.lookAt(scene.position)
設定場景的霧化效果,可以讓極遠處的粒子有種模糊美,而相機視角及位置稍微拉大拉遠來觀察更多的粒子。
今天實作了粒子系統中一個靜態的雪花畫面,從建立粒子系統並透過載入貼圖的方法設定粒子材質的屬性,讓場景中出現滿滿的雪花,明天會繼續將雪花飄落的動畫完成,我們明天見!
用vscode打開smokeflake.png那張圖,可以發現它是不帶透明度的
這邊給你參考一下:THREE.AdditiveBlending指的不是去背,是加色喔
下面是Three.js的原始碼
我們直接看不是premultipliedAlpha的部分
(premultipliedAlpha指的是:圖片像素存的是color.rgb=color.rgb*color.a)
case AdditiveBlending:
gl.blendFunc(gl.SRC_ALPHA, gl.ONE);
// 上面的意思是
// finalColor = srcColor.rgba*srcColor.a + desColor.rgba*1
// srcColor、desColor都有rgba
// srcColor是指跑完pixelshader(它會修改顏色)後的像素顏色
// destinationColor是指已經在畫布(color buffer)上的像素
break;
原來畫面的背景是黑的所以
desColor =(0,0,0,1)
在畫雪片時,如果該像素抓到的貼圖是白的(1,1,1,1)
srcColor =(1,1,1,1)
但是你的material有設transparent
let material = new THREE.PointsMaterial({
size: 5,
map: texture,
blending: THREE.NormalBlending,
depthTest: false,
transparent: true,
opacity: 0.7
})
所以跑完pixelshader傳出的顏色是(0.7,0.7,0.7,1)
就是srcColor =(0.7,0.7,0.7,1)
finalColor = srcColor.rgba * srcColor.a + desColor.rgba * 1
finalColor = (0.7,0.7,0.7,1).rgba * srcColor.a + (0,0,0,1) * 1
finalColor = (0.7,0.7,0.7,1).rgba * 1 + (0,0,0,1) * 1
finalColor = (0.7,0.7,0.7,1).rgba * 1 + (0,0,0,1) * 1
alpha的範圍應該會被限制在0~1,所以finalColor = (0.7,0.7,0.7,1)
如果該像素抓到的貼圖是黑的(1,1,1,1)
執行完THREE.AdditiveBlending,finalColor就還是黑的
看起來就像去背,但它真的不是去背也
(如果要去背,用的是alphaTest,然後貼圖要帶透明資訊a)
gl.blendFunc和gl.blendFuncSeparate的差別可以參考這裡
http://www.jiazhengblog.com/blog/2017/01/04/2989/
最近跟著這篇系列文在認識 three.js,感謝 CK Chuang 的教學。
因為從 r125 版開始 THREE.Geometry()
就被移除了。
分享一下 r139 版的範例
// 主要元件
let scene, renderer, camera, material, cameraControl;
const loader = new THREE.TextureLoader();
// 粒子系統 變數
const particleCount = 1500; // 粒子數量
// 粒子系統 函式
function createSnows() {
const snowUrl = "https://i.postimg.cc/d0SHZk3J/snow.png"
const particleTexture = loader.load(snowUrl)
const geometry = new THREE.BufferGeometry();
const vertices = [];
const range = 250; // 粒子分佈範圍
for (let i = 0; i < particleCount; i++) {
const x = THREE.Math.randInt(-range, range);
const y = THREE.Math.randInt(-range, range);
const z = THREE.Math.randInt(-range, range);
vertices.push(x, y, z);
}
geometry.setAttribute(
'position',
new THREE.Float32BufferAttribute(vertices, 3)
);
material = new THREE.PointsMaterial({
size: 5,
map: particleTexture,
sizeAttenuation: true,
alphaTest: 0.5,
transparent: true,
})
const particles = new THREE.Points(geometry, material);
scene.add(particles);
}