·约 4 分钟阅读·DeepSeek

一颗珠子就是一枚透镜,2304 颗让平面印刷品看起来是立体的

那些玻璃珠画不是印刷戏法:每颗珠子都是一枚微小的球透镜,它只取后方图像的一小条并把它放大。我把这个效应的真实 GPU 光追移植进了浏览器,你可以自己拧旋钮。

embeddedmeta

阵列里的每一颗珠子,看到的只是它后方图像中不到一毫米的一小段。

按默认参数——2 毫米珠、玻璃 n = 1.5、图像在后方 2 毫米——每颗珠子取样的窗口宽 0.667 毫米,并把它放大 3 倍。它看不到整张图。它看到一小条,被放大;旁边那颗珠子看到的是稍微不同的一小条。

这就是全部的机关,而且它不是印刷效果。珠子是透镜,你透过它们看到的那幅画面,是”实际印了什么”的摩尔纹。

玻璃珠透镜实验室

拖动旋转视角,滚轮缩放。改参数看摩尔纹与视差怎么变。

焦距 f
—
放大率 |M|
—
单珠取样跨度
—

每颗珠子只取一小条,然后把它放大

光学部分只有一个公式的深度。半径 R、折射率 n 的玻璃球,从球心量起的焦距是

f = n·R / (2(n−1))

半径 1 毫米、n = 1.5 的珠子,f = 1.5 毫米。把图像放在透镜平面后方 d 处,通常的薄透镜关系给出放大率 |M| = f / (d − f)——默认 d = 2 毫米 时约 3 倍。

而真正要紧的那个数,是实验室里标为单珠取样跨度的量:

span = 2R · |1 − d/f|

0.667 毫米,就是一颗珠子”负责”的那段图像宽度。透过阵列看到的一切,都是这些小条被放大、被重复。

所以平滑照片会变成马赛克,而细密图案会变成摩尔纹

拖动滑块,同一张图会做出两件完全不同的事:

  • 把图像缩小、或把珠子调大,每颗珠子就覆盖一大片平滑区域。你得到的是马赛克——每颗珠子一个纯色块。什么都没有坏:透镜对着一个光滑表面,本来就没有东西可以分辨。
  • 把珠子调小、或让源图变细密,每颗珠子那 0.667 毫米的窗口里就有了结构。此时相邻珠子取样到的是几乎相同、但略微错开的结构,阵列的重复与图像的重复相互拍频。这就是摩尔纹放大——一个比珠子和印刷都大得多的图案,而且它会随着你的视角移动。视差就是深度。

实验室里那张波带片是有意放的。它的圆环越往边缘越细,所以它会在各处同时产生这种拍频。

GPU 是逐像素、用一个着色器做完的

这个实验室不是视频,也不是预渲染的循环。它是一次真实光线追迹的移植:画布上的每个像素,片元着色器从针孔相机发出一条主光线,找到它命中的珠子,然后要么(理想模式)用薄透镜近似让光线偏折,要么(逐面折射模式)与球面求交并在每个表面应用斯涅尔定律、用弹射上限处理全反射;最后与后方的图像平面求交、采样纹理。

这个着色器我最初是为一个桌面程序写的,移植到这里几乎没改——片元着色器是同一份代码,只是从 #version 330 core 改到 WebGL2 的 #version 300 es。真正的工作在它周围的管道:程序化生成的波带片作为源图,以及一个可轨道旋转的相机。

两种模式画的不是同一张画

把理想球透镜切成逐面折射,看边缘。旁轴模式是一条捷径——它把每颗珠子当成薄透镜,忽略没打中中心的光线会怎样。折射模式与真实球面求交,所以靠近边缘的光线偏折不同,有些会以大角度打在半球平坦的背面,视场边缘随之变暗。

两种模式在中央一致、在边缘分道扬镳——这就是一个近似所付出的代价,诚实的画法。

交互版给不了你的东西

两件,我宁愿说出来,而不是让这个实验室暗示它做得到。

它是正向仿真:展示观察者透过给定珠子阵列、看给定图像时看到什么。它不解决逆问题——也就是生成一张可打印的图,透过珠子看到一个 3D 物体。那件事在桌面程序里,是另一件工作。

而且它是模型:单一折射率,所以没有色散、没有玻璃本身的彩边;针孔相机,所以没有景深;没有表面反射。它复现的是几何,而幻觉就住在几何里。

最值得带走的一点是:那层深度不是画上去的。它来自一枚物理透镜对图像一小条的取样,而当你移动时,那一小条会变。