双三次贝塞尔曲面-canvas 实现4x4网格图片变化功能

前言

本文主要介绍一下什么是贝塞尔曲线,以及它在图形学当中的应用,最后利用双三次贝塞尔曲面的思路来实现一个4x4的图形变化功能

什么是贝塞尔曲线

相信大家对这个名词应该并不陌生,特别是在css中的应用,可能你碰到过cubic-bezier这个属性,那么你就知道贝塞尔曲线是什么

关于贝塞尔曲线的具体定义,这里不写太多,大家可以参考更加官方的解释说明 贝塞尔曲线

贝塞尔曲线是一条由点定义的线,如果是两个点定义出来的,那就是一阶,三个点就是二阶,以此类推,今天要说到的是双三阶贝塞尔曲面,这又是什么概念呢

首先了解一下三次贝塞尔曲线,它是由四个点定义的一条曲线,具体大家可以参考这篇文章,里面不仅介绍了每一阶的曲线,而且还有对应的demo项目

canvas使用贝塞尔曲线优化自由画笔并探究其原理前言 大纲: 贝塞尔曲线的原理,控制点,曲线方程,二次与三次贝塞尔曲线 - 掘金

那么由此我们就可以得知,三次贝塞尔曲线其实原理就是根据

这个方程式得到的

那么今天所要讲述的双三次贝塞尔曲面,就是由 16个点,也就是二维的一个曲面,它由 4x4 的点位来控制这个曲面的一个扭曲程度,学过ps的可能就会觉得有点熟悉,没错,这个就跟ps的那个变形功能,是有点类似的。

双三阶贝塞尔曲面的实现原理

面是由无数条线组合而成的

那我们是不是可以理解为,我对一条曲线上的每一个点,跟另一条曲线上对应的每一个点,在做一次贝塞尔曲线,那么交叉起来,是不是就变成了面?只要你这个交叉的密度够大,这个曲面就越接近,有点类似于多边形的边只要够多,就越接近圆形的这种思路

那么首先我们参考上面的例子的公式实现一个计算三阶贝塞尔曲线的函数

js 复制代码
// 三次贝塞尔曲线插值
function cubic(p0, p1, p2, p3, t) {
    const t2 = t * t;
    const t3 = t2 * t;
    const mt = 1 - t;
    const mt2 = mt * mt;
    const mt3 = mt2 * mt;

    return {
        x: mt3 * p0.x + 3 * mt2 * t * p1.x + 3 * mt * t2 * p2.x + t3 * p3.x,
        y: mt3 * p0.y + 3 * mt2 * t * p1.y + 3 * mt * t2 * p2.y + t3 * p3.y,
        z: mt3 * p0.z + 3 * mt2 * t * p1.z + 3 * mt * t2 * p2.z + t3 * p3.z,
    };
}

通过一条线来验证一下这个公式的正确性

js 复制代码
 // 使用cubic函数生成曲线上的点
for (let t = 0; t <= 1.0001; t += 0.01) {
    // 为了使用cubic函数,我们需要添加z坐标
    const p0 = {...curveControlPoints[0], z: 0};
    const p1 = {...curveControlPoints[1], z: 0};
    const p2 = {...curveControlPoints[2], z: 0};
    const p3 = {...curveControlPoints[3], z: 0};

    const point = cubic(p0, p1, p2, p3, t);
    curveCtx.lineTo(point.x, point.y);
}

关键代码,以 0.01 为步长,生成 100 个在这条线上的点,然后将这些点连起来便是当前的贝塞尔曲线了。

代码仓库

既然曲线是通过 100 个点形成的,那么我们是不是可以二维化,通过 100*100=10000 个点来形成一个曲面?

那么接着我们同样通过三阶贝塞尔曲线的方程,通过两次循环计算出 100*100=10000 点的位置

js 复制代码
 // 绘制u方向的网格线
for (let u = 0; u <= 1.0001; u += uStep) {
  const points = [];
  for (let v = 0; v <= 1.0001; v += 0.01) {
    const point = bicubic(u, v);
    const projectedPoint = project3Dto2D(point);
    points.push(projectedPoint);
  }

  // 绘制u方向的网格线
  ctx.strokeStyle = "#2196F3"; // 蓝色
  ctx.lineWidth = 0.5;
  ctx.beginPath();
  ctx.moveTo(points[0].x, points[0].y);
  for (let i = 1; i < points.length; i++) {
    ctx.lineTo(points[i].x, points[i].y);
  }
  ctx.stroke();
}

// 计算双三次贝塞尔曲面上的点
function bicubic(u, v) {
  // 在v方向上的四个点
  const curvePoints = [];

  // 对每一行进行三次贝塞尔插值
  for (let i = 0; i < 4; i++) {
    curvePoints.push(
      cubic(
        controlPoints[i][0],
        controlPoints[i][1],
        controlPoints[i][2],
        controlPoints[i][3],
        u
      )
    );
  }

  // 对得到的四个点再次进行三次贝塞尔插值
  return cubic(
    curvePoints[0],
    curvePoints[1],
    curvePoints[2],
    curvePoints[3],
    v
  );
}

通过绘制蓝色的网格来代表当前的贝塞尔曲面

代码仓库

接着我们将曲面应用到图片上去,按照前面的做法来看,其实贝塞尔曲面也只是根据步长来定义的好多个点,并不是一个真的完整的面,但是对于图片来说,我们只能去渲染一张完整的图片,变形也只能统一进行变形,那有什么办法可以对图片进行贝塞尔曲线的变形吗?

我们可以通过将图片细分为无数个小块,对每一个小块使用变形,这样小块组合起来,就能够形成一个贝塞尔曲面了。

我们通过将一个区间分为两个三角形来定义这一部分的图片,然后只要计算好四个角的点位,就能够绘制这两个三角形。

js 复制代码
// 设置三角形路径
ctx.beginPath();
ctx.moveTo(x0, y0);
ctx.lineTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.closePath();

// 裁剪到三角形区域
ctx.clip();

最后得到一张曲面

代码仓库

总结

最后图片的例子并没有写的很完美,图片是写死的资源,因为这个利用canvas完成的只是一个学习思路使用的demo,之后会更新使用 fabric.js 实现的,这才是最终的目的

引用

贝塞尔曲线

canvas使用贝塞尔曲线优化自由画笔并探究其原理前言 大纲: 贝塞尔曲线的原理,控制点,曲线方程,二次与三次贝塞尔曲线 - 掘金

相关推荐
Hello eveybody1 小时前
C++介绍整数二分与实数二分
开发语言·数据结构·c++·算法
小小小小宇2 小时前
前端 Service Worker
前端
Mallow Flowers3 小时前
Python训练营-Day31-文件的拆分和使用
开发语言·人工智能·python·算法·机器学习
只喜欢赚钱的棉花没有糖3 小时前
http的缓存问题
前端·javascript·http
小小小小宇3 小时前
请求竞态问题统一封装
前端
loriloy3 小时前
前端资源帖
前端
源码超级联盟3 小时前
display的block和inline-block有什么区别
前端
GISer_Jing3 小时前
前端构建工具(Webpack\Vite\esbuild\Rspack)拆包能力深度解析
前端·webpack·node.js
让梦想疯狂3 小时前
开源、免费、美观的 Vue 后台管理系统模板
前端·javascript·vue.js
GalaxyPokemon4 小时前
LeetCode - 704. 二分查找
数据结构·算法·leetcode