使用 SVG 和 CSS 绘制逼真的云彩

Avatar of Beau Jackson
Beau Jackson

DigitalOcean 为您旅程的每个阶段提供云产品。 立即开始使用 200 美元的免费积分!

希腊神话讲述了宙斯创造云仙女涅斐勒的故事。 与其他希腊神话一样,这个故事变得相当离奇和色情。 这是一个非常简短、礼貌的版本。

据说,涅斐勒是由宙斯以他美丽妻子的形象创造的。 一个凡人遇见了涅斐勒,爱上了她,并且他们一起睡了一觉™。 最后,在一次奇怪的转变中,云生下了半人半马的肯陶洛斯婴儿。

奇怪吧?就我个人而言,我无法理解它。 谢天谢地,在浏览器中创建云彩的过程要简单得多,而且风险也小得多。

袁川的云彩细节。(演示

最近,我发现开发者 袁川 实现了代码生成的、照片般真实的云彩。 对我来说,在浏览器中实现这种效果一直是神话般的东西。

只需看一眼 这个笔 中的代码,我们就可以想象,通过使用 CSS 的 box-shadow 以及包含两个 SVG 滤镜作为补充的 <filter> 元素,可以实现令人信服的单个云彩。

我们想要的逼真效果是通过 feTurbulencefeDisplacementMap 的巧妙混合实现的。 这些 SVG 滤镜功能强大、复杂且提供了非常令人兴奋的功能(包括 一个获得奥斯卡奖的算法)! 但是,在幕后,它们的复杂性可能会有点令人生畏。

虽然 SVG 滤镜的物理学超出了本文的范围,但 MDNw3.org 上提供了大量可用的文档。 一个关于 feTurbulencefeDisplacement 的非常有见地的 页面是免费提供的(并作为 这本精彩的书 的一个章节)。

对于本文,我们将专注于学习如何使用这些 SVG 滤镜来获得惊人的效果。 我们不需要深入研究算法背后的工作原理,就像艺术家不需要了解颜料的分子结构就能渲染出令人惊叹的风景一样。

相反,让我们密切关注少量对在浏览器中绘制令人信服的云彩至关重要的 SVG 属性。 它们的应用将使我们能够将这些强大的滤镜屈服于我们的意志,并学习如何在自己的项目中精确地自定义它们。

让我们从一些基础知识开始

CSS 的 box-shadow 属性有五个值值得密切关注

box-shadow: <offsetX> <offsetY> <blurRadius> <spreadRadius> <color>;

让我们提高这些值(可能高于 任何理智的开发者都会使用的值),以便这个阴影本身成为舞台上的一个角色。

演示
#cloud-square {
  background: turquoise;
  box-shadow: 200px 200px 50px 0px #000;
  width: 180px;
  height: 180px;
}

#cloud-circle {
  background: coral;
  border-radius: 50%;
  box-shadow: 200px 200px 50px 0px #000;
  width: 180px;
  height: 180px;
}

你制作过或见过影子玩偶吗?

鸣谢:Double-M

就像手改变形状来改变阴影一样,我们 HTML 中的“源形状”可以移动和变形以移动和改变在浏览器中渲染的阴影的形状。 box-shadow 复制原始大小和 border-radius 的“变形”功能。 SVG 滤镜应用于元素及其阴影。

<svg width="0" height="0"> 
  <filter id="filter">
    <feTurbulence type="fractalNoise" baseFrequency=".01" numOctaves="10" />
    <feDisplacementMap in="SourceGraphic" scale="10" />
  </filter>
</svg>

这是我们 SVG 的标记。 它不会渲染,因为我们还没有定义任何视觉效果(更不用说零宽度和高度了)。 它的唯一目的是保存一个我们将馈送到我们的 SourceGraphic我们的 <div>)的滤镜。 我们的源 <div>及其阴影都通过滤镜独立变形。

我们将添加必要的 CSS 规则,使用其 ID 将 HTML 元素(`#cloud-circle`)链接到 SVG 滤镜。

#cloud-circle {
  filter: url(#filter);
  box-shadow: 200px 200px 50px 0px #fff;
}

瞧!

好的,所以不可否认,添加 SVG 滤镜相当不起眼

(演示)

别担心! 我们才刚刚触及表面,还有很多好的东西需要查看。

尝试使用 feDisplacementMap scale 属性

对这个属性进行一些科学的实验可以产生戏剧性的效果。 目前,让我们保持 feTurbulence 中的所有值不变,只需调整 DisplacementMapscale 属性。

随着 scale 的增加(以 30 的增量),我们的源 <div> 变得扭曲,并投射出一个阴影来反映云彩在天空出现时的随机形式。

<feDisplacementMap in="SourceGraphic" scale="180"/>
scale 属性以 30 的值递增。(演示

好的,我们取得了一些进展! 让我们稍微更改一下颜色,以产生更令人信服的云彩并“增强”效果。

body {
  background: linear-gradient(165deg, #527785 0%, #7FB4C7 100%);
}

#cloud-circle {
    width: 180px;
    height: 180px;
    background: #000;
    border-radius: 50%;
    filter: url(#filter);
    box-shadow: 200px 200px 50px 0px #fff;
}

现在我们越来越接近逼真的云彩效果了!

修改 box-shadow blur 值

以下图像集显示了 blur 值对 box-shadow 的影响。 在这里,blur 以 10 像素的增量增加。

随着 blur 值的增加,云变得“更柔和”。

为了使我们的云彩具有一定的积云效果,我们可以稍微加宽我们的源 <div>

#cloud-circle {
  width: 500px;
  height: 275px;
  background: #000;
  border-radius: 50%;
  filter: url(#filter);
  box-shadow: 200px 200px 60px 0px #fff;
}
太好了,现在源元素挡路了。😫

等等! 我们加宽了源元素,现在它挡住了我们称为云彩的白色阴影。 让我们将阴影“重新投射”到更远的距离,这样我们的云彩就不会再被源图像遮挡。(可以将其想象成将你的手从墙上移开,这样就不会挡住影子玩偶的视野。)

这可以通过一些 CSS 定位很好地实现。 <body> 是我们云彩的父元素,默认情况下是静态定位的。 让我们通过一些绝对定位将我们的源 <div> 往上和往外移开。 最初,这也会重新定位我们的阴影,因此我们还需要增加阴影与元素的距离,并稍微更多地推动元素。

#cloud-circle {
  width: 500px;
  height: 275px;
  background: #000;
  border-radius: 50%;
  filter: url(#filter);
  box-shadow: 400px 400px 60px 0px #fff; /* Increase shadow offset */
  position: absolute; /* Take the parent out of the document flow */
  top: -320px; /* Move a little down */
  left: -320px; /* Move a little right */
}

是的! 我们已经得到了一个非常有说服力的云彩。

查看 Beau Haus 在 CodePen 上的笔
(@beauhaus)
CodePen 上。

渲染到浏览器中的内容是云彩的相当不错的描述——但是,我不确定……这朵云真的能体现云仙女涅斐勒吗? 我相信我们可以做得更好!

通过图层传达深度

这是我们想要的效果

A photo of clouds against a blue sky. The clouds have shades of gray that provide depth.
鸣谢:pcdazero

从这张照片中云彩的深度、纹理和丰富性来看,有一点很清楚:宙斯上了艺术学校。 至少,他一定读过 设计通用原则,该书阐述了一个强大但又看似普通的概念

[...] 照明偏差在深度和自然度的解释中起着重要作用,并且可以由设计师以各种方式进行操控……利用明暗区域之间的对比度来改变深度的外观。

这段话为我们提供了一个线索,说明我们如何才能极大地改进我们自己生成的代码云。 通过将不同形式、大小和颜色的图层叠加在一起,我们可以以相当高的保真度渲染我们的云彩,使其与参考图像中的云彩相符。 这只需要根据需要调用我们的 filter 多次即可。

<svg width="0" height="0">
    <!-- Back Layer -->
    <filter id="filter-back">
      <feTurbulence type="fractalNoise" baseFrequency="0.012" numOctaves="4" />
      <feDisplacementMap  in="SourceGraphic" scale="170" />
    </filter>
    <!-- Middle Layer -->
    <filter id="filter-mid">
      <feTurbulence type="fractalNoise" baseFrequency="0.012" numOctaves="2" />
      <feDisplacementMap  in="SourceGraphic" scale="150" />
    </filter>
    <!-- Front Layer -->
    <filter id="filter-front">
      <feTurbulence type="fractalNoise" baseFrequency="0.012" numOctaves="2" />
      <feDisplacementMap  in="SourceGraphic" scale="100" />
    </filter>
</svg>

应用我们的图层将使我们有机会探索 feTurbulence 并实现其多功能性。 我们将选择可用的更平滑的类型:fractalNoise,并将 numOctaves 调高到 6。

<feTurbulence type="fractalNoise" baseFrequency="n" numOctaves="6"/>

这一切意味着什么? 现在,让我们特别关注 baseFrequency 属性。 以下是在我们增加 n 的值时得到的结果

值越低,得到的云彩越圆润和模糊。 值越高,得到的云彩越圆润和坚硬。

诸如**湍流**、**噪声**、**频率**和**倍频程**之类的词语可能显得奇怪,甚至令人困惑。但不要害怕!实际上,将此滤镜的效果比作声波是完全准确的类似于声波。我们可以将低频(baseFrequency=0.001)等同于低沉、模糊的噪音,并将升高的频率(baseFrequency=0.1)等同于更高、更清晰的音调。

我们可以看到,对于类似积云的效果,baseFrequency的最佳范围可能舒适地位于约 0.005 和 0.01 之间。

使用 numOctaves 添加细节

递增 numOctaves 允许我们以极其细致的细节渲染图像。这需要大量的计算,因此请注意:高值会显著影响性能。除非您的浏览器戴着头盔和护膝,否则请尽量不要增加此值。

我们将 numOctaves 设置的值越高,云的细节就越精细。

好消息是,我们不需要将此值调得太高就能产生细节和精致感。如上图所示,我们可以将 numOctaves 值设置为 4 或 5 即可满足需求。

结果如下

查看 CodePen
由 Beau Haus (@beauhaus) 创建
CodePen 上。

使用 seed 属性创建无限变化

关于 seed 属性有很多话要说,因为它提供了一丝幕后魔法的线索。但是,就我们的目的而言,seed 的实用性可以概括为四个字:“不同的值,不同的形状”。

Perlin 噪声函数(前面提到过)使用此值作为其随机数生成器的起点。选择包含此属性将使 seed 默认设置为零。但是,如果包含此属性,无论我们为 seed 提供什么值,都不需要担心性能下降。

Animation showing thr shape of a cloud changing as the seed value changes.
不同的 seed 值会产生不同的形状。

上面的 GIF 展示了 seed 可以提供的一些功能。请记住,每朵云都是分层的复合云。(虽然我调整了每一层的属性,但我保持了它们各自的 seed 值相同。)

鸣谢:Brockenhexe

在这里,仔细观察参考图像,我将 3 个具有不同不透明度的云 <div> 分层到单个基础 div 上。通过反复试验和输入任意 seed 值,我最终得到了一个类似于照片中云朵形状的形状。

查看 CodePen
Nephele 参考图像研究
由 BEAU.HAUS (@beauhaus) 创建
CodePen 上。

天高任鸟飞

当然,认为我们绘制到浏览器上的 <div> 可以胜过宙斯的涅斐勒,这未免有些自负。

然而,我们能够从 CSS 和 SVG 滤镜中提取的奥秘越多,我们就越能够创造出视觉上令人惊叹的东西,并高度忠实于雷神最初的创造。然后,我们可以继续进行更多实验!

反射薄雾

动画反射薄雾

高积云

高积云

在本文中,我们只是触及了力量和复杂性海洋的表面。SVG 滤镜通常看起来令人难以理解且难以接近。

但是,就像在一个 Div 项目项目或戴安娜·史密斯的绘画技巧中找到的示例一样,一种充满趣味和实验性的方法将始终会带来惊人的结果!

成就解锁!涅斐勒云生成器

我相信你们很多人对制作云所需的全部技术细节感到兴奋,但可能更喜欢一些更简单的方法来在项目中使用云。我开发了一个小工具来帮助生成云并试验形状和变化。

制作云彩!

有任何问题、建议或意见?在推特上联系我或在此帖中发表评论。


非常感谢Amelia Bellamy-Royds对本文提供的宝贵建议。