我正在尝试在 js 中制作动画柏林噪声模拟,但遇到了平滑函数的一些问题。
这是我的js代码(我删除了一些不相关的代码以便于阅读)
代码制作了一个带有 n 网格的盒子,里面有 n 块。每个网格有四个向量。
calculate
函数计算每个块与该块所在网格的四个向量的点积。
var GRIDSIZE = 0;
const VECTOR_COUNT = 3;
const DETAIL = 50;
const MARGIN = 100;
var FADE = .5;
const MAX_ROTATIONSPEED = 0.03;
const MIN_ROTATIONSPEED = -MAX_ROTATIONSPEED;
var vectorList = [];
var BLOCK_LIST = [];
var blockList = [];
// Set up main grid
mainEl.style.width = GRIDSIZE + "px";
mainEl.style.height = GRIDSIZE + "px";
mainEl.style.position = "absolute";
mainEl.style.left = window.innerWidth / 2 - mainEl.offsetWidth / 2 + "px";
mainEl.style.top = window.innerHeight / 2 - mainEl.offsetHeight / 2 + "px";
if(window.innerHeight - MARGIN > window.innerWidth - MARGIN) {
GRIDSIZE = window.innerWidth - MARGIN;
} else {
GRIDSIZE = window.innerHeight - MARGIN;
}
mainEl.style.width = GRIDSIZE + "px";
mainEl.style.height = GRIDSIZE + "px";
mainEl.style.left = window.innerWidth / 2 - mainEl.offsetWidth / 2 + "px";
mainEl.style.top = window.innerHeight / 2 - mainEl.offsetHeight / 2 + "px";
// functions
var DOT = (angle, x, y) => x * Math.cos(angle) + y * Math.sin(angle);
var lerp = (a, b, t) => a + (b - a) * t;
var fade = (t) => t * t * t * (t * (t * 6 - 15) + 10);
var normalize = (value, max, min) => (value - min) / (max - min);
var hypotenuse = (x, y) => Math.sqrt(x*x + y*y);
// Setup
setUpVectors();
setUpBlocks();
// console.log(BLOCK_LIST);
var calculate = () => {
var VECTOR_GRID = [];
vectorList.forEach((vector, i) => {
if(i % VECTOR_COUNT < VECTOR_COUNT-1 && Math.floor(i / VECTOR_COUNT) < VECTOR_COUNT-1) {
var v0 = vector;
var v1 = vectorList[i+1];
var v2 = vectorList[i+VECTOR_COUNT];
var v3 = vectorList[i+VECTOR_COUNT+1];
var trueV0 = {
x: v0.tipX / Math.sqrt(v0.tipX*v0.tipX + v0.tipY*v0.tipY),
y: v0.tipY / Math.sqrt(v0.tipX*v0.tipX + v0.tipY*v0.tipY),
angle: v0.angle
}
var trueV1 = {
x: v1.tipX / Math.sqrt(v1.tipX*v1.tipX + v1.tipY*v1.tipY),
y: v1.tipY / Math.sqrt(v1.tipX*v1.tipX + v1.tipY*v1.tipY),
angle: v1.angle
}
var trueV2 = {
x: v2.tipX / Math.sqrt(v2.tipX*v2.tipX + v2.tipY*v2.tipY),
y: v2.tipY / Math.sqrt(v2.tipX*v2.tipX + v2.tipY*v2.tipY),
angle: v2.angle
}
var trueV3 = {
x: v3.tipX / Math.sqrt(v3.tipX*v3.tipX + v3.tipY*v3.tipY),
y: v3.tipY / Math.sqrt(v3.tipX*v3.tipX + v3.tipY*v3.tipY),
angle: v3.angle
}
VECTOR_GRID.push([trueV0, trueV1, trueV2, trueV3]);
// console.log(trueV0, trueV1, trueV2, trueV3);
}
});
var state = 0;
var dotList0 = []; //dot product for each blocks top left corner
var dotList1 = []; //dot product for each blocks top right corner
var dotList2 = []; //dot product for each blocks bottom left corner
var dotList3 = []; //dot product for each blocks bottom right corner
var DOT_LIST = [];
var state = 0;
VECTOR_GRID.forEach((vector, i) => {
vector.forEach((v, j) => {
for(var k = 0; k < BLOCK_LIST[i].length; k++) {
if(j === 0) {
var x = BLOCK_LIST[0][k].senterX * (GRIDSIZE/100)
var y = BLOCK_LIST[0][k].senterY * (GRIDSIZE/100)
var trueB = {
x: x / hypotenuse(x, y),
y: y / hypotenuse(x, y)
}
dotList0.push(DOT(vector[j].angle, trueB.x, trueB.y));
} else if(j === 1) {
var x = GRIDSIZE/(VECTOR_COUNT-1) - BLOCK_LIST[0][k].senterX * (GRIDSIZE/100)
var y = BLOCK_LIST[0][k].senterY * (GRIDSIZE/100)
// console.log(x, y);
var trueB = {
x: -x / hypotenuse(x, y),
y: y / hypotenuse(x, y)
}
dotList1.push(DOT(vector[j].angle, trueB.x, trueB.y));
} else if(j === 2) {
var x = BLOCK_LIST[0][k].senterX * (GRIDSIZE/100)
var y = GRIDSIZE/(VECTOR_COUNT-1) - BLOCK_LIST[0][k].senterY * (GRIDSIZE/100)
var trueB = {
x: x / hypotenuse(x, y),
y: -y / hypotenuse(x, y)
}
dotList2.push(DOT(vector[j].angle, trueB.x, trueB.y));
} else if(j === 3) {
var x = GRIDSIZE/(VECTOR_COUNT-1) - BLOCK_LIST[0][k].senterX * (GRIDSIZE/100)
var y = GRIDSIZE/(VECTOR_COUNT-1) - BLOCK_LIST[0][k].senterY * (GRIDSIZE/100)
var trueB = {
x: -x / hypotenuse(x, y),
y: -y / hypotenuse(x, y)
}
dotList3.push(DOT(vector[j].angle, trueB.x, trueB.y));
}
}
});
});
var DOT_LIST = [dotList0, dotList1, dotList2, dotList3];
var colorList = [];
for (var k = 0; k < DOT_LIST[0].length; k++) {
var dot01 = lerp(DOT_LIST[0][k], DOT_LIST[1][k], fade(FADE));
var dot23 = lerp(DOT_LIST[2][k], DOT_LIST[3][k], fade(FADE));
var dot0123 = lerp(dot01, dot23, fade(FADE));
// console.log(dot0123);
var color = Math.floor(normalize(dot0123, 1, -1) * 255);
colorList.push(color);
}
ALL_BLOCKS.forEach((block, i) => {
block.block.style.backgroundColor = `rgb(${colorList[i]},${colorList[i]},${colorList[i]})`;
});
};
function animate() {
updateVectors();
updateMainEl();
calculate();
requestAnimationFrame(animate);
}
animate();
现在每个网格都会产生可信的梯度,但网格应该彼此“连接”,但它并没有这样做。我怀疑我的淡入淡出功能不起作用。
为了平滑地跨网格连接渐变,您当前的淡入淡出函数 (t * t * t * (t * (t * 6 - 15) + 10)) 用作缓动曲线,但您可能无法跨网格边界正确应用它.
确保应用的淡入淡出值在网格边界上保持一致。问题似乎在于 FADE 是一个常数值 (0.5),它不会根据每个块在其网格内的位置进行调整。试试这个:
使用网格内标准化的 x 和 y 坐标计算每个块的淡入度值。 动态地对这些值应用 fade(),以便每个块根据位置平滑地进行插值。 这是一个可能的修改:
var fade = (t) => t * t * t * (t * (t * 6 - 15) + 10);
for (var k = 0; k < DOT_LIST[0].length; k++) {
let blockX = BLOCK_LIST[0][k].senterX / GRIDSIZE; // normalized x
let blockY = BLOCK_LIST[0][k].senterY / GRIDSIZE; // normalized y
let fadeX = fade(blockX);
let fadeY = fade(blockY);
let dot01 = lerp(DOT_LIST[0][k], DOT_LIST[1][k], fadeX);
let dot23 = lerp(DOT_LIST[2][k], DOT_LIST[3][k], fadeX);
let dot0123 = lerp(dot01, dot23, fadeY);
let color = Math.floor(normalize(dot0123, 1, -1) * 255);
colorList.push(color);
}
这使用块的位置来计算淡入淡出,确保网格上的无缝渐变。希望这有帮助。谢谢。
如果引入二维向量构造,即
Vector2D
,则可以使颜色列表逻辑更容易理解。一旦你做到了这一点,计算点积就变得非常简单。
在矢量和块的归一化中心之间插入淡入淡出,然后根据矢量的大小(长度)计算点积。
const calculate = () => {
const colorList = BLOCK_LIST.map((block, k) => {
const normalizedCenter = block.center.normalize();
const vector = vectorList[k];
const lerped = vector.lerp(normalizedCenter, fade(FADE));
const dotProduct = lerped.dot(vector.magnitude());
return Math.floor(normalize(dotProduct, 1, -1) * 255);
});
BLOCK_LIST.forEach((blockObj, i) => {
const color = `rgb(${new Array(3).fill(colorList[i]).join(',')})`;
blockObj.block.style.backgroundColor = color;
});
};
这是使用
Vector2D
类的演示。
// Utility functions
const fade = (t) => t ** 3 * (t * (t * 6 - 15) + 10);
const lerp = (a, b, t) => a + (b - a) * t;
const normalize = (value, max, min) => (value - min) / (max - min);
// A two-dimensional vector class
class Vector2D {
constructor(x, y) {
this.x = x;
this.y = y;
}
magnitude() {
return Math.hypot(this.x, this.y);
}
normalize() {
const mag = this.magnitude();
return mag === 0 ? new Vector2D(0, 0) : new Vector2D(this.x / mag, this.y / mag);
}
dot(angle) {
return this.x * Math.cos(angle) + this.y * Math.sin(angle);
}
lerp(target, t) {
return new Vector2D(
lerp(this.x, target.x, t),
lerp(this.y, target.y, t)
);
}
static rand(maxX, maxY) {
return new Vector2D(
Math.random() * maxX,
Math.random() * maxY
);
}
}
// Select the main element on the page
const mainEl = document.querySelector("#mainEl");
// Constants
const VECTOR_COUNT = 10;
const MARGIN = 50;
const FADE = 0.5;
// State
let GRIDSIZE = 300;
const vectorList = [];
const BLOCK_LIST = [];
const setupMainEl = () => {
GRIDSIZE = Math.min(window.innerWidth, window.innerHeight) - MARGIN;
// Use a grid layout
Object.assign(mainEl.style, {
width: `${GRIDSIZE}px`,
height: `${GRIDSIZE}px`,
position: 'absolute',
left: `${(window.innerWidth - GRIDSIZE) / 2}px`,
top: `${(window.innerHeight - GRIDSIZE) / 2}px`,
display: 'grid',
gridTemplateColumns: `repeat(${VECTOR_COUNT}, 1fr)`
});
};
const setUpVectors = () => {
for (let i = 0; i < VECTOR_COUNT ** 2; i++) {
const angle = Math.random() * 2 * Math.PI;
vectorList.push(new Vector2D(Math.cos(angle), Math.sin(angle)));
}
};
const setUpBlocks = () => {
const blockSize = GRIDSIZE / VECTOR_COUNT;
for (let i = 0; i < VECTOR_COUNT ** 2; i++) {
const block = document.createElement('div');
Object.assign(block.style, {
width: `${blockSize}px`,
height: `${blockSize}px`,
backgroundColor: 'rgb(0, 0, 0)'
});
BLOCK_LIST.push({
block,
center: Vector2D.rand(GRIDSIZE, GRIDSIZE)
});
mainEl.appendChild(block);
}
};
const calculate = () => {
const colorList = BLOCK_LIST.map((block, k) => {
const normalizedCenter = block.center.normalize();
const vector = vectorList[k];
const lerped = vector.lerp(normalizedCenter, fade(FADE));
const dotProduct = lerped.dot(vector.magnitude());
return Math.floor(normalize(dotProduct, 1, -1) * 255);
});
BLOCK_LIST.forEach((blockObj, i) => {
const color = `rgb(${new Array(3).fill(colorList[i]).join(',')})`;
blockObj.block.style.backgroundColor = color;
});
};
const onResize = () => {
setupMainEl();
setUpBlocks();
};
function animate() {
calculate();
requestAnimationFrame(animate);
}
setupMainEl();
setUpVectors();
setUpBlocks();
window.addEventListener("resize", onResize);
animate();
<div id="mainEl"></div>