把 3D 塔渲染成 2D 像素
一条 Dead Cells 式三渲二管线:Blender 正交渲染 32 帧、640×360、48 色、1px 描边;Codex 出背景,canvas 按滚动切帧。首屏 JS 从 three 的 100KB+ 降到 110KB。
上一版首屏是实时 3D:一座四十单位高的塔、草、魔法阵、粒子,全部在 WebGL 里跑。
能跑,但不对。手机上第一帧要编译一堆 shader;低端机掉到 20fps;而且不管怎么调材质,它都像“3D 渲染图”,不像《重生细胞》那种 2D 像素。
这次换了管线:3D 只负责出图,网页只负责放 2D 像素。
为什么是“三渲二”
《重生细胞》的角色和场景不是纯手绘像素,而是先做 3D 模型,再用固定机位渲染成 2D 序列帧。好处是:
- 3D 保证透视、比例、光影一致,不会一帧一个样
- 渲染完降到低分辨率、限色、加描边,就得到像素画
- 运行时只是切图,不需要 WebGL、不需要 shader 编译
代价是:镜头不能随便动。想要自由运镜,就得提前把每一帧都渲出来。
这次正好是“沿塔身下降”的固定路线,可以预渲染。
32 帧怎么来
Blender 里保留原来的 3D 塔,换成 Eevee + 正交相机:
- 分辨率 1280×720,透明背景
- 相机从塔尖
y=30降到塔基y=2 - 每帧沿塔身下降一点,3/4 视角,共 32 帧
- 石头材质用
Shader to RGB+ 四段 ColorRamp,做成硬边色阶,提前把“三渲二”的调子定下来
这里踩了一个大坑:Blender 的 Z 轴朝上。第一版相机写成 (10, y, 16),把高度放进了 Y,渲出来前 20 帧全透明,后几帧是一个贴脸的窗户。正确写法是 (10, -20, y),Z 才是高度。
另一个坑是 ortho_scale:它不是“高度”,在竖屏下是较大的那条边。桌面 16:9 用 30 刚好,竖屏海报用 22 会把塔裁成一面墙,最后改成 44 才装下整座塔。
像素化
渲染出的 1280×720 不能直接当像素画。后处理三步:
- 用
BOX重采样到 640×360,保留面积平均,不会像NEAREST那样丢细节 - 从 32 帧里抽 8 帧拼一张接触表,
MEDIANCUT量化出 48 色全局调色板;每帧用同一张 palette - 对 alpha 做 1px 内描边,颜色
#060a07,给塔一个硬轮廓
这里也踩了坑:一开始把背景和塔放进同一张 palette。背景的月亮和高光太亮,量化时把塔的深绿全拉成了浅灰。后来塔单独用 48 色,背景保留自己的调色板,塔才回到暗绿色。
最后导出 无损 WebP。32 帧一共约 560KB,比一张 2K PNG 还小。
Codex 出背景
塔必须由 Blender 确定性渲染,否则 32 帧之间会漂移。背景可以交给图像模型。
这次用 Codex 的内置图像生成,提示词很短:
pixel-art night background, deep green-black sky, pale moon, distant pine hills; no text or tower
出了一张 640×360 的夜景:松林、山脊、月亮、薄云。直接当底层,塔的透明帧叠上去。草和魔法阵没有让模型出——它们需要透明通道和可控的重复纹理,程序化画更稳。
前端只剩一个 canvas
PixelStage 里没有 three:
- 一个 640×360 的 canvas,CSS
image-rendering: pixelated - 预加载 32 张 WebP,滚动进度 0→1 映射到帧号
- 背景图轻微上移,前景草缘和魔法光点做视差
- 魔法阵只在接近塔基时浮现,加法混合
- 手机不跑序列,直接用一张 390×844 的竖屏像素海报
首屏 JS 从 100KB+ 降到 110KB(包含 React/Next 的共享 chunk),默认路径里没有 three。
数据
| 项 | 数值 |
|---|---|
| 塔序列 | 32 帧 / 640×360 |
| 调色板 | 48 色 |
| 塔资源 | 约 560KB WebP |
| 背景 + 草 + 魔法 + 手机海报 | 约 300KB |
| 首屏 JS | 110KB |
| 运行时 | 2D canvas,无 WebGL |
踩坑表
| 现象 | 真相 |
|---|---|
| 前 20 帧全透明,后几帧贴脸 | 相机高度写进了 Y,Blender 的 Z 才是上 |
| 竖屏海报只剩一面墙 | ortho_scale 在竖屏按长边算,22 太小,44 才装下整塔 |
| 塔被量化成浅灰 | 背景月亮的高光进了同一张 palette,塔要用独立调色板 |
| 手机首屏是一面墙 | 16:9 canvas 用 cover 铺竖屏会裁掉两侧,手机必须单独出竖屏海报 |
| 塔身比预想矮 | Three.js 里 Box3 量出来 35.9,不是 Blender 里的 39.8;相机视点别按 Blender 数字硬套 |
最后
现在首页是:Codex 出的松林夜景、32 帧下降的像素塔、塔基一圈青绿魔法阵、前景草缘和光点。
它不是实时 3D 了,但首屏更快、手机更稳,也更接近我想要的《重生细胞》那种“暗色场景 + 高饱和发光物”的质感。
想对比旧版,可以加 ?mode=3d。