教程文档示例工程【免费下载链接】website-archiveArchive of the Coding Train website (first version)项目地址https://gitcode.com/gh_mirrors/we/website-archive点击查看免费下载导读本文以 Coding Train 第 168 期编程挑战《The Mandelbulb》为核心完整讲解曼德博集合Mandelbrot Set如何扩展到三维空间成为 Mandelbulb曼德博球并基于仓库中的 Processing 源码从三重数Triplex Number数学原理、球坐标迭代公式、体素Voxel遍历到带 PeasyCam 交互与颜色/贴图优化的点云渲染给出可直接运行、可逐步调参的完整实现路径。读完本文你将掌握 Mandelbulb 的核心迭代公式、点云边界提取算法以及如何用 ProcessingJava搭建一个可旋转、可着色的三维分形查看器。关联文档位于 _CodingChallenges/168-mandelbulb.md配套完整源码位于 CodingChallenges/CC_168_Mandelbulb/Processing包含 5 个可运行的 Processing 工程。一、从 Mandelbrot 到 Mandelbulb问题定义1.1 二维 Mandelbrot 集的迭代本质Mandelbrot 集的定义基于复数平面上的迭代zₙ₊₁ zₙ² c其中c是复平面上的一个点z₀ 0。如果迭代序列zₙ不发散模长始终不超过 2则该点属于 Mandelbrot 集否则属于集合外部迭代次数n决定了其逃逸速度可用于着色。仓库中该主题的前置挑战 CC_021_Mandelbrot 用 p5.js 实现了这一经典算法其核心循环是var aa a * a - b * b; // 实部平方减虚部平方 var bb 2 * a * b; // 交叉项 a aa ca; b bb cb; if (a * a b * b 16) { // 模长平方超过 16 即视为逃逸 break; }注意逃逸判定用的是a*a b*b 16即模长r 4就中断迭代而在 Mandelbulb 版本中判定阈值取r 2两者是等价的判定思路临界逃逸半径只是系数习惯不同。1.2 三维化面临的数学难题二维复数的乘法可以用模长相乘、辐角相加来描述但三维空间中没有与复数完全等价的三重数triplex number代数系统直接定义z² c的三维乘法是数学上不成立的。因此社区尤其是 Daniel White 与 Paul Nylander 的探索参见视频中引用的 skytopia 资料采用了一个工程上可行的替代方案在三维空间中用球坐标来描述迭代点把复数的辐角相乘替换为球坐标角度乘以幂次 n。这正是 Mandelbulb 的核心思想也是视频时间轴中Triplex numbers4:31与Spherical coordinates7:19两节所讲解的内容。二、核心数学三重数幂公式与球坐标转换2.1 幂运算的球坐标形式令三维迭代点p (x, y, z)将其转换到球坐标r sqrt(x² y² z²) θ atan2(sqrt(x² y²), z) // 与 z 轴的夹角 φ atan2(y, x) // 与 x 轴的方位角则幂pⁿ的计算公式为pⁿ (rⁿ·sin(nθ)·cos(nφ), rⁿ·sin(nθ)·sin(nφ), rⁿ·cos(nθ))这就是仓库中 MandelBulb_Cloud.pde 的迭代核心Spherical c spherical(zeta.x, zeta.y, zeta.z); float newx pow(c.r, n) * sin(c.theta*n) * cos(c.phi*n); float newy pow(c.r, n) * sin(c.theta*n) * sin(c.phi*n); float newz pow(c.r, n) * cos(c.theta*n); zeta.x newx x; zeta.y newy y; zeta.z newz z;这里n 8是 Mandelbulb 最经典的幂次power 8不同n值会生成不同形状的花瓣状分形。zeta是迭代变量对应二维中的z每轮迭代后都要加上初始点(x, y, z)对应二维中的c。2.2 笛卡尔坐标转球坐标的实现仓库三个主要变体都复用了同一个转换函数以 MandelBulb_Color.pde 为例Spherical spherical(float x, float y, float z) { float r sqrt(x*x y*y z*z); float theta atan2( sqrt(x*xy*y), z); float phi atan2(y, x); return new Spherical(r, theta, phi); }要点说明θ使用atan2(sqrt(x²y²), z)分母是z而非常见的atan2(y, x)式的极角这是以z 轴为极轴的球坐标约定φ使用atan2(y, x)方位角以 x 轴为参考atan2两个参数顺序不能写反否则角度会反转生成的形状将发生镜像逃逸判据是c.r 2模长超过逃逸半径一旦逃逸立即跳出循环不再加入点云。三、体素化遍历如何挖出分形表面3.1 三维网格遍历策略Mandelbulb 是三维点集无法像二维那样逐像素扫描。仓库采用体素Voxel扫描 边界提取的策略把[-1, 1]³的空间均匀切分为DIM³个小格子对每个格子中心点执行迭代判断其是否属于集合内部。以 MandelBulb_Cloud.pde 的三重循环为例for (int i 0; i DIM; i) { for (int j 0; j DIM; j) { boolean edge false; for (int k 0; k DIM; k) { float x map(i, 0, DIM, -1, 1); float y map(j, 0, DIM, -1, 1); float z map(k, 0, DIM, -1, 1); // ... 迭代 zeta若 20 次迭代后仍未逃逸则视为集合内部 } } }关键参数一览参数取值作用与影响DIM128Cloud/ 256Color/ 64Shape体素网格分辨率越大表面越精细但三重循环的计算量按立方增长n8三重数幂次决定花瓣数量与整体造型maxiterations20最大迭代次数判定是否属于集合的阈值空间范围map(i, 0, DIM, -1, 1)把体素索引映射到[-1, 1]的立方体空间逃逸半径c.r 2超过即逃逸停止迭代3.2 边界提取的巧妙之处注意三重循环的排列顺序ix与jy在外层kz在最内层且edge标志在j循环内重置。每扫描完一整条 z 轴方向上的体素线后只有首次进入集合内部的格子才会被记录if (iteration maxiterations) { if (!edge) { edge true; mandelbulb.add(new PVector(x, y, z)); } break; }这里的逻辑是只有当某个体素经 20 次迭代仍未逃逸判定为集合内部时才将其加入点云由于edge标志的存在同一条扫描线上重复命中的内部格子不会重复添加从而只保留分形表面的薄壳轮廓。正是这种仅渲染表面点云的策略让 128³ ≈ 209 万个体素中只有约 3.4 万个点被渲染仓库生成的 mandelbulb.txt 实际包含 34252 行坐标数据既保证了形状完整性又控制了绘制开销。从源码结构看这种扫线取边界的方式与体素扫描本身是耦合的如果改为只判断iteration maxiterations而不加edge标志会得到实心点云同样能看但绘制点数与耗时都会显著上升。四、绘制与交互PeasyCam 点云渲染4.1 加入 PeasyCam 实现三维漫游三个主要变体都通过 PeasyCam 库实现鼠标拖拽旋转import peasy.*; int DIM 128; PeasyCam cam; void setup() { size(600, 600, P3D); cam new PeasyCam(this, 600); // 视点距离 600 }需要 Processing 3D 渲染模式P3D否则无法显示三维点new PeasyCam(this, 600)的第二个参数是相机初始距离使用前需通过 Processing 库管理器安装 PeasyCam 库Sketch → Import Library。4.2 点云的绘制循环setup()阶段完成全部体素迭代与数据导出draw()阶段则只负责绘制保证每帧开销极低void draw() { background(0); rotateX(PI/4); rotateY(-PI/3); for (PVector v : mandelbulb) { stroke(255); strokeWeight(1); point(v.x*200, v.y*200, v.z*200); // 放大 200 倍显示 } }其中v.x*200等比例放大把[-1,1]的模型坐标映射到 600×600 画布的可视范围。rotateX(PI/4); rotateY(-PI/3);给了一个固定视角PeasyCam 的交互旋转在此基础上叠加。4.3 体素渲染的另一种形态仓库中的 Voxels.pde 展示了完全不同的可视化思路——不生成点云而是直接绘制体素立方体int DIM 16; float angle -PI/3; void draw() { translate(width/2, height/2, 100); rotateY(angle); angle 0.02; // 自动旋转 for (int i 0; i DIM; i) for (int j 0; j DIM; j) for (int k 0; k DIM; k) { float x map(i, 0, DIM, -range, range); // ... if (y x y z) { // 只绘制满足条件的体素 translate(x, y, z); box(2*range/DIM); } } }它演示了三个概念box(2*range/DIM)让相邻体素无缝拼接是体素渲染的通用写法if (y x y z)这类条件过滤可以快速裁剪掉不关心的体素区域angle 0.02实现无需交互的自动旋转动画。这段代码不涉及 Mandelbulb 迭代是视频时间轴 5:11 处用于铺垫体素概念的辅助演示可作为理解 Mandelbulb 点云渲染原理的入门对照。五、变体进阶颜色、贴图与光影5.1 按迭代次数着色的点云MandelBulb_ColorMandelBulb_Color.pde 在 Cloud 版基础上引入了MandelPoint数据结构把逃逸前的最后一次迭代次数lastIteration随坐标一起保存class MandelPoint { PVector v; float i; // 逃逸时的迭代次数 MandelPoint(PVector v, float i) { this.v v; this.i i; } }在while循环的逃逸分支记录迭代次数if (c.r 2) { lastIteration iteration; // 记录逃逸时的迭代次数 if (edge) { edge false; } break; }绘制时用 HSB 色环映射迭代次数让表面的环带结构随颜色呈现colorMode(HSB, 255); for (MandelPoint m : mandelbulb) { stroke(map(m.i, 0, maxiterations, 255, 0), 255, 255); point(m.v.x, m.v.y, m.v.z); }其中map(m.i, 0, maxiterations, 255, 0)把[0, 20]的迭代次数反向映射到[255, 0]的色相区间——迭代越慢逃逸数值越小的位置偏暖色迭代越接近 20 的位置偏冷色形成从外到内的渐变环带这正是二维 Mandelbrot 逃逸时间着色的三维对应物。5.2 用贴图点精灵代替小点MandelBulb_ShapeMandelBulb_Shape.pde 是点云 贴图的优化版把单个point()换成带纹理的四边形billboard 点精灵让每个点呈现柔和光斑效果dot loadImage(dot.png); // 320×320 径向渐变圆点贴图 for (MandelPoint m : mandelbulb) { push(); translate(m.v.x, m.v.y, m.v.z); imageMode(CENTER); image(dot, 0, 0, 400/(DIM), 400/(DIM)); // 贴图尺寸随分辨率自适应 // box(400/(DIM)); // 也可替换为立方体体素 pop(); }细节要点400/(DIM)使点精灵尺寸与体素分辨率成反比DIM 64时每个点约6.25像素密集排布后近似连续表面imageMode(CENTER)让贴图以点坐标为中心同一位置可用box(400/(DIM))替换贴图退化回体素渲染便于两种效果对比配合lights(); ambientLight(255, 100, 200);开启光照点精灵会受环境光着色产生立体感该贴图即仓库中的 dot.png是一张 320×320 的径向渐变圆形纹理。5.3 光线追踪 SDF 演示RayMarching_SDF仓库还包含一个独立的 SDF有符号距离场光线追踪演示 RayMarching_SDF.pde用于铺垫如何用 raymarching 渲染三维分形float boxSDF(PVector b, PVector r) { PVector d abs(b); d.sub(r); float m max(d, new PVector(0, 0, 0)).mag(); return m min(max(d.x, max(d.y, d.z)), 0.0); }其渲染流程注释中注明灵感来自 observablehq 的leon196/raymarch对每个像素构造归一化方向射线ray从相机位置pos (0, 0, -10)出发每步计算dist boxSDF(pos, ...)沿射线前进dist距离当dist 0.001时判定命中表面用剩余步数n/count作为明暗shade写入像素。float count 20; float shade 0; for (float n count; n 0; --n) { float dist boxSDF(pos, new PVector(2, 2, 2)); if (dist .001) { shade n / count; // 命中越早越亮 break; } pos.add(PVector.mult(ray, dist)); }此外文件中还预留了wackySDF对空间做 mod 取模后减去 1.0 的球体距离场与注释掉的rotateY(ray, ...)/rotateX(ray, ...)用于实验运动网格与射线旋转。需要说明的是该文件实现的是 SDF raymarching 的概念演示球体/立方体距离场并未包含 Mandelbulb 的完整迭代距离场是理解分形 光线追踪组合方向的起点社区贡献中出现的 Mandelbulb Ray Marching in p5.js based on shadertoy.com/view/wdjGWR 等作品即沿此路线延伸。六、参数调节指南与运行环境6.1 运行前提环境Processing 3.xJava 模式需安装 PeasyCam 库仅点云变体需要Voxels 与 RayMarching_SDF 不依赖外部库数据文件点云变体会在草图目录生成mandelbulb.txt内含x y z三列空格分隔的坐标数据格式见 MandelBulb_Cloud 的产物计算开销DIM 256意味着 256³ ≈ 1678 万次体素迭代在setup()中一次性完成首次运行会有数秒到数十秒的等待属于正常现象Color 版注释即提示注意计算量。6.2 参数调参建议目标参数调整说明提升表面精度增大DIM如 256→512分辨率每翻倍体素数变为 8 倍计算量陡增需权衡改变花瓣造型调整n如 6、8、10n决定绕 z 轴的花瓣数量与纵向分层更清晰的集合判定增大maxiterations阈值越高边界越精细但更慢点云密度/疏密调整绘制时的缩放系数如v.x*200仅影响显示尺度不影响几何观察视角修改rotateX(PI/4); rotateY(-PI/3);调整默认视角配合 PeasyCam 拖拽旋转6.3 衍生方向来自文档的 contributions 记录关联文档记录了社区在该主题上的多种衍生实现可作为继续探索的路线图p5.js 移植如 Rainbow Mandelbulb Cloud (p5js)、Mandelbulb with raymarching and shaders、Cinderella mandelbulb rendered in p5.js with phong illumination lighting把体素迭代或 SDF raymarching 迁移到浏览器端WebGL/Three.js如 Three.js (React) Mandelbulb Port using Web Worker用 Web Worker 分担计算边缘检测优化如 Mandelbulb with better edge detection改进边界提取算法以减少噪点。这些社区作品表明本挑战的核心产出幂公式、体素扫描、逃逸着色是一套可复用到任意渲染栈的分形生成管线。七、结语一条从 2D 到 3D 的完整分形管线回看整个挑战可以提炼出一条清晰的实现链路二维 Mandelbrotz² c → 三维扩展球坐标幂公式 p⁸ c → 体素网格扫描 边界提取 → 点云/体素/贴图精灵渲染 → PeasyCam 交互 逃逸次数着色 →可选SDF raymarching 光照渲染每一步在仓库中都有可运行的对应工程MandelBulb_Cloud 是最简版本MandelBulb_Color 增加迭代着色MandelBulb_Shape 增加贴图与光照Voxels 演示体素渲染概念RayMarching_SDF 铺垫光线追踪路线。若想从二维起点温习可对照 CC_021 的 p5.js 实现 与 CC_025 的球坐标几何后者与本文的球坐标转换一脉相承。推荐实验路径先以DIM 64、n 8跑通最简版再逐步提高到 128、256观察花瓣结构与边界精度随分辨率的变化随后把n改为 6 或 10对比造型差异即可直观理解幂次决定分形拓扑这一核心结论。赞分享教程文档示例工程【免费下载链接】website-archiveArchive of the Coding Train website (first version)项目地址https://gitcode.com/gh_mirrors/we/website-archive点击查看免费下载相关推荐Unity高斯点云渲染实战手册从零构建实时3D可视化系统想要在Unity中实现惊艳的实时3D点云渲染效果吗Unity Gaussian Splatting项目正是你需要的利器这个基于SIGGRAPH 2023重磅图形学3D渲染咖啡烘焙师的数字实验室5个关键功能让你从直觉烘焙迈向数据驱动咖啡烘焙师的数字实验室5个关键功能让你从直觉烘焙迈向数据驱动 你是否曾经在烘焙咖啡时只能依靠感觉来判断豆子的状态当一爆声响起时你是否会犹豫是否应该提桌面应用数据可视化数据分析UnityGaussianSplatting 完整使用指南从零开始掌握三维高斯点云渲染UnityGaussianSplatting 完整使用指南从零开始掌握三维高斯点云渲染 想要在Unity中实现令人惊艳的实时三维场景渲染吗UnityGaus图形学3D渲染上一篇ECS集群安全更新策略AMI更新与实例替换最佳实践指南下一篇React Grab 实战指南把浏览器里的 UI 元素一键变成 Agent 可直接使用的源码上下文创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
阅读完成 · 觉得有帮助?