用两个预定义的替换随机生成的(熔岩灯的)动画调色板?

问题描述 投票:0回答:1

所以我一直在关注一个 博客,它详细介绍了 vanilla js+html lava/plasma canvas 动画的创建。它使用 pixelBuffer,并通过 requestAnimationFrame 和 tick 进行动画处理。

最终代码是为 2 个调色板生成随机颜色,但我想要 2 个预定义的调色板,因为我只想要 3 种主要颜色,并且它们的阴影可以交互/混合/等离子...

所以,我尝试了一些简单的编辑(将包括下面的之前和之后),并且在保存/更新代码时 - 我只是得到白屏。我不完全理解代码,但我不清楚问题出在哪里。那么有人可以指出来,或者问我主要问题,你会在哪里寻找解决办法?

codesandbox 中的之前/原始/随机颜色代码:https://codesandbox.io/s/crazy-surf-jci4z?file=/index.html

我不能分享我的,因为它需要专业版,但我唯一改变的地方(见“颜色助手”部分原始代码的结尾)是删除 randomColour 函数、makeRandomPalette 函数、makeFiveColourGradient 和 const 调色板以下:

我知道这是不对的,并且可以看到一些我没有编辑的错误,但我想弄清楚我的思考方向是否正确。也许对其他人来说很明显,一些预定义的调色板是如何工作的。

// color helpers

    const interpolate = (c1, c2, f) => {
      return {
        r: Math.floor(c1.r + (c2.r - c1.r) * f),
        g: Math.floor(c1.g + (c2.g - c1.g) * f),
        b: Math.floor(c1.b + (c2.b - c1.b) * f)
      };
    };

    // random color was returned here before

    const palette1 = [
      { r: 255, g: 255, b: 255 },
      { r: 255, g: 0, b: 0 },
      { r: 0, g: 255, b: 0 },
      { r: 0, g: 0, b: 255 },
      { r: 255, g: 255, b: 0 }
    ];

    const palette2 = [
      { r: 0, g: 0, b: 0 },
      { r: 255, g: 255, b: 255 },
      { r: 255, g: 0, b: 0 },
      { r: 0, g: 255, b: 0 },
      { r: 0, g: 0, b: 255 }
    ];

    // offsets for moving height maps
    let dx1 = 0;
    let dy1 = 0;

    let dx2 = 0;
    let dy2 = 0;

    // adjust height maps offsets
    const moveHeightMaps = (t) => {
      dx1 = Math.floor(
        (((Math.cos(t * 0.0002 + 0.4 + Math.PI) + 1) / 2) * mapSize) / 2
      );
      dy1 = Math.floor((((Math.cos(t * 0.0003 - 0.1) + 1) / 2) * mapSize) / 2);
      dx2 = Math.floor((((Math.cos(t * -0.0002 + 1.2) + 1) / 2) * mapSize) / 2);
      dy2 = Math.floor(
        (((Math.cos(t * -0.0003 - 0.8 + Math.PI) + 1) / 2) * mapSize) / 2
      );
    };

    // two palettes we interpolate between
    const palettes = [palette1, palette2];

    // current palette is edstablished durting animation
    let palette = [];

    // stores whether we're interpolating colors
    // from palette 0 -> 1 (1) or 1 -> 0 (-1)
    let prevDirection = 1;

    const updatePalette = (t) => {
      const timeScale = 0.0005;
      const x = t * timeScale;

      // normalized value 0..1 used to interpolate palette colors
      const inter = (Math.cos(x) + 1) / 2;

      // did we switch direction, and should ergo pick a new palette
      // random palette to interpolate towards?

      const direction = -Math.sin(x) >= 0 ? 1 : -1;
      if (prevDirection != direction) {
        prevDirection = direction;
        if (direction == -1) {
          palettes[0] = makeRandomPalette();
        } else {
          palettes[1] = makeRandomPalette();
        }
      }

      // create interpolated palette for current frame
      for (let i = 0; i < 256; i++) {
        palette[i] = interpolate(palettes[0][i], palettes[1][i], inter);
      }
    };

    const updateImageData = () => {
      for (let u = 0; u < imgSize; u++) {
        for (let v = 0; v < imgSize; v++) {
          // indexes into height maps for pixel
          const i = (u + dy1) * mapSize + (v + dx1);
          const k = (u + dy2) * mapSize + (v + dx2);

          // index for pixel in image data
          // remember it's 4 bytes per pixel
          const j = u * imgSize * 4 + v * 4;

          // height value of 0..255
          let h = heightMap1[i] + heightMap2[k];
          // get color value from current palette
          let c = palette[h];

          // h = heightMap2[i];
          // c = { r: h, g: h, b: h };

          // set pixel data
          image.data[j] = c.r;
          image.data[j + 1] = c.g;
          image.data[j + 2] = c.b;
        }
      }
    };

    // helper to create a linear gradient palette
    const linearGradient = (c1, c2) => {
      const g = [];

      // interpolate between the colors
      // in the gradient

      for (let i = 0; i < 256; i++) {
        const f = i / 255;
        g[i] = interpolate(c1, c2, f);
      }

      return g;
    };

    const tick = (time) => {
      moveHeightMaps(time);
      updatePalette(time);
      updateImageData();

      c.putImageData(image, 0, 0);

      requestAnimationFrame(tick);
    };

感谢任何指出我正确方向的人。

animation math html5-canvas requestanimationframe
1个回答
0
投票

要了解发生了什么,我们需要检查原始代码在做什么。

const palettes = [makeRandomPalette(), makeRandomPalette()];

这段代码调用

makeRandomPalette()
函数两次,将返回的任何内容推送到
palettes
数组中。

所以如果我们查找

makeRandomPalette()
函数定义:

 const makeRandomPalette = () => {
      const c1 = randomColor();
      const c2 = randomColor();
      const c3 = randomColor();
      const c4 = randomColor();
      const c5 = randomColor();

      return makeFiveColorGradient(c1, c2, c3, c4, c5);
    };

我们可以看到它用

c1
函数返回的rgb颜色对象填充了5个常量
c5
-
randomColor()
并且将这些常量发送到另一个名为
makeFiveColorGradient()
的函数,该函数采用5种颜色并将其转换为数组256 种颜色值 - 这是最终的调色板。该数组实际上被推入了
palettes
数组,并且在您的编辑中完全缺失。

你的东西有点像这样:

const palette1 = [
      { r: 255, g: 255, b: 255 },
      { r: 255, g: 0, b: 0 },
      { r: 0, g: 255, b: 0 },
      { r: 0, g: 0, b: 255 },
      { r: 255, g: 255, b: 0 }
    ];

    const palette2 = [
      { r: 0, g: 0, b: 0 },
      { r: 255, g: 255, b: 255 },
      { r: 255, g: 0, b: 0 },
      { r: 0, g: 255, b: 0 },
      { r: 0, g: 0, b: 255 }
    ];

const palettes = [palette1, palette2];

所以您只是将 5 个颜色值的数组推入

palettes
数组,而不用
makeFiveColorGradient()
函数处理它们。

这是您的固定密码:

const canvas = document.getElementById("canvas");
const c = canvas.getContext("2d");

// size of canvas
const imgSize = 512;

canvas.width = imgSize;
canvas.height = imgSize;

// init image data with black pixels
const image = c.createImageData(imgSize, imgSize);
for (let i = 0; i < image.data.length; i += 4) {
  image.data[i] = 0; // R
  image.data[i + 1] = 0; // G
  image.data[i + 2] = 0; // B
  image.data[i + 3] = 255; // A
}

// size of our height maps
const mapSize = 1024;

// returns the distance of point x,y from the origin 0,0
const distance = (x, y) => Math.sqrt(x * x + y * y);

// init height map 1
const heightMap1 = [];
for (let u = 0; u < mapSize; u++) {
  for (let v = 0; v < mapSize; v++) {
    // index of coordinate in height map array
    const i = u * mapSize + v;

    // u,v are coordinates with origin at upper left corner
    // cx and cy are coordinates with origin at the
    // center of the map
    const cx = u - mapSize / 2;
    const cy = v - mapSize / 2;

    // distance from middle of map
    const d = distance(cx, cy);

    // stretching so we get the desired ripple density on our map
    const stretch = (3 * Math.PI) / (mapSize / 2);

    // wavy height value between -1 and 1
    const ripple = Math.sin(d * stretch);

    // wavy height value normalized to 0..1
    const normalized = (ripple + 1) / 2;

    // height map value 0..128, integer
    heightMap1[i] = Math.floor(normalized * 128);
  }
}

const heightMap2 = [];
for (let u = 0; u < mapSize; u++) {
  for (let v = 0; v < mapSize; v++) {
    const i = u * mapSize + v;
    const cx = u - mapSize / 2;
    const cy = v - mapSize / 2;

    // skewed distance as input to chaos field calculation,
    // scaled for smoothness over map distance
    const d1 = distance(0.8 * cx, 1.3 * cy) * 0.022;
    const d2 = distance(1.35 * cx, 0.45 * cy) * 0.022;

    const s = Math.sin(d1);
    const c = Math.cos(d2);
    // height value between -2 and +2
    const h = s + c;

    // height value between 0..1
    const normalized = (h + 2) / 4;
    // height value between 0..127, integer
    heightMap2[i] = Math.floor(normalized * 127);
  }
}

// color helpers

const interpolate = (c1, c2, f) => {
  return {
    r: Math.floor(c1.r + (c2.r - c1.r) * f),
    g: Math.floor(c1.g + (c2.g - c1.g) * f),
    b: Math.floor(c1.b + (c2.b - c1.b) * f)
  };
};

// returns a random color
const randomColor = () => {
  const r = Math.floor(Math.random() * 255);
  const g = Math.floor(Math.random() * 255);
  const b = Math.floor(Math.random() * 255);
  return {
    r,
    g,
    b
  };
};

// returns a random color palette with 256 color entries
const makeRandomPalette = () => {
  const c1 = randomColor();
  const c2 = randomColor();
  const c3 = randomColor();
  const c4 = randomColor();
  const c5 = randomColor();

  return makeFiveColorGradient(c1, c2, c3, c4, c5);
};

const generatePalette = (palette) => {
  return makeFiveColorGradient(...palette);
};

const makeFiveColorGradient = (c1, c2, c3, c4, c5) => {
  const g = [];

  for (let i = 0; i < 64; i++) {
    const f = i / 64;
    g[i] = interpolate(c1, c2, f);
  }

  for (let i = 64; i < 128; i++) {
    const f = (i - 64) / 64;
    g[i] = interpolate(c2, c3, f);
  }

  for (let i = 128; i < 192; i++) {
    const f = (i - 128) / 64;
    g[i] = interpolate(c3, c4, f);
  }

  for (let i = 192; i < 256; i++) {
    const f = (i - 192) / 64;
    g[i] = interpolate(c4, c5, f);
  }

  return g;
};

// offsets for moving height maps
let dx1 = 0;
let dy1 = 0;

let dx2 = 0;
let dy2 = 0;

// adjust height maps offsets
const moveHeightMaps = t => {
  dx1 = Math.floor(
    (((Math.cos(t * 0.0002 + 0.4 + Math.PI) + 1) / 2) * mapSize) / 2
  );
  dy1 = Math.floor((((Math.cos(t * 0.0003 - 0.1) + 1) / 2) * mapSize) / 2);
  dx2 = Math.floor((((Math.cos(t * -0.0002 + 1.2) + 1) / 2) * mapSize) / 2);
  dy2 = Math.floor(
    (((Math.cos(t * -0.0003 - 0.8 + Math.PI) + 1) / 2) * mapSize) / 2
  );
};

const palette1 = [{
    r: 255,
    g: 255,
    b: 255
  },
  {
    r: 255,
    g: 0,
    b: 0
  },
  {
    r: 0,
    g: 255,
    b: 0
  },
  {
    r: 0,
    g: 0,
    b: 255
  },
  {
    r: 255,
    g: 255,
    b: 0
  }
];

const palette2 = [{
    r: 0,
    g: 0,
    b: 0
  },
  {
    r: 255,
    g: 255,
    b: 255
  },
  {
    r: 255,
    g: 0,
    b: 0
  },
  {
    r: 0,
    g: 255,
    b: 0
  },
  {
    r: 0,
    g: 0,
    b: 255
  }
];

// two palettes we interpolate between
const palettes = [generatePalette(palette1), generatePalette(palette2)];

// current palette is edstablished durting animation
let palette = [];

// stores whether we're interpolating colors
// from palette 0 -> 1 (1) or 1 -> 0 (-1)
let prevDirection = 1;

const updatePalette = t => {
  const timeScale = 0.0005;
  const x = t * timeScale;

  // normalized value 0..1 used to interpolate palette colors
  const inter = (Math.cos(x) + 1) / 2;

  // did we switch direction, and should ergo pick a new palette
  // random palette to interpolate towards?

  const direction = -Math.sin(x) >= 0 ? 1 : -1;
  if (prevDirection != direction) {
    prevDirection = direction;
    if (direction == -1) {
      palettes[0] = generatePalette(palette1);
    } else {
      palettes[1] = generatePalette(palette2);
    }
  }

  // create interpolated palette for current frame
  for (let i = 0; i < 256; i++) {
    palette[i] = interpolate(palettes[0][i], palettes[1][i], inter);
  }
};

const updateImageData = () => {
  for (let u = 0; u < imgSize; u++) {
    for (let v = 0; v < imgSize; v++) {
      // indexes into height maps for pixel
      const i = (u + dy1) * mapSize + (v + dx1);
      const k = (u + dy2) * mapSize + (v + dx2);

      // index for pixel in image data
      // remember it's 4 bytes per pixel
      const j = u * imgSize * 4 + v * 4;

      // height value of 0..255
      let h = heightMap1[i] + heightMap2[k];
      // get color value from current palette
      let c = palette[h];

      // h = heightMap2[i];
      // c = { r: h, g: h, b: h };

      // set pixel data
      image.data[j] = c.r;
      image.data[j + 1] = c.g;
      image.data[j + 2] = c.b;
    }
  }
};

// helper to create a linear gradient palette
const linearGradient = (c1, c2) => {
  const g = [];

  // interpolate between the colors
  // in the gradient

  for (let i = 0; i < 256; i++) {
    const f = i / 255;
    g[i] = interpolate(c1, c2, f);
  }

  return g;
};

const tick = time => {
  moveHeightMaps(time);
  updatePalette(time);
  updateImageData();

  c.putImageData(image, 0, 0);

  requestAnimationFrame(tick);
};

requestAnimationFrame(tick);
<canvas id="canvas" style="width: 100%; height: 100%; padding: 0;margin: 0;"></canvas>

© www.soinside.com 2019 - 2024. All rights reserved.