小盆友进来听听巫娜姐姐的《飞升》
<style>#tz { --state: running; margin: 30px 0; left: calc(50% - 81px); transform: translateX(-50%);width: clamp(600px, 90vw, 1400px); min-height: 80vh; aspect-ratio: 16/9; background: #666 url('https://638183.freep.cn/638183/t24/webp3/scene.webp') no-repeat center/cover; box-shadow: 2px 2px 10px rgba(0,0,0,.65); z-index: 1; position: relative; }
#player { position: absolute; left: 3%; top: 2%; z-index: 9; clip-path: circle(45%); transition: filter .7s; cursor: pointer; animation: rot 2s infinite linear var(--state); }
#player:hover { filter: hue-rotate(120deg); }
#btnFs { left: 4.5%; top: 22%; width: 5%; color: #eee; text-align: center; }
#btnFs:hover { color: red; }
@keyframes rot { to { transform: rotate(360deg); } }
</style>
<div id="tz">
<audio id="aud" src="https://music.163.com/song/media/outer/url?id=304962" autoplay loop></audio>
<img id="player" src="https://638183.freep.cn/638183/small/202501.png" width="10%" title="播放/暂停" />
</div>
<script type="importmap">
{
"imports": {
"three": "https://esm.sh/three@0.176.0?target=es2022",
"three/addons/": "https://esm.sh/three@0.176.0/addons/"
}
}
</script>
<script type="module">
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
import { RapierPhysics } from 'three/addons/physics/RapierPhysics.js';
import { FS } from 'https://638183.freep.cn/638183/web/ku/fscreen.js';
let camera, scene, renderer;
let physics, position;
let boxes, spheres;
init();
FS(tz, player);
async function init() {
physics = await RapierPhysics();
position = new THREE.Vector3();
camera = new THREE.PerspectiveCamera(50, tz.offsetWidth / tz.offsetHeight, 0.1, 100);
camera.position.set(- 1, 1.5, 5);
camera.lookAt(0, 0.5, 0);
scene = new THREE.Scene();
const hemiLight = new THREE.HemisphereLight();
scene.add(hemiLight);
const dirLight = new THREE.DirectionalLight(0xffffff, 3);
dirLight.position.set(5, 5, 5);
dirLight.castShadow = true;
dirLight.shadow.camera.zoom = 2;
scene.add(dirLight);
const shadowPlane = new THREE.Mesh(
new THREE.PlaneGeometry(10, 10),
new THREE.ShadowMaterial({
color: 0x444444
}),
);
shadowPlane.rotation.x = - Math.PI / 2;
shadowPlane.receiveShadow = true;
scene.add(shadowPlane);
const floorCollider = new THREE.Mesh(
new THREE.BoxGeometry(10, 5, 10),
new THREE.MeshBasicMaterial({ color: 0x666666 })
);
floorCollider.position.y = - 2.5;
floorCollider.userData.physics = { mass: 0 };
floorCollider.visible = false;
scene.add(floorCollider);
const material = new THREE.MeshLambertMaterial();
const matrix = new THREE.Matrix4();
const color = new THREE.Color();
const geometryBox = new THREE.BoxGeometry(0.075, 0.075, 0.075);
boxes = new THREE.InstancedMesh(geometryBox, material, 400);
boxes.instanceMatrix.setUsage(THREE.DynamicDrawUsage);
boxes.castShadow = true;
boxes.receiveShadow = true;
boxes.userData.physics = { mass: 1 };
scene.add(boxes);
for (let i = 0; i < boxes.count; i ++) {
matrix.setPosition(Math.random() - 0.5, Math.random() * 2, Math.random() - 0.5);
boxes.setMatrixAt(i, matrix);
boxes.setColorAt(i, color.setHex(0xffffff * Math.random()));
}
const geometrySphere = new THREE.IcosahedronGeometry(0.05, 4);
spheres = new THREE.InstancedMesh(geometrySphere, material, 400);
spheres.instanceMatrix.setUsage(THREE.DynamicDrawUsage);
spheres.castShadow = true;
spheres.receiveShadow = true;
spheres.userData.physics = { mass: 1 };
scene.add(spheres);
for (let i = 0; i < spheres.count; i ++) {
matrix.setPosition(Math.random() - 0.5, Math.random() * 2, Math.random() - 0.5);
spheres.setMatrixAt(i, matrix);
spheres.setColorAt(i, color.setHex(0xffffff * Math.random()));
}
physics.addScene(scene);
renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(tz.offsetWidth, tz.offsetHeight);
renderer.setAnimationLoop(animate);
renderer.shadowMap.enabled = true;
tz.appendChild(renderer.domElement);
const controls = new OrbitControls(camera, renderer.domElement);
controls.target.y = 0.5;
controls.update();
setInterval(() => {
let index = Math.floor(Math.random() * boxes.count);
position.set(0, Math.random() + 1, 0);
physics.setMeshPosition(boxes, position, index);
index = Math.floor(Math.random() * spheres.count);
position.set(0, Math.random() + 1, 0);
physics.setMeshPosition(spheres, position, index);
}, 1000 / 60);
window.addEventListener('resize', onWindowResize);
}
function onWindowResize() {
camera.aspect = tz.offsetWidth / tz.offsetHeight;
camera.updateProjectionMatrix();
renderer.setSize(tz.offsetWidth, tz.offsetHeight);
}
function animate() {
if (aud.paused) return;
renderer.render(scene, camera);
}
</script> 帖子代码(threejs 特效源于 threejs 的一个公开示例):
<style>
#tz { --state: running; margin: 30px 0; left: calc(50% - 81px); transform: translateX(-50%);width: clamp(600px, 90vw, 1400px); min-height: 80vh; aspect-ratio: 16/9; background: #666 url('https://638183.freep.cn/638183/t24/webp3/scene.webp') no-repeat center/cover; box-shadow: 2px 2px 10px rgba(0,0,0,.65); z-index: 1; position: relative; }
#player { position: absolute; left: 3%; top: 2%; z-index: 9; clip-path: circle(45%); transition: filter .7s; cursor: pointer; animation: rot 2s infinite linear var(--state); }
#player:hover { filter: hue-rotate(120deg); }
#btnFs { left: 4.5%; top: 22%; width: 5%; color: #eee; text-align: center; }
#btnFs:hover { color: red; }
@keyframes rot { to { transform: rotate(360deg); } }
</style>
<div id="tz">
<audio id="aud" src="https://music.163.com/song/media/outer/url?id=304962" autoplay loop></audio>
<img id="player" src="https://638183.freep.cn/638183/small/202501.png" width="10%" title="播放/暂停" />
</div>
<script type="importmap">
{
"imports": {
"three": "https://esm.sh/three@0.176.0?target=es2022",
"three/addons/": "https://esm.sh/three@0.176.0/addons/"
}
}
</script>
<script type="module">
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
import { RapierPhysics } from 'three/addons/physics/RapierPhysics.js';
import { FS } from 'https://638183.freep.cn/638183/web/ku/fscreen.js';
let camera, scene, renderer;
let physics, position;
let boxes, spheres;
init();
FS(tz, player);
async function init() {
physics = await RapierPhysics();
position = new THREE.Vector3();
camera = new THREE.PerspectiveCamera(50, tz.offsetWidth / tz.offsetHeight, 0.1, 100);
camera.position.set(- 1, 1.5, 5);
camera.lookAt(0, 0.5, 0);
scene = new THREE.Scene();
const hemiLight = new THREE.HemisphereLight();
scene.add(hemiLight);
const dirLight = new THREE.DirectionalLight(0xffffff, 3);
dirLight.position.set(5, 5, 5);
dirLight.castShadow = true;
dirLight.shadow.camera.zoom = 2;
scene.add(dirLight);
const shadowPlane = new THREE.Mesh(
new THREE.PlaneGeometry(10, 10),
new THREE.ShadowMaterial({
color: 0x444444
}),
);
shadowPlane.rotation.x = - Math.PI / 2;
shadowPlane.receiveShadow = true;
scene.add(shadowPlane);
const floorCollider = new THREE.Mesh(
new THREE.BoxGeometry(10, 5, 10),
new THREE.MeshBasicMaterial({ color: 0x666666 })
);
floorCollider.position.y = - 2.5;
floorCollider.userData.physics = { mass: 0 };
floorCollider.visible = false;
scene.add(floorCollider);
const material = new THREE.MeshLambertMaterial();
const matrix = new THREE.Matrix4();
const color = new THREE.Color();
const geometryBox = new THREE.BoxGeometry(0.075, 0.075, 0.075);
boxes = new THREE.InstancedMesh(geometryBox, material, 400);
boxes.instanceMatrix.setUsage(THREE.DynamicDrawUsage);
boxes.castShadow = true;
boxes.receiveShadow = true;
boxes.userData.physics = { mass: 1 };
scene.add(boxes);
for (let i = 0; i < boxes.count; i ++) {
matrix.setPosition(Math.random() - 0.5, Math.random() * 2, Math.random() - 0.5);
boxes.setMatrixAt(i, matrix);
boxes.setColorAt(i, color.setHex(0xffffff * Math.random()));
}
const geometrySphere = new THREE.IcosahedronGeometry(0.05, 4);
spheres = new THREE.InstancedMesh(geometrySphere, material, 400);
spheres.instanceMatrix.setUsage(THREE.DynamicDrawUsage);
spheres.castShadow = true;
spheres.receiveShadow = true;
spheres.userData.physics = { mass: 1 };
scene.add(spheres);
for (let i = 0; i < spheres.count; i ++) {
matrix.setPosition(Math.random() - 0.5, Math.random() * 2, Math.random() - 0.5);
spheres.setMatrixAt(i, matrix);
spheres.setColorAt(i, color.setHex(0xffffff * Math.random()));
}
physics.addScene(scene);
renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(tz.offsetWidth, tz.offsetHeight);
renderer.setAnimationLoop(animate);
renderer.shadowMap.enabled = true;
tz.appendChild(renderer.domElement);
const controls = new OrbitControls(camera, renderer.domElement);
controls.target.y = 0.5;
controls.update();
setInterval(() => {
let index = Math.floor(Math.random() * boxes.count);
position.set(0, Math.random() + 1, 0);
physics.setMeshPosition(boxes, position, index);
index = Math.floor(Math.random() * spheres.count);
position.set(0, Math.random() + 1, 0);
physics.setMeshPosition(spheres, position, index);
}, 1000 / 60);
window.addEventListener('resize', onWindowResize);
}
function onWindowResize() {
camera.aspect = tz.offsetWidth / tz.offsetHeight;
camera.updateProjectionMatrix();
renderer.setSize(tz.offsetWidth, tz.offsetHeight);
}
function animate() {
if (aud.paused) return;
renderer.render(scene, camera);
}
</script>
本帖特效的核心在于 RapierPhysics,这是一个模拟物理现象的API库,里面包含引力对物体的作用、物体运动过程中的惯性、边界与碰撞等等,做的十分齐全。
光源也是重要的核心内容。示例中使用了两种光源,一个负责对3d对象的渲染,里一个负责阴影的营造。
Threejs的示例是全屏模式即整个页面用于渲染效果,所有的示例都是这样。帖子将其作为帖子容器的子内容,这样符合做帖规范。 本帖 Threejs 特效不使用 WebGPU 实现,在兼容性方面比较理想,普通甚至略微低端的硬件设备也可以轻松观赏,前提是浏览器要支持 WebGL。
另外,特效部分可以使用鼠标滚轮令其变大变小,还可以按下鼠标左键不方去翻转粒子整体。支持移动端的相关操作。 开篇瞬间看到是呈立方体形式的堆叠,然后再如喷泉似的四散开来
博客里的效果比论坛里的感觉快一点,是过会才看到的。 马黑黑 发表于 2025-5-6 19:56
本帖特效的核心在于 RapierPhysics,这是一个模拟物理现象的API库,里面包含引力对物体的作用、物体运动过 ...
最初看到的时候,就觉得模拟的很逼真,无论是流动还是重力及在地板上的运动,都无限接近自然
原来 这些设计都包含在内了。
看来写代码,不仅要数学,还得学好物理学。。{:4_173:} 关于光源和阴影,特别留意看了一下,
立柱光影很明显, 每个小球运动的时候,影子也随之运动,
且鼠标在拖动的时候,根据位置不同,影子形状也会随之变化,
跟真实的一样一样。。 马黑黑 发表于 2025-5-6 20:00
本帖 Threejs 特效不使用 WebGPU 实现,在兼容性方面比较理想,普通甚至略微低端的硬件设备也可以轻松观赏 ...
按左键移动整个效果的时候,感觉是固定在一个透明玻璃上,
翻到底面看还可以看到几何体落下时产生重力发生的移动,
俯瞰时也是同样。。
这可以看到生活中看不到多物体坠落的立体全方位。。。
不止是3D了吧。。。{:4_173:} 巫姐姐的琴声经典,魔音一样入耳入心,好听。。 花飞飞 发表于 2025-5-6 20:53
巫姐姐的琴声经典,魔音一样入耳入心,好听。。
{:4_181:} 花飞飞 发表于 2025-5-6 20:51
按左键移动整个效果的时候,感觉是固定在一个透明玻璃上,
翻到底面看还可以看到几何体落下时产生重力发 ...
实际上是3d,3d就是酱紫的,全息影 花飞飞 发表于 2025-5-6 20:47
关于光源和阴影,特别留意看了一下,
立柱光影很明显, 每个小球运动的时候,影子也随之运动,
且鼠标在 ...
模拟的挺像的 花飞飞 发表于 2025-5-6 20:42
开篇瞬间看到是呈立方体形式的堆叠,然后再如喷泉似的四散开来
博客里的效果比论坛里的感觉快一点,是过会 ...
是这么个原理:论坛加载的东东多,按线程一一加载 哇,这些小球好可爱,还能鼠标互动,真有趣{:4_187:} 这些粒子有的圆有的方,堆积的挤压和相互碰撞还能改变它们的运动形态,这个太神奇了{:4_199:} 红影 发表于 2025-5-6 21:20
哇,这些小球好可爱,还能鼠标互动,真有趣
小盆友晚上好 哇塞,幼儿园的滑滑梯{:4_170:} 啊~~~有钱,小辣椒立马来劲了{:4_170:} 这么多的彩色粒子 马黑黑 发表于 2025-5-6 19:56
本帖特效的核心在于 RapierPhysics,这是一个模拟物理现象的API库,里面包含引力对物体的作用、物体运动过 ...
看到了,阴影还是各不相同的。
这个好像不是完全的物理现象,有些外侧的小球会忽然极速往前面来。受到黑洞影响么{:4_173:}