效果
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演示)