足球
<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>
帖子代码
<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>
帖子视频外的动画:
在 ThreeJS 环境下使用 tween.js 驱动足球的部分运动。
足球有三个运动项目:一,纹理旋转(68行);二,在场景中整体旋转(67行);三、从原点到中心行进再折回,这是用 tween 实现的机制,代码分散在各处,主要有 38~48 行(设计)、64 行(全局指令)、85 行(控制)。tween 相关介绍请参阅 https://www.huachaowang.com/forum.php?mod=viewthread&tid=84371&extra=page%3D1 这个精彩,足球会自动到线框里去。{:4_187:} 音乐像魔咒一样,{:4_173:} 这个用到帖子里还挺复杂的,主要有它原本的旋转运动,还有tween 带来的补间也好动态也好,搅在一起很难想象它的具体运动是怎样的。
当然实际的运动效果很不多,可以先跑篮筐里再四散开来,很奇妙{:4_199:} 原来这个篮筐是车削体的运用,又需要回去复习一下车削了{:4_173:} 这么多足球,咱们的国足那么差,也挺讽刺的。
这首歌奇特的,在最后的地方好像有人在喊,喊的是中文{:4_173:} 动感十足,谢谢马老师经典示范与精彩分享{:4_176:} 梦江南 发表于 2025-6-28 08:36
这个精彩,足球会自动到线框里去。
那个线框是特殊材料制成的,有强大的引力{:4_170:} 梦江南 发表于 2025-6-28 08:38
音乐像魔咒一样,
祝2022卡塔尔世界杯的歌曲 杨帆 发表于 2025-6-28 11:04
动感十足,谢谢马老师经典示范与精彩分享
{:4_191:} 红影 发表于 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 10:49
这么多足球,咱们的国足那么差,也挺讽刺的。
这首歌奇特的,在最后的地方好像有人在喊,喊的是中文{:4_17 ...
歌曲创作者是新疆人 红影 发表于 2025-6-28 10:46
原来这个篮筐是车削体的运用,又需要回去复习一下车削了
车削要设计 Vector2 顶点,应该算是简单的 马黑黑 发表于 2025-6-28 13:02
设计总体上是明晰的:足球隶属于缓冲几何体,这个几何体由 pointsMaterial 构成,贴上足球纹理,总体是一 ...
这样一说,把那一大批足球迷惑的运动说得十分清晰了,让它们都在约束的规律里运行了呢{:4_199:} 马黑黑 发表于 2025-6-28 13:03
歌曲创作者是新疆人
原来歌曲的语言是新疆语啊,还以为是外语呢,那他们喊出中文来不奇怪了{:4_173:} 马黑黑 发表于 2025-6-28 13:04
车削要设计 Vector2 顶点,应该算是简单的
嗯嗯,简单而漂亮实用{:4_187:} 红影 发表于 2025-6-28 15:36
嗯嗯,简单而漂亮实用
它也可以设计出复杂的形状 红影 发表于 2025-6-28 15:36
原来歌曲的语言是新疆语啊,还以为是外语呢,那他们喊出中文来不奇怪了
{:4_189:}