柏林噪声&双线性插值初步了解(js)

效果

在这里插入图片描述

js代码

可直接执行,不做过多解释,了解更多按照顺序点击最后面的参考链接

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>双线性插值</title>
</head>
<body>
<canvas id="cv" width="1400px" height="1400px"></canvas>
<script>
  /*==================================工具==================================*/
  /*
  *柏林函数
  */
  function fade(t) {
    return t * t * t * (t * (t * 6 - 15) + 10)
  }
  /*
   *插值,默认使用柏林函数作为平滑函数
   */
  function lerp(a, b, weight, type) {
    //根据不同的type 选择不同的平滑方式
    if (!type) {
      return a + (b - a) * fade(weight);
    }
    if (type == 1) {
      return a + (b - a) * weight
    }
    if (type == 2) {
      return a + (b - a) * lerp(Math.sin(weight), fade(weight), 0.5, 1)
    }
  }

  /**
   * 向量点乘
   */
  function dp(v1, v2) {
    return v1.x * v2.x + v1.y * v2.y
  }

  /**
   * 区间映射,不考虑错误的使用方式
   */
  function mirror(data, fa, fb, ta, tb) {
    return ta + (data - fa) / (fb - fa) * (tb - ta)
  }

  /**
   * 随机二维数
   */
  function hash22(x, y) {
    let arr = [];
    arr[0] = x * 127.1 + y * 311.7;
    arr[1] = x * 269.5 + y * 183.3;

    let sin0 = Math.sin(arr[0]) * 43758.5453123;
    let sin1 = Math.sin(arr[1]) * 43758.5453123;
    arr[0] = (sin0 - Math.floor(sin0)) * 2.0 - 1.0;
    arr[1] = (sin1 - Math.floor(sin1)) * 2.0 - 1.0;

    // 归一化,尽量消除正方形的方向性偏差
    let len = Math.sqrt(arr[0] * arr[0] + arr[1] * arr[1]);
    arr[0] /= len;
    arr[1] /= len;

    return {x: arr[0], y: arr[1]};
  }


  /**
   * 生成指定晶格顶点的随机向量
   * @param x 横向多少
   * @param y 纵向多少
   */
  let cds = [];// CrystalDicts
  function loadCrystal(x, y) {
    if (cds[x] && cds[x][y]) {
      return cds[x][y]
    } else {
      if (!cds[x]) {
        cds[x] = [];
      }
      return cds[x][y] = {
        x: x,
        y: y,
        vector: {//晶格顶点的随机,每次随机后缓存
          x: mirror(Math.random() * 2, 0, 2, -1, 1),
          y: mirror(Math.random() * 2, 0, 2, -1, 1),
        }
      }
    }
  }


  //声明画板
  let el = document.getElementById('cv');
  let context = el.getContext("2d");

  //主函数
  function loadTwo() {
    let cel_num_x = 10,//横向晶格数量
      cel_num_y = 10,//纵向晶格数量
      split = 10,//每个晶格绘制/插入 像素的数量
      pixel_size = 3,//实际像素绘制尺寸
      pixel_num_x = cel_num_x * split,//横向像素总数
      pixel_num_y = cel_num_y * split;//纵向像素总数

    // 绘制像素点,横纵像素点数量由晶格数量和每个晶格插值数量决定
    for (let i = 0; i < pixel_num_y; i++) {
      for (let j = 0; j < pixel_num_x; j++) {
        setTwoVal(j, i, pixel_size, split);
      }
    }
  }

  /**
   * 二维插值
   * @param x  像素的横向位置索引
   * @param y  像素的纵向位置索引
   * @param size 像素实际绘制尺寸
   * @param split 一个晶格分为 split*split 个像素,用于计算像素相对晶格左上角的晶格单位偏移
   */
  function setTwoVal(x, y, size, split) {
    //像素索引,即第几列第几行
    let pixel_x = x, pixel_y = y;
    
    // 计算晶体格的四周点,先计算左上角的晶体格的索引
    let loc_lt = {x: Math.floor(pixel_x / split), y: Math.floor(pixel_y / split)};
    let a0 = loadCrystal(loc_lt.x, loc_lt.y);
    let a1 = loadCrystal(loc_lt.x + 1, loc_lt.y);
    let a2 = loadCrystal(loc_lt.x, loc_lt.y + 1);
    let a3 = loadCrystal(loc_lt.x + 1, loc_lt.y + 1);

    //计算像素相对于所在晶格左上角偏移多少个晶格单位 => x,y
    x = Math.floor(x % split) / split;
    y = Math.floor(y % split) / split;
    
    //计算像素的晶格化坐标ox,oy
    //比如 像素p索引为 x:11 y:0 每个晶格分十个像素  则像素p的晶格坐标为1.1
    let ox = loc_lt.x + x;
    let oy = loc_lt.y + y;

    //计算像素权重,计算方法为:晶格四个顶点对像素影响权重的三次插值
    let fadeType = 0;//插值使用的平滑函数类型
    let val = lerp(
      lerp(
        dp({x: ox - a0.x, y: oy - a0.y}, a0.vector),
        dp({x: ox - a1.x, y: oy - a1.y}, a1.vector),
        x, fadeType),
      lerp(
        dp({x: ox - a2.x, y: oy - a2.y}, a2.vector),
        dp({x: ox - a3.x, y: oy - a3.y}, a3.vector),
        x, fadeType),
      y, fadeType);

    val = mirror(val, -1, 1, 0, 1);

    //绘制像素,注释掉的为修改生成像素颜色,默认以透明度绘制

    // if(val > 150){
    //   context.fillStyle = `rgb(186, 187, 183,${mirror(val,150,255,0.5,1)})`;
    // }
    // if(val>100&&val<=150){
    //   context.fillStyle = `rgb(0, 210, 123,${mirror(val,100,150,0.5,1)})`;
    // }
    // if(val <=100){
    //   context.fillStyle = `rgb(30, 132, 219,${mirror(val,0,100,0.5,1)})`;
    // }

    context.fillStyle = `rgb(0, 0, 0,${val})`;
    context.fillRect(pixel_x * size, pixel_y * size, size, size)
  }

  loadTwo();
</script>
</body>
</html>

参考链接(参考资料使用python演示)

插值
一维柏林噪声
二维柏林噪声

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值