Three.js流星划过效果[粒子系统]

本文档展示了如何使用Three.js库结合自定义的顶点和片元着色器,创建一个流星效果。代码中定义了MeteorClass类,该类用于生成流星粒子,并通过调整参数如颜色、目标位置、弧度、播放时间和消失时间来实现不同的视觉效果。同时,自定义的MeteorShaderMaterial材质通过控制点的大小、位置和颜色变化,实现了流星轨迹的动态变化。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

请添加图片描述

实现:使用粒子效果和自定义shader实现效果 (用图片应该更方便)

/*
 * @Author: hongbin
 * @Date: 2022-12-29 13:54:24
 * @LastEditors: hongbin
 * @LastEditTime: 2022-12-30 12:29:52
 * @Description:流星
 */
import * as THREE from "three";
import { BufferAttribute, BufferGeometry } from "three";
import { MeteorShaderMaterial } from "./shader/material";

const defaultProps = {
    numberOfPoints: 1000,
    color: {
        left: new THREE.Color("#5555ff"),
        right: new THREE.Color("#00ffff"),
    },
    target: new THREE.Vector3(250, 20, 0),
};

type Props = typeof defaultProps;

export class MeteorClass {
    group = new THREE.Group();
    initial = {
        length: 10,
        deviation: 1,
    };
    target: Props["target"];
    numberOfPoints: Props["numberOfPoints"];
    color: Props["color"];
    clock = new THREE.Clock();
    t = 0;
    second: number | undefined;
    //不使用MeteorShaderMaterial 使用克隆体不改变原有材质 方便创建多个实例
    material = MeteorShaderMaterial.clone();
    hideSecond: number | undefined;
    initSize = this.material.uniforms.size.value;

    constructor(
        /**
         * 生成粒子数量
         */
        numberOfPoints?: Props["numberOfPoints"],
        /**
         * 前进方向 如 x轴100 y轴20 new Vector3(100,20,0)
         */
        target?: Props["target"],
        /**
         * 粒子渐变颜色
         */
        color?: Props["color"],
        /**
         * 线段弧度级别
         */
        radian?: number,
        /**
         * 多少秒播放完毕
         */
        second?: number,
        /**
         * 流星轨迹消散时间
         */
        hideSecond?: number,
        /**
         * 切断流星片段
         */
        cut?: [number, number]
    ) {
        this.numberOfPoints = numberOfPoints || defaultProps.numberOfPoints;
        this.color = color || defaultProps.color;
        this.target = target || defaultProps.target;
        this.second = second;
        this.hideSecond = hideSecond;
        radian && (this.material.uniforms.radian.value = radian);
        if (cut) {
            this.material.uniforms.cutStart.value = cut[0];
            this.material.uniforms.cutEnd.value = cut[1];
        }
        this.initObj();
    }

    genGeometry() {
        const numberOfPoints = this.numberOfPoints;
        const geometry = new BufferGeometry();
        const positions = new Float32Array(numberOfPoints * 3);
        const colors = new Float32Array(numberOfPoints * 3);
        const percents = new Float32Array(numberOfPoints);
        const { left: leftColor, right: rightColor } = this.color;
        //! 不能用THREE.Color.sub减法函数 返回值最小0 影响生成正确颜色
        const gradientColor = {
            r: leftColor.r - rightColor.r,
            g: leftColor.g - rightColor.g,
            b: leftColor.b - rightColor.b,
        };

        /**
         * 1最小 numberOfPoints最大
         * 一条横着的渐渐变宽的粒子线条
         */
        for (let i = 0, currLength = 0; i <= numberOfPoints; i++) {
            const i3 = i * 3;
            // const deviation = Math.random() * this.initial.deviation;
            // const x = currLength + deviation;
            // const x = deviation * 10;
            const x = 0;
            const y = 0;
            const z = 0;
            positions[i3] = x;
            positions[i3 + 1] = y;
            positions[i3 + 2] = z;
            const percent = i / numberOfPoints;
            percents[i] = percent;

            colors[i3] = leftColor.r - gradientColor.r * percent;
            colors[i3 + 1] = leftColor.g - gradientColor.g * percent;
            colors[i3 + 2] = leftColor.b - gradientColor.b * percent;

            currLength = (currLength + 1) % this.initial.length;
        }

        geometry.setAttribute("position", new BufferAttribute(positions, 3));
        geometry.setAttribute("color", new BufferAttribute(colors, 3));
        geometry.setAttribute("percent", new BufferAttribute(percents, 1));
        return geometry;
    }

    initObj() {
        const geometry = this.genGeometry();
        this.material.uniforms.target = {
            value: this.target,
        };
        const mesh = new THREE.Points(geometry, this.material);
        this.group.add(mesh);
    }

    /**
     * 更新进度
     */
    update(p?: number) {
        let percent = p!;
        if (this.second) {
            this.t += this.clock.getDelta();
            percent = this.t / this.second;

            if (percent >= 1 && this.hideSecond) {
                let over = 1 - (this.t - this.second) / this.hideSecond;

                if (over <= 0) {
                    this.t = 0;
                    over = 1;
                    percent = 0;
                }
                this.material.uniforms.size.value = this.initSize * over;
            }
        } else {
            if (percent > 1) {
                this.material.uniforms.size.value /= 1.1;
            } else this.material.uniforms.size.value = this.initSize;
        }
        this.material.uniforms.progress.value = percent;
    }
}

material

/*
 * @Author: hongbin
 * @Date: 2022-11-10 10:54:21
 * @LastEditors: hongbin
 * @LastEditTime: 2022-12-30 14:07:21
 * @Description:自定义shader 材质
 */
import * as THREE from "three";
import vertexShader from "./vt.glsl";
import fragmentShader from "./fm.glsl";

export const MeteorShaderMaterial = new THREE.ShaderMaterial({
    uniforms: {
        time: { value: 0 },
        //弥补自定义shader没有PointsMaterial材质的size属性
        size: { value: 8 },
        progress: { value: 0 },
        color: { value: new THREE.Color("#0f00f0") },
        /**
         * 流星的弧度 0为直线
         */
        radian: { value: 0 },
        /**
         * 切断开始
         */
        cutStart: { value: 0 },
        /**
         * 切断结束
         */
        cutEnd: { value: 0 },
    },
    blending: THREE.AdditiveBlending,
    // side: 2,
    transparent: true,
    // blending: THREE.AdditiveBlending,
    vertexShader,
    //弥补自定义shader没有PointsMaterial材质的sizeAttenuation属性
    fragmentShader,
    // alphaTest: 0.001,
    // depthTest: false,
    depthWrite: false,
});

顶点着色器

varying vec3 vColor;
uniform float time;
uniform float size;
uniform float progress;
uniform vec3 target;
uniform float wave;
uniform float radian;
uniform float cutStart;
uniform float cutEnd;
attribute float percent;
attribute vec3 color;

void main() {
    vec3 dispatchPos;

    float p = min(progress, 1.);
    float rp = p >= percent ? percent : p;

    if(rp > cutStart && rp < cutEnd) {
        rp = cutStart;
    }
    dispatchPos = position + (target - position) * rp;
    dispatchPos.y *= sin(rp * 0.4) * radian;

    vColor = color;

    vec4 viewPosition = modelViewMatrix * vec4(dispatchPos, 1.0);

    gl_Position = projectionMatrix * viewPosition;

    gl_PointSize = size;

    //最后进度等于1的点 放大
    // gl_PointSize += step(1., rp) * 3.;

    //近大远小效果 值自己调节
    gl_PointSize *= (120. / -(modelViewMatrix * vec4(dispatchPos, 1.0)).z);

    //后面细 前面粗
    gl_PointSize *= (0.2 + rp);
    // gl_PointSize *= (0.2 + (1. - rp));
}

片元着色器

// uniform vec3 color;
varying vec3 vColor;

void main() {
    float strength = distance(gl_PointCoord, vec2(0.5));
    strength = step(0.5, strength);
    strength = 1.0 - strength;
    gl_FragColor = vec4(vColor, strength);
    // gl_FragColor = vec4(0.2431, 0.0039, 0.9608, 1.0);
}

使用

const Meteor = new MeteorClass(
1000,
new THREE.Vector3( 400,450, 0),
{
    left: new THREE.Color("#f00"),
    right: new THREE.Color("#00f"),
},
1,
3,
1
);
scene.add(Meteor.group);
Meteor.group.position.set(100 * Math.random(), 120 * random, 0);
Meteor.group.rotateZ(Math.PI / 0.9);

然后逐帧调用更新

Meteors.update();
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值