M.C. Escher 的作品中充满了令人惊叹的数学美感,其中「螺旋」主题尤具代表性。从《画廊》(Print Gallery)中的递归景观到《生命之路径》(Path of Life III)的螺旋铺砖,Escher 将严谨的数学结构转化为视觉上极具冲击力的图像。在现代 Web 开发中,利用 WebGL 和 Three.js 完全可以在浏览器中实时渲染这类数学艺术作品。本文将从数学原理出发,探讨如何在 Three.js 框架下通过自定义着色器实现 Escher 风格的螺旋效果。
螺旋的数学本质:从极坐标到对数螺旋
实现 Escher 螺旋的第一步是理解其背后的数学模型。绝大多数 Escher 风格螺旋都可以用对数螺旋(logarithmic spiral)来描述,其极坐标方程为 $r = a \cdot e^{b\theta}$,其中 $a$ 控制起始半径,$b$ 控制螺旋的 tightness(紧密程度)。当 $b$ 为正值时,螺旋随角度增大而向外卷展;为负值时则向内收敛。
在直角坐标系中,对数螺旋上的点可以表示为:
$$ x = r \cdot \cos\theta = a \cdot e^{b\theta} \cdot \cos\theta $$
$$ y = r \cdot \sin\theta = a \cdot e^{b\theta} \cdot \sin\theta $$
这种数学表达式的优势在于其自相似性:旋转一定角度后,螺旋会与自身重合。这种特性正是 Escher 在创作中反复利用的核心属性。
域变换:从欧几里得空间到螺旋空间
Escher 螺旋的核心技巧在于域变换(domain transformation)。在 WebGL 片段着色器中,我们并不是直接绘制螺旋,而是将每个像素的坐标映射到一个新的空间,在这个空间中,螺旋变成了规则的重复图案。这一技术最初由数学家用于分析 Escher 的《画廊》,该作品实际上应用了所谓的 Droste 效应 —— 一种特殊的螺旋递归。
具体的域变换公式可以表示为 $h (w) = w^\alpha$,其中 $w$ 是复平面上的坐标,$\alpha$ 是一个复数:
$$ \alpha = \frac{2\pi i + \ln(\text{scale})}{2\pi i} $$
在 GLSL 着色器中,这一变换的实现方式如下:
vec2 escherDeformation(in vec2 uv) {
float lnr = log(length(uv));
float th = atan(uv.y, uv.x) + (0.4 / 256.0) * deformationScale;
float sn = -log(deformationScale) * (1.0 / (2.0 * 3.1415926));
float l = exp(lnr - th * sn);
vec2 ret = vec2(l);
ret.x *= cos(sn * lnr + th);
ret.y *= sin(sn * lnr + th);
return ret;
}
这个函数将标准坐标转换为 Escher 螺旋坐标,其中 deformationScale 控制从普通 Droste 效应到螺旋形态的变形程度。
Three.js 着色器架构设计
在 Three.js 中实现 Escher 螺旋,推荐使用 ShaderMaterial 创建全屏 quad(两个三角形组成的矩形),所有的数学计算都在片段着色器中完成。这种方式避免了传统的顶点几何操作,能够以极低的开销实现复杂的数学可视化。
基本的 Three.js 场景设置如下:
const uniforms = {
time: { value: 0.0 },
resolution: { value: new THREE.Vector2() },
zoom: { value: 1.0 },
deformationScale: { value: 1.0 }
};
const material = new THREE.ShaderMaterial({
vertexShader: `
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = vec4(position, 1.0);
}
`,
fragmentShader: `...`, // 核心螺旋逻辑
uniforms: uniforms
});
const plane = new THREE.Mesh(new THREE.PlaneGeometry(2, 2), material);
scene.add(plane);
顶点着色器极为简洁,仅传递 UV 坐标。所有的视觉魔法都发生在片段着色器中。Uniform 变量 time 用于驱动动画,deformationScale 控制螺旋的形变程度。
片段着色器核心实现
以下是实现 Escher 螺旋的关键着色器逻辑框架:
precision mediump float;
uniform float time;
uniform vec2 resolution;
uniform float zoom;
uniform float deformationScale;
varying vec2 vUv;
vec2 escherTransform(vec2 uv) {
vec2 centered = (uv - 0.5) * 2.0;
centered.x *= resolution.x / resolution.y;
float lnr = log(length(centered));
float th = atan(centered.y, centered.x);
float sn = -log(deformationScale) / (6.28318530718);
float l = exp(lnr - th * sn);
vec2 transformed;
transformed.x = l * cos(sn * lnr + th);
transformed.y = l * sin(sn * lnr + th);
return transformed;
}
void main() {
vec2 uv = vUv;
vec2 transformedUv = escherTransform(uv * zoom);
// 基于变换后的坐标生成颜色
float pattern = length(transformedUv);
vec3 col = 0.5 + 0.5 * cos(pattern * 12.0 - time * 0.5);
col += 0.3 * sin(pattern * 8.0 + time * 0.3);
gl_FragColor = vec4(col, 1.0);
}
这段代码展示了 Escher 螺旋变换的基本框架。关键点在于 escherTransform 函数,它将原始的 UV 坐标映射到螺旋空间中,使得原本平直的图案呈现出螺旋状的视觉效果。
工程化参数与调优策略
在实际项目中,以下参数需要根据具体需求进行调优:
螺旋紧密程度(tightness):由对数螺旋方程中的 $b$ 参数控制。在着色器中,这对应于 deformationScale 的对数值。值越大,螺旋绕转越紧密。建议初始值为 0.85 至 0.95 之间。
缩放因子(zoom):控制图案在屏幕上的整体大小。较大的 zoom 值会显示更多的螺旋层级,但单层的细节会减少。
时间步长(time step):控制动画速度。过快的动画可能导致视觉疲劳,建议将时间乘以 0.1 至 0.3 的系数。
颜色映射方案:可以使用基于距离的颜色映射、相位映射或迭代深度映射。相位映射尤其适合表现螺旋的连续性。
性能优化与监控要点
由于片段着色器对每个像素都执行复杂的数学运算,性能是需要重点关注的方面。以下监控指标值得注意:
帧率监控:使用 requestAnimationFrame 配合帧时间计算,确保在目标设备上能够维持 60fps。对于复杂场景,可以考虑降低分辨率或减少迭代次数。
着色器复杂度:每次域变换的迭代次数直接影响性能。在移动设备上,建议将迭代次数控制在 8 次以内;桌面端可以适当增加。
Uniform 更新频率:避免在每一帧更新大量 uniform 变量。将静态参数在初始化时一次性传入,只在必要时更新动态参数。
从静态到交互的演进
在基础实现完成后,可以考虑增加交互性以提升用户体验。鼠标位置可以映射为额外的 uniform 变量,控制螺旋的中心偏移或旋转角度。触摸设备上的手势识别也可以用来实时调整 deformationScale,让用户通过拖拽感受从平面到螺旋的连续形变。
另一个有价值的扩展是在螺旋的每个层级上显示不同的内容。例如,可以使用多个纹理 sampler,在不同的递归层级上映射不同的图像,创造出类似《画廊》中「画中画」的视觉效果。这种多层次的内容映射需要额外维护一个层级计数器,在着色器中根据 deformationScale 动态判断当前像素属于哪个层级。
小结
在 WebGL 中实现 M.C. Escher 风格螺旋的核心在于域变换技术的应用。通过将常规坐标映射到对数螺旋空间,原本平直的几何图案能够呈现出优雅的螺旋形态。Three.js 的 ShaderMaterial 为这种技术提供了一条简洁的工程化路径:顶点着色器负责坐标传递,片段着色器负责所有的数学变换与颜色计算。掌握好 deformationScale、zoom、time 等关键 uniform 变量,配合适当的性能监控与交互设计,就能够在浏览器中实现流畅且富有数学美感的 Escher 螺旋效果。
参考资料
- Reinder Nijhoff 的 WebGL 片段着色器实现分析了 Escher《画廊》的数学结构:https://reindernijhoff.net/2014/05/escher-droste-effect-webgl-fragment-shader/
- 城市大学关于高级 Escher 螺旋铺砖生成的学术研究:https://scholars.cityu.edu.hk/files/81502619/80194871.pdf