在线体验 关于项目
六块面板是场景里真实的 WebGL 几何体——有厚度、有弧度,会遮挡角色也会被角色遮挡,从任何角度看彼此的前后关系都正确。它们不是浮在 canvas 之上的 DOM 卡片。
整个交互建立在一条分工之上:三维用于发现,二维用于控制。面板负责浏览和选曲,它们环绕角色漂浮、随手势旋转;真正的播放控制则交给底部那块稳定的屏幕空间界面。选中一首歌时,这两种状态之间不是切换,而是一次连续的移交——面板本体飞下来变成播放器,再选另一首时,旧的那块飞回它原本的三维槽位。
体验要点
- 拖拽空白处,音乐卡片绕角色轴心旋转,松手后带惯性减速;拖拽角色本身则只有角色转动,相机与面板纹丝不动。两者各自缓慢自转且方向相反——这种对向运动让面板读起来像环绕的独立物体,而非贴在背后的背景板
- 点击任意面板,它向前脱离三维空间、绕过角色下沉,在底部展开为横向播放器;再选另一首,两块面板同时在飞——旧的收拢回它精确的原始位置,新的迎面而来
- 滚轮推拉相机(4.6–16 世界单位):推近时前景面板大到溢出画面,退远时整片空间收成一团可俯瞰的音乐环绕场景
- 自定义光标的四角取景框会捕捉面板真实的投影四边形——括号落在真实的透视角点上,随面板的倾斜一起旋转
技术解析
3D 资产 · 原封不动的 Tripo 导出
Artist.glb 是不做任何处理的 Tripo 导出原件:59MB、102 万顶点、8192² 底色贴图——不减面、不缩图,按作者精度全量渲染。代价算得清楚(贴图 mipmap 后约 350MB 显存、每帧重绘百万顶点),两个决策把它压住:重网格退出射线检测——否则每次指针移动都要对百万三角求交、卡死主线程——改由按模型包围盒生成的隐形胶囊代理拾取;导出没有动画剪辑,就给模型组级 idle(缓摆加毫米级呼吸)而非伪造骨骼动画。
scripts/ 里备着一条把同一文件压到 2.9MB 的 meshopt 管线;模型在加载时测量并归一化,全量与精简构建互换零代码改动。
材质 · Tripo 导出的体积材质降级
Tripo 导出给网格打了 KHR_materials_volume 标记,进 three.js 会落成带 transmission/thickness 路径的 MeshPhysicalMaterial——这份透射渲染开销既不需要也付不起。加载时统一降级为 MeshStandardMaterial,同时保留全套 PBR 贴图,观感不变、开销砍掉。
自定义 ShaderMaterial 则显式 include colorspace_fragment——缺了它,线性值直写 sRGB 帧缓冲,画面近乎全黑。
音乐面板 · 曲面上的真几何
每块面板由四种几何组成——正面、扫出厚度的边缘条、半透明背板、四块隐形点击热区——全部共享同一套缓冲,只有组变换随卡而异。所有内容先平面作画,再整体卷贴到一个大立式圆柱面上:进度条与时钟是烘进同一曲面的元素而非子节点,进度填充是注入 MeshBasicMaterial 的 UV 截断——缩放网格会让远端翘离曲面。
面板画布按更新频率拆三层:1320×680 的正面只画一次、时间条每秒重画、播放图标状态翻转才画,渲染循环里零昂贵重绘。六块全透明面板不写深度,每帧按相机距离分配带状 renderOrder,任意角度前后遮挡都正确。
3D 到 2D 接力 · 卡片落成播放器
WebGL 网格变不成 DOM 节点,连续感靠三件事拼出:投影器每帧维护每块卡的屏幕框——包括被隐藏的那块,回程要有落点;DOM 面板的第一帧直接用卡片 WebGL 纹理的同一张画布位图,而不是永远差一点的 HTML 重绘;原卡在同一帧隐藏,两个东西占一个位置、其中一个不可见。
飞行到约 35% 才开始拉宽——2:1 的卡中途拉成 11:1 会把封面拉糊;路径是向侧面鼓出的二次贝塞尔,从左上落下的卡会绕过角色而不是从其身上斜穿。落点每帧重读,因为星座仍在漂移,起飞时采一次样会让面板落在卡旁边。


