Troika: 文字大纲

创建于 2020-08-20  ·  39评论  ·  资料来源: protectwise/troika

我试图弄清楚如何在白色文本周围有一个黑色文本轮廓,以便可以轻松阅读文本。 我不确定,但听起来我可能需要使用着色器来处理 troika-three-text? 您能否提供一些有关如何实现此效果的指导?

谢谢!

最有用的评论

感谢您提供有关 Mapbox 的信息,@anzorb。 我肯定会更仔细地检查他们的实现,但是从那篇博客文章(他们称大纲为“光环”)听起来他们使用了我之前提到的 SDF alpha 阈值方法。 但是他们的 SDF 生成和图集打包策略与我的不同,并且每个字形的距离场大小不均匀的问题也不相同。 不过,它可能具有相同的最大轮廓宽度限制。

实际上,我在使用标准导数的 _screen space_ 轮廓上取得了一些进展。 屏幕空间意味着轮廓将是例如 1 个屏幕像素宽,无论文本的大小/比例或倾斜度如何。 如果目标只是增加与背景的对比,看起来这可能与随文本大小一起缩放的轮廓一样好(甚至更可取)。 你对此有意见吗?

所有39条评论

我几乎觉得我需要的可能是由 debugSDF 标志完成的,只要我能给它上色。
image
显示 sdf 基本上给了我一个轮廓,如果我可以对其进行着色、调整大小并可能使其稍微更清晰,那么它可能会起作用。

这不应该太难做到。 不久前我开始使用 textOutline 作为一个功能,但从未完成它。

理论上,字形边缘可以简单地通过更改与边缘对应的有符号距离字段中的值来扩展。 这是当前着色器代码中的0.5 ,但可以通过将其作为统一传递来进行配置。

理想情况下,着色器可以在一次绘制调用中同时处理普通字形及其周围的任何轮廓,因此您需要一些新的制服:轮廓宽度(不确定这将采用什么单位?)和颜色轮廓区域。

不久前我开始使用 textOutline 作为一个功能,但从未完成它。

如果本机支持它,我会非常高兴:)
但是,我对着色器很陌生,所以我不确定我能提供多少帮助。 我会密切关注这个问题。

我又回到了这一点,并很快意识到为什么我最初没有完成它。 虽然 SDF _can_ 可用于概述,但有一些警告:

  • 轮廓的最大潜在宽度受限于距离场本身的范围(64x64 SDF 纹理中约 8 个纹素)。
  • 这并不对应于所有字形的一致视觉大小,因为 SDF 纹理被缩放到每个字形的边界。

所以像“W”这样的大字形可以得到一个相当粗的轮廓,但是像句号这样的小字形只能得到一个非常细的轮廓。 并且每个字形都需要使用不同的 SDF 截止点,以使其轮廓在视觉上保持一致。

我不认为这是不可克服的,但并不像我最初想象的那么简单。

@lojjic ,喜欢这个库🥇,但文本大纲是我们项目的要求......

我又回到了这一点,并很快意识到为什么我最初没有完成它。 虽然 SDF _can_ 可用于概述,但有一些警告:

  • 轮廓的最大潜在宽度受限于距离场本身的范围(64x64 SDF 纹理中约 8 个纹素)。
  • 这并不对应于所有字形的一致视觉大小,因为 SDF 纹理被缩放到每个字形的边界。

所以像“W”这样的大字形可以得到一个相当粗的轮廓,但是像句号这样的小字形只能得到一个非常细的轮廓。 并且每个字形都需要使用不同的 SDF 截止点,以使其轮廓在视觉上保持一致。

我不认为这是不可克服的,但并不像我最初想象的那么简单。

我很好奇,我们可以使用着色器进行某种后期处理来实现轮廓和/或阴影效果吗? 提前致谢!
这是我想要达到的效果:
Screen Shot 2020-08-31 at 4 32 03 PM

编辑:这是来自 mapbox gl,我认为他们也在使用 SDF https://blog.mapbox.com/drawing-text-with-signed-distance-fields-in-mapbox-gl-b0933af6f817 ,也许我们可以剖析他们的代码,看看他们做了什么。 https://github.com/mapbox/mapbox-gl-js

感谢您提供有关 Mapbox 的信息,@anzorb。 我肯定会更仔细地检查他们的实现,但是从那篇博客文章(他们称大纲为“光环”)听起来他们使用了我之前提到的 SDF alpha 阈值方法。 但是他们的 SDF 生成和图集打包策略与我的不同,并且每个字形的距离场大小不均匀的问题也不相同。 不过,它可能具有相同的最大轮廓宽度限制。

实际上,我在使用标准导数的 _screen space_ 轮廓上取得了一些进展。 屏幕空间意味着轮廓将是例如 1 个屏幕像素宽,无论文本的大小/比例或倾斜度如何。 如果目标只是增加与背景的对比,看起来这可能与随文本大小一起缩放的轮廓一样好(甚至更可取)。 你对此有意见吗?

标准化宽度比缩放宽度更受欢迎。 它也会以这种方式表现得更像 css。
通过 css 应用的任何text-shadow都是独立于font-size配置的。
如果您提出的解决方案@lojjic执行相同的方式,那么那将是🎉 🎉 🎉

感谢您的及时回复@lojjic!

目标增加对比度,您的提案看起来很棒。 你的意思是大纲是静态的吗? 即无法控制它(如@stephencorwin所描述的,使用text-shadow-ish 方法)。

老实说,我不认为这是一个问题,只要 1 像素在超高密度显示器上可见(因为我们目前将字体大小乘以设备像素比以实现“相同”比例,无论密度如何) ,我认为您的建议意味着轮廓在更高 DPI 设备上会明显变薄,不是吗?

提前致谢!

我认为你的提议意味着在更高 DPI 的设备上轮廓会明显更薄,不是吗?

是的,这是屏幕空间方法的一个缺点。 对于许多场景来说这可能很好,但作为设计师,我会在设备上拥有不同的外观,这会让我感到困扰。

另一个很大的缺点是(我认为)增加光环厚度会对性能产生影响,因为需要更多的纹理读取。 我需要继续研究这个。

我也需要这个功能。 我已经在我多年前开发的 Android 应用程序中实现了这样的功能,并且只供我自己和朋友使用。 我已经开始用 React 重新实现这个应用程序,并开始寻找你的项目。
和其他人一样,我要感谢您提供这个图书馆。 我很惊讶。

关于你对更多纹理阅读的评论,我认为你不应该担心那么多。 您需要的额外纹素将是相邻片段所需的纹素,因此我认为它们无论如何都会在 GPU 的缓存中,而且它们的成本几乎为零。

感谢@FunMiles 的输入。 (科罗拉多代表!😄)

我认为你可能适合 1 的轮廓,也许 2 片段厚。 我担心的是,随着厚度的增加,它会爆炸纹理读取的数量(4 = 56 读取的厚度?)以及这些读取的成本可能会更高。 但我只是根据阅读一些内容进行推测,无论如何我都不是 GPU 专家。 我可能只需要尝试一下。

@lojjic (你们在北边😄)我试图找到片段着色器的代码。 我承认很难弄清楚(我想我在某处读过)你修改着色器而不是完全编写它。
然而,虽然我无法弄清楚完整的着色器是什么样的以及你是如何真正插入它的,但我想我得到了一些核心例程......

现在,您使用texture2D(uTroikaSDFTexture, vTroikaSDFTextureUV).r;读取像素的标量距离。 如果我理解您之前写的内容,那么一个问题与字形周围的空间有关。 你能更详细地解释一下吗?
回到您的“W”和“”示例,纹理中的 vTroikaSDFTextureUV 跨度是否与字形完全吻合,还是有一些额外的空间? 片段着色器是否知道当前字形的 UV 边界? 如果是这样,对于会落在外部的片段,您可以将当前 UV 投影到边界并直接读取内部数据。

我认为可以通过最多 5 次纹素读取或使用衍生品来确定需要什么(我看到你确实检查它们是否可用)。

回到您的“W”和“”示例,纹理中的 vTroikaSDFTextureUV 跨度是否与字形完全吻合,还是有一些额外的空间?

每个字形的真实路径边界周围都有一小部分额外空间,足以容纳距离场的外部部分。 这是 SDF 中的 8 个纹素,但由于每个 SDF 都是统一的 64x64 缩放到不同大小的字形四边形上,因此可见边距因字形而异。

片段着色器是否知道当前字形的 UV 边界? 如果是这样,对于会落在外部的片段,您可以将当前 UV 投影到边界并直接读取内部数据。

该信息应该可用。 不过,我并没有关注您“将当前的 UV 投影到边界并直接读取内部数据”。

如果您知道一种用更少的纹素读取来获得更粗的轮廓的方法,我将不胜感激! 我一直将其概念化为:对于不在字形路径内的片段,进行径向搜索以查看 r= 内是否有任何片段将落在字形路径内。 这可能只是错误的思考方式。

一个问题是当字形并排放在一起时会发生什么。 暂且不说,让我们以绘制一个具有相当粗轮廓t的单个字符为例。 正在绘制的字符周围有一个矩形,该矩形足够大以容纳轮廓。
片段着色器有两个主要信息: vTroikaSDFTextureUVvTroikaGlyphUV

我的理解是vTroikaGlyphUV的值在字形内部时被限制在 0 和 1 之间。 当我谈到投影时,我的意思是如果vTroikaGlyphUV超出了这些范围,您可以检索“投影”到字形边界上的点的距离。 也就是说,如果 uv = (1.05, 0.7),则投影点为 (1.0, 0.7)。 通过使用 (1.0, 0.7+epsilon) 和 (1.0, 0.7-epsilon) 处的值,您可以检索到距离的梯度,并使用它计算 (1.05, 0.7) 处的近似距离。
当我为此做自己的工作时,我记得读过一篇文章,有人在使用纹理渐变的 SDF。 这种方法避免了读取三个纹素,但它使纹理成为 3 分量纹理并增加了构建纹理的复杂性。

这回答了你的问题了吗?

现在,当您有几个角色并排时,每个角色都必须注意邻居,这使这变得更加复杂。 您不仅需要为当前角色携带 GlyphUV 和 TextureUV,还需要为邻居携带。 这可能需要将每个字符切割成两个矩形,以便邻居可以是第一个矩形之前的一个和第二个矩形之后的一个。 我不确定如何处理多行...。我不必在我的应用程序中处理这种情况。 我的应用程序是一张地图,每个标签都是单行的。

PS:我可能误解了您的“每个字形的真实路径边界周围的额外空间”。 你是说对于一个 X,例如,它被封装在一个矩形中,并且有四个三角形没有有效值?

@FunMiles我_认为_也许我知道你要去哪里。 我需要一些时间来考虑清楚,但我现在需要专注于其他截止日期的工作。

PS:一点有趣的数学笔记:
万一有人想知道如何从两个或三个对齐的点获得梯度,必须记住距离的梯度有一个固定的范数(取决于选择的比例)。 渐变肯定指向字形框的外部。 因此,例如,如果示例 (1.0, 0.7)、(1.0, 0.7+epsilon) 和 (1.0, 0.7-epsilon) 的所有 3 个点距离相同,则梯度为 (scale, 0)。

虽然它绝对不是高性能的,但可以使用偏移量。 我正在使用react-three-fiber ,它利用 troika-text 和 drei 包,但我想我会分享以防其他人正在寻找解决方案作为权宜之计,直到库本身支持笔画:

import React from 'react';
import {Text} from 'drei';

const StrokedText: React.FC<
  {
    strokeWidth?: number;
    strokeColor?: string;
    strokeResolution?: number;
    bold?: boolean;
  } & any
> = ({
  strokeWidth = 1,
  strokeColor: color = '#000000',
  strokeResolution = 100,
  position,
  bold,
  ...props
}) => {
  const font = bold ? FONTS.BOLD : undefined;
  const sharedProps = {
    ...props,
    font,
    color,
    sdfGlyphSize: 12,
    debugSDF: true,
  };

  let zOffset = 0;
  const offset = () => (zOffset += 0.001);

  return (
    <group name="Stroked Text" position={position}>
      {Array(strokeWidth)
        .fill({})
        .map((_, i) => {
          const s= i / strokeResolution;
          return (
            <React.Fragment key={i}>
              {/* <Text {...sharedProps} position={[-s, 0, offset()]} />*/}
              {/* <Text {...sharedProps} position={[s, 0, offset()]} />*/}
              <Text {...sharedProps} position={[0, -s, offset()]} />
              <Text {...sharedProps} position={[0, s, offset()]} />
              <Text {...sharedProps} position={[-s, -s, offset()]} />
              <Text {...sharedProps} position={[s, s, offset()]} />
              {/* <Text {...sharedProps} position={[-s, s, offset()]} /> */}
              {/* <Text {...sharedProps} position={[s, -s, offset()]} /> */}
            </React.Fragment>
          );
        })}
      <Text name="Text" {...props} position={[0, 0, strokeWidth ? offset() : 0]} />
    </group>
  );
};

export default StrokedText;

然后在其他地方,我可以用<StrokedText />而不是<Text />通常调用它:

<StrokedText
  color="#ffffff"
  fontSize={fontSize}
  clipRect={[-0.5, -0.15, 0.5, 0.15]}
  textAlign="center"
  position={[0, 0, 0.01]}
>
  {text}
</StrokedText>

image

@FunMiles我终于有时间分析你的建议了。 我认为假设整个字形的 SDF 矩形都包含有用的(> 0.0)距离值,这将是有意义的。 目前情况并非如此。 我认为您在关于“x”的后续行动中意识到了这一点,其中 SDF 在四边形的范围内很好地下降到零,因此有重要的区域没有可以推断的有用距离梯度:

image

也许我可以考虑更改 SDF 生成器以确保整个四边形的非零梯度......? 大声思考,这可能具有距离精度的含义,并且可能会在图集纹理中的角色之间引入伪影......🤔

也许我可以考虑更改 SDF 生成器以确保整个四边形的非零梯度

我已经尝试过了,是的,它允许将距离场外推到四边形边界之外,但正如我担心的那样,它会显着降低字形本身的质量。 只有 8 位梯度,这是可以预料的。 将其分散到更大的距离会导致每个纹素的精度降低。 :(

也许我可以只为轮廓生成一个单独的 SDF——一个更广泛但精度更低的字段——编码到第二个纹理通道中。 它会使纹理大小翻倍,但不应明显变慢。 🤔

编码到第二个纹理通道。

@lojjic这听起来很合理,基本上模仿了我现在的解决方法。 另外,它可以选择加入,因此如果用户不要求提供轮廓,则不会出现任何性能问题。

@lojjic @stephencorwin建议的选择加入方法将确保没有人支付超过他们准备支付的费用。

回到技术方面。 让我们在您的回复中使用 X 图像。 我是否正确理解所有字形都是 64x64 像素? 使用 8 位,如果您必须对整个距离空间进行编码,则意味着您的精度为 2 _fractional_ 位。 我猜你是说这种准确性是不够的。

也许有一个技巧可以用来以很少的成本获得准确性。 不是做 64x64 的 8 位网格,而是将 2x2 块的数据压缩成 32 位数据。 两个像素之间有符号距离的一个明显特征是它始终在 [-1,1] 左右、上下和对角线 [-sqrt(2), sqrt(2)] 范围内。 因此,假设您使用 12 位作为 2x2 块中心的 SD,每个像素中心将有 5 位来编码它们的中心和 2x2 中心之间的差异。 这 5 位最多代表 sqrt(2)/2 距离。 实际上,您的准确度为 5.5 位。
中心点 12 位在最大 64 的距离上编码至少 6 位精度。从技术上讲,如果盒子在角落只有一个点,则 12 位需要能够编码到 sqrt(2)*64,但是我认为这实际上是不可能的,因为该框可以合理地以每个字形为中心。 实际上,离任何字形边缘越远,增量必须编码的信息就越少(当您远离字形边缘时,梯度场在邻域中变得几乎均匀)所以从信息论的角度来看,它甚至可以改进编码
有更多的实际信息......但我不会从这样一个额外的优化开始。

这种方法的一个有趣结果是不会让纹理硬件进行任何插值。 但另一方面,人们可以免费获得渐变。

如果您需要帮助,我可以实现所有这些的编码/解码。

PS:我记得在其中一个 README.md 中看到了关于更复杂的 SDF 的讨论。 我做梦了吗? 😛 有人可以指点我看看其他系统是否可以在这里提供帮助吗?

@FunMiles非常聪明的压缩想法。 如果其他选项失败,我会记住这一点。 硬件丢失线性插值是我宁愿不必做出的权衡。 😉

我突然想到,使用 0.5 作为“零”距离会加剧我的位数不足的问题,因此只有一半的 alpha 值可用于对字形 _outside_ 的距离进行编码。 我可能会改变它以使用更多的外部距离值和更少的内部距离值,从而在外围获得一些精度。

回覆。 “更复杂的 SDF”,您可能是指使用多个颜色通道的“MSDF”?

@FunMiles非常聪明的压缩想法。 如果其他选项失败,我会记住这一点。 硬件丢失线性插值是我宁愿不必做出的权衡。 😉

我认为您不应该担心丢失该插值。 成本非常低,但在我看来,在没有硬件支持的情况下始终具有渐变(甚至是一点曲率)的好处更为重要。 实际上,您将采样点舍入并获得舍入后的采样点的新偏移值。 计算片段的部分覆盖以进行抗锯齿的过程与通常在梯度可用的情况下一样。
我突然想到,使用 0.5 作为“零”距离会加剧我的位数不足的问题,因此只有一半的 alpha 值可用于对字形 _outside_ 的距离进行编码。 我可能会改变它以使用更多的外部距离值和更少的内部距离值,从而在外围获得一些精度。

这最多只会让你获得一点准确性。 不要被打喷嚏,但仍然没有那么重要。

回覆。 “更复杂的 SDF”,您可能是指使用多个颜色通道的“MSDF”?
我正是这个意思。 我确实在 C++ 中找到了一个关于它的 GitHub 项目。 你有什么要使用的吗,还是我只是感到困惑,把我两周前查到的各种东西混在一起?

顺便说一句,我认为 MSDF 不会有帮助。
请问您是否可以有一个小的 .md 文件来解释您如何在 JavaScript 中构建 SDF? 有了这个,我想我可以创建代码来实现我的压缩想法。

SDF 是在这里构建的: https ://github.com/protectwise/troika/blob/master/packages/troika-three-text/src/worker/SDFGenerator.js#L134 - 主要是没有太多解释将纹素映射到字体单位并将测量的距离写入纹素值。 我们必须在其中为每个 2x2 块的中心点添加一些额外的距离测量值。

试图让我的大脑完全围绕这一点......一旦你有 4 个最接近的值,在 GLSL 中复制双线性插值看起来很简单/便宜。 为了在使用您的压缩方案对其进行编码时获取这些值,我认为这将涉及:

  • 如果恰好在纹素的中心:2 或 3 个纹理样本
  • 如果在 2x2 块的 4 个纹理像素之间:4 个纹理样本
  • 如果在两个相邻块之间:~9 个纹理样本~7 或 8 个纹理样本
  • 如果在四个相邻块之间:~11 个纹理样本~13 个纹理样本

我的理解正确吗?

哦,等等,我还在考虑单通道纹理。 使用四个 rgba 通道,每个数据块只有一次读取。 所以最多4个纹理样本。

我已经开始阅读 SDFGenerator.js。 我会多看的。

我认为您只需要读取每个片段的单个纹素,除非您想针对可能处于四周被字形边缘包围的角落情况专门改进 alpha 混合。 您只需要片段中心所在的 2x2 块的最近中心。
但是,我意识到可能会有一个我没有预见到的困难...... WebGL 1.0 在它提供的可用于此方面非常有限:没有无符号整数类型uint甚至不是 32 位整数! 🤯 并且没有按位运算......所以我建议的压缩使用 16 位整数编写有点棘手。

所有这些都在 WebGL 2.0 中可用,但我想我们想在这里定位 WebGL 1.0?

所有这些都在 WebGL 2.0 中可用,但我想我们想在这里定位 WebGL 1.0?

我认为将文本大纲限制为 webgl 2 并仅检测用户是否具有该浏览器兼容性可能是合理的。 虽然,我可以理解可能同时使用 webgl 2 最佳版本和 webgl 1 后备。 在我看来,我们可以从 webgl 2 开始,并在立即尝试支持两者之前让社区沉浸其中。

我想我有另一种解决精度问题的方法,所以@FunMiles现在不用担心与压缩问题作斗争。

@lojjic ,只是检查一下。 有什么进展或我们可以提供帮助的事情吗?

旁注:Safari 终于开始支持 WebGL2,因此这个功能可能不支持 WebGL1。

我们的 WebGL2 实现已经足够好了,我们应该默认启用它以进行更广泛的测试。
https://trac.webkit.org/changeset/267027/webkit

我有一部永远不会有 WebGL 2.0 的 iPhone 6 😒

有趣的是,WebGL 1.0 规定的要求比我想象的还要糟糕。 整数实际上并不存在:

4.1.3 整数
整数主要被支持作为编程辅助。 在硬件层面,实整数会有所帮助
循环和数组索引的有效实现,以及引用纹理单元。 然而,没有
要求语言中的整数映射到硬件中的整数类型。 预计不会
底层硬件完全支持广泛的整数运算。 OpenGL ES 着色
语言实现可以将整数转换为浮点数以对其进行操作。 因此,没有便携式
包装行为。

然而我并没有失去希望。 我只需要重新考虑一下...
同时, @lojjic ,您介意告诉我们您想到的替代方法吗?

@FunMiles当然。 通过使用非线性比例对距离值进行编码,我能够将距离场扩展到纹理的边缘,同时仍然为字形的形状保持足够的精度。 因此,非常靠近字形路径的距离(在 1-2 纹素内)有很多值可以使用,而更远的距离则更少。 着色器只需要知道如何转换回原始线性距离。 字形的正确形状看起来很棒,并且挤出轮廓的较低精度几乎不明显,因为无论如何它们都被四舍五入了。

我已经使用两层线性比例和指数比例证明了这一点。 两者都有优点和缺点。

我现在正在实施您的(非常聪明的)方法,即使用相邻边缘值来近似四边形之外的梯度。 到目前为止,这是有道理的,尽管我不确定如何处理角落以外的区域。 一旦我深入了解它可能会变得很明显,但如果你有一个简单的答案我会很感激:)

我现在正在实施您的(非常聪明的)方法,即使用相邻边缘值来近似四边形之外的梯度。 到目前为止,这是有道理的,尽管我不确定如何处理角落以外的区域。 一旦我深入了解它可能会变得很明显,但如果你有一个简单的答案我会很感激:)

我不确定您所说的_在角落之外_是什么意思。 您是指植根于每个角落的四分之一平面,最近的投影是角落本身吗? 我认为您可以在垂直和水平边缘的边缘上使用该规则,并根据到每个边缘的距离进行加权平均。

PS:今天早些时候,在阅读了 WebGL 整数废话之后,我突然想到了将精度降低得很远的想法......更多的位和更多的测试。 即使用rgbaa可以保持平均值的 8 位,然后对于每个rgb ,它们将被签名并且符号将代表每个精度的 1 位平均值,最后是在 0-127 范围内的余数,减去值 64 以表示所需 4 个值中的 3 个的 7 位。 实际值将是中心(范围从 0 到 2^11-1)+ dist_i。 第 4 个值将通过减去它们的总和来恢复,因为总和必须是平均值。 一个只能得到 11 位,但这可能就足够了。 要获得更多位,需要测试值或rgb是否小于 -64、正数或负数、大于 64。这本质上是为中心提供 14 位,为差异仅提供 6 位。 可能是一个很好的妥协? 我需要测试 WebGL 1.0 对准确性的非常松懈的保证不会干扰这种想法......

是的,这就是我的意思,U 和 V 都在 0-1 之外的区域。 谢谢,我会试一试。

@lojjic作为一个问题,在上一篇文章中,您似乎担心每个 SDF 纹素有两个纹理值,因为内存。 虽然我自己更喜欢减少内存,但 64x64 网格上的 256 个字形仅消耗 1/4MB 的纹理内存。 我知道有些语言可能需要更多的字形,但是 BBC 说要阅读报纸,只需要 2000/3000 个字符。 因此,4000 个字符将构成 4MB,每个 texel SDF 一个字节。 值得担心吗?

@FunMiles公平点,我实际上并不太关心这个问题。

还有一些出色的工作要做,但是 b19cd3aff4b0876253d76b3fc66b7e2a1f16a7e5 解决了这个问题。 对于那些使用 react-three-fiber 的人,这已在 drei v2.2.0 中发布
https://github.com/pmndrs/drei/pull/156

我知道这已关闭,但我想说感谢您的工作。 我让它在我的应用程序中工作。 花了一点时间弄清楚属性不会只接受轮廓大小的数字。 然而结果正是我所需要的。
Screen Shot 2020-11-09 at 7 44 52 PM

@FunMiles看起来不错! 您_应该_能够为outlineWidth使用数值,所以如果由于某种原因这对您不起作用,请告诉我。

感谢您一路上的投入。 我仍然无法使用您提到的那种技术对四边形边界之外的距离进行平滑的外推,因此粗轮廓目前特别是在拐角处有凹凸不平的位,但对于大多数情况来说已经足够了(最多〜字体大小的 10%)。 我绝对愿意尝试改进它。

此页面是否有帮助?
0 / 5 - 0 等级