缘起
去年冬天开始收集鸣潮的官方立绘,攒了一整个文件夹。图片躺在硬盘里是不会呼吸的,我想让它们站到光下面来。
所以有了这一页:六张卡沿轨道公转,中间一颗星核。点开任意一张,卡会翻面、会随鼠标转动、会在不同角度泛出不同的镭射色。首屏那六张不是图片,是真在跑的 3D。 轨道上的是缩略图,点开才去拉那张卡的全套资源 —— 首屏总共只吃两百多千字节。
先说清楚口径:立绘版权归库洛游戏所有,这里只做个人收藏与展示,不商用、不二次分发。页面上的卡面、卡背、配色与文字排版都是同人二次创作,非官方周边。
| # | 角色 | 拉丁名 | 版次 | 镭射 ramp |
|---|---|---|---|---|
| 01 | 今汐 | JINXI | 01 / 06 | 冰蓝 → 亮白 → 淡金 |
| 02 | 卡提希娅 | CARTETHYIA | 02 / 06 | 紫罗兰 → 水晶白 → 淡金 |
| 03 | 爱弥斯 | AEMEATH | 03 / 06 | 粉白 → 藕紫 → 星蓝 |
| 04 | 绯雪 | FEIXUE | 04 / 06 | 靛蓝 → 雪白 → 绯红 |
| 05 | 心 | XIN | 05 / 06 | 金棕 → 朱红 → 墨 |
| 06 | 守岸人 | SHOREKEEPER | 06 / 06 | 靛蓝 → 青白 → 银 |
一、五层,一层一个深度
一张卡拆成五层,这不是为了好看,是因为卡片「看着平」的头号原因就是层与层之间没有深度差——两层落在同一个深度上,会糊成一整块平面。
| 层 | 文件 | 职责 | 深度 |
|---|---|---|---|
| subject | subject.png | 角色本体,带真 alpha | 0.4 |
| effects | effects.png | 花瓣/光点前置层,浮在主体与文字之间 | 0.5 |
| background | background.png | 不透明环境,由原海报去主体后补全 | −0.25 |
| lineart | lineart.png | 深色轮廓 + 白底,必须从 subject 同源派生 | 跟随主体 |
| text | text.png | 标题/副标题/版次/边框,无视差 | 0 |
两条硬约束值得单独说。
第一,线稿必须同源。它是从 subject 的 alpha 边缘做 Sobel 取幅值、再按阈值二值化出来的,深色线落在白底上。拿一张重新描的线稿来对位,边缘一定会错开一两个像素,动起来就是挥之不去的重影。
第二,text 层无视差。标题和版次是印在卡上的字,卡倾斜时字跟着卡走,不跟着景深走。把它们放进视差里,看起来就像字浮在卡片前面——这是最容易犯、也最难自己看出来的错。
至于镭射,有一句话我想写在这里:镭射是材质,不是画出来的。我一开始把彩虹扫光画进了图层,结果到了 Blender 里那层箔材无光可扫 —— 因为它已经被”画死”在贴图上了。正确的做法是图层只留底色和明暗,箔材交给着色器按卡片本地的视角方向实时算。
二、抠图是整条链路上最难的一步
其余的步骤都只是麻烦,唯独抠图是难的。
官方海报的构图是为排版服务的:角色压在高细节的插画背景上,纱边、发丝、半透明的花瓣与背景互相渗透。想干净地把主体抠出来,等于要求算法分辨”这缕发丝属于她”还是”属于背景的风”。
我走了三步:
- 首轮自动抠图。六张里有四张干净通过,主体保留率 60%~100%。
- 卡提希娅与守岸人失败。服务返回了近乎全不透明的结果——也就是把整张画布原样还给了我。换提示词、重裁画布再来,都没用。
- 换素材重来。改用官方放出的角色展示海报(主体更居中、背景更简洁),这两张才抠出来。
即便如此,这两张的 subject 层至今仍带着一块矩形底板——抠图保留了原海报的一块方形区域。你在首屏点开守岸人就能看见:角色周围有一圈比背景略亮的方形。这是这一版留下的、我自己也知道的问题,下一版会换更精细的抠图重做。
写下来是因为:一个项目最值得记录的部分,往往不是你做成了什么,而是你还没做成什么。
三、Blender:把五层压成一枚 GLB
图层进 Blender,按各自的深度排开,卡面是分层平面,卡背贴第二张整图裁切后的 2:3 图,再印上编号。导出 GLB 的时候只带走几何与材质名,贴图留在外面——正是这一点让”换图不用重建模型”成为可能:卡片几何里只记着 web_subject/web_effects/web_text 这样的角色名,具体用哪张图由运行时配置决定。
所以后面每次调整素材,我只需要覆盖同名文件,不必再开一次 Blender。这个决定后来救了我很多时间。
卡背不是贴图,是当场用 canvas 画的:内双描边、大字首字母、系列名与版次。画出来的好处是每个角色的首字母、版次都能自动对上,不必手工出六张背面图。
四、上站:一颗球,六条轨道
博客是 Astro 静态站,页面本体是一篇全幅文章。三个工程上的取舍值得记下来。
其一,中心球没有用 Three.js。 它只是一个全屏片元着色器:SDF 球面求交,两层 fbm 噪声当星云表面,再加一道 fresnel 边缘光,最后用加色混合把光晕糊到页面背景上。整个球大约 2KB,而 Three.js 是 170KB 级的开销——那是点开卡片时才该付的账。所以 Three.js 走的是动态 import,用户不点开任何一张卡,就永远不会下载它。
其二,轨道没有用 offset-path,也没有用 CSS 关键帧。 六张卡每帧由 requestAnimationFrame 写一次 transform。这样”悬停加速""展开停摆""前后分栏”都只是同一个循环里的几个判断,不需要在 CSS 和 JS 之间来回同步状态。
前后分栏这件事值得多说一句:只靠缩放和透明度是做不出遮挡的。要让转到底部的卡真的被星核挡住,卡必须真的在不同的层里。所以轨道分成 --back 与 --front 两栏,卡越过前后分界时直接搬一次 DOM——转一圈每张卡才搬两次,完全不心疼。
其三,同一套 DOM,两套布局。 桌面是绝对定位的椭圆轨道;窄屏把两个轨道容器设为 display: contents,卡就自然成为网格项,退回文档流排成三列两行。不重复渲染两套 DOM,键盘 Tab 序也就跟着文档流走了。
五、几个花了不少时间的坑
opacity: 0 不挡命中测试。 入场时看不见的元素依然能被点到、能被 Tab 聚焦。这个坑我在上一个全幅页就踩过一次,这次在卡片入场动画上又遇到一遍,解决办法是在动画跑完前挂一道 pointer-events: none 闸门。
入场动画的 forwards 填充会永久压住 hover 的 transform。 动画结束后必须把动画类摘掉,否则悬停时的位移永远不生效——元素看起来”能点但不动”。
深色底上的单根弧线会被吃掉。 水母层是这样,星核的边缘光也是这样。解决方式从来不是加粗线条,而是给形状一个能自我闭合的轮廓层次。
代理会掐住下载。 本机装 Blender 时走 HTTP 代理只有约 10KB/s,380MB 要下十一个小时;绕开代理直连是 16MB/s,三分钟结束。同一台机器、同一条网络,差别只在一个环境变量。
六、接下来
- 重做卡提希娅与守岸人的抠图,把那两块矩形底板去掉;
- 卡背现在还是”整图 + 脚本压字”,下一步想把它也拆成五层,背面同样做出厚度;
- 加一个”洗卡”动作:点击后箔材扫过一遍,全息感会更足。
三代同框的收藏册,差不多就是这个样子了。