Tripo3D
返回

青石巷

一段三到四分钟的网页实时体验:拆迁区里一间关了门的家电修理铺,老师傅用旧家电零件拼的小机器人还坐在店里。按住,它的镜头睁开,光照到哪里,店里和对街就回到九十年代的傍晚。店里的旧家电、家具和这只机器人,二十多件模型都由 Tripo 生成。

作者: Theodore
在线体验
青石巷在线体验

描述

《青石巷》是一段三到四分钟的网页实时体验。黄昏,一条等着拆迁的老街:对街的店铺卷帘门都拉下了,墙上圈着「拆」,有一栋已经拆了一半。只有一间家电修理铺的卷帘门还半开着,老师傅用旧收音机外壳、相机镜头和水管拼的小机器人,还坐在工作台旁的凳子上。

点击画面开始,镜头从街上走进店里,绕到机器人面前。按住鼠标左键或空格,它的镜头光圈打开——第一次总会失败,松开再按住一次。按住时移动鼠标,它的头跟着转,光照到哪里,哪里就回到从前。

第一幕在店里。光束外是「现在」:东西积着灰、褪了色,货架上挂着蛛网,地上散着墙皮和碎玻璃,纸箱上那台电视、墙上的挂钟、台扇和电子管收音机都坏了。光照到的地方,一切回到从前的样子:电视放着节目,收音机的刻度灯亮着、响着曲子,挂钟在走。

过一会儿,对街隐约传来同一支曲子。机器人转过头去听,画面退到门口;它的光开始不稳,曲子里混进杂音,画面一阵阵失去同步,在一次撕裂之后切到第二幕。

第二幕在街上。光照到对街五家店里的哪一家,那家就回到九十年代中期的傍晚:店里亮着灯,阳台上晾着衣服,邻居坐在门口的矮凳上,同时响起那家店的声音。找到的店会一直亮着,还没找到的会用招牌和声音叫你。看的时候电量一直在掉;最后一眼回到放着那支曲子的电器店,整条街亮起片刻,随后电量耗尽,光束闪了几下熄灭,曲子停在半句,街回到现在。结尾从店里望出去,机器人仍面朝漆黑的街,对街亮起一盏灯,最后一句是:「它记得这条街亮着灯的样子。」

结尾字卡出现后,点击画面可以重看。界面默认英文,标题页上有切换中文的链接;手机上按住屏幕、滑动手指来操作。推荐戴耳机,用支持 WebGPU 的浏览器(如新版 Chrome / Edge)打开,不支持 WebGPU 时自动改用 WebGL2。第一次打开要先下载模型、预编译着色器,需要等一会儿。

Tripo 用在哪里

Tripo 生成了修理铺里的二十多件物件:机器人、拆开的和关着的两台电子管收音机、两台显像管电视、台扇、挂钟、闹钟、录音机、台灯、电烙铁、放大镜、工具箱、零件柜、工作台、二八自行车、暖水瓶、搪瓷杯,还有备用的手臂、腿和躯干,另外专门做了四件残破版。房间的墙地、货架和上面的纸箱、线缆、工具墙、台面小件是代码搭的几何体;对街那一排店铺是两张生成的照片(「现在」和「从前」),不是模型。

  • 从参考图到模型:每件旧物先出一张浅灰背景的产品图,再交给 Tripo 建模。动手前拿拆开的电子管收音机比了三种做法——单图直接生成、单图先生成多视图再建模、自己拼四视图再建模,大多数模型最后用的是「单图 → 多视图 → 3D」;几何和贴图都用精细档,带 PBR 贴图,并去掉贴图里烘焙的光照,交给店里的台灯、门外的暮色和机器人的光束来打光。
  • 机器人:Tripo 生成的机器人连头带身子、连同凳子是一整块网格。作者没有把头切下来,而是在顶点着色器里做了一段「软脖子」:按顶点位置算权重,收音机外壳做的头是 1,身子和肩膀是 0,脖子和周围的线缆逐渐过渡;转头时每个顶点按权重绕颈部扭转,网格不切就不会裂出接缝,线缆还会跟着缠绕。凳子用 Blender 脚本单独分了出来,第二幕整个机器人绕着凳子转向街面。脸上的「上海」铬标和胸前的「人民电器」铭牌都是模型自带的几何体;同一设计还生成了备用的手臂、腿和躯干,挂在墙钩上、靠在工作台边。
  • 残破版:为了让光束外的「现在」和光里的「从前」对得上,纸箱上的电视、挂钟、台扇和电子管收音机各多做了一个破旧版本:先把参考图改成破损的样子,再用同样的 Tripo 设置建模。两个版本摆在同一位置,光束照到的像素显示完好版,其余显示残破版。
  • 原始网格怎么上网页:Tripo 生成的原始模型每件约 190 万个三角面,带 4K 贴图(机器人是 8K)。作者没有用 Blender 合并减面——那样会撕开贴图接缝,近看出现白色裂纹——而是保留接缝做 meshopt 简化,每件分成几级细节,每级都记下实测误差。运行时每件东西只显示误差不到一个屏幕像素、贴图不比屏幕粗的那一级:远处用简化级,走近换成细的;线上版最细的一级和原始网格只差零点几毫米。开场先加载各件较粗的级别,其余按需下载,显存超出预算时自动释放。
  • 模型检视:地址后面加上 ?review 进入检视模式,换成明亮的检视灯光,可以拖动环绕、滚轮缩放、按数字键 1–8 切换机位;双击任意一件模型,镜头会飞到它跟前,左下角显示它的名字和 Tripo 原始面数。

在浏览器里怎么跑起来

体验没有用游戏引擎,直接基于 three.js 的 WebGPU 渲染器,用 Vite 构建;不支持 WebGPU 的浏览器会自动改用 WebGL2。

「从前」是逐像素算出来的。机器人的眼睛投出一道光锥,渲染时逐个像素判断它是否落在光里:光里换成完好的模型、亮着的电视和暖色调,光外留在积灰褪色的「现在」,残破版和完好版就按这张遮罩切换。对街的两张照片同样按像素在「现在」和「从前」之间切换;电器店的橱窗做成了一间真正的三维小店,里面的电视也在放节目。

打开快、不卡顿主要靠三件事:着色器全部异步并行编译,没编好的物体这一帧先不画,帧不用等它;加载画面里把每个物体在每个渲染通道(深度预通道、主通道、每张阴影图)里各画一遍,预热完成后点击开始就不再临时编译;渲染像素上限约 210 万(相当于 1080p),帧时间偏长时自动降低分辨率。模型换级时两级同时画、各占一组互补的随机像素,在 0.35 秒内交叉淡入,由时间抗锯齿抹平,近看不会「闪一下」。地址加 ?q=low 可以切到低画质(关掉环境光遮蔽和屏幕空间反射,阴影贴图减半)。

声音由 CC0 现场录音和 Web Audio 实时合成叠成:门外黄昏的街声、灯管和市电的嗡声、钟摆的嘀嗒、电视测试音、机器人的舵机声和开眼声,收音机里那支五声音阶小调是原创的;第二幕对街每家店都有自己的声音。

做的过程中还修了 three.js r186 WebGPU 渲染器的一个问题:两个模型的顶点只是法线的存储类型不同(Int8 与 Int16)时,渲染器会让它们共用一条渲染管线,后画的那个读错顶点格式,表面出现迷彩一样的错乱光照;WebGL2 没有这个问题。