借助全新的画布渲染模块 Qt Canvas Painter,我们致力于打造最好的现代 C++ 绘画 API。本博客将介绍它为 Qt 用户带来的一些新的创新画布渲染特性,并解释它们是什么以及如何使用。
在阅读本篇博客之前,请查看上一篇介绍 Qt Canvas Painter 的博客。了解了 Qt Canvas Painter 的基础概念后,您可以继续阅读本文,了解其提供的新画布渲染特性的详细信息。
Qt Canvas Painter 基本上是移植到 Qt C++ 的 HTML Canvas 2D 上下文 API,并做了一些删减和增补。在上一篇博客中,我们介绍了相较于 2D 上下文,canvas 渲染功能的主要缺失部分,因此在本篇博客中,我们将更多地讨论新增功能。这些新增功能旨在实现此 API 的主要目标:在硬件加速的命令式绘制器上尽可能有效地结合性能和开发效率。
接下来,我们将逐一介绍这些新功能,但首先,请注意以下警告:
Qt Canvas Painter 是 Qt 6.11 的技术预览版。这意味着我们还不能保证 API 或 ABI 的稳定性。
详细介绍的 canvas 渲染特性有:
可调抗锯齿:可自由调整路径填充和笔触的抗锯齿,例如,用于渲染阴影和发光。
盒状渐变:一种新颖的渐变类型,仅需几个三角形即可实现圆角矩形渐变。
方框阴影:采用 SDF 方法实现高性能动态阴影。
网格图案:用于动态网格和条形图的画笔,即使在动画效果下性能也非常出色。
自定义着色器笔刷:使用自定义片段和顶点着色器的定制笔刷。
色彩效果:调整填充和描边的透明度、亮度、对比度和饱和度。
在博客的最后,还简要提到了其他一些功能。
抗锯齿功能是现代用户界面的重要组成部分,在 2D 画布渲染中也是如此。QPainter 的默认光栅后端支持抗锯齿,但实现抗锯齿的方法会增加 CPU 使用率。另一方面,QPainter 的 OpenGL 后端支持使用多重采样(MSAA)技术实现抗锯齿。这需要 GL_EXT_framebuffer_multisample 和 GL_EXT_framebuffer_blit 扩展的支持,桌面端设备通常支持这些扩展,但嵌入式设备却不一定支持。
根据多年来许多用户的反馈,由于多种原因,只选择 MSAA 来进行抗锯齿并不理想。出于性能或资源使用方面的考虑,选择在整个呈现目标中使用 MSAA 对某些人来说并不合适,而对其他人来说,在整个绘图过程中打开 MSAA 在视觉上并不合适。Qt Canvas Painter 支持 MSAA 和顶点抗锯齿方法,可实现无锯齿边缘的平滑渲染,而无需启用 MSAA。
顶点抗锯齿功能强大的一点是,抗锯齿量可以自由调整。设计师可以不采用常见的 1 像素抗锯齿,而选择例如 1.5 像素或 3 像素的抗锯齿量,使绘画效果更平滑。而且,抗锯齿量还可以调整,不同的路径可以使用不同的抗锯齿量。
这种可自由调节的抗锯齿可以创造性地用于渲染阴影和发光等效果。
对于文本,方法则略有不同。对于文本渲染,Canvas Painter 使用符号距离场 (SDF) 方法。基本上,字体字形被渲染为模糊的小纹理,然后使用片段着色器 smoothstep() 来创建清晰、可缩放的文本。但 SDF 也允许使用部分模糊效果来使文本更平滑。由于 SDF 字体纹理的分辨率较低,这种平滑效果受到一定限制,但通过此技术仍然可以实现更平滑的文本效果。
要了解有关这些功能的更多信息,请参阅 QCanvasPainter 的 setAntialias() 和 setTextAntialias() 方法。
Qt Canvas Painter 支持所有常见的渐变类型:线性渐变、径向渐变和锥形渐变。这些渐变功能与 2D 上下文的功能一致,其 API 对于 QPainter 和 C++ 用户来说比带有许多参数的 create*Gradient() 方法更自然一些。
但 Qt Canvas Painter 还支持一种额外的渐变类型,即 QCanvasBoxGradient。现代用户界面通常不同程度的圆角的矩形。使用 roundRect() 方法很容易创建这些矩形,但如果您希望边缘更平滑,或使用多种颜色为圆角矩形着色,该怎么办呢?在这种情况下,您可以使用 QCanvasBoxGradient 笔刷,用它来绘制非圆角矩形。
QCanvasBoxGradient 笔刷可以单独调整半径和羽化,因此适用于边角较尖或较软的不同用例。
基准测试和性能将在下一篇博文客中讨论,但我们还是简要谈谈盒状渐变。最初,人们可能会认为下图显示的是两个绘制相似的圆角矩形,对吗?
但事实并非如此。左边是带有填充和描边的 roundRect(),右边是带有填充的盒状渐变。它们的输出外观非常相似,但在底层实现上却截然不同。左侧图形在当前分辨率下,使用了 202 个三角形,描边使用了 280 个三角形,因此总共使用了 484 个三角形。右图只使用了 10 个三角形即可实现抗锯齿填充,因此它的三角化处理速度更快。因此,如果三角化处理或顶点处理在特定目标硬件上成为性能瓶颈,而片段着色器仍有空闲处理能力,不妨考虑采用盒状渐变来绘制圆角矩形。
Canvas Painter 的 QCanvasBoxShadow 是上述盒状渐变的近亲,其 API 与CSS 盒状阴影相匹配。但它没有使用速度较慢的高斯模糊方法,而是在片段着色器上使用了高性能的 SDF 方法。这也可以看作是 Qt Quick 的 RectangularShadow 元素的命令式版本。
由于这些方框阴影是通过相当简单的数学计算得出的,因此它们只支持圆角矩形阴影,与高斯模糊阴影的渲染输出并不完全匹配。但如果面临因性能问题而完全无法使用阴影,或是采用快速盒阴影之间的抉择,后者无疑是值得选择的方案,因为它们具有极快的渲染和动画速度。
了解 QPainter 的人可能还知道,QBrush 包含 Qt::BrushStyle,其中提供了一些针对条形和网格的不同图案样式。这些样式被硬编码为特定的模式,如今在示例之外已很少使用,因为设计师通常希望使用更具体的模式。为满足这一需求并提供更现代化的网格与条形图案方案,Qt Canvas Painter 推出了 QCanvasGridPattern 笔刷。
与 QBrush 样式相比,该类通过毫不费力地定义线宽、位置、旋转、羽化量等,实现了更大的自由度。渲染是通过相当简单的片段着色器代码完成的,因此即使图案值是动态的,性能也非常好。
当然,网格也可以通过描线来绘制。但不同的是,使用网格图案时,无论网格包含 10 条线还是 1000 条线,性能都完全相同。
现在,Qt Canvas Painter 为描边和填充提供了相当丰富的画笔材质,这一点已经很明显了。但还有一点:自定义着色器画笔。
由于 Qt Canvas Painter 是在 QRhi 的基础上构建的,因此实际上需要 GPU 支持,我们也可以构建需要着色器的 API。这样,填充和描边就不受内置笔刷的限制,可以广泛定制。下面是几个使用自定义笔刷的示例。
当然,自定义笔刷也可用于文本处理。例如,自定义顶点着色器可用于动态调整字符位置。
QCanvasCustomBrush 是技术预览期间将有所变动的的类之一,因为我们希望使其功能更强大,以涵盖多种使用场景。
HTML 2D 上下文有一个名为 globalAlpha 的属性,用于设置填充/描边的当前透明度。在 QPainter 中,同样的功能称为 setOpacity()。同样,Canvas Painter 也允许设置全局 alpha,但它还支持额外的色彩效果:亮度、对比度和饱和度。下面是一个结合了上述所有效果的示例,将背景绘画变为纯色和深色,这样顶部的弹出窗口就会更加突出,便于用户注意。
通过使用这些特性,可以轻松地为单个路径或整个画布应用色彩特效并制作动画。由于这些特效是作为画布呈现器片段着色器的最后一步应用的,而且不需要呈现到屏幕外的缓冲区,因此使用这些特效几乎没有任何开销。
与 HTML 2D 上下文相比,还存在一些其他相对次要的附加 Canvas 渲染功能,但在此可以简要提及:
为路径添加圆的简单方法。(2D 上下文 API 要求使用从 0 到 2 * PI 的 arc() 来绘制圆)。
在实体路径中添加孔路径的简单环绕方法。
离屏画布类允许渲染到缓冲区中,以便缓存或与图像模式一起使用。其用法与 HTML 画布 2D 上下文OffscreenCanvas 相似,但使用的是相同的 QCanvasPainter,而不是单独的渲染上下文。
使用 setBrushTransform() 对画笔进行单独变换。画笔和路径都会受到状态变换(变换、旋转、缩放等)的影响。Canvas Painter 还为画笔材质提供了一个额外的矩阵,使其能够独立于路径进行变换。
以上示例只是 Qt Canvas Painter 在 HTML 2D 上下文 API 基础上提供的一些额外画布渲染特性。随着 Canvas Painter 的不断发展,我们将对这些功能进行微调,并添加更多功能。
若想参与这场激动人心的开发进程,请安装 Qt 6.11 预发行版或从源代码中构建 Qt 并试用 Canvas Painter 的功能。同时欢迎提交错误报告和建议工单。获取真实用户反馈是我们改进产品、使其脱离技术预览阶段并成为 Qt 解决方案完全支持功能的最佳途径。有关本博文和 Canvas Painter 的问题也可以在相关的Qt Forum 主题中提出。
期待在下一期博客中与您再会,我们将在博客中详细介绍新版 Canvas Painter 无与伦比的性能表现!