当前位置: 首页 > news >正文

Three.js TSL实战:5分钟打造酷炫粒子鼠标跟随效果(附完整代码)

Three.js TSL实战:5分钟打造酷炫粒子鼠标跟随效果(附完整代码)

在Web 3D开发领域,Three.js一直是前端工程师的首选工具库。而随着WebGPU技术的普及,Three.js团队推出的TSL(Three.js Shading Language)为开发者提供了更高效的着色器编写方式。本文将带你快速实现一个令人惊艳的粒子鼠标跟随效果,不仅视觉效果出众,代码也极具复用价值。

1. 环境准备与基础配置

首先确保你的开发环境已经准备好。我们需要使用Three.js的最新版本(r176+),它原生支持TSL语法。创建一个基础的HTML文件,并添加以下依赖:

<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>TSL粒子效果</title> <style> body { margin: 0; overflow: hidden; } canvas { display: block; } </style> </head> <body> <script type="importmap"> { "imports": { "three": "https://cdn.jsdelivr.net/npm/three@0.176.0/build/three.module.js", "three/tsl": "https://cdn.jsdelivr.net/npm/three@0.176.0/build/three.tsl.js", "three/addons/": "https://cdn.jsdelivr.net/npm/three@0.176.0/examples/jsm/" } } </script> <script type="module" src="app.js"></script> </body> </html>

提示:使用importmap可以避免路径问题,确保所有依赖都能正确加载。

接下来创建app.js文件,初始化基础场景:

import * as THREE from 'three'; import { Fn, If, uniform, float, vec3, instancedArray, instanceIndex } from 'three/tsl'; import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; // 初始化场景、相机和渲染器 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 }); renderer.setSize(window.innerWidth, window.innerHeight); document.body.appendChild(renderer.domElement); // 添加轨道控制器方便调试 const controls = new OrbitControls(camera, renderer.domElement); camera.position.set(0, 0, 50); controls.update();

2. 创建粒子系统核心逻辑

TSL的强大之处在于可以用JavaScript风格的语法编写着色器代码。我们将创建50万个粒子,它们会对鼠标移动做出反应。

// 粒子数量 - 50万 const PARTICLE_COUNT = 500000; // 创建粒子位置和速度的实例化数组 const positions = instancedArray(PARTICLE_COUNT, 'vec3'); const velocities = instancedArray(PARTICLE_COUNT, 'vec3'); const colors = instancedArray(PARTICLE_COUNT, 'vec3'); // 初始化粒子位置 const initParticles = Fn(() => { const position = positions.element(instanceIndex); const color = colors.element(instanceIndex); // 在球面上随机分布粒子 const radius = float(20).mul(hash(instanceIndex.add(1))); const theta = hash(instanceIndex).mul(Math.PI * 2); const phi = hash(instanceIndex.add(2)).mul(Math.PI); position.x = radius.mul(theta.sin()).mul(phi.cos()); position.y = radius.mul(phi.sin()); position.z = radius.mul(theta.cos()).mul(phi.cos()); // 随机颜色 color.assign(vec3( hash(instanceIndex), hash(instanceIndex.add(3)), hash(instanceIndex.add(5)) )); })().compute(PARTICLE_COUNT);

粒子物理更新逻辑是效果的核心,我们使用TSL编写:

// 物理参数 const gravity = uniform(-0.0005); const bounce = uniform(0.7); const friction = uniform(0.98); const mouseForce = uniform(5); const mousePosition = uniform(new THREE.Vector3(0, 0, 0)); // 粒子更新函数 const updateParticles = Fn(() => { const position = positions.element(instanceIndex); const velocity = velocities.element(instanceIndex); // 应用重力 velocity.y = velocity.y.add(gravity); // 鼠标吸引力 const direction = position.sub(mousePosition).normalize(); const distance = position.distance(mousePosition); const force = float(1).div(distance.add(0.1)).mul(mouseForce); velocity.x = velocity.x.add(direction.x.mul(force)); velocity.y = velocity.y.add(direction.y.mul(force)); velocity.z = velocity.z.add(direction.z.mul(force)); // 更新位置 position.addAssign(velocity); velocity.mulAssign(friction); // 地面碰撞检测 If(position.y.lessThan(-20), () => { position.y = -20; velocity.y = velocity.y.negate().mul(bounce); velocity.x = velocity.x.mul(0.9); velocity.z = velocity.z.mul(0.9); }); })().compute(PARTICLE_COUNT);

3. 渲染粒子与鼠标交互

现在我们需要将计算好的粒子渲染到屏幕上,并添加鼠标交互:

// 创建粒子材质 const particleMaterial = new THREE.PointsMaterial({ size: 0.1, vertexColors: true, transparent: true, opacity: 0.8, blending: THREE.AdditiveBlending }); // 创建粒子几何体 const particleGeometry = new THREE.BufferGeometry(); particleGeometry.setAttribute('position', positions.toAttribute()); particleGeometry.setAttribute('color', colors.toAttribute()); // 创建粒子系统 const particles = new THREE.Points(particleGeometry, particleMaterial); scene.add(particles); // 鼠标移动交互 const raycaster = new THREE.Raycaster(); const mouse = new THREE.Vector2(); function onMouseMove(event) { // 将鼠标坐标归一化为-1到1的范围 mouse.x = (event.clientX / window.innerWidth) * 2 - 1; mouse.y = -(event.clientY / window.innerHeight) * 2 + 1; // 更新射线投射器 raycaster.setFromCamera(mouse, camera); // 计算鼠标在3D空间中的位置 const intersects = raycaster.intersectObjects([new THREE.Mesh( new THREE.PlaneGeometry(1000, 1000), new THREE.MeshBasicMaterial({ visible: false }) )]); if (intersects.length > 0) { mousePosition.value.copy(intersects[0].point); mousePosition.value.z = 0; // 限制在XY平面 } } window.addEventListener('mousemove', onMouseMove);

4. 动画循环与性能优化

最后设置动画循环,确保粒子系统流畅运行:

function animate() { requestAnimationFrame(animate); // 更新粒子位置 renderer.compute(updateParticles); // 更新几何体属性 particleGeometry.attributes.position.needsUpdate = true; // 渲染场景 renderer.render(scene, camera); controls.update(); } // 处理窗口大小变化 function onWindowResize() { camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth, window.innerHeight); } window.addEventListener('resize', onWindowResize); animate();

注意:对于大量粒子,建议使用WebGPU渲染器以获得更好性能。只需将WebGLRenderer替换为WebGPURenderer即可。

5. 进阶效果与自定义

基础效果实现后,你可以通过调整参数获得不同视觉效果:

  • 参数调优表
参数默认值效果说明
gravity-0.0005重力大小,负值表示向下
bounce0.7碰撞反弹系数
friction0.98摩擦力,值越小减速越快
mouseForce5鼠标吸引力强度
particleSize0.1粒子显示大小

尝试修改这些值,观察效果变化。例如,增加mouseForce会让粒子对鼠标移动更敏感:

// 更强的鼠标交互 mouseForce.value = 10;

你还可以添加更多视觉效果,如:

// 添加辉光效果 import { UnrealBloomPass } from 'three/addons/postprocessing/UnrealBloomPass.js'; import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js'; const composer = new EffectComposer(renderer); composer.addPass(new UnrealBloomPass( new THREE.Vector2(window.innerWidth, window.innerHeight), 1.5, 0.4, 0.85 )); // 在animate()中替换renderer.render为composer.render

通过组合不同的参数和后期处理效果,你可以创造出独一无二的粒子动画。在实际项目中,这种技术可应用于数据可视化、游戏特效或网页背景等多个领域。

http://www.cnnetsun.cn/news/1421697.html

相关文章:

  • QCustomPlot图表范围控制完全指南:从rescaleAxes到setRange的5种应用场景
  • Anaconda管理深度学习训练环境:多版本Python控制
  • 嵌入式SHA256轻量实现:抗侧信道、恒定时间、MCU级哈希引擎
  • HarmonyOS开发实战指南(三)——从零构建鸿蒙原子化服务与Ability框架解析
  • 解决Overleaf中伪代码排版难题:从基础到高级配置全指南
  • 基于STM32+LiteOS的多传感器空气质量监测系统设计
  • java毕业设计基于springboot+vue的企业员工考勤管理系统
  • M2LOrder GPU算力适配方案:RTX 3060显存优化+FP16推理加速实测
  • 哪个降AI率的好?先看这5个评判标准再做选择
  • OpenClaw版本升级:Qwen3-32B兼容性测试与回滚方案
  • 效率直接起飞!AI论文网站 千笔·专业论文写作工具 VS Checkjie,全行业通用首选
  • ThinkPHP 6.x 安全漏洞深度解析:如何避免任意文件写入风险
  • Qwen3-ForcedAligner-0.6B应用实战:快速为卡拉OK音频生成精准歌词字幕
  • 嵌入式系统中的数据驱动编程实践
  • 5个实用技巧:轻松掌握BilibiliDown的视频下载功能
  • 从Kaggle实战看损失函数选择:为什么我的交叉熵模型总过拟合?(附解决方案)
  • 别再傻傻分不清了!一文搞懂Java中的ISO 8601、RFC 3339和微信支付time_expire
  • 从DHCP到静态IP:Ubuntu22.04网络配置全面解析(附Netplan最佳实践)
  • 告别手动配置!用Python脚本自动化你的CanFestival PDO映射(附源码)
  • 告别调参焦虑:用Simplify3D的‘打印进程’功能,为不同模型快速切换配置文件
  • jQuery Mobile 导航栏深度解析
  • 告别手动配环境:用Amber18的tleap脚本一键搞定蛋白-配体复合物拓扑文件
  • 告别云端API调用!手把手教你用Ollama+AnythingLLM在Windows/Mac上搭建个人DeepSeek知识库
  • Python 属性描述符:从原理到 ORM 实践详解
  • 用Nordic52832和6轴传感器DIY一个空中鼠标:从硬件选型到代码调试全记录
  • STM32开发实战:手把手教你实现代码重定位与BSS段清零(附完整代码)
  • Carsim多车仿真场景设置全攻略:从基础到高级(含常见问题解答)
  • 从Linux迁移到Windows?用Anaconda PowerShell Prompt无缝衔接你的终端习惯
  • LangGraph:大模型智能体编排的图计算革命
  • IDEA终端报错?一招教你修复Cannot open Local Terminal问题(附PowerShell路径设置)