双三次贝塞尔曲面-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使用贝塞尔曲线优化自由画笔并探究其原理前言 大纲: 贝塞尔曲线的原理,控制点,曲线方程,二次与三次贝塞尔曲线 - 掘金

相关推荐
陈随易15 小时前
有生之年系列,Nodejs进程管理pm2 v7.0发布
前端·后端·程序员
王老师青少年编程15 小时前
csp信奥赛C++高频考点专项训练之贪心算法 --【哈夫曼贪心】:合并果子
c++·算法·贪心·csp·信奥赛·哈夫曼贪心·合并果子
冰暮流星15 小时前
javascript之事件代理/事件委托
前端
叼烟扛炮15 小时前
C++第二讲:类和对象(上)
数据结构·c++·算法·类和对象·struct·实例化
天疆说15 小时前
【哈密顿力学】深入解读航天器交会最优控制中的Hamilton函数
人工智能·算法·机器学习
陈随易16 小时前
AI时代,你还在坚持手搓文章吗
前端·后端·程序员
wuweijianlove16 小时前
关于算法设计中的代价函数优化与约束求解的技术7
算法
leoufung17 小时前
LeetCode 149: Max Points on a Line - 解题思路详解
算法·leetcode·职场和发展
样例过了就是过了17 小时前
LeetCode热题100 最长公共子序列
c++·算法·leetcode·动态规划
HXDGCL17 小时前
矩形环形导轨:自动化循环线的核心运动单元解析
运维·算法·自动化