跳到主要内容
浏览器
导航
工程图纸浏览器

矢量路径与线宽映射

矢量路径由直线、圆弧与曲线段构成,渲染时需要按容差离散化并叠加描边。本文讨论曲线的离散容差、线宽在世界坐标与设备坐标两种定义下的换算、非等比缩放造成的线宽畸变、虚线图案的弧长分布与相位,以及连接与端点样式对边缘的影响。

发布于 5 分钟阅读 1773 字

矢量图形由路径定义几何,再由描边与填充决定外观。与位图按像素保存颜色不同,矢量路径保存的是参数化的几何描述,显示时需要把几何转换为屏幕上可绘制的线段集合。这一步涉及两类映射:几何本身的离散化,以及样式参数在不同坐标系之间的换算。

视图缩放时,路径的几何与线宽未必按同一规则变化;若两者的换算关系没有理清,就会出现放大后线条粗细与预期不符、虚线疏密反常等现象。下面从路径的表示开始,逐层说明这些映射。

路径的表示

一条路径由若干首尾相接的段构成,每段都有参数化定义:

段类型参数说明
直线段起点、终点一次参数化,可直接绘制
圆弧圆心、半径、起止角、方向由三角函数定义,可保留解析形式
二次贝塞尔起点、控制点、终点二次多项式
三次贝塞尔起点、两个控制点、终点三次多项式
椭圆弧圆心、长短轴、起止角、旋转角可由圆弧经非等比缩放得到
样条控制点、节点向量、权值有理参数曲线

多数实现采用分层处理:曲线统一转换为若干基础段,圆弧既可保留解析表示,也可离散为折线。保留解析表示的好处是可以在任意缩放下按需细分,避免折线在小比例下露出多边形轮廓。

曲线离散化与容差

离散化的目标是让折线在视觉上与被替代的曲线一致。常用判据是弓高,即折线弦与曲线之间的垂距上界不超过给定容差。对半径为 R 的圆弧,单段对应的圆心角 θ 与弓高的关系约为 s = R(1 − cos(θ/2)),据此可由容差反解 θ,进而得到段数:

// 按弓高容差估算圆弧需要离散成多少段
function arcSegments(radius, sweepRad, tol) {
  if (tol <= 0 || radius <= 0) return 1;
  // 由 s = R(1 - cos(theta/2)) 反解单段圆心角上限
  const theta = 2 * Math.acos(Math.max(-1, 1 - tol / radius));
  return Math.max(1, Math.ceil(Math.abs(sweepRad) / theta));
}

容差可以定义在世界坐标单位下,也可以定义在设备像素下。前者在缩放后屏幕上的偏差会随之变化;后者需要在每次视图变化后重新离散。多数实现把容差设在设备空间,以得到较稳定的视觉质量,代价是缩放时需要重建几何缓存。

线宽在世界坐标与设备坐标下的区别

线宽既可以作为世界单位下的长度,也可以作为设备像素单位的宽度,两者的缩放行为不同:

定义方式缩放行为典型用途
世界坐标单位屏幕上的粗细随缩放同步变化线宽本身是图纸语义的一部分
设备像素屏幕上的粗细保持不变界面标注、辅助线与选中高亮

把世界单位线宽换算为设备像素,需要乘以视图的缩放系数。若视图在两个轴上的缩放系数不同,就不存在单一的换算系数,这正是下一节讨论的畸变来源。

非等比缩放与线宽畸变

严格意义的描边是把路径沿法线方向向两侧扩展一定宽度。若视图在 X、Y 方向的缩放不一致,圆在屏幕上会变成椭圆;等宽的描边经过同样的变换后,其轮廓也不再等宽,因为法线方向被拉伸或压缩了。

多数实现在三种处理方式中选择其一:

  • 取视图的一个标量缩放系数(例如两轴缩放系数中较小者或几何平均)换算线宽,畸变被舍弃,但线宽量级可控
  • 先把路径变换到设备坐标,再按固定的像素宽度扩展轮廓,此时描边不随缩放变化
  • 把描边轮廓当作几何对象一并参与变换,线宽随缩放变化,且畸变被完整保留

选择哪一种,取决于线宽在业务上表达的是图纸语义还是界面语义。

线型图案与相位

虚线、点划线等线型图案由「划长加间隔」的重复序列定义。渲染时需要沿路径按弧长推进,逐段判断当前位置落在划内还是间隔内。要处理的核心问题是相位,也就是图案从路径起点的哪个位置开始铺排。若每条路径都各自从零开始,折线顶点处会出现图案断裂或两段重叠。

// 沿路径按弧长推进虚线图案,返回起始段与段内偏移
function dashCursor(pattern, phase = 0) {
  const total = pattern.reduce((a, b) => a + b, 0);
  let pos = ((phase % total) + total) % total;
  let idx = 0;
  while (pos >= pattern[idx]) {
    pos -= pattern[idx];
    idx = (idx + 1) % pattern.length;
  }
  return { idx, consumed: pos };
}

对闭合路径,多数实现会让图案总长与周长对齐,即微调图案比例,使起点与终点的衔接处保持连续。图案的推进一般以世界单位的弧长计量,因此视图缩放时需要通过线型比例做补偿,否则屏幕上的划长会随缩放变化。

连接样式、端点样式与抗锯齿

当路径由多段构成时,顶点处的连接样式决定外侧如何补角:斜接延长两条边直到相交,夹角过小时通常回退为切角;圆接以圆弧填充缺口;切角直接以直线连接两侧轮廓。端点样式则决定开放路径两端的形状,常见的有平头、方头与圆头,三者在端点到轮廓的延伸量上不同。

抗锯齿在 GPU 上通常借助多重采样或覆盖率计算完成。使用覆盖率时,片段着色器输出 0 到 1 的透明度,其值与路径边缘到像素中心的距离相关。对宽度小于一个像素的细线,覆盖率近似会让线条显得偏淡,部分实现改为按对齐到屏幕像素的粗细绘制,或为线宽设定一个像素级下限。若几何本身已经离散为折线,线宽与容差的关系会一起影响观感,这部分与图层管理与坐标系变换中讨论的变换链配合生效。

小结

矢量路径的渲染可以拆成两条并行的链路:几何链路把解析曲线离散为可绘制的线段,其精度由容差控制;样式链路把线宽、线型与端点样式映射到设备空间,其正确性取决于变换在哪个空间施加。两条链路的耦合点是视图变换。当两轴缩放不一致,或需要在缩放时保持屏幕粗细不变时,就必须明确线宽与容差究竟定义在哪个坐标系中,否则同一份几何在不同缩放下的表现会彼此矛盾。

工程图纸浏览器 · 相关文章