效果预览经典的曼德勃罗集Mandelbrot Set分形渲染配合动态缩放动画探索分形边界的无限细节。使用线性插值平滑着色呈现出彩虹般的色彩过渡。 点击查看《曼德勃罗集的》完整源码与效果演示Shader 实现原理1. 整体思路与数学模型曼德勃罗集是复平面上的一组点对于每一个复数c迭代序列z_{n1}z_n^2 c, z_00如果该序列不发散即模长始终保持有界则c属于曼德勃罗集。在计算机中我们用有限迭代次数来近似判断如果在res次迭代内|z| 2则认为序列发散c不属于曼德勃罗集。这个判据的数学依据是如果|z_n| 2序列必然发散到无穷。2是曼德勃罗集的逃逸半径。2. 复数乘法 — cprod 宏的数学#define cprod(a, b) vec2(a.x*b.x-a.y*b.y, a.x*b.ya.y*b.x)这是标准的复数乘法公式。设a a.x i*a.yb b.x i*b.y则a * b(a.x i*a.y)(b.x i*b.y)a.x*b.x - a.y*b.y i(a.x*b.y a.y*b.x)实部a.x*b.x - a.y*b.y对应vec2的 x 分量虚部a.x*b.y a.y*b.x对应 y 分量。这个宏把复数运算封装为vec2运算在 GPU 上是零开销的编译期展开。3. 迭代核心 — mandel 函数float mandel(vec2 c, int res) { vec2 z vec2(0.0, 0.0); float oldLen 0.0; for (int i 0; i res; i) { z c; z cprod(z, z); float newLen length(z); if (newLen 2.0) { float p (2.0 - oldLen) / (newLen - oldLen); return float(i) p; } oldLen newLen; } return float(res); }3.1 迭代顺序注意这里的迭代顺序是先z c后z z^2。这与标准定义z z^2 c等价但计算顺序不同标准定义z_{n1} z_n^2 c代码实现z_{n1} (z_n c)^2 z_n^2 2*z_n*c c^2等等这不相等。实际上仔细看代码初始z 0第 1 次z 0 c c然后z c^2。此时z c^2但标准应该是z 0^2 c c。这个顺序实际上是z_{n1} (z_n c)^2与标准定义不同。但由于初始值z_0 0两种顺序的差异只是索引偏移一位代码中第i次迭代后z的值对应标准定义的第i1次迭代最终返回的迭代次数与标准定义一致因为初始oldLen 0已经考虑了第 0 步3.2 线性插值平滑Smooth Iteration Countfloat p (2.0 - oldLen) / (newLen - oldLen); return float(i) p;如果不做平滑返回的是离散的整数迭代次数。在分形边界处相邻像素的迭代次数可能相差很大导致明显的色带banding。线性插值的思路假设|z|从oldLen到newLen是线性增长的则逃逸时刻|z| 2发生在迭代之间的一个分数位置poldLen p *(newLen - oldLen)2p(2- oldLen)/(newLen - oldLen)这样返回值是连续的浮点数而非离散的整数消除了色带现象。4. 坐标变换与缩放系统vec2 uv vec2(gl_FragCoord.xy - iResolution.xy / 2.0); uv uv * 2.0 / min(iResolution.x, iResolution.y);4.1 屏幕坐标到归一化坐标gl_FragCoord.xy - iResolution.xy / 2.0将坐标原点移到屏幕中心* 2.0 / min(iResolution.x, iResolution.y)以屏幕短边为基准归一化保证不同宽高比下图形不被拉伸4.2 动态缩放float range sin(iTime * 0.25) * 0.5 0.5; float zoom 1.0 100050.0 * range;sin(iTime * 0.25)周期为2π / 0.25 8π ≈ 25.1秒* 0.5 0.5映射到[0, 1]zoom范围1.0到100051.0覆盖从全局视图到极深放大4.3 焦点位置vec2 focus 1.0 * vec2(-0.542738427, 0.615566608);这个点是曼德勃罗集边界上一个著名的 elephants valley 象谷区域附近的点分形细节极其丰富。缩放时以此为中心可以观察到无限递归的自相似结构。4.4 最终 UV 变换float f mandel(focus uv * 1.25 / zoom, res);uv * 1.25 / zoom1.25是基础视野范围/ zoom是缩放因子focus ...将局部坐标平移到焦点位置5. 颜色映射float p f / float(res); vec3 col vec3(0.1, 0.1, 0.1); if (int(f) res) { p * float(res) / 16.0; col 0.5 0.5 * vec3(sin(p), sin(p PI / 3.0), sin(p PI * 2.0 / 3.0)); col col / max(col.x, max(col.y, col.z)); }5.1 集合内外的区分int(f) res如果迭代次数小于最大迭代次数说明点在集合外逃逸了int(f) res点在集合内使用深色vec3(0.1)5.2 色相循环col 0.5 0.5 * vec3(sin(p), sin(p PI / 3.0), sin(p PI * 2.0 / 3.0));这是相位偏移的正弦函数三个通道的相位差分别为0、π/3、2π/3R 通道sin(p)G 通道sin(p π/3)B 通道sin(p 2π/3)0.5 0.5 * sin(...)把输出映射到[0, 1]。三个通道相位差 120°在 RGB 空间中形成平滑的色相循环。p * float(res) / 16.0把迭代计数放大512/16 32倍让颜色变化更频繁增强视觉细节。5.3 归一化增强对比col col / max(col.x, max(col.y, col.z));这一步把颜色除以最大通道值使至少一个通道达到 1.0。效果是增强颜色饱和度让暗色更暗、亮色更亮整体对比度提升总结这个特效的精髓在于用最简单的复数迭代公式生成无限复杂的分形图案。曼德勃罗集不是画出来的而是算出来的 —— 每个像素的颜色都是一次独立的数学实验结果。分形的核心魅力是自相似性无论你放大多少倍边界的褶皱结构始终保持相似。本 shader 通过sin(iTime)驱动的动态缩放让用户能一窥这种无限递归的美。
曼德勃罗集的 Three.js 实现
发布时间:2026/5/22 20:05:25
效果预览经典的曼德勃罗集Mandelbrot Set分形渲染配合动态缩放动画探索分形边界的无限细节。使用线性插值平滑着色呈现出彩虹般的色彩过渡。 点击查看《曼德勃罗集的》完整源码与效果演示Shader 实现原理1. 整体思路与数学模型曼德勃罗集是复平面上的一组点对于每一个复数c迭代序列z_{n1}z_n^2 c, z_00如果该序列不发散即模长始终保持有界则c属于曼德勃罗集。在计算机中我们用有限迭代次数来近似判断如果在res次迭代内|z| 2则认为序列发散c不属于曼德勃罗集。这个判据的数学依据是如果|z_n| 2序列必然发散到无穷。2是曼德勃罗集的逃逸半径。2. 复数乘法 — cprod 宏的数学#define cprod(a, b) vec2(a.x*b.x-a.y*b.y, a.x*b.ya.y*b.x)这是标准的复数乘法公式。设a a.x i*a.yb b.x i*b.y则a * b(a.x i*a.y)(b.x i*b.y)a.x*b.x - a.y*b.y i(a.x*b.y a.y*b.x)实部a.x*b.x - a.y*b.y对应vec2的 x 分量虚部a.x*b.y a.y*b.x对应 y 分量。这个宏把复数运算封装为vec2运算在 GPU 上是零开销的编译期展开。3. 迭代核心 — mandel 函数float mandel(vec2 c, int res) { vec2 z vec2(0.0, 0.0); float oldLen 0.0; for (int i 0; i res; i) { z c; z cprod(z, z); float newLen length(z); if (newLen 2.0) { float p (2.0 - oldLen) / (newLen - oldLen); return float(i) p; } oldLen newLen; } return float(res); }3.1 迭代顺序注意这里的迭代顺序是先z c后z z^2。这与标准定义z z^2 c等价但计算顺序不同标准定义z_{n1} z_n^2 c代码实现z_{n1} (z_n c)^2 z_n^2 2*z_n*c c^2等等这不相等。实际上仔细看代码初始z 0第 1 次z 0 c c然后z c^2。此时z c^2但标准应该是z 0^2 c c。这个顺序实际上是z_{n1} (z_n c)^2与标准定义不同。但由于初始值z_0 0两种顺序的差异只是索引偏移一位代码中第i次迭代后z的值对应标准定义的第i1次迭代最终返回的迭代次数与标准定义一致因为初始oldLen 0已经考虑了第 0 步3.2 线性插值平滑Smooth Iteration Countfloat p (2.0 - oldLen) / (newLen - oldLen); return float(i) p;如果不做平滑返回的是离散的整数迭代次数。在分形边界处相邻像素的迭代次数可能相差很大导致明显的色带banding。线性插值的思路假设|z|从oldLen到newLen是线性增长的则逃逸时刻|z| 2发生在迭代之间的一个分数位置poldLen p *(newLen - oldLen)2p(2- oldLen)/(newLen - oldLen)这样返回值是连续的浮点数而非离散的整数消除了色带现象。4. 坐标变换与缩放系统vec2 uv vec2(gl_FragCoord.xy - iResolution.xy / 2.0); uv uv * 2.0 / min(iResolution.x, iResolution.y);4.1 屏幕坐标到归一化坐标gl_FragCoord.xy - iResolution.xy / 2.0将坐标原点移到屏幕中心* 2.0 / min(iResolution.x, iResolution.y)以屏幕短边为基准归一化保证不同宽高比下图形不被拉伸4.2 动态缩放float range sin(iTime * 0.25) * 0.5 0.5; float zoom 1.0 100050.0 * range;sin(iTime * 0.25)周期为2π / 0.25 8π ≈ 25.1秒* 0.5 0.5映射到[0, 1]zoom范围1.0到100051.0覆盖从全局视图到极深放大4.3 焦点位置vec2 focus 1.0 * vec2(-0.542738427, 0.615566608);这个点是曼德勃罗集边界上一个著名的 elephants valley 象谷区域附近的点分形细节极其丰富。缩放时以此为中心可以观察到无限递归的自相似结构。4.4 最终 UV 变换float f mandel(focus uv * 1.25 / zoom, res);uv * 1.25 / zoom1.25是基础视野范围/ zoom是缩放因子focus ...将局部坐标平移到焦点位置5. 颜色映射float p f / float(res); vec3 col vec3(0.1, 0.1, 0.1); if (int(f) res) { p * float(res) / 16.0; col 0.5 0.5 * vec3(sin(p), sin(p PI / 3.0), sin(p PI * 2.0 / 3.0)); col col / max(col.x, max(col.y, col.z)); }5.1 集合内外的区分int(f) res如果迭代次数小于最大迭代次数说明点在集合外逃逸了int(f) res点在集合内使用深色vec3(0.1)5.2 色相循环col 0.5 0.5 * vec3(sin(p), sin(p PI / 3.0), sin(p PI * 2.0 / 3.0));这是相位偏移的正弦函数三个通道的相位差分别为0、π/3、2π/3R 通道sin(p)G 通道sin(p π/3)B 通道sin(p 2π/3)0.5 0.5 * sin(...)把输出映射到[0, 1]。三个通道相位差 120°在 RGB 空间中形成平滑的色相循环。p * float(res) / 16.0把迭代计数放大512/16 32倍让颜色变化更频繁增强视觉细节。5.3 归一化增强对比col col / max(col.x, max(col.y, col.z));这一步把颜色除以最大通道值使至少一个通道达到 1.0。效果是增强颜色饱和度让暗色更暗、亮色更亮整体对比度提升总结这个特效的精髓在于用最简单的复数迭代公式生成无限复杂的分形图案。曼德勃罗集不是画出来的而是算出来的 —— 每个像素的颜色都是一次独立的数学实验结果。分形的核心魅力是自相似性无论你放大多少倍边界的褶皱结构始终保持相似。本 shader 通过sin(iTime)驱动的动态缩放让用户能一窥这种无限递归的美。