Underground
<style>#tz { margin: 30px 0; left: calc(50% - 81px); transform: translateX(-50%); width: clamp(600px, 90vw, 1400px); min-height: 80vh; aspect-ratio: 16/9; background: #eee url('https://638183.freep.cn/638183/t24/w4/underground.webp') no-repeat center/cover; box-shadow: 2px 2px 8px #000; display: grid; place-items: center; z-index: 1; position: relative; }
#btnFs { bottom: 20px; color: #eee; text-align: center; }
#btnFs:hover { color: red; }
#player { position: absolute; top: -1000px; }
</style>
<div id="tz">
<audio id="aud" src="https://music.163.com/song/media/outer/url?id=1374221428" autoplay loop></audio>
<div id="player" title="播放/暂停"></div>
</div>
<script type="module">
import * as THREE from 'https://638183.freep.cn/638183/3dev/build/three.module.min.js';
import { FS } from 'https://638183.freep.cn/638183/web/ku/FS.js';
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(45, tz.offsetWidth/tz.offsetHeight, 0.1, 1000);
camera.position.z = 10;
const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
renderer.setClearAlpha(0.0);
renderer.setSize(tz.offsetWidth, tz.offsetHeight);
const clock = new THREE.Clock();
tz.appendChild(renderer.domElement);
const texture = new THREE.TextureLoader().load('https://638183.freep.cn/638183/small/2025/t01.webp');
const mesh = new THREE.Mesh(
new THREE.SphereGeometry(1, 64, 64),
new THREE.MeshBasicMaterial({ map: texture })
);
mesh.position.set(0, -2, 0);
scene.add(mesh);
const particleContainer = new THREE.Object3D(); // 粒子容器
const total = 1000;
for (let i = 0; i < total; i++) {
const faces = Math.floor(Math.random() * 3 + 1);
const geometry = new THREE.SphereGeometry(0.4);
const material = new THREE.MeshBasicMaterial({
color: new THREE.Color(Math.random(), Math.random(), Math.random()),
map: texture
});
const particle = new THREE.Mesh(geometry, material);
particle.position.set(
Math.random() * 100 - 50,
Math.random() * 100 - 50,
Math.random() * 300 - 150
);
particleContainer.add(particle);
}
scene.add(particleContainer);
const isMess = (event) => {
const raycaster = new THREE.Raycaster();
const pointer = new THREE.Vector2();
let intersects = [];
pointer.x = (event.offsetX / tz.offsetWidth) * 2 - 1;
pointer.y = -(event.offsetY / tz.offsetHeight) * 2 + 1;
raycaster.setFromCamera(pointer, camera);
intersects = raycaster.intersectObjects(, true);
return intersects.length > 0;
}
tz.onmousemove = (e) => {
isMess(e)
? (tz.style.cursor = 'pointer', tz.title = '播放/暂停 Alt+X')
: (tz.style.cursor = 'default', tz.title = '');
};
tz.onclick = (e) => {
if (isMess(e)) player.click();
};
const animate = () => {
requestAnimationFrame(animate);
const delta = clock.getDelta();
mesh.rotation.x += delta;
mesh.rotation.y -= delta;
particleContainer.rotation.x += delta / 5;
particleContainer.rotation.y -= delta / 5;
renderer.render(scene, camera);
};
window.onresize = () => {
camera.aspect = tz.offsetWidth / tz.offsetHeight;
camera.updateProjectionMatrix();
renderer.setSize(tz.offsetWidth, tz.offsetHeight);
};
aud.onplaying = aud.onpause = () => aud.paused ? clock.stop() : clock.start();
animate();
FS(tz, player);
</script> 帖子代码
<style>
#tz { margin: 30px 0; left: calc(50% - 81px); transform: translateX(-50%); width: clamp(600px, 90vw, 1400px); min-height: 80vh; aspect-ratio: 16/9; background: #eee url('https://638183.freep.cn/638183/t24/w4/underground.webp') no-repeat center/cover; box-shadow: 2px 2px 8px #000; display: grid; place-items: center; z-index: 1; position: relative; }
#btnFs { bottom: 20px; color: #eee; text-align: center; }
#btnFs:hover { color: red; }
#player { position: absolute; top: -1000px; }
</style>
<div id="tz">
<audio id="aud" src="https://music.163.com/song/media/outer/url?id=1374221428" autoplay loop></audio>
<div id="player" title="播放/暂停"></div>
</div>
<script type="module">
import * as THREE from 'https://638183.freep.cn/638183/3dev/build/three.module.min.js';
import { FS } from 'https://638183.freep.cn/638183/web/ku/FS.js';
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(45, tz.offsetWidth/tz.offsetHeight, 0.1, 1000);
camera.position.z = 10;
const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
renderer.setClearAlpha(0.0);
renderer.setSize(tz.offsetWidth, tz.offsetHeight);
const clock = new THREE.Clock();
tz.appendChild(renderer.domElement);
const texture = new THREE.TextureLoader().load('https://638183.freep.cn/638183/small/2025/t01.webp');
const mesh = new THREE.Mesh(
new THREE.SphereGeometry(1, 64, 64),
new THREE.MeshBasicMaterial({ map: texture })
);
mesh.position.set(0, -2, 0);
scene.add(mesh);
const particleContainer = new THREE.Object3D(); // 粒子容器
const total = 1000;
for (let i = 0; i < total; i++) {
const faces = Math.floor(Math.random() * 3 + 1);
const geometry = new THREE.SphereGeometry(0.4);
const material = new THREE.MeshBasicMaterial({
color: new THREE.Color(Math.random(), Math.random(), Math.random()),
map: texture
});
const particle = new THREE.Mesh(geometry, material);
particle.position.set(
Math.random() * 100 - 50,
Math.random() * 100 - 50,
Math.random() * 300 - 150
);
particleContainer.add(particle);
}
scene.add(particleContainer);
const isMess = (event) => {
const raycaster = new THREE.Raycaster();
const pointer = new THREE.Vector2();
let intersects = [];
pointer.x = (event.offsetX / tz.offsetWidth) * 2 - 1;
pointer.y = -(event.offsetY / tz.offsetHeight) * 2 + 1;
raycaster.setFromCamera(pointer, camera);
intersects = raycaster.intersectObjects(, true);
return intersects.length > 0;
}
tz.onmousemove = (e) => {
isMess(e)
? (tz.style.cursor = 'pointer', tz.title = '播放/暂停 Alt+X')
: (tz.style.cursor = 'default', tz.title = '');
};
tz.onclick = (e) => {
if (isMess(e)) player.click();
};
const animate = () => {
requestAnimationFrame(animate);
const delta = clock.getDelta();
mesh.rotation.x += delta;
mesh.rotation.y -= delta;
particleContainer.rotation.x += delta / 5;
particleContainer.rotation.y -= delta / 5;
renderer.render(scene, camera);
};
window.onresize = () => {
camera.aspect = tz.offsetWidth / tz.offsetHeight;
camera.updateProjectionMatrix();
renderer.setSize(tz.offsetWidth, tz.offsetHeight);
};
aud.onplaying = aud.onpause = () => aud.paused ? clock.stop() : clock.start();
animate();
FS(tz, player);
</script>
帖子粒子系统使用的是基于ThreeJS的 Object3d 几何体。
HTML player元素依然存在,但是它隐藏起来,点击权限交给 ThreeJS 绘制的在固定地方旋转的大圆球(运动中的粒子可能也会随机出现大圆球,其位置不会固定)。 这个粒子风群体行为向四面八方自由飞翔,
粒子的样式跟球体的纹理差不多,差色有差别,
看到几个随机大球个头跟,只是晃一下就跑了{:4_173:} 原来3D几何体还可以整成粒子样式,也是很稀罕的。。
粒子运动感觉很复杂,跟最早之前的群体积木差不多。。{:4_173:} 这个球体纹理贴图挺好看的,跟水立方一样,看来如果仔细找还是找得到合适图片的。 有趣
基于ThreeJS的 Object3d 几何体的粒子
谢谢马老师经典分享{:4_191:} 杨帆 发表于 2025-6-8 22:06
有趣
基于ThreeJS的 Object3d 几何体的粒子
{:4_190:} 谢谢老师辛苦,欣赏学习了。 梦江南 发表于 2025-6-9 15:36
谢谢老师辛苦,欣赏学习了。
{:4_191:} 星球左右漂移,还不时有大球球落下,
仿佛置身于太空,这个震撼{:4_178:} 朵拉 发表于 2025-6-10 22:21
星球左右漂移,还不时有大球球落下,
仿佛置身于太空,这个震撼
有点立体感 这样的漂浮不受限的感觉,仿佛在虚空里自由着任性着的感觉{:4_199:} 那么浩浩荡荡,间或还有大球出来炫那么一下{:4_173:} 红影 发表于 2025-6-11 15:48
那么浩浩荡荡,间或还有大球出来炫那么一下
我正准备封装一个只需配置就可以运行的 ThreeJS 粒子系统,它可能很简单,主要基于精灵 马黑黑 发表于 2025-6-11 20:17
我正准备封装一个只需配置就可以运行的 ThreeJS 粒子系统,它可能很简单,主要基于精灵
太好了,等着看黑黑封装好的{:4_187:} 红影 发表于 2025-6-11 21:32
太好了,等着看黑黑封装好的
这个有些难度,毕竟 ThreeJS 是个特殊的存在 马黑黑 发表于 2025-6-11 21:32
这个有些难度,毕竟 ThreeJS 是个特殊的存在
看到黑黑已经封装好了,不管它特殊不特殊,在黑黑面前没有攻克不了的难关。太厉害了{:4_199:}{:4_199:}{:4_199:} 红影 发表于 2025-6-12 14:32
看到黑黑已经封装好了,不管它特殊不特殊,在黑黑面前没有攻克不了的难关。太厉害了{: ...
有很多问题解决不了的。比方映射目录结构问题:ThreeJS额外封装的很多库非常有用,但是,需要映射机制才能正确导入,而 ES6 模块没有这个功能,JS映射目录结构时需要在 web 页中实现。
就是说,只需简单使用核心库,采用 ES6 时可以的,但要使用附加库,ES6 可能没有办法导入 ThreeJS 封装的 马黑黑 发表于 2025-6-12 18:42
有很多问题解决不了的。比方映射目录结构问题:ThreeJS额外封装的很多库非常有用,但是,需要映射机制才 ...
也就是说,因为存在映射的问题, ES6 缺少这个功能,所以用不了ThreeJS封装好的很多好东西。
这的确是遗憾。
页:
[1]
2