马黑黑 发表于 2025-6-8 20:00

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>

马黑黑 发表于 2025-6-8 20:00

帖子代码

<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>

马黑黑 发表于 2025-6-8 20:03

帖子粒子系统使用的是基于ThreeJS的 Object3d 几何体。

HTML player元素依然存在,但是它隐藏起来,点击权限交给 ThreeJS 绘制的在固定地方旋转的大圆球(运动中的粒子可能也会随机出现大圆球,其位置不会固定)。

花飞飞 发表于 2025-6-8 20:44

这个粒子风群体行为向四面八方自由飞翔,
粒子的样式跟球体的纹理差不多,差色有差别,
看到几个随机大球个头跟,只是晃一下就跑了{:4_173:}

花飞飞 发表于 2025-6-8 20:50

原来3D几何体还可以整成粒子样式,也是很稀罕的。。
粒子运动感觉很复杂,跟最早之前的群体积木差不多。。{:4_173:}

花飞飞 发表于 2025-6-8 20:52

这个球体纹理贴图挺好看的,跟水立方一样,看来如果仔细找还是找得到合适图片的。

杨帆 发表于 2025-6-8 22:06

有趣

基于ThreeJS的 Object3d 几何体的粒子

谢谢马老师经典分享{:4_191:}

马黑黑 发表于 2025-6-8 23:12

杨帆 发表于 2025-6-8 22:06
有趣

基于ThreeJS的 Object3d 几何体的粒子


{:4_190:}

梦江南 发表于 2025-6-9 15:36

谢谢老师辛苦,欣赏学习了。

马黑黑 发表于 2025-6-9 18:27

梦江南 发表于 2025-6-9 15:36
谢谢老师辛苦,欣赏学习了。

{:4_191:}

朵拉 发表于 2025-6-10 22:21

星球左右漂移,还不时有大球球落下,
仿佛置身于太空,这个震撼{:4_178:}

马黑黑 发表于 2025-6-10 23:11

朵拉 发表于 2025-6-10 22:21
星球左右漂移,还不时有大球球落下,
仿佛置身于太空,这个震撼

有点立体感

红影 发表于 2025-6-11 15:46

这样的漂浮不受限的感觉,仿佛在虚空里自由着任性着的感觉{:4_199:}

红影 发表于 2025-6-11 15:48

那么浩浩荡荡,间或还有大球出来炫那么一下{:4_173:}

马黑黑 发表于 2025-6-11 20:17

红影 发表于 2025-6-11 15:48
那么浩浩荡荡,间或还有大球出来炫那么一下

我正准备封装一个只需配置就可以运行的 ThreeJS 粒子系统,它可能很简单,主要基于精灵

红影 发表于 2025-6-11 21:32

马黑黑 发表于 2025-6-11 20:17
我正准备封装一个只需配置就可以运行的 ThreeJS 粒子系统,它可能很简单,主要基于精灵

太好了,等着看黑黑封装好的{:4_187:}

马黑黑 发表于 2025-6-11 21:32

红影 发表于 2025-6-11 21:32
太好了,等着看黑黑封装好的

这个有些难度,毕竟 ThreeJS 是个特殊的存在

红影 发表于 2025-6-12 14:32

马黑黑 发表于 2025-6-11 21:32
这个有些难度,毕竟 ThreeJS 是个特殊的存在

看到黑黑已经封装好了,不管它特殊不特殊,在黑黑面前没有攻克不了的难关。太厉害了{:4_199:}{:4_199:}{:4_199:}

马黑黑 发表于 2025-6-12 18:42

红影 发表于 2025-6-12 14:32
看到黑黑已经封装好了,不管它特殊不特殊,在黑黑面前没有攻克不了的难关。太厉害了{: ...

有很多问题解决不了的。比方映射目录结构问题:ThreeJS额外封装的很多库非常有用,但是,需要映射机制才能正确导入,而 ES6 模块没有这个功能,JS映射目录结构时需要在 web 页中实现。

就是说,只需简单使用核心库,采用 ES6 时可以的,但要使用附加库,ES6 可能没有办法导入 ThreeJS 封装的

红影 发表于 2025-6-12 21:05

马黑黑 发表于 2025-6-12 18:42
有很多问题解决不了的。比方映射目录结构问题:ThreeJS额外封装的很多库非常有用,但是,需要映射机制才 ...

也就是说,因为存在映射的问题, ES6 缺少这个功能,所以用不了ThreeJS封装好的很多好东西。
这的确是遗憾。
页: [1] 2
查看完整版本: Underground