使用粒子效果动画组成模型[自定义shader实现]

news2024/11/27 4:22:50

文章目录

        • 优点
        • 实现思路
        • 传递给Shader的数据
        • 根据模型数据生成数据传递给Shader
        • 自定义shader 连接cpu与gpu
        • 顶点着色器 计算位置
        • 片元着色器

请添加图片描述

优点

性能卓越
上一篇使用的更改坐标实现 9万个点 页面非常卡顿 光是计算9万个点坐标更替的js就已经造成了堵塞 尝试了在顶点着色器中实现动画发现无丝毫卡顿发生,实为粒子动效正道

实现思路

实现思路
计算交给GPU而不是js线程计算 这样页面不会卡顿
交给Shader的几个数据: 当前的进度 开始坐标 终点坐标 持续时间

传递给Shader的数据

/**
* 粒子控制器
* @param {number} numberOfPoints 数量必须大于所有模型中最大的点数量 不然显示不全
*/
constructor(numberOfPoints: number) {
   this.numberOfPoints = numberOfPoints;
   this.particlesGeometry = new BufferGeometry();
   //顶点着色器的坐标
   this.positions = new Float32Array(numberOfPoints * 3);
   //顶点着色器的变更前的坐标
   this.oldPositions = new Float32Array(numberOfPoints * 3);
   //顶点着色器要前往的目标位置
   this.toPositions = new Float32Array(this.numberOfPoints * 3);
   //顶点着色器要前往的目标位置的时间
   this.toPositionsDuration = new Float32Array(this.numberOfPoints);
   this.particlesGeometry.setAttribute("position", new BufferAttribute(this.positions, 3));
   this.particlesGeometry.setAttribute("oldPositions", new BufferAttribute(this.oldPositions, 3));
   this.particlesGeometry.setAttribute("toPositions", new BufferAttribute(this.toPositions, 3));
   this.particlesGeometry.setAttribute("toPositionsDuration", new BufferAttribute(this.toPositionsDuration, 1));

   // const textureLoader = new TextureLoader();
   this.particles = new Points(this.particlesGeometry, PointsShaderMaterial);
}

根据模型数据生成数据传递给Shader

    /**
     * 向某形状变换
     * @param {ArrayLike<number>} array 数据
     * @param { min: number; max: number } duration 时间段
     */
    to(array: ArrayLike<number>, duration: { min: number; max: number }) {
        const { length } = array;
        const pointCount = length / 3;

        for (let i = 0, realCount = 0; i < this.numberOfPoints; i++, realCount++) {
            //模型的点和生成的点数量未必相等 多的则重合前面的位置
            realCount = realCount % pointCount;
            const i3 = i * 3;
            const r3 = realCount * 3;
            //设置给顶点着色器
            //保存起点
            this.oldPositions[i3] = this.positions[i3];
            this.oldPositions[i3 + 1] = this.positions[i3 + 1];
            this.oldPositions[i3 + 2] = this.positions[i3 + 2];
            //设置终点
            this.toPositions[i3] = array[r3];
            this.toPositions[i3 + 1] = array[r3 + 1];
            this.toPositions[i3 + 2] = array[r3 + 2];
            //设置运动时间
            const useDuration = duration.min + Math.random() * (duration.max - duration.min);

            this.toPositionsDuration[i] = useDuration;
        }
        console.log(this.particlesGeometry);
    }

在生成数据后 至关重要的一步是更新这些属性否则同步不到GPU中

this.particlesGeometry.attributes.position.needsUpdate = true;
this.particlesGeometry.attributes.toPositions.needsUpdate = true;
this.particlesGeometry.attributes.oldPositions.needsUpdate = true;
this.particlesGeometry.attributes.toPositionsDuration.needsUpdate = true;

自定义shader 连接cpu与gpu

然后通过uniform 传递当前的时间供shader计算应该当前应该是什么位置
通过自定义shader实现

import * as THREE from "three";
import vertexShader from "./points.vt.glsl";
import fragmentShader from "./points.fs.glsl";
import { AdditiveBlending } from "three";

export const PointsShaderMaterial = new THREE.ShaderMaterial({
    uniforms: {
        time: { value: 0 },
        //弥补自定义shader没有PointsMaterial材质的size属性
        size: { value: 8 }
    },
    blending: AdditiveBlending,
    transparent: true,
    vertexShader,
    fragmentShader,
    alphaTest: 0.001,
    depthTest: false,
    depthWrite: false,
});

逐帧更新 uniforms 的time

 const clock = new Clock();
    let t = 0;
    td.animation(() => {
        t += clock.getDelta();
        PointsControl.update(t * 1000);
    });

顶点着色器 计算位置

最后工作就交给 顶点着色器来计算粒子的位置

uniform float time;
uniform float size;
attribute vec3 toPositions;
attribute vec3 oldPositions;
attribute float toPositionsDuration;

void main() {
    vec3 dispatchPos = position;
    //顶点位置移动
    //当前时间在点的运行时间中占比
    float percent = time / toPositionsDuration;
    vpercent = percent;
    if(percent <= 1.) {
        dispatchPos.x = oldPositions.x + percent * (toPositions.x - oldPositions.x);
        dispatchPos.y = oldPositions.y + percent * (toPositions.y - oldPositions.y);
        dispatchPos.z = oldPositions.z + percent * (toPositions.z - oldPositions.z);
    } else {
        dispatchPos = toPositions;
    }

    vec4 viewPosition = modelViewMatrix * vec4(dispatchPos, 1.0);
    gl_Position = projectionMatrix * viewPosition;
    gl_PointSize = size;

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

片元着色器

片元着色器基本不需要改动设置一个颜色即可

void main() {
    gl_FragColor = vec4(vec3(0.1, 0.5, 0.4), 0.8);
}

完成代码

粒子控制器

/*
 * @Author: hongbin
 * @Date: 2022-12-14 10:48:40
 * @LastEditors: hongbin
 * @LastEditTime: 2022-12-14 15:34:19
 * @Description:使用shader计算粒子位置 减小开销
 */
import {
    AdditiveBlending,
    BufferAttribute,
    BufferGeometry,
    Color,
    Material,
    Points,
    PointsMaterial,
    TextureLoader,
} from "three";
import TWEEN, { Tween } from "@tweenjs/tween.js";
import starMap from "../assets/img/star_09.png";
import { PointsShaderMaterial } from "./shader/points.material";

type ITween = Tween<{
    x: number;
    y: number;
    z: number;
}>;

export class PointsControlClass {
    numberOfPoints: number;
    positions: Float32Array;
    particles: Points<BufferGeometry, Material>;
    // particlesMaterial: PointsMaterial;
    particlesGeometry: BufferGeometry;
    toPositions: Float32Array;
    toPositionsDuration: Float32Array;
    oldPositions: Float32Array;

    /**
     * 粒子控制器
     * @param {number} numberOfPoints 数量必须大于所有模型中最大的点数量 不然显示不全
     */
    constructor(numberOfPoints: number) {
        this.numberOfPoints = numberOfPoints;
        this.particlesGeometry = new BufferGeometry();
        //顶点着色器的坐标
        this.positions = new Float32Array(numberOfPoints * 3);
        //顶点着色器的变更前的坐标
        this.oldPositions = new Float32Array(numberOfPoints * 3);
        //顶点着色器要前往的目标位置
        this.toPositions = new Float32Array(this.numberOfPoints * 3);
        //顶点着色器要前往的目标位置的时间
        this.toPositionsDuration = new Float32Array(this.numberOfPoints);
        this.particlesGeometry.setAttribute("position", new BufferAttribute(this.positions, 3));
        this.particlesGeometry.setAttribute("oldPositions", new BufferAttribute(this.oldPositions, 3));
        this.particlesGeometry.setAttribute("toPositions", new BufferAttribute(this.toPositions, 3));
        this.particlesGeometry.setAttribute("toPositionsDuration", new BufferAttribute(this.toPositionsDuration, 1));

        // const textureLoader = new TextureLoader();
        this.particles = new Points(this.particlesGeometry, PointsShaderMaterial);
        this.init();
    }

    /**
     * 根据初始的粒子数量生成对应的动画控制器
     */

    /**
     * 向某形状变换
     * @param {ArrayLike<number>} array 数据
     * @param { min: number; max: number } duration 时间段
     */
    to(array: ArrayLike<number>, duration: { min: number; max: number }) {
        const { length } = array;
        const pointCount = length / 3;

        for (let i = 0, realCount = 0; i < this.numberOfPoints; i++, realCount++) {
            //模型的点和生成的点数量未必相等 多的则重合前面的位置
            realCount = realCount % pointCount;
            const i3 = i * 3;
            const r3 = realCount * 3;
            //设置给顶点着色器
            //保存起点
            this.oldPositions[i3] = this.positions[i3];
            this.oldPositions[i3 + 1] = this.positions[i3 + 1];
            this.oldPositions[i3 + 2] = this.positions[i3 + 2];
            //设置终点
            this.toPositions[i3] = array[r3];
            this.toPositions[i3 + 1] = array[r3 + 1];
            this.toPositions[i3 + 2] = array[r3 + 2];
            //设置运动时间
            const useDuration = duration.min + Math.random() * (duration.max - duration.min);

            this.toPositionsDuration[i] = useDuration;
        }
        console.log(this.particlesGeometry);
        //至关重要
        this.particlesGeometry.attributes.position.needsUpdate = true;
        this.particlesGeometry.attributes.toPositions.needsUpdate = true;
        this.particlesGeometry.attributes.oldPositions.needsUpdate = true;
        this.particlesGeometry.attributes.toPositionsDuration.needsUpdate = true;
    }

    /**
     * 更新粒子系统
    //  * @param {number} progress 动画进度
     * @param {number} time 当前时间
     */
    update(time: number) {
        // PointsShaderMaterial.uniforms.progress.value = progress;
        PointsShaderMaterial.uniforms.time.value = time;
    }

    setModelData(array: ArrayLike<number>) {
        const { length } = array;
        const pointCount = length / 3;

        for (let i = 0, realCount = 0; i < this.numberOfPoints; i++, realCount++) {
            //模型的点和生成的点数量未必相等 多的则重合前面的位置
            realCount = realCount % pointCount;
            const i3 = i * 3;
            const r3 = realCount * 3;
            this.positions[i3] = array[r3];
            this.positions[i3 + 1] = array[r3 + 1];
            this.positions[i3 + 2] = array[r3 + 2];
        }

        this.particlesGeometry.attributes.position.needsUpdate = true;
    }

    /**
     * 初始化粒子系统 随机方形排布
     * @param {number} range 范围
     */
    init(range: number = 1000) {
        for (let i = 0; i < this.numberOfPoints; i++) {
            const i3 = i * 3;
            const x = (0.5 - Math.random()) * range;
            const y = (0.5 - Math.random()) * range;
            const z = (0.5 - Math.random()) * range;
            this.positions[i3] = x;
            this.positions[i3 + 1] = y;
            this.positions[i3 + 2] = z;
            this.oldPositions[i3] = x;
            this.oldPositions[i3 + 1] = y;
            this.oldPositions[i3 + 2] = z;
        }
        this.particlesGeometry.attributes.position.needsUpdate = true;
    }
}

自定义shader

/*
 * @Author: hongbin
 * @Date: 2022-11-10 10:54:21
 * @LastEditors: hongbin
 * @LastEditTime: 2022-12-14 12:39:11
 * @Description:粒子材料
 */
import * as THREE from "three";
import vertexShader from "./points.vt.glsl";
import fragmentShader from "./points.fs.glsl";
import { AdditiveBlending } from "three";

export const PointsShaderMaterial = new THREE.ShaderMaterial({
    uniforms: {
        time: { value: 0 },
        //弥补自定义shader没有PointsMaterial材质的size属性
        size: { value: 8 },
    },
    blending: AdditiveBlending,
    // side: 2,
    transparent: true,
    // blending: THREE.AdditiveBlending,
    vertexShader,
    //弥补自定义shader没有PointsMaterial材质的sizeAttenuation属性
    fragmentShader,
    alphaTest: 0.001,
    depthTest: false,
    depthWrite: false,
});

着色器代码上述已列出

调用

/**
* 生成粒子系统控制器 传入粒子数量
*/
const PointsControl = new PointsControlClass(90686);
scene.add(PointsControl.particles);
const clock = new Clock();
let t = 0;
td.animation(() => {
   t += clock.getDelta();
   PointsControl.update(t * 1000);
});


//...加载完模型后 传递模型数据
const targetMesh = g.scene.children[0] as Mesh;
if (!targetMesh) return new Error("获取目标物体失败");
const { array, count } = targetMesh.geometry.attributes.position;
PointsControl.to(array, { min: 1000, max: 4000 });

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/88705.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

大数据Kudu(五):Kudu基于Cloudera Manager安装及配置

文章目录 Kudu基于Cloudera Manager安装及配置 一、启动CM集群 二、登录ClouderaManager平台安装Kudu

TCO-PEG-RGD 反式环辛烯聚乙二醇线肽RGD

反式环辛烯(TCO)作为亲双烯体与S-四嗪(Tetrazine)在生理条件下的反应有无需催化剂、反应速率快的优点&#xff0c;被广泛应用于生物和材料科学的研究中。 产品名称 TCO-PEG-RGD 反式环辛烯聚乙二醇线肽RGD 中文名称 线肽-聚乙二醇-反式环辛烯 英文名称 TCO-PEG-RGD 分…

“引进来,走出去”,锦江国际集团多重创新力引领绿色新发展

2022年12月13日&#xff0c;由南方财经全媒体集团指导&#xff0c;21世纪经济报道主办的“21世纪住宿业高峰论坛&#xff08;2022&#xff09;暨2022&#xff08;第十九届&#xff09;【金枕头】酒店大赏发布典礼”在上海如期举行。锦江国际集团副总裁周维应邀出席并发表“创新…

【开源项目】SFUD--通用串口Flash驱动库的移植和使用

1.简介 SFUD 是一款开源的串行 SPI Flash 通用驱动库。由于现有市面的串行 Flash 种类居多&#xff0c;各个 Flash 的规格及命令存在差异&#xff0c; SFUD 就是为了解决这些 Flash 的差异现状而设计&#xff0c;让我们的产品能够支持不同品牌及规格的 Flash&#xff0c;提高了…

JeecgBoot部署(Nginx+Tomcat)

环境搭建JeecgBoot搭建、Linux安装Nginx、Linux安装JDK8、Linux安装MySql8、Linux安装Redis、Linux安装Tomcat9前端打包 1.进入目录&#xff1a;cd D:\win11\git_data\jeecg_3.4.4\jeecgboot-vue32.安装依赖&#xff1a;pnpm install-->node_modules3.打包编译&#xff1a;p…

矩阵树定理

用途 矩阵树一般用于生成树计数的问题&#xff0c;比如求一个无向图中生成树的个数。用矩阵树定理能极大地降低时间复杂度。 前置知识&#xff1a;行列式 此部分可粗略浏览&#xff0c;了解即可。 对于一个一阶行列式&#xff0c;可写作 det(a1,1)a1,1det \left( \begin{ma…

[附源码]Node.js计算机毕业设计房车营地在线管理系统Express

项目运行 环境配置&#xff1a; Node.js最新版 Vscode Mysql5.7 HBuilderXNavicat11Vue。 项目技术&#xff1a; Express框架 Node.js Vue 等等组成&#xff0c;B/S模式 Vscode管理前后端分离等等。 环境需要 1.运行环境&#xff1a;最好是Nodejs最新版&#xff0c;我…

大数据学习:shell脚本

文章目录一、执行shell脚本1、直接执行脚本&#xff08;1&#xff09;绝对路径方式执行脚本&#xff08;2&#xff09;相对路径方式执行脚本2、利用source命令执行脚本3、利用bash或sh命令执行脚本二、shell脚本实战任务一&#xff1a;显示当前用户主目录1&#xff09;编写脚本…

3ds Max发生闪退怎么办?

3ds Max闪退 最近&#xff0c;小编在后台收到了一位炫云小伙伴的反馈&#xff1a;“我打开3ds Max后&#xff0c;总是出现闪退&#xff0c;究竟是什么情况&#xff1f;”实际上&#xff0c;闪退也细分为多种类型。例如&#xff1a;打开3ds Max直接闪退&#xff08;logo加载界面…

某轻工制造企业“三步走”战略,搭建一站式数据应用平台

某轻工制造企业成立于1994年&#xff0c;是中国轻工业塑料行业十强企业之一。该企业信息系统之间烟囱化&#xff0c;数据融合难、共享难&#xff0c;无法形成数据资产体系为企业创造价值。因此&#xff0c;该企业与亿信华辰合作&#xff0c;建设一站式数据应用平台&#xff0c;…

【雕爷学编程】Arduino动手做(110)---JDY-31 蓝牙模块

37款传感器与执行器的提法&#xff0c;在网络上广泛流传&#xff0c;其实Arduino能够兼容的传感器模块肯定是不止这37种的。鉴于本人手头积累了一些传感器和执行器模块&#xff0c;依照实践出真知&#xff08;一定要动手做&#xff09;的理念&#xff0c;以学习和交流为目的&am…

【Kubernetes】资源管理(命令)总结

kubernetes&#xff0c;是一个全新的基于容器技术的分布式架构领先方案&#xff0c;是谷歌严格保密十几年的秘密武器----Borg系统的一个开源版本&#xff0c;于2014年9月发布第一个版本&#xff0c;2015年7月发布第一个正式版本。 kubernetes的本质是一组服务器集群&#xff0…

Java+Swing图书管理系统2.0

JavaSwing图书管理系统2.0一、系统介绍二、功能展示1.用户登陆页面2.首页3.图书查询4.图书入库&#xff08;管理员&#xff09;5.图书借还情况&#xff08;管理员&#xff09;6.图书证管理&#xff08;管理员&#xff09;7.借书&#xff08;学生、老师&#xff09;8.还书&#…

pikahcu靶场-12 目录遍历,敏感信息泄露,不安全的URL跳转

目录遍历&#xff0c;敏感信息泄露&#xff0c;不安全的URL跳转 目录遍历漏洞 概述 在web功能设计中,很多时候我们会要将需要访问的文件定义成变量&#xff0c;从而让前端的功能便的更加灵活。 当用户发起一个前端的请求时&#xff0c;便会将请求的这个文件的值(比如文件名称…

Netflix:用神经网络改善视频质量

点击上方“LiveVideoStack”关注我们▲扫描图中二维码或点击阅读原文▲了解音视频技术大会更多信息编者按Editors note眼看用户视频的增长将超过服务器的算力上限&#xff0c;既没有额外的服务器来支持&#xff0c;也不能对用户体验造成大的冲击&#xff0c;Instagram的工程师找…

C罗轮播图(HTML+CSS+JS)

✅作者简介&#xff1a;热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 &#x1f49e;当前专栏&#xff1a;前端案例分…

RFM 模型

RFM 模型顾客价值分析顾客价值分析 由于激烈的市场竞争&#xff0c;各个公司相继推出了多样灵活的优惠方式来吸引更多的客户。 对一个没有购买力的顾客&#xff0c;你打电话推销优惠活动毫无作用&#xff0c;可一个高价值顾客&#xff0c;会说有优惠活动怎么不通知我呢&#…

交叉梯度函数的MATLAB实现及代码分享02

交叉梯度函数的MATLAB实现及代码分享02 交叉梯度函数可用于反演成像中。作为一个连接不同物性参数的桥梁&#xff0c;交叉梯度函数可以实现不同物性参数的联合反演成像。 本文是对上一个博文的补充&#xff0c;详见交叉梯度函数的MATLAB实现及代码分享01&#xff0c;上一篇博…

人工智能导论课堂笔记

人工智能导论时间&#xff1a;2022年10月19日下午 班级&#xff1a;2022级人工智能应用技术1班 作业问题&#xff1a; Python安装注意事项 1.下载Python3.X的版本&#xff0c;如&#xff1a;3.10, 3.9&#xff0c; 3.8&#xff0c;不推荐下载2.7版本&#xff08;已经不使用&…

【Mysql】慢sql分析优化案例汇总

【Mysql】慢sql分析优化案例汇总&#xff08;一&#xff09;案例一&#xff1a;阿里云慢sql挑战赛实战&#xff08;一&#xff09;案例一&#xff1a;阿里云慢sql挑战赛实战 190毫秒干到2毫秒 【1】表结构 【2】待优化sql 【3】第一次explain分析 【4】选取驱动表 优先选择…