Rössler 吸引子
打开 Rössler 吸引子实验。支持在画布上任意拖拽以变换三维观察视角;通过参数滑块可调节分岔参数 (范围 3 至 12);点击暂停/播放按钮可随时定格或恢复轨迹标记流动。
动力学系统原理
Otto Rössler 1976 年的论文提出了一个旨在用最少非线性项产生连续混沌的经典三维微分方程组:
标准初始参数为 、、。在 平面内,系统表现为振幅不断放大的平面旋转振荡;而当 超过阈值 时, 方程中的非线性项 迅速激活,将轨迹猛烈弹射至第三维度 ,随后折叠重回螺旋平面——这构成了混沌动力学中极其纯粹的“单带折叠”相空间。
改变参数 会使系统经历倍周期分岔,展现出不同结构的混沌条带与周期轨道。
四阶 Runge–Kutta(RK4)数值积分
朴素的欧拉积分具有一阶截断误差,在混沌系统中会迅速发散并抹平相空间精细结构。实现中采用步长 的经典四阶 Runge–Kutta 积分算法:
function derivative(x, y, z, {a, b, c}) {
return [-y - z, x + a * y, b + z * (x - c)];
}
function rk4Step([x, y, z], parameters) {
const {dt} = parameters;
const k1 = derivative(x, y, z, parameters);
const k2 = derivative(
x + (dt * k1[0]) / 2,
y + (dt * k1[1]) / 2,
z + (dt * k1[2]) / 2,
parameters,
);
const k3 = derivative(
x + (dt * k2[0]) / 2,
y + (dt * k2[1]) / 2,
z + (dt * k2[2]) / 2,
parameters,
);
const k4 = derivative(
x + dt * k3[0],
y + dt * k3[1],
z + dt * k3[2],
parameters,
);
return [
x + (dt * (k1[0] + 2 * k2[0] + 2 * k3[0] + k4[0])) / 6,
y + (dt * (k1[1] + 2 * k2[1] + 2 * k3[1] + k4[1])) / 6,
z + (dt * (k1[2] + 2 * k2[2] + 2 * k3[2] + k4[2])) / 6,
];
}
为滤除初始瞬态跳变,计算从 出发先执行 3,000 个预热步并丢弃,随后采样 28,000 个点,足以重构出流形完整的螺旋与折叠几何。
Three.js 几何体与光谱管线
直接用单像素线段渲染 28,000 个离散点缺乏三维纵深感与体量。实现中对采样点进行间隔抽样、空间居中,利用 Catmull–Rom 样条平滑插值并挤出为实体三维管线:
const positions = generateRosslerTrajectory({c: parameter, sampleCount: 28000});
const vertices = [];
// 每隔 6 点(一维数组步长 18)抽样一次,平衡曲面精度与顶点开销
for (let i = 0; i < positions.length; i += 18) {
vertices.push(new THREE.Vector3().fromArray(positions, i));
}
// 计算点集包围盒并居中,使三维自转轴精确穿过吸引子几何中心
const box = new THREE.Box3().setFromPoints(vertices);
const center = box.getCenter(new THREE.Vector3());
vertices.forEach((v) => v.sub(center));
const curve = new THREE.CatmullRomCurve3(vertices);
const segments = 4800;
const geometry = new THREE.TubeGeometry(curve, segments, 0.12, 5, false);
// 沿弧长赋予多色阶光谱顶点色
const SPECTRUM = ['#337dcc', '#28a5a0', '#b1b748', '#ecaa38', '#e26743', '#b766ac', '#337dcc'];
const stops = SPECTRUM.map((c) => new THREE.Color(c));
const colors = new Float32Array(geometry.attributes.position.count * 3);
const color = new THREE.Color();
for (let i = 0; i < colors.length / 3; i++) {
const t = Math.floor(i / 6) / segments * (stops.length - 1);
const k = Math.min(Math.floor(t), stops.length - 2);
color.copy(stops[k]).lerp(stops[k + 1], t - k).toArray(colors, i * 3);
}
geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3));
const material = new THREE.MeshBasicMaterial({vertexColors: true});
颜色循序渐变对应沿轨迹的时间先后顺序,帮助读者直观分辨相空间中的流向。
实例化流动标记与高对比外框
为展现相空间流动,十二个高光光斑沿着插值曲线做匀速弧长运动。实现避免了重复创建十二组 Mesh,而是通过两组 THREE.InstancedMesh 一次性完成批量渲染:
- 内部发光小球(
color: '#fff2bc')。 - 外部深色剪影外壳(
color: '#191a16', side: THREE.BackSide,放大 1.4 倍)。
const beadCount = 12;
const beadGeometry = new THREE.SphereGeometry(0.36, 12, 8);
const beadMaterial = new THREE.MeshBasicMaterial({color: '#fff2bc'});
const outlineMaterial = new THREE.MeshBasicMaterial({color: '#191a16', side: THREE.BackSide});
const beads = new THREE.InstancedMesh(beadGeometry, beadMaterial, beadCount);
const outlines = new THREE.InstancedMesh(beadGeometry, outlineMaterial, beadCount);
// 标记需连续游走整条曲线,禁用视锥体剔除以免边界误判
beads.frustumCulled = false;
outlines.frustumCulled = false;
反向法线的暗色描边确保光斑在浅色主题、深色主题以及管线高亮区重叠时,均拥有清晰的视觉边界。
性能限制与资源释放
- 控帧节流: 通过时间戳限制最高 30 FPS 渲染,避免高刷显示器上空转 GPU。
- 离屏停动: 容器滚出视口或切后台时,彻底暂停动画帧请求。
- 内存回收: 组件卸载或参数重算时,显式调用几何体、材质与实例化矩阵的
.dispose(),并强制释放 WebGL 上下文。