3723 文字
19 分
Ghosttyのcustom-shaderを自作する — マーブル・煙・インクの実装と、Shadertoy移植でぶつかる計算量

前回、Ghostty の custom-shader で文字が読めなくなる問題を、輝度で文字を判定して背景にだけエフェクトを乗せる「グリフマスク」で片付けました。できあがった pool.glsl は、プールの底から水面を見上げたような背景です。今回は、そこからほかの背景も試しています。

この記事は、一日のあいだに背景シェーダーを作り直した記録です。マーブル、煙、インク、Shadertoy の Auroras を順に試し、最終的に underwater に差し替えています。

途中で出てきた「水っぽい」「レトロ」「重い」といった見た目の印象が、コードのどこから来ていたかを中心に記録します。

ここのshadersの設定をお気に入りとして設定したいです。
これからちょっと触りたいので、でも、gitにしているからいいのかな。
できれば気分で変更できるようにしたいんですよね。

orangeVaper

お気に入りを保存しておく#

最初にやったのは、今の状態を守ることです。

git で管理しているので戻せますが、どのコミットが該当するかを後から探す手間があります。今のコミットに favorite というタグを付けました。

git tag -a favorite -m "お気に入り: pool + cursor_blaze + mode-glow"

これで .glsl をどれだけ壊しても、git checkout favorite -- shaders/ で中身ごと戻せます。

もう一つ、気分で切り替えられるように、シェーダーの組み合わせを presets/ に分けました。Ghostty の config-file は別の設定ファイルを読み込めるので、本体の config にはプリセットを1行書くだけにします。

# config
config-file = presets/favorite.conf
# presets/favorite.conf
custom-shader = ../shaders/pool.glsl
custom-shader = ../shaders/cursor_blaze.glsl
custom-shader = ../shaders/mode-glow.glsl
WARNING

custom-shader の相対パスは、それが書かれたファイルを基準に解決されます。 presets/ の中に書くなら ../shaders/ です。shaders/pool.glsl と書くと presets/shaders/pool.glsl を探しにいきます。解決後のパスは ghostty +show-config で確認できます。

あとは config-file の1行を書き換えて、⌘⇧, でリロードします。この仕組みを土台に、以降の差し替えを行いました。

マーブル: 加算合成と色の層#

最初に作ったのはマーブル模様です。ノイズの座標をノイズでさらにずらす「ドメインワープ」という定番の手法で、絵の具が混ざったような模様を作ります。

できあがったものは、絵の具というより水っぽい見え方でした。

水っぽく見えていたのは、色を足していることと、ウィンドウの透過によるものです。背景を暗く保つために暗い色をうっすら加算していたので、絵の具ではなく色水になっていました。background-opacity = 0.85 にしているので、後ろのデスクトップがぼけて透けます。前回も書いたとおり、この合成はシェーダーより後に行われます。

直し方は、背景ピクセルを絵の具の色で置き換えて、アルファも 1 にすることでした。

// 背景だけを絵の具で置き換え、アルファも 1 にして後ろを透けさせない
fragColor = vec4(mix(src.rgb, col, bg), mix(src.a, 1.0, bg));

bg はグリフマスクの値(背景なら 1、文字なら 0)です。シェーダーの出力アルファを 1 にすれば、background-opacity を下げていても、その部分は透けなくなります。

透けなくなると、今度は色と模様が強く出ました。

渦巻きが強すぎるマーブル。明るい青とほぼ黒の差が大きく、模様が画面の主役になっている

ここから配色を往復しました。紫と青緑のような補色どうしを層にすると濁るため、近い色だけでまとめます。色の差は、3色の平均に寄せる CONTRAST を足して1つの値で弱められるようにしました。青いマーブルは、LazyVim の標準テーマ(Tokyo Night)の青いキーワードやコメントと重なって沈むので、最終的にはエディタのテーマ自身の背景色でマーブルを作っています。文字色はその背景で読めるように作られているためです。

配色を Rosé Pine の背景色(ほんのり葡萄色の黒)に振ったところ、出てきたのは昭和のレトロ感でした。

色がはっきりした**層(縞)**になって流れる模様は、70年代のサイケ柄やマーブル紙、喫茶店の壁紙の文法そのものです。くすんだ紫がそれに拍車をかけていました。縞をなくして境目をぼかしきると、今どきの「メッシュグラデーション」に近づきます。色の境目のぼかし幅という数字1つが、「昭和」と「今どき」を分けていました。

煙からインクへ: 境目のぼかし幅#

マーブルの次は煙を作りました。画面の下から立ちのぼる煙に、口でふーっと吹いたくらいの大きさの煙がランダムに湧く、という構成です。

ただ、煙は動きが単調でした。そこで「コップの中で混ざるような」動きに変えました。水にインクやミルクを垂らしたときの、あの感じです。

インクのしずくが落ちて、キノコ型の渦を巻きながら沈みます。色を青・紫・水色・ミルクにして、ここまでで見え方は煙に近いままでした。次に直したのは、境界がくっきりしたまま混ざる点です。

煙に見えていた原因は境目のぼかしです。本物のインクは、水の中で輪郭を保ったまま渦を巻き、糸のように引き伸ばされていきます。ぼやっと広がるのは、ずっと後になって溶けてからです。

やることは、濃さの値にしきい値を入れて、境目の幅を細くするだけでした。

// 境目をくっきりさせる。水に溶ける前のインクは、ぼやけず輪郭を保ったまま渦を巻く
float body = smoothstep(EDGE, EDGE + EDGE_SHARP, v);  // EDGE_SHARP = 0.03

EDGE_SHARP を大きくすると煙に戻り、小さくするとインクになります。「煙」と「インク」の違いは、この数字1つでした。

あとは混ざり具合・渦の強さ・動きの勢いを何往復か調整して、次のように落ち着きました。

  • しずくは一定の速さでゆっくり沈む(最初に勢いよく沈んで急に止まる動きが「慣性っぽさ」の原因でした)
  • 時間がたつほど、しずくの内側が折りたたまれてマーブル状に混ざっていく
  • 色は Rosé Pine のアクセント色(淡い水色・ローズ・ゴールド・藤色)で、1つのしずくに2色を巻き込む

インクの完成形。縁のくっきりしたローズ・ゴールド・水色のインクが、渦を巻いて混ざりながら沈んでいく

フレーム落ちと、計算回数の削減#

インクの見た目が固まってきたところで、「重たい」という問題が出ました。

最初は、画面がインクで埋まって見た目が重たいという意味に取り、しずくの数を減らし、色を薄くし、インクを細くしました。これは読み違いでした。重かったのは動作のほうで、症状はフレーム落ちです。見た目の変更は戻して、計算を削りにかかります。

最初に入れたのは「しずくから遠いピクセルは計算を飛ばす」処理でした。多くのピクセルはどのしずくからも遠いためですが、カクつきは残りました。しずくの数を 6 から 4 に減らしても、カーソルの動きがもたつくままでした。

見直すと、この「遠いピクセルは飛ばす」処理は、しずくが大きく広がる後半ではほとんど働いていませんでした。しずくは周期の終わりには画面の高さの3〜4割まで育ちます。さらに「混ぜる」ために座標を大きくずらすので、「遠い」と判定できる範囲がほぼ画面全体に広がっていたのです。

カクつきが解消したのは、1ピクセルあたりの計算回数そのものを減らしたあとです。

  • 画面全体にかけていた「水の流れ」を、ノイズ2回から sin の組み合わせに置き換える
  • しずく1つあたり5回あったノイズ計算を、共通のゆらぎ1つで兼ねて2回にする
  • ノイズを重ねる回数(オクターブ)を 4 から 3 に減らす

これでピクセルあたりの計算は、ざっと半分になりました。

Shadertoy のオーロラの移植#

ここで、シェーダーの作例がまとまっている場所を探しました。Ghostty のシェーダーは Shadertoy と同じ書き方です。mainImage、iTime、iResolution、iChannel0 は、全部 Shadertoy の決まりごとです。

そこで移植してみたのが、nimitz さんの有名な Auroras です。夜空に揺れるオーロラと星、地平線の下の水面への映り込みが入ります。

最初のつまずきは、コードの取得でした。Shadertoy は Cloudflare のボット対策が入っていて、curl で叩くと「Just a moment…」の HTML しか返ってきません。GitHub のコード検索でシェーダーの ID(XtGGRt)を探すと、ほかのツール向けに変換されたコピーがいくつも見つかったので、そこから持ってきました。

移植自体は問題なく終わりました。文字を避ける処理を足し、空が画面の大半を占めるようにカメラを少し見上げる角度にします。見た目は再現できましたが、動作が極端に重い状態でした。

このシェーダーは、空を立体の空間として扱い、オーロラを奥行き方向に何段も重ねて描きます。1ピクセルあたり、ざっとこれだけ回ります。

設定オーロラの段数 × 模様の計算1ピクセルあたり
原作50 × 5250 回
最初の移植30 × 5150 回
2回目20 × 480 回
3回目12 × 448 回

原作の5分の1まで削っても、常用できる軽さにはなりませんでした。段数を減らすほど帯が粗くなるので、見た目との兼ね合いでもこのあたりが限界です。

ブラウザの Shadertoy では、小さなプレビュー枠の中で動いています。ターミナルで背景にすると、Retina の画面全体の数百万ピクセルで、毎フレーム、文字の描画と一緒にこれが走ります。しかも見た範囲では、Ghostty のシェーダーには、解像度を落として描いたり、前のフレームを使い回したりする仕組みがありませんでした。つまり、Shadertoy で快適に動くことは、ターミナルの背景として使えることの保証になりません。

オーロラは「お役御免」になり、ファイルごと削除しました。

underwater への差し替え#

Ghostty 向けのシェーダーがまとまっている場所として見つかったのが、GitHub の 0xhckr/ghostty-shaders です。前回使った mnoise.glsl や water.glsl の出典(旧 hackr-sh/ghostty-shaders)と同じリポジトリで、背景・画面フィルター・カーソル合わせて40本ほど入っています。最初から Ghostty 向けに作られているので、重さもほどほどに抑えられています。

その中から underwater.glsl(水中に差し込む光の筋)を試しました。光の筋は sin の計算だけでできていて、とても軽いです。ところが、画面に出してみると、何が起きているのかほとんど分かりませんでした。

改造前の underwater。光の筋はほとんど見えず、後ろのデスクトップがぼけて透けている

ここもマーブルのときと同じで、元のコードは光の筋を最後に 0.3 倍してかなり控えめにしていて、ウィンドウの透過もそのまま残す作りです。光の筋が、透けたデスクトップに埋もれていました。

この日に作ったシェーダーと同じ扱いに直します。

  • 背景を深い青緑の水の色で塗りつぶし、アルファを 1 にする
  • 光の筋を 0.3 倍から 0.45 倍にする
  • 背景と文字の判定を、二択(step)からなめらかな smoothstep にする

改造後の underwater。深い青緑の水に光の筋が差し込み、文字もくっきり読める

この状態を既定にしました。一日かけて自作したものではなく、既製品に手を入れたものを使うことになりました。

この日に分かったこと#

見た目の印象と、その原因

印象原因
水っぽい色の加算合成と、ウィンドウの透過
昭和レトロ色がはっきり層(縞)に分かれていたこと
煙っぽいインクと水の境目のぼかし幅
よくわからない光の筋の 0.3 倍と、ウィンドウの透過

どれも、原因は具体的な1行か、数字1つでした。

重さの削り方

計算を飛ばす範囲を絞る方法では解消せず、1ピクセルあたりの計算回数を減らして解消しました。インクでは、範囲を絞る処理を入れてもカクつきが残り、ノイズ計算を5回から2回に減らして解消しています。オーロラは、原作の250回を48回まで減らしても常用できませんでした。

元に戻す手段

favorite タグとプリセットを先に作ったので、シェーダーを書き換えても config の1行で元の状態に戻せました。

この日に書いたマーブル・煙・インクは、presets/ に残したままにしてあります。現在の既定は underwater です。

結局思い通りの感じにするにはパラメータに詳しくないとできないというw
プリセットは残してあるので、気分で切り替えつつまた触ってみます。

orangeVaper
この記事は AI(Claude) を活用して書いています
アイキャッチ画像は ComfyUI + Flux.1 で生成しています
Ghosttyのcustom-shaderを自作する — マーブル・煙・インクの実装と、Shadertoy移植でぶつかる計算量
https://orangevaper.dev/posts/ghostty-shader-marble-ink-underwater/
作者
orangeVaper
公開日
2026-09-22
ライセンス
CC BY-NC-SA 4.0