Escher-Droste 变换:从复指数映射到 WebGL 实时渲染

史上最聪明的韩小凡! Lv6

背景

埃舍尔的《画廊》(Print Gallery, 1956)是 20 世纪最迷人的版画之一——一个年轻人站在画廊里看画,画中的城市蜿蜒伸展,画中画循环嵌套,中心留下一个空白漩涡。2002 年,荷兰数学家 de Smit 和 Lenstra 用复分析填补了那个 46 年悬而未决的空白。

这个工具就是把那篇论文的核心思想——复指数映射极坐标重参数化——搬进浏览器,变成拖拽即用的实时交互。

数学核心

复指数映射

Droste 效应的本质是把线性周期折叠成环形周期。核心变换只有一行:

1
w = exp(α · log(z))

其中 α = a + bi 是复参数:

  • 实部 a 控制缩放——每一圈放大多少倍
  • 虚部 b 控制旋转——每一圈扭转多少度

物理意义:取对数 → 线性缩放+旋转 → 取指数回去。对数空间里的直线对应原空间里的螺旋。

三种映射模式

复指数平铺(Log Tile)

适合无缝纹理和重复图案。把图片当作对数平面中的纹理单元,通过复指数映射回普通平面。公式最简洁:

1
2
3
4
float denom = u_alpha_r * u_alpha_r + u_alpha_i * u_alpha_i;
float zx = (u_alpha_r * ln_r + u_alpha_i * theta) / denom;
float zy = (u_alpha_r * theta - u_alpha_i * ln_r) / denom;
vec2 tc = vec2(zx, zy);

每一层都是上一层的缩放+旋转复制,层与层之间自然衔接,得到经典的螺旋嵌套效果。

圆形周期(Circular Period)

适合单向递归的图像——画廊、走廊、门框、镜中镜。把原本向一个方向深入的空间折叠成闭合圆环:

1
2
径向距离 → 递归深度(对数映射)
角度方向 → 横向周期(fract 平铺 + 螺旋扭转)

圆心是最内层(原图远处),外圈是最外层(原图近处),一圈一圈向外展开。

论文幂映射(Power Map)

遵循 de Smit & Lenstra 原始论文的思路:

1
F(z) = F₀(z^(1/β))

通过幂映射把原图空间弯曲,更接近数学本质,但对非数学化的输入图像不稳定。

技术实现

WebGL 片段着色器

整个变换运算都在 GPU 上完成。834 行单 HTML 文件,核心 shader 仅 89 行 GLSL

架构:

1
2
3
4
5
6
7
JavaScript 控制面板
↓ uniform 参数更新
WebGL Fragment Shader
↓ 逐像素逆映射
纹理采样 → 屏幕输出

preserveDrawingBuffer → 导出 PNG

关键设计决策:

  1. 逐像素逆映射:对输出图像的每个像素,反算它在源图中的纹理坐标。这样做的好处是不需要预计算任何网格或点云,任意分辨率直接渲染。

  2. 延迟渲染(debounce):拖动滑块时不是每帧都重绘,而是用 requestAnimationFrame 合并连续事件,避免卡顿。

  3. preserveDrawingBuffer: true:WebGL 默认渲染后丢弃缓冲区内容。开启这个标志后 toDataURL 能直接导出当前画面,无需额外的离屏渲染。

  4. 导出模式高分辨率:导出 PNG 时临时把 canvas 调到 2048px 长边,渲染完再恢复预览尺寸。用 gl.finish() 确保 GPU 完成绘制再读像素。

边缘模式

模式 行为
循环平铺(Repeat) fract(tc) —— 无穷复制
钳制(Clamp) 超出 [0,1] 的纹理坐标透明化
镜像(Mirror) abs(fract(tc * 0.5 + 0.5) * 2.0 - 1.0) —— 折叠式反射

非 2 的幂纹理处理

WebGL 1.0 对非 2 的幂纹理有限制:不能使用 mipmap。工具检测到非 POT 图片时自动使用 LINEAR 过滤 + CLAMP_TO_EDGE 包裹模式,禁用 mipmapping,保证任意尺寸图片都能正常渲染。

参数指南

参数 推荐范围 效果
旋转角度/层 0° ~ 8°(画廊图) 控制螺旋扭转程度
缩放倍率/层 1.8 ~ 2.2 每层的缩放因子,决定螺旋密度
整体缩放 -2 ~ 2 对数域的全局缩放
中心 X/Y 30% ~ 70% 螺旋中心在画面中的位置

一个对画廊图效果极好的起点配置:

1
2
3
4
5
映射方式:圆形周期
缩放倍率/层:2.0
中心 X:50.5%
中心 Y:34.5%
边缘模式:镜像

设计选择:为什么是单文件 HTML

没有构建工具、没有 npm 依赖、没有打包步骤,834 行一个文件搞定。理由很明确:

  1. 零启动成本:双击打开即用,python -m http.server 即可本地开发
  2. 部署极简:丢到 GitHub Pages 就行,不需要 CI/构建/压缩
  3. 可审计:所有代码在一个文件里,右键查看源码一次看完,没有压缩混淆

参考


在线体验

🚀 立即试用

拖入一张图片,选择映射方式,滑动参数,点击导出。所有渲染在本地 GPU 上完成,无服务器参与。