马黑黑 发表于 2025-6-28 08:19

足球

<style>
        #papa { margin: 30px 0; left: calc(50% - 81px); transform: translateX(-50%);width: clamp(600px, 90vw, 1400px); height: auto; aspect-ratio: 16/9; background: url('https://638183.freep.cn/638183/t24/w4/soccerfield.webp') no-repeat center/cover; box-shadow: 2px 2px 8px #000; display: grid; place-items: center; z-index: 1; position: relative; }
        #btnFs { bottom: 30px; color: #eee; }
        #player { position: absolute; left: -1000px; }
        #vid {position: absolute; width: 100%; height: 100%; object-fit: cover; mask: radial-gradient(transparent 20%, red); -webkit-mask: radial-gradient(transparent 20%, red); opacity: 0.5; pointer-events: none; }
</style>

<div id="papa">
        <audio id="aud" src="https://music.163.com/song/media/outer/url?id=2002253707" autoplay loop></audio>
        <video id="vid" src="https://bpic.588ku.com/video_listen/588ku_video/22/11/04/01/54/03/video6364003b0509a.mp4" autoplay loop muted></video>
        <div id="player"></div>
</div>

<script type="module">
        import { THREE, scene, camera, renderer, clock, basic3, click3 } from 'https://638183.freep.cn/638183/3dev/3/3basic.js?v=1.0';
        import TWEEN from 'https://638183.freep.cn/638183/3dev/examples/jsm/libs/tween.module.js';
        import { FS } from 'https://638183.freep.cn/638183/web/ku/FS.js';
        basic3(papa, aud.paused);

        // 足球纹理
        const texture = new THREE.TextureLoader().load('https://638183.freep.cn/638183/small/2025/soccer.png');
        texture.colorSpace = THREE.SRGBColorSpace;
        texture.center.set(0.5,0.5);
        const vertices = []; // 足球顶点数组
        for (let i = 0; i < 160; i ++) {
                const x = THREE.MathUtils.randFloatSpread(10);
                const y = THREE.MathUtils.randFloatSpread(5);
                const z = THREE.MathUtils.randFloatSpread(5);
                vertices.push(x, y, z);
        }
        // 足球
        const pointsGeometry = new THREE.BufferGeometry();
        pointsGeometry.setAttribute('position', new THREE.Float32BufferAttribute(vertices, 3));
        const pointsMaterial = new THREE.PointsMaterial({ size: 0.25, map: texture, transparent: true });
        const points = new THREE.Points(pointsGeometry, pointsMaterial);
        scene.add(points);
        // 设计tween动画
        const startPositions = pointsGeometry.getAttribute('position');
        for(let i = 0; i < startPositions.count; i++) {
                const tween = new TWEEN.Tween(startPositions.array)
                .to({ : 0, : 0, : 0 }, 3000 * Math.random() + 3000)
                .easing(TWEEN.Easing.Exponential.In)
                .onUpdate(() => startPositions.needsUpdate = true)
                .repeat(Infinity)
                .repeatDelay(1000)
                .yoyo(1500)
                .start();
        }

        // 车削
        const lathePoints = [
                new THREE.Vector2(1, 0.5),
                new THREE.Vector2(1, 0),
                new THREE.Vector2(0.5, -0.5)
        ];
        const latheGeometry = new THREE.LatheGeometry(lathePoints, 24);
        const latheMaterial = new THREE.MeshNormalMaterial({ wireframe: true });
        const lathe = new THREE.Mesh(latheGeometry, latheMaterial);
        scene.add(lathe);
        lathe.rotateX(THREE.MathUtils.degToRad(35));
        lathe.position.set(0, 0.35, 0);

        const animate = () => {
                TWEEN.update();
                const delta = clock.getDelta();
                lathe.rotation.y += delta;
                points.rotation.y += delta / 2;
                pointsMaterial.map.rotation += delta * 2;
                renderer.render(scene, camera);
                requestAnimationFrame(animate);
        };

        papa.onclick = (e) => {
                if (click3(lathe, e)) player.click();
        };

        papa.onmousemove = (e) => {
                papa.title = click3(lathe, e) ? '播放/暂停(Alt+X)' : '';
                papa.style.cursor = click3(lathe, e) ? 'pointer' : 'default';
        };

        const tweens = TWEEN.getAll(); // 获取所有tween动画

        aud.onplaying = aud.onpause = () => {
                tweens.forEach(tween => aud.paused ? tween.pause() : tween.resume());
                aud.paused ? clock.stop() : clock.start();
        };

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

马黑黑 发表于 2025-6-28 08:20


帖子代码
<style>
    #papa { margin: 30px 0; left: calc(50% - 81px); transform: translateX(-50%);width: clamp(600px, 90vw, 1400px); height: auto; aspect-ratio: 16/9; background: url('https://638183.freep.cn/638183/t24/w4/soccerfield.webp') no-repeat center/cover; box-shadow: 2px 2px 8px #000; display: grid; place-items: center; z-index: 1; position: relative; }
    #btnFs { bottom: 30px; color: #eee; }
    #player { position: absolute; left: -1000px; }
    #vid {position: absolute; width: 100%; height: 100%; object-fit: cover; mask: radial-gradient(transparent 20%, red); -webkit-mask: radial-gradient(transparent 20%, red); opacity: 0.5; pointer-events: none; }
</style>

<div id="papa">
    <audio id="aud" src="https://music.163.com/song/media/outer/url?id=2002253707" autoplay loop></audio>
    <video id="vid" src="https://bpic.588ku.com/video_listen/588ku_video/22/11/04/01/54/03/video6364003b0509a.mp4" autoplay loop muted></video>
    <div id="player"></div>
</div>

<script type="module">
    import { THREE, scene, camera, renderer, clock, basic3, click3 } from 'https://638183.freep.cn/638183/3dev/3/3basic.js?v=1.0';
    import TWEEN from 'https://638183.freep.cn/638183/3dev/examples/jsm/libs/tween.module.js';
    import { FS } from 'https://638183.freep.cn/638183/web/ku/FS.js';
    basic3(papa, aud.paused);

    // 足球纹理
    const texture = new THREE.TextureLoader().load('https://638183.freep.cn/638183/small/2025/soccer.png');
    texture.colorSpace = THREE.SRGBColorSpace;
    texture.center.set(0.5,0.5);
    const vertices = []; // 足球顶点数组
    for (let i = 0; i < 160; i ++) {
      const x = THREE.MathUtils.randFloatSpread(10);
      const y = THREE.MathUtils.randFloatSpread(5);
      const z = THREE.MathUtils.randFloatSpread(5);
      vertices.push(x, y, z);
    }
    // 足球
    const pointsGeometry = new THREE.BufferGeometry();
    pointsGeometry.setAttribute('position', new THREE.Float32BufferAttribute(vertices, 3));
    const pointsMaterial = new THREE.PointsMaterial({ size: 0.25, map: texture, transparent: true });
    const points = new THREE.Points(pointsGeometry, pointsMaterial);
    scene.add(points);
    // 设计tween动画
    const startPositions = pointsGeometry.getAttribute('position');
    for(let i = 0; i < startPositions.count; i++) {
      const tween = new TWEEN.Tween(startPositions.array)
      .to({ : 0, : 0, : 0 }, 3000 * Math.random() + 3000)
      .easing(TWEEN.Easing.Exponential.In)
      .onUpdate(() => startPositions.needsUpdate = true)
      .repeat(Infinity)
      .repeatDelay(1000)
      .yoyo(1500)
      .start();
    }

    // 车削
    const lathePoints = [
      new THREE.Vector2(1, 0.5),
      new THREE.Vector2(1, 0),
      new THREE.Vector2(0.5, -0.5)
    ];
    const latheGeometry = new THREE.LatheGeometry(lathePoints, 24);
    const latheMaterial = new THREE.MeshNormalMaterial({ wireframe: true });
    const lathe = new THREE.Mesh(latheGeometry, latheMaterial);
    scene.add(lathe);
    lathe.rotateX(THREE.MathUtils.degToRad(35));
    lathe.position.set(0, 0.35, 0);

    const animate = () => {
      TWEEN.update();
      const delta = clock.getDelta();
      lathe.rotation.y += delta;
      points.rotation.y += delta / 2;
      pointsMaterial.map.rotation += delta * 2;
      renderer.render(scene, camera);
      requestAnimationFrame(animate);
    };

    papa.onclick = (e) => {
      if (click3(lathe, e)) player.click();
    };

    papa.onmousemove = (e) => {
      papa.title = click3(lathe, e) ? '播放/暂停(Alt+X)' : '';
      papa.style.cursor = click3(lathe, e) ? 'pointer' : 'default';
    };

    const tweens = TWEEN.getAll(); // 获取所有tween动画

    aud.onplaying = aud.onpause = () => {
      tweens.forEach(tween => aud.paused ? tween.pause() : tween.resume());
      aud.paused ? clock.stop() : clock.start();
    };

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

马黑黑 发表于 2025-6-28 08:30

帖子视频外的动画:

在 ThreeJS 环境下使用 tween.js 驱动足球的部分运动。

足球有三个运动项目:一,纹理旋转(68行);二,在场景中整体旋转(67行);三、从原点到中心行进再折回,这是用 tween 实现的机制,代码分散在各处,主要有 38~48 行(设计)、64 行(全局指令)、85 行(控制)。tween 相关介绍请参阅 https://www.huachaowang.com/forum.php?mod=viewthread&tid=84371&extra=page%3D1

梦江南 发表于 2025-6-28 08:36

这个精彩,足球会自动到线框里去。{:4_187:}

梦江南 发表于 2025-6-28 08:38

音乐像魔咒一样,{:4_173:}

红影 发表于 2025-6-28 10:44

这个用到帖子里还挺复杂的,主要有它原本的旋转运动,还有tween 带来的补间也好动态也好,搅在一起很难想象它的具体运动是怎样的。
当然实际的运动效果很不多,可以先跑篮筐里再四散开来,很奇妙{:4_199:}

红影 发表于 2025-6-28 10:46

原来这个篮筐是车削体的运用,又需要回去复习一下车削了{:4_173:}

红影 发表于 2025-6-28 10:49

这么多足球,咱们的国足那么差,也挺讽刺的。
这首歌奇特的,在最后的地方好像有人在喊,喊的是中文{:4_173:}

杨帆 发表于 2025-6-28 11:04

动感十足,谢谢马老师经典示范与精彩分享{:4_176:}

马黑黑 发表于 2025-6-28 12:40

梦江南 发表于 2025-6-28 08:36
这个精彩,足球会自动到线框里去。

那个线框是特殊材料制成的,有强大的引力{:4_170:}

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

梦江南 发表于 2025-6-28 08:38
音乐像魔咒一样,

祝2022卡塔尔世界杯的歌曲

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

杨帆 发表于 2025-6-28 11:04
动感十足,谢谢马老师经典示范与精彩分享

{:4_191:}

马黑黑 发表于 2025-6-28 13:02

红影 发表于 2025-6-28 10:44
这个用到帖子里还挺复杂的,主要有它原本的旋转运动,还有tween 带来的补间也好动态也好,搅在一起很难想象 ...

设计总体上是明晰的:足球隶属于缓冲几何体,这个几何体由 pointsMaterial 构成,贴上足球纹理,总体是一个 points,这个points实际上是众多个足球“点”的随机布局而形成的一个网格(mess)图像,可以把points想象成是一个组。一定要注意,points是一个整体,里面有众多的足球,静态的话,这些足球开始时时随机分布的。

points的旋转和组的旋转一样,足球点会一起跟这旋转。

每一个足球又各自运行独立的 twenn 补间动画,它们从原点出发,到场景的中央,又 yoyo 到原点(回位用时1秒半,yoyo(1500) ),期间会停留一秒中( repeatDelay(1000) )。这个运动会受到 points 旋转运动的影响,所以会出现“香蕉球”效果。

所以,并不复杂,关键点可能在对缓冲几何体的理解之上。缓冲几何体是一个需要自定义形状的几何体,帖子里给N多个“足球”设计了随机顶点,这些顶点就是描述足球的位置,足球是缓冲几何体这个骨架的皮肉。这些顶点演变为形象的足球外观,但帖子要处理的其实只是顶点数据,利用这些随机的顶点数据生成 tween 补间动画的初始位置数据,然后令这些数据变为 (0,0,0) ,然后一切交给 tween 去运算和运行。

马黑黑 发表于 2025-6-28 13:03

红影 发表于 2025-6-28 10:49
这么多足球,咱们的国足那么差,也挺讽刺的。
这首歌奇特的,在最后的地方好像有人在喊,喊的是中文{:4_17 ...

歌曲创作者是新疆人

马黑黑 发表于 2025-6-28 13:04

红影 发表于 2025-6-28 10:46
原来这个篮筐是车削体的运用,又需要回去复习一下车削了

车削要设计 Vector2 顶点,应该算是简单的

红影 发表于 2025-6-28 15:35

马黑黑 发表于 2025-6-28 13:02
设计总体上是明晰的:足球隶属于缓冲几何体,这个几何体由 pointsMaterial 构成,贴上足球纹理,总体是一 ...

这样一说,把那一大批足球迷惑的运动说得十分清晰了,让它们都在约束的规律里运行了呢{:4_199:}

红影 发表于 2025-6-28 15:36

马黑黑 发表于 2025-6-28 13:03
歌曲创作者是新疆人

原来歌曲的语言是新疆语啊,还以为是外语呢,那他们喊出中文来不奇怪了{:4_173:}

红影 发表于 2025-6-28 15:36

马黑黑 发表于 2025-6-28 13:04
车削要设计 Vector2 顶点,应该算是简单的

嗯嗯,简单而漂亮实用{:4_187:}

马黑黑 发表于 2025-6-28 17:07

红影 发表于 2025-6-28 15:36
嗯嗯,简单而漂亮实用

它也可以设计出复杂的形状

马黑黑 发表于 2025-6-28 17:07

红影 发表于 2025-6-28 15:36
原来歌曲的语言是新疆语啊,还以为是外语呢,那他们喊出中文来不奇怪了

{:4_189:}
页: [1] 2 3 4 5
查看完整版本: 足球