柏林噪声中的平滑函数

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

我正在尝试在 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();

现在每个网格都会产生可信的梯度,但网格应该彼此“连接”,但它并没有这样做。我怀疑我的淡入淡出功能不起作用。

javascript css perlin-noise
2个回答
0
投票

为了平滑地跨网格连接渐变,您当前的淡入淡出函数 (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);
}

这使用块的位置来计算淡入淡出,确保网格上的无缝渐变。希望这有帮助。谢谢。


0
投票

如果引入二维向量构造,即

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>

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