跳到正文
Pian

把 3D 塔渲染成 2D 像素

一条 Dead Cells 式三渲二管线:Blender 正交渲染 32 帧、640×360、48 色、1px 描边;Codex 出背景,canvas 按滚动切帧。首屏 JS 从 three 的 100KB+ 降到 110KB。

4 分钟读完#3D #像素 #Blender #三渲二

上一版首屏是实时 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 不能直接当像素画。后处理三步:

  1. 用 BOX 重采样到 640×360,保留面积平均,不会像 NEAREST 那样丢细节
  2. 从 32 帧里抽 8 帧拼一张接触表,MEDIANCUT 量化出 48 色全局调色板;每帧用同一张 palette
  3. 对 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
首屏 JS110KB
运行时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。