所以我一直在关注一个 博客,它详细介绍了 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);
};
感谢任何指出我正确方向的人。
要了解发生了什么,我们需要检查原始代码在做什么。
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>