1. 准备 threejs 的渲染操作 基础绘制坐标系
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
const renderer = new THREE.WebGLRenderer({
antialias: true // 消除锯齿
});
camera.position.set(0, 0, 1);
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
// 绘制坐标轴
const axesHelper = new THREE.AxesHelper(10);
scene.add(axesHelper);
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(window.devicePixelRatio);
document.body.appendChild(renderer.domElement);
// 渲染函数
const render = (callback, time) => {
camera.updateProjectionMatrix();
renderer.render(scene, camera);
callback && callback(time);
requestAnimationFrame((time) => render(callback, time));
}
render();
2. 准备 vertexShader | fragmentShader 烟花效果基础着色器
2.1 基础的顶点着色器格式
- modelPosition 为模型的位置信息 设置 xyz 可修改位置
void main() {
vec4 modelPosition = modelMatrix * vec4(position, 1.0);
gl_Position = projectionMatrix * (viewMatrix * modelPosition);
}
2.2 基础片元着色器格式
- gl_FragColor 设置基础的颜色 vec4 = rgba;
void main() {
gl_FragColor = vec4(1.0);
}
3 基础着色器使用 封装创建Box的方法 并 return 当前材质
- uniforms 传递参数给着色器内部接收使用
- uColor = rgb;
const createBox = (color,y = -1) => {
const geometry = new THREE.BoxGeometry( 0.3, 0.1, 0.1 );
const BoxMaterial = new THREE.ShaderMaterial({
uniforms:{
uColor:{
value: new THREE.Color(0x73162392)
}
},
vertexShader:`
void main() {
vec4 modelPosition = modelMatrix * vec4(position, 1.0);
gl_Position = projectionMatrix * (viewMatrix * modelPosition);
}
`,
fragmentShader:`
uniform vec3 uColor;
void main() {
gl_FragColor = vec4(uColor,1.0);
//gl_FragColor = vec4(1.0,1.0,1.0,1.0);
}
`,
});
const cube = new THREE.Mesh( geometry, BoxMaterial );
cube.position.y = y;
scene.add( cube );
return {
BoxMaterial:BoxMaterial
}
}
//执行
createBox();
- 效果如下 立方体图形
4 基础着色器使用 创建单个点 (烟花绽放效果就是由很多的点散开形成)
- gl_PointSize 着色器材质创建点时需要在 顶点着色器设置 点的 大小 (不然点会显示不出来)
- distance 计算两个向量的距离
- gl_PointCoord 和 uv 相似但是又不同
-
opacity 设置点的 透明度 (结合代码解析)
- 1 float opacity = distance(gl_PointCoord , vec2(0.5)); 得到一个 两个向量距离的透明度
- 2 透明度反转 可以看到透明度不明显 float opacity = 1.0 - distance(gl_PointCoord , vec2(0.5));
- 3 我们 再乘一个值使效果明显float opacity = 1.0 - distance(gl_PointCoord , vec2(0.5)) * 3.0;
-
代码
const geometry = new THREE.BufferGeometry();
const material = new THREE.ShaderMaterial({
vertexShader: `
attribute vec3 aStep;
uniform float uTime;
uniform float uSize;
void main() {
vec4 modelPosition = modelMatrix * vec4( position, 1.0 );
gl_Position = projectionMatrix * viewMatrix * modelPosition;
gl_PointSize = 20.0;
}
`,
fragmentShader: `
void main() {
// 解析这里
float opacity = distance(gl_PointCoord , vec2(0.5));
gl_FragColor = vec4(1.0,0,0,opacity);
}
`,
transparent: true, // 如果设置材质透明则必须要设置
blending: THREE.AdditiveBlending, // 混合目标 表示两个点 重合 需要怎么显示
depthWrite: false, //渲染此材质是否对深度缓冲区有任何影响。默认为true。
});
// 点的位置
const postions = new Float32Array([0, 0, 0]);
geometry.setAttribute(
'position',
new THREE.BufferAttribute(postions, 3)
);
// 生成点
const points = new THREE.Points(geometry, material);
scene.add(points);
点效果
5. 封装创建点发射一个点到指定的位置的方法
5.1 createClick 方法
- pointsArray 创建的点添加到数组中
- color 随机颜色值
- from 点开始发射的位置
- position 随机指定位置
- createBox 创建一个 Box 盒子
let pointsArray = [];
let box = createBox();
const createClick = () => {
// 随机颜色值
let color = new THREE.Color(`hsl(${Math.floor(Math.random() * 360)},100%,80%)`);
let position = {
x: Math.random() - 0.5,
y: Math.abs(Math.random() + 0.5),
z: Math.random() - 0.5
}
// 点开始的位置
let from = { x: 0, y: -1, z: 0 };
const point = createPoint(color, position, from);
box.material.uniforms.uColor.value = color;
point.addScene();
pointsArray.push(point);
}
window.addEventListener('click', createClick);
5.2 创建点的方法 createPoint
- modelPosition 可以修改模型的位置
- modelPosition.xyz += (aStep*uTime); aStep 就是to 的 xyz ,uTime 就是 [0,1]
function createPoint (color, position, from) {
const geometry = new THREE.BufferGeometry();
const material = new THREE.ShaderMaterial({
uniforms: {
uTime: {
value: 0
},
uColor: {
value: color
},
uSize: {
value: 20.0
}
},
vertexShader: `
attribute vec3 aStep;
uniform float uTime;
uniform float uSize;
void main() {
vec4 modelPosition = modelMatrix * vec4( position, 1.0 );
// 设置点的 位置 aStep 就是 to 的 xyz uTime 就是 [0,1];
modelPosition.xyz += (aStep*uTime);
gl_Position = projectionMatrix * viewMatrix * modelPosition;
gl_PointSize = uSize;
}
`,
fragmentShader: `
uniform vec3 uColor;
void main() {
float str = 1.0 - distance(gl_PointCoord , vec2(0.5)) * 3.0;
gl_FragColor = vec4(uColor,str);
}
`,
transparent: true,
blending: THREE.AdditiveBlending,
depthWrite: false
});
// 设置点开始的位置
const buffreArray = new Float32Array(3);
buffreArray[0] = from.x;
buffreArray[1] = from.y;
buffreArray[2] = from.z;
// 点发射到什么位置 也可以直接 写 to.x to.y to.z
const asteArray = new Float32Array(3);
asteArray[0] = to.x - from.x;
asteArray[1] = to.y - from.y;
asteArray[2] = to.z - from.z;
// 设置属性 传递倒着色器中
geometry.setAttribute(
'aStep',
new THREE.BufferAttribute(asteArray, 3)
);
geometry.setAttribute(
'position',
new THREE.BufferAttribute(buffreArray, 3)
);
// 创建点
const points = new THREE.Points(geometry, material);
// 获取一个 执行的时间
const clock = new THREE.Clock();
// 创建烟花
const fireworks = createFireworks(color , to);
fireworks.addScene();
return {
material,
// 添加材质 到 scene 中
addScene(){
scene.add(points);
},
update() {
// 获取时间
const elapsedTime = clock.getElapsedTime();
if (elapsedTime < 1) {
material.uniforms.uTime.value = elapsedTime;
} else {
const time = elapsedTime - 1;
material.uniforms.uSize.value = 0;
points.clear();
geometry.dispose();
scene.remove(points);
// 这里是创建烟花的代码 ....
// 设置烟花显示
fireworks.update(time);
}
}
}
}
5.3 在 render 方法中对生成的点进行运动
- 更新生成的每个点的 update 方法 自动发射点
let oldTime = 0;
render((time) => {
if(time - oldTime > 200){
createPoint();
oldTime = time;
}
objectF.forEach((item) => {
item.update();
})
});
5.4 效果
6 烟花散开 createFireworks 函数封装
function createFireworks(color, to) {
const fireworkGeometry = new THREE.BufferGeometry();
const fireworkMateial = new THREE.ShaderMaterial({
vertexShader:`
attribute float aScale;
attribute vec3 aRandom;
uniform float uTime;
uniform float uSize;
void main() {
vec4 modelPosition = modelMatrix * vec4(position, 1.0);
modelPosition.xyz += aRandom*uTime;
gl_Position = projectionMatrix * (viewMatrix * modelPosition);
gl_PointSize = (uSize*aScale)-uTime*20.0;
}
`,
fragmentShader:`
uniform vec3 uColor;
void main() {
float distanceTo = distance(gl_PointCoord , vec2(0.5));
float str = distanceTo * 2.0;
str = 1.0 - str;
str = pow(str,1.5);
gl_FragColor = vec4(uColor,str);
}
`,
blending:THREE.AdditiveBlending,
depthWrite:false,
uniforms:{
uTime:{
value:0
},
uSize:{
value:0.0
},
uColor:{
value:color
}
}
});
// 随机的烟花数量
const fireworkConst = 180 + Math.floor(Math.random() * 180);
const positionFireworksArray = new Float32Array(fireworkConst * 3); // 位置
const scaleFireworkArray = new Float32Array(fireworkConst); // 大小
const direcationArray = new Float32Array(fireworkConst * 3); // 移动方向
for (let index = 0; index < fireworkConst; index++) {
// 开始烟花的位置
positionFireworksArray[index * 3 + 0] = to.x;
positionFireworksArray[index * 3 + 1] = to.y;
positionFireworksArray[index * 3 + 2] = to.z;
// 设置烟花所有粒子的初始大小
scaleFireworkArray[index] = Math.random();
// 设置旋转的角度 四周发射的角度
let theta = Math.random() * 2 * Math.PI;
let beta = Math.random() * 2 * Math.PI;
// 半径
let r = Math.random();
// 最不了解的是这里 三角形的弧度值
direcationArray[index * 3 + 0] = r * Math.sin(theta) + r * Math.sin(beta);
direcationArray[index * 3 + 1] = r * Math.cos(theta) + r * Math.cos(beta);
direcationArray[index * 3 + 2] = r * Math.sin(theta) + r * Math.cos(beta);
}
fireworkGeometry.setAttribute(
'position',
new THREE.BufferAttribute(positionFireworksArray, 3)
);
// 大小
fireworkGeometry.setAttribute(
'aScale',
new THREE.BufferAttribute(scaleFireworkArray, 3)
);
// 随技方向
fireworkGeometry.setAttribute(
'aRandom',
new THREE.BufferAttribute(direcationArray, 3)
);
const foreworksPoints = new THREE.Points(fireworkGeometry, fireworkMateial);
return {
fireworkGeometry,
fireworkMateial,
delete() {
fireworkMateial.uniforms.uSize.value = 0;
foreworksPoints.clear();
fireworkGeometry.dispose();
scene.remove(foreworksPoints);
},
update(time){
fireworkMateial.uniforms.uTime.value = time;
fireworkMateial.uniforms.uSize.value = 20.0;
if(time > 5){
this.delete();
}
},
addScene() {
scene.add(foreworksPoints);
}
}
}