使用p5.js的曼德布洛特套装的平滑颜色

Smooth color for mandelbrot set using p5.js

提问人:Loyown 提问时间:11/11/2023 最后编辑:Paul WheelerLoyown 更新时间:11/13/2023 访问量:49

问:

我和p5.js一起玩,试图重现曼德布洛特的集合。 我已经创建了套装本身,但我在以正确的方式着色方面遇到了问题。

我已经跟进了 wiki 页面中的伪代码,但我无法用正确的颜色着色。

这是我写的p5.js代码:

let slider;

function setup() {
  createCanvas(400, 400);
  background(51)
  colorMode(HSL, 360, 100, 100)
  slider = createSlider(10, 1000, 500, 1)
}

let k = 0;

function draw() {
  loadPixels()

  for (let i = 0; i < width; i++) {
    for (let j = 0; j < height; j++) {
      let index = (i + j * width) * 4
      let x0 = map(i, 0, width, -2.0, 0.47);
      let y0 = map(j, 0, height, -1.12, 1.12);

      let x = 0;
      let y = 0;

      let iteration = 0;
      let maxIteration = slider.value();

      while (magn(x, y) <= 4 && iteration < maxIteration) {
        let xTemp = x * x - y * y + x0;
        y = 2 * x * y + y0;
        x = xTemp;
        iteration++

      }

      pixels[index + 0] = pow(iteration / maxIteration * 360, 1.5) % 360
      pixels[index + 1] = 50
      pixels[index + 2] = (iteration / maxIteration) * 100

    }
  }
  
  updatePixels()
}

function magn(a, b) {
  return a * a + b * b;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.8.0/p5.js"></script>

这是生成的图像:

mandelbrot set

图像本身还不错,但我不明白为什么我得到这种绿色的背景,而且我无法在 wiki 页面中显示颜色。

我想要一个类似于这个家伙的效果,但我无法用 c 代码理解它。

JavaScript p5.js 复数 曼德布洛特

评论

0赞 Weather Vane 11/13/2023
没有一种“正确”的方法来给它着色,但我使用的一种方法是准备一个颜色值数组,用于每个“迭代深度”模组“数组的长度”(作为 C 链接中的第二张图片)。另一种方法(如 C 示例中的第一张图所示)是准备一个可以索引迭代限制的数组,该数组可能具有非线性关系。
0赞 George Profenza 11/13/2023
@Loyown保罗的回答很棒(+1)!关于 GLSL 代码,如果您想通过 WebGL 尝试它,您可以通过 shader() p5.js。HTH

答:

1赞 Paul Wheeler 11/13/2023 #1

在链接到的代码中,着色器(第一个代码片段是 GLSL 而不是 C)根据当前种子点转义所需的最大迭代次数的分数从一维纹理中获取颜色值。因此,在不知道他们如何生成构成该纹理的颜色的情况下,很难推断出他们正在使用的算法。

但是,在您的代码中,存在一些可识别的问题:1) 像素数组始终使用 RGB,无论颜色模式如何,2) 当您使用高 maxIterations 值时,您看到的许多点最终都具有非常相似的低值。这是因为在查看缩小的曼德布洛特集时,许多不是很接近边界的点在经过几次迭代后就会逃逸,因此所有这些点最终都具有非常相似(且非常暗)的颜色。

因此,要解决此问题,您需要手动将所需的 HSV 值转换为 RGB 以在像素数组中使用,并减少默认的 maxIterations 值。

let slider;

function setup() {
  createCanvas(400, 400);
  pixelDensity(1)
  background(51)
  // This had no effect
  // colorMode(HSL, 360, 100, 100)
  
  // Decreased the default to 80 instead of 500
  slider = createSlider(10, 1000, 80, 1)
  
  // Avoid unnecessary re-renders
  slider.input(() => {
    redraw();
  });
  noLoop();
}

let k = 0;

function draw() {
  loadPixels()

  for (let i = 0; i < width; i++) {
    for (let j = 0; j < height; j++) {
      let index = (i + j * width) * 4
      let x0 = map(i, 0, width, -2.0, 0.47);
      let y0 = map(j, 0, height, -1.12, 1.12);

      let x = 0;
      let y = 0;

      let iteration = 0;
      let maxIteration = slider.value();

      while (magn(x, y) <= 4 && iteration < maxIteration) {
        let xTemp = x * x - y * y + x0;
        y = 2 * x * y + y0;
        x = xTemp;
        iteration++

      }
      
      if (iteration < maxIteration) {
        let [r, g, b] = hsv2rgb(
          pow(iteration / maxIteration * 360, 1.5) % 360,
          0.5,
          (iteration / maxIteration) * 0.8 + 0.2
        );

        pixels[index + 0] = r * 255;
        pixels[index + 1] = g * 255;
        pixels[index + 2] = b * 255;
      } else {
        pixels[index + 0] = 0;
        pixels[index + 1] = 0;
        pixels[index + 2] = 0;
      }

    }
  }
  
  updatePixels()
}

function magn(a, b) {
  return a * a + b * b;
}

// Source: https://stackoverflow.com/a/54024653/229247
// Author: Kamil Kiełczewski
// CC-BY-SA 4.0 
function hsv2rgb(h, s, v) {                              
  function f(n) {
    const k = (n + h / 60) % 6;
    return v - v * s * Math.max(Math.min(k, 4 - k, 1), 0);     
  }
  return [f(5),f(3),f(1)];       
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.8.0/p5.js"></script>