css透明色 【第1980期】CSS 幻术 – 抗锯齿

前言

奇怪的知识点可以Get了。今日早读文章由@仿生狮子授权分享。

@仿生狮子,平时喜欢捣鼓一些好玩儿东西的前端。现在正在深入学习前端各种知识,期待成为技术专家的那天早日到来。工作之余喜欢弹琵琶吉他,欢迎朋友们来B站找仿生狮子玩哦~

正文从这开始~~

传统网页的呈现是基于像素单位的,所以图片不能和 SVG 一样进行任意尺寸缩放后还保持边缘平整。也就是说,放大像素逻辑的图片,必然导致可视质量下降(信息失真)。所以我们往往会使用技术手段去规避失真,如:

如果不得已,被迫进行像素操作,我们也有多种手段用来矫正失真:

这篇文章将会简单的提及以上几点,并介绍一种通过 CSS BackgroundImage 抗锯齿的全新思路(我称之为 Pixel-Offset Anti-Aliasing)。要提前说明的是,当下手机的屏幕分辨率已经相当高,同时处理器性能却十分薄弱,这直接导致我们没有在手机端浏览器讨论抗锯齿的必要。本文所述几乎都局限于桌面端的大显示器(我祈祷你不是在用 8K 分辨率的显示器看这篇博客)。

抗锯齿及相关技术抗锯齿的形成

信息失真(Aliasing)和图像锯齿不是一码事儿,但是对于游戏玩家来说,几乎可以把两者划上等号。要使用 CSS 抗锯齿,我们不得不先提及锯齿的形成。

为什么会有锯齿?

我们的眼睛能对物体的形状进行感知,意识到到一条实际上并不存在的“线条”。见下图,我们能感受到线条,虽然看起来不太平整:

下面这幅图中,带箭头的线代表我们感知的线段,其余线段相交的网格代表像素网格。从上图可以发现,只要是带箭头的线经过的地方,就会被黄颜色填充。不过理想中的线段是完美的,它完全平滑的。把不定方向的平滑线段,映射到像素排列的低 DPI 的屏幕上,就会出现信息丢失的情况。像素颗粒越大,信息丢失情况就越严重(以下就简称为锯齿)。

css 设置透明滚动条样式_css透明色_淘宝透明导航css代码

怎么样看起来才没有锯齿?

这里我画了一张图,可以先仔细观察,然后再站在离显示器稍微远一些的地方眯起眼睛看:

css透明色_css 设置透明滚动条样式_淘宝透明导航css代码

在像素周围,我用黄色涂鸦将丢失的信息稍加补充。图中黄色涂鸦的大小代表了像素透明度。这里有一张抗锯齿的成品图片,可以看处图形的边缘被填充了有透明度的像素:

淘宝透明导航css代码_css 设置透明滚动条样式_css透明色

常见抗锯齿技术

在音频领域,我们可以通过高质量的播放器和无损音频减少传入耳朵的信息失真。但在游戏领域,普通玩家不可能在家里准备了8K显示器。伴随显示器分辨率从 720p 到 1080p 发展的,是几种同样跟随游戏业界发展而成长起来的抗锯齿技术。

SSAA(Super Sampling Anti-Aliasing)

超级采样抗锯齿,它会把当前画面渲染的分辨率成倍提高,比如 1024×768 的图形开启2倍 SSAA 后,显卡实际运算就变成了 2048×1536,这之后,再降采样,将多个像素融合,映射回显示器的单个像素。像素融合能使颜色过渡更自然,看起来没有明显的毛刺。不过css透明色,因为硬件的运算增加(指数级),可以想象它会消耗极高的性能。

MSAA(Multi Sampling Anti-Aliasing)

多重采样抗锯齿,它针对特定缓存区域的数据进行多重采样——可以简单理解为对多边形的边缘进行多重采样。性能消耗较高css透明色,但效果也不错。

FXAA(Fast Approximate Anti-Aliasing)

快速近似抗锯齿,它找到画面中所有图形的边缘并进行平滑处理。尽管很多图形边缘并不对应游戏实际建模的边缘(如材质和纹理),但 FXAA 性能消耗小,性价比高,不失为一种抗锯齿的常用选择。

DLSS(Deep Learning Super Sampling)

深度学习超级采样,它通过硬件加速的深度学习算法,根据几何、着色、时域多个方面的数据(说人话就是根据过往帧、形状、像素动量等数据)对实时渲染的低分辨率图像重建多倍超级采样结果。相对于传统渲染,不仅能极大提高画质,还能极大提高帧率。

CSS 抗锯齿技术

以下,我们提及几种常见的抗锯齿技术。

CSS Font-Smoothing

字体平滑属性属于早期的 CSS 规范,后来因为种种原因又被移除了。不过现在仍可以通过前缀属性兼容(如 -webkit-font-smoothing)。一般来说,字体平滑有三个值可选,none、subpixel-antialiased、antialiased。值的作用正如其名,分别是无抗锯齿,亚像素级抗锯齿和(全像素)抗锯齿。

一般来说,屏幕上的每一个像素点,都是由三原色条纹(可能如红、绿、蓝三个发光点)组合而成。亚像素级抗锯齿,意味着字体渲染时,将以亚像素(如红光)为单位。不发光的像素显示黑色,其余像素在抗锯齿处理时则会显示暗色,见下图:

css 设置透明滚动条样式_淘宝透明导航css代码_css透明色

全像素抗锯齿,则以整颗像素(包含红蓝绿三个条纹)为单位渲染字体。抗锯齿处理时,字体若超出了一个像素的单位,会以一颗与之相邻的透明暗色像素作平滑,见下图:

css 设置透明滚动条样式_css透明色_淘宝透明导航css代码

“后浪”的“后”字,中间那一横,实际的宽度要小于一个像素,所以也用透明暗色渲染。除了单字,在 @MAXVOLTAR 这篇博客,有英文排版的示例图片,以下直接引用了:

none

subpixel-antialiased

antiliasing

那三种值应该如何选择呢?

我的建议是,仅仅了解渲染机制和呈现方式就行。像素抗锯齿会使字体呈现稍细,而亚像素级抗锯齿则使字体呈现过粗。黑色背景下则反之。倒不必因为知道它就必须使用上——这三种方式有各自的优点和缺陷。一般来说,扔掉这个属性,让浏览器自行判断字体渲染的方式就可以了。如果你引入了特殊字体(比如印刷字体)进行平滑处理。(我相信中文的网页版面下,能自由发挥的范围应该很有限。)

附,感兴趣的话,文末我留了相关链接,可以再查阅。

CSS Image-Rendering

Image-Rendering 属性用于设置图像缩放算法,这个属性有几种常见的值。见下组件:

可以发现,Pixelated 值设置之后,浏览器不会对边缘进行平滑处理,而 Auto 则对整幅图像进行柔和处理。也就是说,使用 Transform Scale 放大图片,浏览器会应用默认的平滑缩放算法(可能是双线性插值之类的)。

那可不可以对图片先放大数倍,再缩小还原为实际尺寸呢?以下是试验结果:

css 设置透明滚动条样式_css透明色_淘宝透明导航css代码

不知道是浏览器对多个 Scale 串联进行了优化,还是使用了某种不损失图像信息的采样算法,总之不改变图片尺寸又想使用平滑图片是行不通的。

硬件加速抗锯齿

关于使用浏览器的硬件加速抗锯齿功能,是我在试验 PXAA 时的偶得(不过已经有博客介绍过了)。当元素通过 Transform:Rotate 旋转之后,如果此元素是被 GPU 渲染的,那么会应用浏览器对应 GPU 的抗锯齿属性——比方说你用 GTX 1060ti 运行浏览器,那么相关配置就能在英伟达控制面板中找到(不过这有相当程度是我的猜测,待验证)。听起来好像有点复杂,看下面例子就一目了然了:

css 设置透明滚动条样式_淘宝透明导航css代码_css透明色

当元素旋转,并应用硬件加速(TranslateZ)之后,渲染出来的边缘会被平滑处理。但是如果仅仅启用硬件加速或是单使用旋转,不能达到效果。经过我的测试,在 Windows 端 Chrome 内核的浏览器,这种抗锯齿方式能得到一些体验——你甚至可以通过仅旋转 0.1° 来柔和边缘(虽然不明显)。CSS 相关的抗锯齿技术就到此为止,下一节开始是新的思路。

Pixel-Offset Anti-Aliasing

像素偏移抗锯齿(下简称 POAA),这是一种很神奇的方法,貌似网上还没人分享过,不过效果确实挺惊艳的。我不知道具体原理是什么,但是它就是有效(It works!)。这里有两副使用 BackgroundImage 属性绘制的图像,我先展示一下应用 POAA 后的结果吧:

效果展示

css 设置透明滚动条样式_淘宝透明导航css代码_css透明色

原理

常见的游戏抗锯齿技术是建立在游戏渲染前或后,从模型到光照多个步骤产生的数据的基础上的,所以我们可以根据帧历史的内容、像素动量、提高采样等方法中进行筛选信息并重建画面。但浏览器给用户展现的内容,可以说就是渲染后的东西。我们能够参与浏览器内部渲染的方式貌似几乎没有(以后可能会有 CSS Houdini)。比方说使用 BackgroundImage 绘图展现在你的屏幕上的这些像素,你无法参与渲染改变它们,你也没有办法用预渲染数据告诉浏览器“你应该这样做”。不过好消息是,程序员都是坚信任何问题都能被解决的人,这里我们换种思路。

我想你应该记得开篇我们提及过 FXAA。FXAA 可以简单概括为边缘寻找->重建边缘这两个步骤(并不专业,也许还会有矫正之类的我不清楚)。在Implementing FXAA这篇博客中,解释了 FXAA 具体是如何运作的。对于一个已经被找到的图形边缘,经过 FXAA 处理后会变成这样,见下两幅图:

css透明色_淘宝透明导航css代码_css 设置透明滚动条样式

给 FXAA 输入源图像,就能通过颜色或对比度确认物体的边缘,并通过改变像素周围的点的透明度,让整体看起来得到平滑。仔细想想,使用 BackgroundImage 绘图时,其实我们已经知道边缘在哪儿了。边缘不藏在国王的帽子里,它就在我们写的代码中。比方,上一小节那个圆形渐变图形的源码是这样的:

css 设置透明滚动条样式_淘宝透明导航css代码_css透明色

  1. .circle-con {

  2. $c1: #cd3f4f;

  3. $c2: #e6a964;

  4. position: relative;

  5. height: 300px;

  6. background-image: repeating-radial-gradient(

  7. circle at 0% 50%,

  8. $c1 0,

  9. $c2 50px

  10. );

  11. }

我们可以轻易找到找到边缘——对,就是那些渐变的颜色改变的地方——0px(50px)。现在我们有了边缘信息,接着就要重建边缘。重建边缘也许可以再拆分,分为以下几个步骤:

这就是大体思路,我们并没有参与浏览器的渲染,而是通过像 FXAA 一样的后处理的方法。在已渲染的图像上做文章。不过将上述步骤仔细考虑后,会发现问题的难点在于如何生成抗锯齿条纹。

css透明色_css 设置透明滚动条样式_淘宝透明导航css代码

总之,我们需要继续改良思路。

在 BackgroundImage 中,像素是基本单位不能再分,点的透明度显然不能通过点的大小来模拟。这里有两种解决方法:

至于线段,也可以用 BackgroundImage 模拟,比如针对上面那段 CSS 代码,可以通过改写成以下方式:

  1. .circle-con {

  2. $c1: #cd3f4f;

  3. $c2: #e6a964;

  4. $line-width: 1px;

  5. position: relative;

  6. height: 300px;

  7. background-image: repeating-radial-gradient(

  8. circle at 0% 50%,

  9. $c1 0,

  10. transparent calc($line-width),

  11. transparent calc(50px - $line-width),

  12. $c2 50px

  13. );

  14. }

取得线段之后,将容器偏移几个单位像素,放到浏览器测试结果:

可以发现,会自然而然得到颜色混合透明度组成的线段。只不过透明度的方向并不是我们想要的。我希望能够得到透明线条反过来的图样:

css 设置透明滚动条样式_css透明色_淘宝透明导航css代码

经过试验,我发现只需简单调换颜色的顺序就行。比方说这是在容器 50% 的位置绘制的一条线段:

  1. .old {

  2. background: linear-gradient(

  3. var(--deg),

  4. transparent,

  5. transparent

  6. calc(50% - var(--line-width)),

  7. yellow 50%,

  8. red 50%,

  9. transparent calc(50% + var(--line-width)),

  10. transparent

  11. );

  12. }

如果将线段颜色调换,就会变成:

  1. .new {

  2. background: linear-gradient(

  3. var(--deg),

  4. transparent,

  5. transparent

  6. calc(50% - var(--line-width)),

  7. red 50%,

  8. yellow 50%,

  9. transparent calc(50% + var(--line-width)),

  10. transparent

  11. );

  12. }

得到了我们想要的线段虚化的效果!这之后要做的事儿是吻合线条。

接下来是见证奇迹的时刻:

css 设置透明滚动条样式_淘宝透明导航css代码_css透明色

Well done!

来一张成品 GIF,稍微离屏幕远一些看效果最好:

css 设置透明滚动条样式_css透明色_淘宝透明导航css代码

成品在吻合线条的基础上还增加了一些内容及调整了相关参数:

成品的代码如下:

  1. .repeat-con {

  2. --c1: #cd3f4f;

  3. --c2: #e6a964;

  4. --c3: #5996cc;

  5. position: relative;

  6. height: 300px;

  7. background-image: repeating-linear-gradient(

  8. var(--deg),

  9. var(--c1),

  10. var(--c1) 10px,

  11. var(--c2) 10px,

  12. var(--c2) 40px,

  13. var(--c1) 40px,

  14. var(--c1) 50px,

  15. var(--c3) 50px,

  16. var(--c3) 80px

  17. );


  18. &.antialiasing {

  19. &:after {

  20. --offsetX: 0.4px;

  21. --offsetY: -0.1px;

  22. --dark-alpha: 0.3;

  23. --light-alpha: 0.6;

  24. --line-width: 0.6px;

  25. content: '';

  26. position: absolute;

  27. top: var(--offsetY);

  28. left: var(--offsetX);

  29. width: 100%;

  30. height: 100%;

  31. opacity: 0.5;

  32. background-image: repeating-linear-gradient(

  33. var(--deg),

  34. var(--c3),

  35. transparent calc(0px + var(--line-width)),

  36. transparent calc(10px - var(--line-width)),

  37. var(--c2) 10px,

  38. var(--c1) 10px,

  39. transparent calc(10px + var(--line-width)),

  40. transparent calc(40px - var(--line-width)),

  41. var(--c1) 40px,

  42. var(--c2) 40px,

  43. transparent calc(40px + var(--line-width)),

  44. transparent calc(50px - var(--line-width)),

  45. var(--c3) 50px,

  46. var(--c1) 50px,

  47. transparent calc(50px + var(--line-width)),

  48. transparent calc(80px - var(--line-width)),

  49. var(--c1) 80px

  50. );

  51. }

  52. }

  53. }

理论上,通过 SCSS 函数,能自动判断代码中线段的位置并生成填充抗锯齿的像素。无论是 LinearGradient、ConicGradient 还是 RadialGradient,都可以抗锯齿。不过我只是当试验品来写,所以没有写相应的工具函数。欢迎各位补充到 Github。

———END———
限 时 特 惠:本站每日持续更新海量各大内部创业教程,一年会员只需128元,全站资源免费下载点击查看详情
站 长 微 信:jiumai99

滚动至顶部