WebGL可视化基础(二)

本文详细介绍了如何在WebGL中建立基于向量和矩阵的数学体系,包括坐标系转换、向量操作(如点线段描述、点乘叉乘)、以及利用参数方程和向量描述曲线的技巧。通过实例演示了如何使用这些概念绘制正多边形、圆、圆锥曲线和抛物线。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

WebGL可视化基础(二)

想要学好可视化,我们要建立一套与各个图形系统无关联的/简单的基于向量和矩阵运算的数学体系,用它来描述所有的几何图形信息。也就是如何建立一套描述几何图形信息的数学体系,以及如何用这个体系来解决我们的可视化图形呈现的问题。

坐标系和坐标映射

HTML采用的是窗口坐标系,以参考对象(参考对象通常是最接近图形元素的position非static的元素)的元素盒子左上角为坐标原点。x轴向右,y轴向下,坐标值对应像素值。

SVG采用的是视区盒子(viewBox)坐标系。这个坐标系在默认情况下,是以svg根元素左上角为坐标原点,x轴向右,y轴向下,svg根元素右下角坐标为它的像素宽高值。如果设置了viewBox 属性,那么svg根元素左上角为viewBox的前来两个值,右下角为viewBox的后两个值。

Canvas采用以画布左上角为坐标原点,右下角坐标值为Canvas的画布宽高值。

WebGL的坐标系比较特殊,是一个三位坐标系。它默认以画布正中间为坐标原点,x轴朝右,y轴朝上,z轴朝外,x轴,y轴画布中范围是 -1 到 1。

为了方便图形,经常需要对坐标系进行转换。因为这四个坐标系都死直角坐标系,所以它们可以很方便地相互转化。其中,HTML/SVG和Canvas 都提供了transform的API能够帮助我们很容易地转换坐标系。而WebGL 不提供 transform的API,但我们可以在shader里做矩阵运算来实现坐标转换。

Canvas实现坐标系转换

之所以要实现坐标系的转换,是因为当我们绘图时,需要知道各个顶点的坐标,如果我们要花费时间在坐标换算上,这会非常的不方便。

首先,我们通过translate变换将Canvas画布的坐标原点,从左上角(0,0)点移动至(256,256)位置,即画布的底边上的中点位置,接着移动了原点后新的坐标为参照,通过scale(1,-1)将y轴向下的部分,即y > 0 的部分沿x轴反转180度,这样坐标系就变成以后画布底边中点为原点,x轴向右,y轴向上的坐标系了。

执行了坐标变换,也就是让坐标系原点在中间之后,我们就可以更方便/直观的计算出几个图形元素的坐标了。

采用坐标变换后,它能够简化计算量,这不仅让代码更容易理解,也可以节省CPU运算的时间。

如何用向量来描述点和线段?

v.length = function(){return Math.hypot(this.x, this.y)}
v.dir = function(){return Math.atan2(this.x, this.y)}
v.x = v.length * Math.cos(v.dir)
v.y = v.length * Math.sin(v.dir)

向量点乘(内积)和叉乘(外积、向量积)概念及几何意义解读

https://blog.youkuaiyun.com/dcrmg/article/details/52416832

如何用向量和参数方程描述曲线?

向量

首先,我们用向量绘制折线的方法来绘制正多边形,我们定义一个函数 regularShape,代码如下:

function regularShape(edges = 3, x, y, step) {
  const ret = []
  const delta = Math.PI * (1 - (edges - 2) / edges);
  let p = new Vector2D(x, y);
  const dir = new Vector2D(step, 0);
  ret.push(p);
  for(let i = 0; i < edges; i++) {
    p = p.copy().add(dir.rotate(delta));
    ret.push(p);
  }
  return ret;
}

我们在regularShape 函数中,给定边数 edges,起点x,y一条边的长度step,就可以绘制一个正多边形了。也就是通过 rotate 旋转向量,然后通过向量加法来计算顶点位置。

具体来说就是,定义初始化为new Vector2D(x, y),初始方向为 x 轴方向new Vector2D(step, 0)。然后循环计算正多边形的顶点位置,也就是初始点开始,每次将方向向量旋转delta角度,delta角度是根据正多边形内角公式计算出来的。最后,我们将当前点和方向向量相加,就得到下一个顶点坐标了。

draw(regularShape(3, 128, 128, 100))  // 绘制三角形
draw(regularShape(6, -64, 128, 50))  // 绘制六边形
draw(regularShape(11, -64, -64, 30))  // 绘制十一边形
draw(regularShape(60, 128, -64, 6))  // 绘制六十边形

当我们将多边形的边数设置非常多时,这个图形就会接近圆。所以,只要利用regularShape函数,将多边形的边数设置得很大,我们就可以绘制出圆形了,也就是利用极限的思维,获取一个近似圆形的多边形。

但是regularShape定义边数,起点,一条边的长度就和我们通常绘制圆的使用习惯不符。一般绘制圆为定义边数,中心,半径。

其次,regularShape无法绘制椭圆,抛物线,贝塞尔曲线等其他曲线。

曲线方程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QpyuFpFu-1649255314206)(/Users/longyusheng/Downloads/下载 (5)].jpeg)

const TAU_SEGMENTS = 60;
const TAU = Math.PI * 2;
function arc(x0, y0, radius, startAng = 0, endAng = Math.PI * 2) {
  const ang = Math.min(TAU, endAng - startAng);
  const ret = ang === TAU ? [] : [[x0, y0]];
  const segments = Math.round(TAU_SEGMENTS * ang / TAU);
  for(let i = 0; i <= segments; i++) {
    const x = x0 + radius * Math.cos(startAng + ang * i / segments);
    const y = y0 + radius * Math.sin(startAng + ang * i / segments);
    ret.push([x, y])
  }
  return ret;
}

draw(arc(0, 0, 100));

画圆锥曲线

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VLzCqL5w-1649255314206)(/Users/longyusheng/Downloads/下载 (6)].jpeg)

画抛物线

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aV8dk6Z8-1649255314207)(/Users/longyusheng/Downloads/下载 (7)].jpeg)

我们只需要修改arc方法中对应的参数同样能够实现椭圆和抛物线的绘制。

const TAU_SEGMENTS = 60;
const TAU = Math.PI * 2;
function arc(x0, y0, radiusX, radiusY, startAng = 0, endAng = Math.PI * 2) {
  const ang = Math.min(TAU, endAng - startAng);
  const ret = ang === TAU ? [] : [[x0, y0]];
  const segments = Math.round(TAU_SEGMENTS * ang / TAU);
  for(let i = 0; i <= segments; i++) {
    const x = x0 + radiusX * Math.cos(startAng + ang * i / segments);
    const y = y0 + radiusY * Math.sin(startAng + ang * i / segments);
    ret.push([x, y])
  }
  return ret;
}

draw(arc(0, 0, 100, 50));
const LINE_SEGMENTS = 60;
function parabola(x0, y0, p, min, max) {
  const ret = [];
  for(let i = 0; i <= LINE_SEGMENTS; i++) {
    const s = i / 60;
    const t = min * (1 - s) + max * s;
    const x = x0 + 2 * p * t ** 2
    const y = y0 + 2 * p * t;
    ret.push([x, y]);
  }
  return ret;
}

draw(parabola(0, 0, 5.5, -10, 10))

参考文章

极客时间:跟月影学可视化

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

MaxLoongLvs

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值