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

Stand With Me

<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/stand.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; left: -1000px; }
</style>

<div id="tz">
        <audio id="aud" src="https://music.163.com/song/media/outer/url?id=29979829" 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 texture1 = new THREE.TextureLoader().load('https://638183.freep.cn/638183/small/texture/wdrop.webp');
        const texture2 = new THREE.TextureLoader().load('https://638183.freep.cn/638183/small/hxxb.webp');

        const mesh = new THREE.Mesh(
                new THREE.SphereGeometry(1, 64, 64),
                new THREE.MeshBasicMaterial({ map: texture1 })
        );
        mesh.position.set(0.55, 2.15, 0)
        scene.add(mesh);

        const group = new THREE.Group();
        for (let i = 0; i < 300; i++) {
                const sprite = new THREE.Sprite(
                        new THREE.SpriteMaterial({ color: Math.random() * 0xff0000, map: texture2 })
                );
                sprite.scale.set(0.5, 0.5, 0.5);
                sprite.position.set(100 * (Math.random() - 0.5), Math.random() * 20 - 10, Math.random() * 10 - 5);
                group.add(sprite);
        }
        scene.add(group);

        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;
                group.children.forEach(sprite => {
                        sprite.position.x += delta / 10;
                        sprite.position.y -= delta;
                        sprite.material.rotation -= delta;
                        if (sprite.position.y < -10) {
                                sprite.position.set(Math.random() * 10 - 6, Math.random() * 10 + 10, Math.random() * 10 - 5);
                                sprite.material.color.set(Math.random() * 0xff0000);
                        }
                });
                renderer.render(scene, camera);
        };

        window.onresize = () => {
                camera.aspect = tz.offsetWidth / tz.offsetHeight;
                camera.updateProjectionMatrix();
                renderer.setSize(tz.offsetWidth, tz.offsetHeight);
        };

        document.onvisibilitychange = () => {
                if (aud.paused) return;
                document.visibilityState === 'hidden' ? clock.stop() : clock.start();
        };

        aud.onplaying = aud.onpause = () => aud.paused ? clock.stop() : clock.start();

        animate();
        FS(tz, player);
</script>

马黑黑 发表于 2025-6-9 19:23

帖子代码

<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/stand.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; left: -1000px; }
</style>

<div id="tz">
        <audio id="aud" src="https://music.163.com/song/media/outer/url?id=29979829" 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 texture1 = new THREE.TextureLoader().load('https://638183.freep.cn/638183/small/texture/wdrop.webp');
        const texture2 = new THREE.TextureLoader().load('https://638183.freep.cn/638183/small/hxxb.webp');

        const mesh = new THREE.Mesh(
                new THREE.SphereGeometry(1, 64, 64),
                new THREE.MeshBasicMaterial({ map: texture1 })
        );
        mesh.position.set(0.55, 2.15, 0)
        scene.add(mesh);

        const group = new THREE.Group();
        for (let i = 0; i < 300; i++) {
                const sprite = new THREE.Sprite(
                        new THREE.SpriteMaterial({ color: Math.random() * 0xff0000, map: texture2 })
                );
                sprite.scale.set(0.5, 0.5, 0.5);
                sprite.position.set(100 * (Math.random() - 0.5), Math.random() * 20 - 10, Math.random() * 10 - 5);
                group.add(sprite);
        }
        scene.add(group);

        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;
                group.children.forEach(sprite => {
                        sprite.position.x += delta / 10;
                        sprite.position.y -= delta;
                        sprite.material.rotation -= delta;
                        if (sprite.position.y < -10) {
                                sprite.position.set(Math.random() * 10 - 6, Math.random() * 10 + 10, Math.random() * 10 - 5);
                                sprite.material.color.set(Math.random() * 0xff0000);
                        }
                });
                renderer.render(scene, camera);
        };

        window.onresize = () => {
                camera.aspect = tz.offsetWidth / tz.offsetHeight;
                camera.updateProjectionMatrix();
                renderer.setSize(tz.offsetWidth, tz.offsetHeight);
        };

        document.onvisibilitychange = () => {
                if (aud.paused) return;
                document.visibilityState === 'hidden' ? clock.stop() : clock.start();
        };

        aud.onplaying = aud.onpause = () => aud.paused ? clock.stop() : clock.start();

        animate();
        FS(tz, player);
</script>

马黑黑 发表于 2025-6-9 19:41

本帖主要尝试:

一、利用伪随机数分布精灵 Sprite :一开始 Y轴 上精灵以 Math.random() * 20 - 10 部署自己的位置,“营造”错落落下的骗局(42行);落下过程中,当精灵在 Y轴 上的位置小于 -10 个距离单位,则重置精灵的位置和颜色,位置方面,精灵在 X轴 上的位置随机性略有改变,Y轴 上的位置改变幅度较大,Z轴 上使用相同的随机变更方式,就是说,随机计算式子不论有无改变,精灵的位置都会发生实际性变化(78行)。

二、性能优化:由于精灵落下使用的是请求关键帧动画,且因为所设计的随机算法有缺陷,因此引入性能优化机制,即,引入 visibilitychange 监听事件 —— 当当前页面不可见时(比如切换到了其它的浏览器标签或其它的windows窗口),令 clock 时钟暂停。这么做的目的时减少不必要的性能开销,同时将状态保留在页面不可见时的状态,下次回复为可见时页面状态从上一回保留的继续。若不作此处理,请求关键帧动画也有节流功能,造成的结果是,当回到帖子页面,精灵统统从最上方开始下落。

三、精灵旋转:其实是精灵的材质旋转(76行),这在ThreeJS入门系列教程中有过介绍。

花飞飞 发表于 2025-6-9 21:38

这些花从天上落下,大小 随机,颜色随机,这个继承了 昨天的球体飞舞。。。今天的色颜更漂亮,好象样式更多。。。
运动轨迹昨天有点飞舞,是有弧度 的,而今天这个垂直从天上落下。。
特别是有的落在球体之前,有的落在球体之后。。
这空间感也是绝了。。
看说明是有精心设计的。。。神来一笔。{:4_173:}

花飞飞 发表于 2025-6-9 21:42

马黑黑 发表于 2025-6-9 19:41
本帖主要尝试:

一、利用伪随机数分布精灵 Sprite :一开始 Y轴 上精灵以 Math.random() * 20 - 10 部署 ...

这个性能优化机制特别实用啊。我切了别的窗口试一试,的确保留在原位不变,考虑周到。
这比回来后统统从上方重新起落更自然。。
动态超级流畅,观看舒适,体验感特别好。。{:4_199:}

花飞飞 发表于 2025-6-9 21:46

球体贴图选得漂亮,这些透明小水珠贴在上面也像一朵花似的,增加球体的颜值 。。。
在高几之上旋转,还有话筒,这感觉就像是快乐的表演。。
{:4_173:}
搭上节奏感十足的音乐,整个场面热闹又喜庆,看着就欢乐。。。

花飞飞 发表于 2025-6-9 21:49

舞台高光和暗部对比明显,特别有层次感和氛围感。。这设计绝妙。。
还有试下电脑全屏震撼,投到大屏更震撼。。。
跟手机观看完全不一样的感觉。。。{:4_199:}

马黑黑 发表于 2025-6-9 22:12

花飞飞 发表于 2025-6-9 21:49
舞台高光和暗部对比明显,特别有层次感和氛围感。。这设计绝妙。。
还有试下电脑全屏震撼,投到大屏更震撼 ...

电视上播放效果拉满

马黑黑 发表于 2025-6-9 22:15

花飞飞 发表于 2025-6-9 21:46
球体贴图选得漂亮,这些透明小水珠贴在上面也像一朵花似的,增加球体的颜值 。。。
在高几之上旋转,还有 ...

不错的吧?

昨天开始学习研究这个泊松圆盘采样,主要是弄弄相对均匀的随机分布算法,弄是弄出来了,代码太多,就先搁置这,用普通伪随机数玩玩

马黑黑 发表于 2025-6-9 22:16

花飞飞 发表于 2025-6-9 21:42
这个性能优化机制特别实用啊。我切了别的窗口试一试,的确保留在原位不变,考虑周到。
这比回来后统统从 ...

精灵对资源的消耗相对低,就是上50000个也不是个事。帖子里才放300个

朵拉 发表于 2025-6-9 22:19

介个精彩,学习下~~

马黑黑 发表于 2025-6-9 22:20

花飞飞 发表于 2025-6-9 21:38
这些花从天上落下,大小 随机,颜色随机,这个继承了 昨天的球体飞舞。。。今天的色颜更漂亮,好象样式更多 ...

Z轴上对精灵的何分布随机,在 Math.random() * 10 - 5 范围内,即 -5 ~ 5 之间;球体在 Z轴 的 0 处即屏幕表面。就酱,有得精灵在球体背后,有的在前方。

马黑黑 发表于 2025-6-9 22:22

朵拉 发表于 2025-6-9 22:19
介个精彩,学习下~~

{:4_191:}

杨帆 发表于 2025-6-10 00:23

精灵 Sprite真神奇,生成的小花真漂亮,马老师真厉害{:4_191:}

樵歌 发表于 2025-6-10 06:33

这个可以有,等小辣椒眼睛好了,来改造一个,俺用起来{:4_189:}{:4_190:}

梦江南 发表于 2025-6-10 09:25

太精彩了!{:4_199:}

马黑黑 发表于 2025-6-10 14:48

樵歌 发表于 2025-6-10 06:33
这个可以有,等小辣椒眼睛好了,来改造一个,俺用起来

小辣椒也住院了吗

马黑黑 发表于 2025-6-10 14:48

梦江南 发表于 2025-6-10 09:25
太精彩了!

{:4_191:}

马黑黑 发表于 2025-6-10 14:49

杨帆 发表于 2025-6-10 00:23
精灵 Sprite真神奇,生成的小花真漂亮,马老师真厉害

{:4_190:}

花飞飞 发表于 2025-6-10 18:52

马黑黑 发表于 2025-6-9 22:12
电视上播放效果拉满

主要是它很立体,逼真。。光影的空间感也好
页: [1] 2 3 4 5 6 7 8 9 10
查看完整版本: Stand With Me