4 ポイント 投稿者 GN⁺ 2023-12-08 | 2件のコメント | WhatsAppで共有
  • Webイラストが平面的なベクターを超えてノイズ質感やライティング・陰影を使う方向へ広がるなか、それを軽量で再利用しやすいSVGとして実装する方法が重要になっている
  • 核心となる目標は、単純な図形にテクスチャ付きの影効果を加え、SvelteやReactコンポーネントへ移して色や形を変えて使えるようにすること
  • 基本構造は、<defs>で図形を一度定義し、<use>で再利用したうえで、masklinearGradientにより色に縛られにくいグラデーションマスクを構成する
  • 粒子感は<feTurbulence>ベースのSVGフィルターで作り、<feComposite><feColorMatrix><feBlend>を組み合わせて、選んだ塗り色と混ぜ合わせる
  • 結果は意図した質感に到達したものの、Safariのレンダリング差が残っており、ブラウザごとの表現や、より複雑なイラストへの適用には追加調整が必要

コードだけでSVGにノイズ質感を追加する

  • 最近のWebデザインではベクターイラストが引き続き使われる一方で、きれいな平面図形よりも粗い粒子感の入ったライティング・陰影スタイルが目立つ
  • こうしたスタイルはイラスト制作ツールからSVGとして書き出すこともできるが、テクスチャがラスターとして入ったり、ベクターのサイズが大きくなったりする可能性がある
  • 目標は、一般的なSVG仕様だけでノイズのあるテクスチャ影効果を作り、コンポーネント化しやすい構造を保つこと
  • 再利用性のため、図形や色を変えても同じ方式が動作するように構成する

基本図形とグラデーション

  • 円や四角形のようなSVG基本図形は簡単に描けるが、再利用可能なコンポーネントでは固定色のグラデーションだけでは不十分
  • 基本色に合わせて変化するグラデーションを作るためにマスク(mask) を使う
    • SVGマスクは、黒と白によって見える領域や隠れる領域を制御する
    • 方式はやや分かりにくい場合があるが、単純な例から始められる
  • 同じ円図形を複数回使う構造なら、<defs>内に<circle id="shape">を定義し、<use href="#shape">で参照する
  • この方式は繰り返しを減らし、図形定義を一か所に集約するDRYな構造

ノイズテクスチャの生成方法

  • グラデーションの上に質感を載せるため、SVGのフィルター(filter) を使う
  • ノイズ生成は<feTurbulence>が担う
    • <feTurbulence>は人工的なテクスチャを作るSVGフィルタープリミティブ
    • 望む雰囲気を得るには複数の値を調整する必要がある
  • 最終フィルターは複数の段階を経て、質感を色と混ぜ合わせる
    • <feTurbulence type="fractalNoise" baseFrequency="19.5" numOctaves="10">でノイズを生成する
    • <feComposite>でturbulenceの結果をSourceAlpha内に合成する
    • <feColorMatrix type="luminanceToAlpha">で色の変化を抑える
    • <feBlend mode="color-burn">で元のグラフィックと合成する
  • フィルターは最終的な<use>要素にfilter="url('#noise')"として適用する

最終的なSVG構成

  • 最終例では、<defs>内に図形、フィルター、マスク、グラデーションをまとめて定義する
  • グラデーションはlinearGradient id="fade"で構成される
    • 0%地点は黒色にstop-opacity="0.6"を使う
    • 65%75%地点は白色を使い、透明度を段階的に調整する
  • 最終図形には塗り色、マスク、フィルターが一緒に適用される
    • fill="hsl(337, 92%, 69%)"
    • mask="url(#gradient)"
    • filter="url('#noise')"
  • 色を追加し、グラデーションをさらに微妙に調整することで、望む視覚効果に近い結果を得る
  • 全体の結果はFull Demoで確認でき、実装コードはrepositoryで公開されている

結果と限界

  • 追加の実験を経て満足できるイラストを作れたが、結果は完璧なレベルではない
  • 特にSafariでは、他のブラウザとは異なるレンダリングになる
  • 参考にした複雑なイラストに比べるとまだ単純だが、さらに磨き込めば、より多くの場面で使える可能性がある
  • 2023年12月7日の追記によると、Hacker Newsへの投稿後、Jimmy Chionによる2021年のCSS-Tricks記事Grainy Gradientsを知った
  • 同記事が到達した手法はこの方式と似ており、補足資料として併せて参考にできる

2件のコメント

 
ndrgrd 2023-12-09

やはり期待を裏切らないSafari! 2020年代のIEという名前がもったいないくらいですね

 
GN⁺ 2023-12-08
Hacker News のコメント
  • 最近 SVG を触っていて、canvas では作りにくかった ドロップシャドウを入れようとしたのですが、Safari では SVG 内の CSS ドロップシャドウが動作せず、SVG ネイティブのドロップシャドウフィルターを使う必要があり、その結果も他のブラウザとは違って見えました
    具体例は https://www.einarmagnus.net/pages/logo にあり、JavaScript で Safari を検出して SVG 版を有効にし、CSS 版を無効にするようにしています
    「再帰的な陰陽」の背景は https://einarmagnus.net/blog/2020/01/22/finding-the-logo にあります
    テクスチャももっと実験してみたいのですが、Safari であまりにも違って見えるのがもどかしいです。いつか SVG の解釈の仕方が収束するのか気になります

    • 当面は収束しなさそうです。残念ながら SVG 仕様全体を実装しているところはなく、SVG 2 となると特にさらに遠い状況です
      この話題については https://razrfalcon.github.io/notes-on-svg-parsing/ が読む価値があります
      SVG ライブラリのバグ報告で「自分の SVG が正しくレンダリングされない」という表現をよく見ますが、実のところ正しくレンダリングされた SVG など存在しない、という内容です。テキスト、フィルター、アニメーションのような 高度な機能を使い始めると、レンダラーごとに簡単に壊れるという趣旨です
    • 「logo」ページで基本の波形だけ見て去ってしまう人は、右下のドロップダウンにある プリセットをいくつか押してみるとよいです
      パラメータを少し変えるだけで scorpion、balance、spruce のようにかなり違うデザインが出てくるのが見事です
  • 筆者が探していた用語は ディザリングに近いです。CSS-Tricks の “Grainy Gradients” も同じ手法を説明しています
    https://css-tricks.com/grainy-gradients/
    CSS で似たように処理する代替案もありますが、画像全体に適用される方式です
    https://github.com/tomren1/dither-with-css

    • これは実際には単なる ノイズに近く、より正確には Perlin ノイズ系に「color burn」のブレンドモードを組み合わせたものです。グラデーションの上に載せることでディザリングのように見えるだけで、ディザリングではありません
      ディザリングは、滑らかな高色深度の画像を限られた色数に量子化するときにバンディングを減らすため、各ピクセルを上下どちらかの値から確率的に選ぶ手法です。目的の色に正確に対応する地点では、ノイズのように見えないこともあります
      ここでは均一なノイズテクスチャが生じるだけで、実際にバンディングを減らしてはいません。美的目的なら、本物のディザリングよりこちらの方がよさそうです
      ただしディザリングのように欠陥を隠し、体感上の鮮明さを高めて、画像品質がよく見える効果はあるかもしれません
    • ディザリングは最終的な効果が似ている実際の技術的手法ですが、それ自体が 粒状感の追加を意味するわけではなく、この例もディザリングで作られたものではありません
      筆者は「flat」「skeuomorphic」「italian style」のように視覚スタイルを呼ぶデザイン用語を探しているようです
      幾何学的な形状と粒状感の組み合わせを見ると Memphis style が思い浮かびます
    • 限られたパレットでより広い色域をまねているのでなければ、本当にディザリングと呼べるのか分かりません
      むしろヴィンテージの旅行ポスターアートを思い出します。例: https://www.graphicart-news.com/wp-content/uploads/2012/09/2...
    • ディザリングと関係はありますが、個人的には 点描法により近く見えます [0]
      [0] https://en.wikipedia.org/wiki/Stippling
    • 少し気まずいです。その記事にリンクするように記事を更新しないといけません
      これを作ったときは本当に見ておらず、一人で試していて同じ結論にたどり着きました
  • フィルム写真家や映画制作者たちは フィルムグレインをなくそうと苦心していたのに、デジタル写真家たちはまたそれを入れたがっている、という光景を思い浮かべます。隣の芝生は青いものです
    何もかもがデジタル的に無菌なものばかりの中で、このページの例はかなり魅力的に見えます

    • あまり一般化しすぎない方がよいと思います。デジタルビデオと、その後のデジタル写真が登場して以来、ノイズを減らそうとする人、なくそうとする人、まねようとする人、意図的に活用しようとする人は常に一緒に存在していました
      ノイズ自体が問題なのではなく、いつも問題なのは多すぎるかどうかでした
    • Foveon X3 がある程度解決したのでは? フィルムのようでありながらデジタルである方式として
  • 実装ごとにどの機能が壊れるのかをまとめた資料はあるのだろうか? SVGでメンタルヘルスを守る方法が知りたい。
    最近、複雑な学会ポスターをSVGで作るという失敗をしてしまい、今ではブラウザやプログラムごとに違うレンダリングになっている。
    https://kxygk.github.io/imergination/
    作っていて経験したことはこうだ。SVGを含めたりリンクしたりすることはできるが、その中にさらに別のSVGが入ると動作しない。たとえばポスター → 図表 → 複数のプロット/地図という構造なら、下位画像が変わったときに動的に更新されるSVGは事実上不可能。
    Webブラウザは「セキュリティ」上の理由で、埋め込まれた/リンクされた画像を勝手に表示しないこともある。上のリンクのSVGを別タブで開くと正しく表示されない: https://raw.githubusercontent.com/wiki/kxygk/imergination/ag...
    複雑なSVGをPDFに変換するのも、不一致なしに処理するのは難しく、メモリが底を突くこともある。SVG-to-PDF変換器ごとに結果が異なる。
    Inkscapeのような複数の実装では、含まれたSVGを任意にラスターグラフィックのように扱うこともあり、いつそれが起きるのかは最後まで特定できなかった。PNGにレンダリングするとき、InkscapeがDPI設定を下位画像へ伝播しない問題もあった。

  • すばらしい。InkscapeはFiltersメニューのさまざまな効果を通じて、この種のノイズ追加をサポートしているようだ。Plain SVGとして書き出してコードを見ると、feTurbulenceを使っていることが確認できる。
    比較のためにAffinity Designer 2のノイズ塗りつぶしも試してみたが、書き出されたSVGにはノイズがbase64 JPEG画像として埋め込まれていた。

    • 今はPCの前にいないので直接確認はできないが、ノイズテクスチャを1つ埋め込んで何度も使うのか、それともノイズを使うたびにテクスチャを新しく作るのかが気になる。
      Adobeライセンスを正当化しづらく、InkScapeもしっくりこない立場からすると、こうした変わった点さえ除けばDesignerは完璧な代替だった。
    • Affinity Designer 2はAdobe Creative Cloudにお金を払わない代替としてはかなり良かったが、SVGを手作業で直さなければならないことが本当に多かった。
  • 「ApeFest」のWebサイト[1]にも動的ノイズが入っていたのでCSSを見てみた。参加者の網膜をUVで焼いた、まさにあのイベントのサイトだ。
    実際にはタイル状のPNGオーバーレイ[2]を使い、CSSアニメーションで「ランダム」っぽく揺らす方式。修正: 実際にはJSで動かしているようだ。かなりローテクだが、動きはする。
    [1] https://apefest.com/
    [2] https://framerusercontent.com/images/rR6HYXBrMmX4cRpXfXUOvpv...

    • apefestの効果はCPUをかなり使い、そのぶんエネルギーも多く使う。
  • こういうSVGフィルターを何度も使いたかったが、問題はエンジンごと、拡大/縮小率ごとに表現が大きく変わることにある。
    そのため、不正確さがあまり重要でない微妙な効果にしか使えず、結局残念ながら継続的にラスターへ戻ることになる。
    またピクセル単位で動作する性質のため、feDisplacementMapのようなものはアンチエイリアシングを壊し、使いたくなる状況の半分くらいを台無しにしてしまう。

  • Perlinノイズとその後継であるフラクタルノイズは、リアリティや有機的な感じを加えるうえで費用対効果がものすごく高い。
    たとえばAladdin (1992)でCave of Wondersから脱出する場面に、とても効果的に使われているのが見られる。

  • これが本来どう見えるべきなのかは分からないが、自分の携帯ではノイズなしの滑らかなグラデーションだけに見える。

    • こちらでも同じ。Android Chromeを使っている。
    • Android Chromeでは、コード内の何らかの機能をサポートしていないようだ。
  • 効果は気に入っているが、拡大/縮小率から独立させる方法はあるだろうか?
    関連して、これもランダム性を加えているようだが、シードを与える方法があるのかも気になる。たとえば特定のサイズで、1つの正しいレンダリング結果を定義できるとよさそうだ。

    • 投稿者です。可能です。SVGの基盤アルゴリズムと実装はシード値を受け取ります: https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/s...
    • 他の回答にもあるようにseed属性があり、乱数関数のアルゴリズムも仕様で186行のCコードとして定義されているので[0]、理論上は実装ごとに同じ結果になるはずだ。
      ただしコードを見ると浮動小数点演算を使っていて、これは実装定義だと理解しているので、プラットフォームごとに異なる動作をする可能性はありそうだ。
      [0]: https://drafts.fxtf.org/filter-effects/#elementdef-feturbule...