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

超酷炫的Three.js示例

今天写一个超级酷炫的Three.js示例,以下是文件源代码:

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>Cool Three.js Page with Stars, Interactions, and Audio</title><style>body { margin: 0; overflow: hidden; background-color: black; }canvas { display: block; }#info {position: absolute;top: 20px;left: 20px;color: white;font-family: Arial, sans-serif;font-size: 20px;z-index: 1;}audio {position: fixed;top: 20px;right: 20px;z-index: 10;width: 300px;}</style>
</head>
<body><div id="info">🚀 Three.js Demo with Stars ✨ + Click/Audio FX</div><audio id="audio" src="https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3" controls autoplay loop></audio><!-- 使用兼容非模块版本的three.js和OrbitControls --><script src="https://cdn.jsdelivr.net/npm/three@0.140.0/build/three.min.js"></script><script src="https://cdn.jsdelivr.net/npm/three@0.140.0/examples/js/controls/OrbitControls.js"></script><script>const scene = new THREE.Scene();const camera = new THREE.PerspectiveCamera(75,window.innerWidth / window.innerHeight,0.1,2000);camera.position.z = 100;const renderer = new THREE.WebGLRenderer({ antialias: true });renderer.setSize(window.innerWidth, window.innerHeight);document.body.appendChild(renderer.domElement);// 注意这里用 THREE.OrbitControls(旧版写法)const controls = new THREE.OrbitControls(camera, renderer.domElement);// 着色器材质代码(glow效果)const vertexShader = `varying vec3 vNormal;void main() {vNormal = normalize(normalMatrix * normal);gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);}`;const fragmentShader = `varying vec3 vNormal;void main() {float intensity = pow(0.6 - dot(vNormal, vec3(0.0, 0.0, 1.0)), 2.0);gl_FragColor = vec4(0.0, 1.0, 1.0, 1.0) * intensity;}`;const shaderMaterial = new THREE.ShaderMaterial({vertexShader,fragmentShader,blending: THREE.AdditiveBlending,side: THREE.FrontSide,  // 改为 FrontSidetransparent: true});const geometry = new THREE.IcosahedronGeometry(2, 1);const glowGroup = new THREE.Group();for (let i = 0; i < 200; i++) {const mesh = new THREE.Mesh(geometry, shaderMaterial);mesh.scale.multiplyScalar(1.5);mesh.position.set((Math.random() - 0.5) * 400,(Math.random() - 0.5) * 400,(Math.random() - 0.5) * 400);glowGroup.add(mesh);}scene.add(glowGroup);// 星空背景粒子const starGeometry = new THREE.BufferGeometry();const starCount = 5000;const starVertices = [];for (let i = 0; i < starCount; i++) {starVertices.push((Math.random() - 0.5) * 2000);starVertices.push((Math.random() - 0.5) * 2000);starVertices.push((Math.random() - 0.5) * 2000);}starGeometry.setAttribute('position', new THREE.Float32BufferAttribute(starVertices, 3));const starMaterial = new THREE.PointsMaterial({ color: 0xffffff, size: 0.7 });const starField = new THREE.Points(starGeometry, starMaterial);scene.add(starField);// 灯光const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);scene.add(ambientLight);const pointLight = new THREE.PointLight(0xffffff, 1);camera.add(pointLight);scene.add(camera);// 鼠标点击爆炸效果const raycaster = new THREE.Raycaster();const mouse = new THREE.Vector2();window.addEventListener('click', event => {mouse.x = (event.clientX / window.innerWidth) * 2 - 1;mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;raycaster.setFromCamera(mouse, camera);const intersects = raycaster.intersectObjects(glowGroup.children);if (intersects.length > 0) {const mesh = intersects[0].object;const explosion = new THREE.Vector3((Math.random() - 0.5) * 100,(Math.random() - 0.5) * 100,(Math.random() - 0.5) * 100);mesh.position.add(explosion);}});// 音频分析器const audio = document.getElementById('audio');const listener = new THREE.AudioListener();camera.add(listener);const sound = new THREE.Audio(listener);const audioLoader = new THREE.AudioLoader();audioLoader.load(audio.src, buffer => {sound.setBuffer(buffer);sound.setLoop(true);sound.setVolume(0.5);sound.play();});const analyser = new THREE.AudioAnalyser(sound, 32);function animate() {requestAnimationFrame(animate);const data = analyser.getAverageFrequency();glowGroup.children.forEach((mesh, i) => {const scale = 1.5 + Math.sin(Date.now() * 0.001 + i) * 0.3 + data / 256;mesh.scale.set(scale, scale, scale);});glowGroup.rotation.y += 0.002;starField.rotation.y += 0.0005;renderer.render(scene, camera);}animate();window.addEventListener('resize', () => {camera.aspect = window.innerWidth / window.innerHeight;camera.updateProjectionMatrix();renderer.setSize(window.innerWidth, window.innerHeight);});</script>
</body>
</html>

一、整体思路

  • 使用非模块版 Three.js(r140)与老写法的 THREE.OrbitControls
  • 场景里有两类物体:
    1. 200 个“发光”小多面体(用自定义 Shader 做到类似辉光的视觉)
    2. 5000 颗 Points 形式的星空粒子
  • 交互:点击“发光体”会被随机“炸开”移动一下。
  • 音频:加载一段 MP3,用 AudioAnalyser 得到频率均值,驱动 200 个发光体按音乐节奏伸缩。
  • 动画:群组及星空做缓慢自转,形成空间流动感。

二、HTML/CSS & 库加载

  • body { overflow: hidden; background:black } 全屏 WebGL 背景。
  • 右上角是 <audio> 播放器。
  • 通过 CDN 引入: three@0.140.0/build/three.min.js three@0.140.0/examples/js/controls/OrbitControls.js 这两者匹配“旧式全局 THREE”写法。

三、场景基础

scene / camera / renderer 标准三件套:

  • 透视相机 FOV 75,near=0.1 / far=2000,Z=100。

  • 抗锯齿渲染器,填满窗口。
    -(可优化)建议:renderer.setPixelRatio(window.devicePixelRatio) 让高 DPI 更清晰(性能充裕时)。

  • 轨道控制器: const controls = new THREE.OrbitControls(camera, renderer.domElement); 允许鼠标旋转/缩放观察。
    想要“丝滑阻尼”,可: controls.enableDamping = true; // 并在动画循环里加 controls.update();

四、自定义 Shader“发光体”

  • 顶点着色器:把法线变换到视图空间,传给片元: vNormal = normalize(normalMatrix * normal);
  • 片元着色器:根据与视线方向(z 轴)夹角计算强度: float intensity = pow(0.6 - dot(vNormal, vec3(0.0,0.0,1.0)), 2.0); gl_FragColor = vec4(0.0, 1.0, 1.0, 1.0) * intensity; 视觉效果:面向镜头的区域更亮,形成“边缘辉光/自发光”的感觉。
  • 材质参数: blending: THREE.AdditiveBlending, side: THREE.FrontSide, transparent: true 使用加色混合以叠加高亮。
    改进建议:加色+透明一般配 depthWrite:false 避免透明深度写入带来的排序伪影: depthWrite: false
  • 几何体:IcosahedronGeometry(2, 1)(二十面体细分一级)。
    批量实例:创建 200 个网格,随机分布在 [-200,200]³(因乘 400 再减半)。
    性能评估:每个约百来个三角形,200 个共 ~几万三角,WebGL 轻松应付;共享同一个 ShaderMaterial,节省材质开销。
    (更进一步)可用 InstancedMesh 把 200 次 draw call 合并为 1 次,但要改为实例化方案。

五、星空粒子

  • BufferGeometry + Float32BufferAttribute 存 5000 个随机顶点。
  • PointsMaterial({ color: 0xffffff, size: 0.7 }) 形成星点。
    (可选)可以加 sizeAttenuation:true(默认就是 true),基于透视缩放更自然;或改用带纹理的点精灵实现更“星星”的感觉。

六、灯光

  • 有环境光和跟随相机的点光,但

    当前两类物体都“几乎不吃光”

    • ShaderMaterial 未开启 lights,着色完全自定义,不受灯光影响;
    • PointsMaterial 也是“自发光色”,不受灯光影响。
  • 因此这两盏灯“视觉贡献≈0”,可留作以后加其他受光物体时使用,也可以删掉减一点场景状态切换。

七、点击“爆炸”交互(Raycaster)

  • 鼠标点击 → 归一化设备坐标 → raycaster.setFromCamera()intersectObjects(glowGroup.children)
  • 若命中,随机向量把该网格位置抖走 50~100 单位。
    (可选)可改成给它一个速度,在 animate 中逐帧衰减,效果会更“物理”。

八、音频与可视化

  • 页面上有 <audio id="audio" controls autoplay loop>,同时 Three.js 里又:

    1. 创建 AudioListener 并挂相机;
    2. AudioLoader.load(audio.src, ...) 再次下载同一路径音频,塞进 THREE.Audioplay()
    3. AudioAnalyser(sound, 32) 获取频域数据均值 getAverageFrequency(),驱动缩放。
  • 潜在问题与改进

    1. 重复播放/重复下载
      页面 <audio> 播放一次、AudioLoader 又播一次,音频可能重叠。
      ➜ 选一种即可。最简方案:复用 <audio> 元素作音源const sound = new THREE.Audio(listener); sound.setMediaElementSource(audioElement); // 直接用 <audio> 的流 const analyser = new THREE.AudioAnalyser(sound, 32); 这样不再重复下载,播放器的播放/暂停也直接影响可视化。

    2. 自动播放策略

      现代浏览器通常禁止带声音的自动播放 。

      • 你虽然写了 autoplaysound.play(),但往往会被拦下,除非用户先有手势(点击等)。
      • 兼容做法:在第一次 pointerdown/click 时执行: const ac = listener.context; if (ac.state === 'suspended') ac.resume(); audioElement.play().catch(()=>{ /* 显示提示或忽略 */ });
    3. 跨域 (CORS)
      若用 AudioLoader加远程 MP3,需要服务器响应 Access-Control-Allow-Origin:*,否则 WebAudio 可能拿不到频谱数据。

      • 复用 <audio crossorigin="anonymous"> + setMediaElementSource 可以更稳。
    4. FFT 分辨率
      new THREE.AudioAnalyser(sound, 32) 频段较少,变化较“钝”。

      • 想要更丰富的律动,可用 128/256,再用 getFrequencyData() 做更细粒度的驱动。
  • 动画里用: const data = analyser.getAverageFrequency(); // 0~255 const scale = 1.5 + Math.sin(time + i) * 0.3 + data / 256; 叠加了“个体相位差的正弦摆动 + 音量项”,既保留群体呼吸感又随音乐起伏。

九、动画循环与窗口自适应

  • requestAnimationFrame(animate) 驱动渲染;群组与星空各自缓慢自转。
  • 监听 resize 更新相机投影与渲染尺寸,属于标准写法。
    (可优化)把 const t = performance.now()*0.001; 放循环开头,少做一次 Date.now()
    若启用 enableDamping,记得每帧 controls.update()

十、数值与视觉小建议

  • Shader 里这句: float intensity = pow(0.6 - dot(vNormal, vec3(0,0,1)), 2.0);dot(...) > 0.6 时底数为负,指数是 2.0(整数),在 GLSL 里通常仍能得到正值,但不同平台精度可能不一致。
    更稳:夹取到非负区间: float intensity = pow(max(0.0, 0.6 - dot(vNormal, vec3(0,0,1))), 2.0);
  • 透明加色材质建议: const shaderMaterial = new THREE.ShaderMaterial({ vertexShader, fragmentShader, blending: THREE.AdditiveBlending, transparent: true, side: THREE.FrontSide, depthWrite: false // ★ 推荐 });
  • 画质/性能开关: renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); 在 4K 屏上能避免过高像素负担。

十一、一步到位的音频改造示例(可直接替换你原来的音频段)

目的:不重复下载,不触发自动播放拦截时的黑屏“无响应”,并让频谱与播放器同步。

<audio id="audio" crossorigin="anonymous"src="https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3"controls loop></audio>
// 音频(替换原有 AudioLoader 部分)
const audioEl = document.getElementById('audio');
const listener = new THREE.AudioListener();
camera.add(listener);const sound = new THREE.Audio(listener);
sound.setMediaElementSource(audioEl); // 直接复用 <audio> 元素
const analyser = new THREE.AudioAnalyser(sound, 128);// 解决自动播放限制:用户首次点击页面时恢复 AudioContext 并尝试播放
let audioInit = false;
function initAudioOnce() {if (audioInit) return;audioInit = true;const ctx = listener.context;if (ctx.state === 'suspended') ctx.resume();audioEl.play().catch(() => {/* 可以提示“请点击播放” */});
}
window.addEventListener('pointerdown', initAudioOnce, { once: true });

你可以直接用复制开头的代码到记事本并另存为.html格式然后在浏览器里跑,实现效果:
在这里插入图片描述
该代码可通过鼠标进行交互。


最后推荐一个超酷的ThreeJS网站:https://ykob.github.io/sketch-threejs/
在这里插入图片描述

重拾编程的乐趣和无尽的探索欲在这里插入图片描述

http://www.xdnf.cn/news/18027.html

相关文章:

  • Java:File类、递归、字符集、IO流体系及Commons-io框架
  • 【大模型核心技术】Dify 入门教程
  • Flow-GRPO:通过在线 RL 训练 Flow matching 模型
  • PS插件整合包!内置数百款PS插件,支持PS2017-PS2025所有版本!
  • 【学习嵌入式day-26-线程间通信】
  • TypeScript快速入门
  • CPP多线程3:async和future、promise
  • ArrayList的contains问题
  • 机器学习 [白板推导](十二)[卡曼滤波、粒子滤波]
  • 第G7周:Semi-Supervised GAN 理论与实战
  • 【科研绘图系列】R语言绘制雷达图
  • 洛谷B3865 [GESP202309 二级] 小杨的 X 字矩阵(举一反三)
  • 从 MySQL 5.7 迁移到 8.0:别让 SQL 文件 “坑” 了你
  • 《从入门到高可用:2025最新MySQL 8.0全栈速通指南》
  • Linux配置Dante使用的pam验证
  • 【攻防实战】红队攻防之Goby反杀
  • 力扣(LeetCode) ——622. 设计循环队列(C语言)
  • Android Jetpack | Lifecycle
  • 6JSON格式转python并实现数据可视化
  • 储能领域大数据平台的设计中如何使用 Hadoop、Spark、Flink 等组件实现数据采集、清洗、存储及实时 / 离线计算,支持储能系统分析与预测
  • 人工智能中的(特征选择)数据过滤方法和包裹方法
  • 2-3〔O҉S҉C҉P҉ ◈ 研记〕❘ 漏洞扫描▸AppScan(WEB扫描)
  • KingbaseES主备读写分离集群安装教程
  • 计算机网络:(十五)TCP拥塞控制与拥塞控制算法深度剖析
  • C++自旋锁的后退机制简介
  • 云原生俱乐部-RH124知识点总结(3)
  • 基于springboot的在线视频教育管理系统设计与实现(源码+文档+部署讲解)
  • 一文了解金融合规
  • 数据结构初阶(17)排序算法——非比较排序(计数排序·动图演示)、排序算法总结
  • Java内功修炼(1)——时光机中的并发革命:从单任务到Java多线程