VYBE 电影感 3D
HyperFrames 视频中的 3D 是一个 <canvas>,其每一帧都是时间的纯函数。
HyperFrames 的 three 适配器发布时间;你来渲染它。
权威参考:/hyperframes-animation → adapters/three.md(安装后)。
契约(不可协商)
- 导入固定:
import * as THREE from "https://cdn.jsdelivr.net/npm/[email protected]/+esm";(或者使用importmap固定相同版本用于附加组件)。 - 在加载时同步构建场景、相机、渲染器、材质;在渲染临界寻找前加载任何模型/纹理(Three 的
DefaultLoadingManager会自动等待)。对于繁重的 CPU 设置,注册window.__hf.buildReady["<name>"] = promise。 - 仅从时间渲染:
window.addEventListener("hf-seek", e => renderAt(e.detail.time)); renderAt(window.__hfThreeTime || 0); - 无需
requestAnimationFrame循环、时钟、增量(deltas)或未种子化的Math.random()。 renderer.setPixelRatio(1); renderer.setSize(W, H, false),其中 W/H 来自根元素的data-width/height。根元素必须包含data-duration。- 标记全出血画布
data-layout-allow-overflow。
骨架代码
<canvas id="gl" width="1920" height="1080" data-layout-allow-overflow></canvas>
<script type="module">
import * as THREE from "https://cdn.jsdelivr.net/npm/[email protected]/+esm";
const root = document.getElementById("vybe-root");
const W = +root.dataset.width, H = +root.dataset.height, D = +root.dataset.duration;
const css = getComputedStyle(document.documentElement);
const tok = (n, f) => css.getPropertyValue(n).trim() || f; // 读取 VYBE 令牌
const renderer = new THREE.WebGLRenderer({ canvas: document.getElementById("gl"), antialias: true });
renderer.setPixelRatio(1); renderer.setSize(W, H, false);
renderer.setClearColor(new THREE.Color(tok("--vy-bg", "#000")), 1);
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(W > H ? 40 : 60, W / H, 0.1, 200); // 竖屏时使用更宽的 FOV
// … 构建 …
function renderAt(t) { const p = t / D; /* 根据 t 更新相机 + 对象 */ renderer.render(scene, camera); }
window.addEventListener("hf-seek", (e) => renderAt(e.detail.time));
renderAt(window.__hfThreeTime || 0);
</script>
当 3D 世界跨越多个场景时(例如相机在影片中弧线运动),将画布放在宿主(index.html)中;仅当 3D 内容仅存在于单个场景时,才将其放在场景文件中。
在其上方叠加 HTML 类型;当 DOM 文本可用时,绝不在 WebGL 中渲染文本。
技巧与配方
- 粒子 / 尘埃 / 星星: 使用
THREE.Points,结合带种子的伪随机数生成器(PRNG)生成位置、从调色板获取顶点颜色,并使用圆形精灵(CanvasTexture径向渐变作为map)——默认点是方形——AdditiveBlending,depthWrite: false。确保每个粒子都超出相机最近的 z 值,否则当相机穿过它时,它会膨胀成一个巨大的精灵(这是真实的 VYBE 缺陷)。 - 英雄球体 / 行星:
SphereGeometry(r, 96, 64)+MeshStandardMaterial(金属度/粗糙度) + 发光光晕TorusGeometry环;随时间点亮它。 - 黑洞: 暗色球体 + 吸积盘 = 使用画布渐变纹理的
RingGeometry,倾斜 70–80°,旋转;透镜环 = 第二个面向相机的薄圆环;恒星弯曲 = 在径向方向上靠近中心缩放背景点图层。 - 分子: 原子位置上的球体(使用
InstancedMesh大量实例化),它们之间的圆柱体(使用quaternion.setFromUnitVectors定向);相机环绕。 - 产品转盘: 通过
GLTFLoader(importmap)加载 GLTF,使用AnimationMixer.setTime(t)处理创作好的动画片段;下方放置工作室灯光;在镜头中缓慢旋转 20–35°。 - 数据雕塑: 使用
InstancedMesh盒子,其高度来自数据,并随时间通过smoothstep函数生长。 - 程序化地形/波浪: 在 JS 中根据
t置换PlaneGeometry(每帧重新计算位置,适用于 ≤40k 顶点)或在ShaderMaterial中使用在renderAt中设置的uTimeuniform。
光照
加载 references/lighting.md。最小灯光配置:主光(点光/方向光)、对比色补光/逆光,以及一个 HemisphereLight 环境光——一个只有一盏光源的网格会在其暗面显示为黑色剪影。以固定的比例,从相同的时间函数驱动灯光强度。
相机
从归一化进度 p = t / D 推导位置,每一幕使用带有 smoothstep 片段的摄影机路径(参见电影模板的 renderAt)。对于剪辑,声明 const CUT_B = 23 一次,并同时用于相机镜头选择器和对象可见性;检查剪辑前后的帧(snapshot --at 22.9667,23)。
性能(headless Chrome 可能使用软件 GL)
- ≤ 100k 三角形,≤ 20k 粒子,无需需要帧历史的后处理通道。辉光效果:使用加法混合的精灵/光晕模拟。
- 在低内存机器上避免为 4K 超大画布启用
antialias;测试草稿渲染。 - 如果 WebGL 在渲染环境中失败,画布会变黑:质量检查(QC)会检测到黑帧;此时应回退到 2D 画布或场景的 CSS-3D 版本。