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

超酷炫的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.dtcms.com/a/334236.html

相关文章:

  • Proteus 入门教程
  • 深度剖析setjmp/longjmp:非局部跳转的内部机制与协程应用限制
  • 双重调度(Double Dispatch):《More Effective C++》条款31
  • RD-Agent for Quantitative Finance (RD-Agent(Q))
  • C#单元测试(xUnit + Moq + coverlet.collector)
  • 深度学习——常见问题与优化改进
  • java中消息推送功能
  • Xiaothink-T6-0.15B混合架构模型深度解析
  • 3 种方式玩转网络继电器!W55MH32 实现网页 + 阿里云 + 本地控制互通
  • 架构调整决策
  • 超越Transformer:大模型架构创新的深度探索
  • 【计算机网络架构】混合型架构简介
  • Blackwell 和 Hopper 架构的 GPGPU 新功能全面综述
  • 【LeetCode每日一题】
  • Mac (三)如何设置环境变量
  • 从希格斯玻色子到 QPU:C++ 的跨维度征服
  • 代码随想录Day52:图论(孤岛的总面积、沉没孤岛、水流问题、建造最大岛屿)
  • 在ubuntu系统上离线安装jenkins的做法
  • 立体匹配中的稠密匹配和稀疏匹配
  • 8.16 pq
  • [系统架构设计师]系统质量属性与架构评估(八)
  • 解锁JavaScript性能优化:从理论到实战
  • 【完整源码+数据集+部署教程】太阳能面板污垢检测系统源码和数据集:改进yolo11-RVB-EMA
  • 地级市+省级气候政策不确定性指数(2000-2023年)-实证数据
  • ollama 自定义模型
  • imx6ull-驱动开发篇27——Linux阻塞和非阻塞 IO(上)
  • 【JS】认识并实现一个chrome扩展程序
  • 如何在 MacOS 上安装 SQL Server
  • MySQL完整重置密码流程(针对 macOS)
  • 硬核北京 | 2025世界机器人大会“破圈”,工业智能、康养科技…… 亦庄上演“机器人总动员”