数式だらけのGLSLを、6つの軸に分けてひも解く。挫折からMengerフラクタルまで デモページを開く 無限に続くMengerスポンジ風の回廊を、パラメータで形・光・色を変えながら飛行できるデモです。全画面で触れます。 デモを開く この記事の前提: 物理演算シミュレーションが得意な Gemini を駆使して、理解を深めていくアプローチで書いています。正直まだいまいちなところもありますが、「何をどうしたらいいか」はだいぶ掴めるようになったので、そのメモとして残しています。 GLSL(GPUで動く描画用のプログラム)やレイマーチングには、何度か手を出しては「数式が多すぎる」「Shader Toyのコードが読めない」で諦めていました。今回は、「いったい何を決めているのか」を6つに分けて考えるアプローチで、もう一度ひもといてみることにしました。職人技のように見えるあのコードが、実は「形・カメラ・レイ・光・色・空気感」の6項目で整理できると知ってから、だいぶ読みやすくなった話です。 世の中には Shader Toy(シェーダートイ)のように、ピクセルごとに数式で色を決めるだけで立体や光が現れるサイトがあります。あれは次のような手法です。 頂点やポリゴンを使わず、画面の一点一点から光線を飛ばして、数式で定義した「形」との当たりを取る。 この記事では、その「何を決めているか」を分割して、現代の技術資料やデモを頼りに理解し直した過程を書きます。 下は、レイマーチの最小版です。球を1つだけ置いて、CPU でレイを進め当たりを取っている。本番デモはこれを GPU(GLSL)で 3D の Menger + 光・色に拡張したものです。 まず「何を描くか」を数式で決める:距離関数(SDF) レイマーチングでは、「この点から立体までどれくらい離れているか」を返す関数が要になります。これを SDF(Signed Distance Function、符号付き距離関数)と呼びます。 役割は一つ: 空間中の点 p を渡したら、一番近い表面までの距離を返す(プラス=外側、マイナス=中側) 中身は数学的な式の塊だが、やっていることはこの一言に尽きる 上のデモは、2次元にしたイメージです。 操作意味マウス位置「点 p」として使う円(球の断面)との距離近い=明るい、遠い=暗いで色付け3D での利用光線を少しずつ進める・当たり判定・法線の計算 本題の Menger スポンジ風の形では、次のパラメータで同じ距離関数の中身を変えています。 繰り返しの幅 … 空間をタイル状に区切る間隔 ボックスの半径 … 箱の大きさ フラクタルで穴をあける回数 … 反復回数 数式は長く見えても、やっていることは 「p をタイル化 → 箱の距離 → 穴あけを何回か反復」 の組み合わせです。 カメラ:どこから・どの向きで見るか 次に決めるのはカメラです。 要素内容レイの起点視点の位置向きベクトル画面の各ピクセルに向かう方向 ここを変えると、見ている位置や視野角が変わります。今回のデモでは、回廊の中心を蛇行するように起点を sin(t) でゆらし、前方向も少し揺らして「飛行している」感じにしています。 パラメータ対応: 「カメラ速度」「視野係数」 レイの進め方:当たりを取るまで一歩ずつ 立体はポリゴンで持っていないので、光線を少しずつ進めて、距離関数の値が十分小さくなったら「当たった」とするやり方になります。これがレイマーチングです。 歩幅: 「今いる点での距離の何割か」にすることが多い。距離が大きいほど遠くまで一気に進み、小さくなったら細かく進んで当たり精度を上げる パラメータ: 「レイステップ係数」と「当たり判定(閾値)」 打ち切り: ステップ数 120、最大距離 50 光・色・空気感:見た目を決める3つ 当たった点が分かると、法線(面の向き)を距離関数の差分で近似できます。 | 項目 | やっていること | | --- | --- | | 光 | 法線と光の向きの内積で明るさ → トゥーン風に3段階(明・中間・暗)、輪郭線 | | 色 | セル(タイル)ごとに ID をハッシュ → 7 色パレット(IQ のコサイン系) | | 空気感 | 距離に応じたフォグ(指数減衰)で奥行き | パラメータ: 「光 X・Y」「ガンマ」「霧の強さ」 スライダーで「フラクタル反復」「光の向き」「霧の強さ」を変えると、同じ構造でも見え方がどう変わるかが直感的に分かります。本番デモでは、これらを 10 個のパラメータでリアルタイムに変えられます。 コードでは「距離関数+メイン3ブロック」にまとまる ここまでをコードに落とすと、距離関数 D(p) と、メインの3ブロックに整理できます。 | ブロック | 内容 | | --- | --- | | 1 | カメラ … 解像度から uv を作り、視点・前・右・上からレイ方向 rd を計算 | | 2 | レイマーチ … D(ro+rd*td) を繰り返し、当たった点で法線とセル ID を取得 | | 3 | 光・トゥーン・輪郭・パレット・フォグ・ガンマ → 最終色を gl_FragColor に代入 | 数式だらけに見えても、「形 → カメラ → レイ → 光・色・空気感」の順で決めているだけ、と頭で切っておくと読みやすくなりました。 GLSL12行コピー// 距離関数のイメージ(Menger風):p をタイル化 → 箱の距離 → 穴あけ反復 float D(vec3 p){ p = mod(p + halfCell, repeatCell) - halfCell; float d = max(max(abs(p.x),abs(p.y)),abs(p.z)) - boxRadius; for(int i = 0; i < iter; i++) { // 穴あけ用の繰り返し座標と距離の更新 s *= 3.0; d = max(d, (holeDistance...) / s); } return d; } midori314/webgl.js の fragmentShader 内 D(p) を簡略化したイメージ。実際は uniform で repeatCell, boxRadius, iter などを渡しています。 つまみを回して、ここに落ち着いた話 フラクタル反復: 最初は 4 に固定。6 や 8 にすると細かくて重い。2 や 3 だと穴が少なく「スポンジ」感が弱い。4 で「回廊っぽさ」と描画負荷のバランスが良かったので、スライダーでは 1〜8 で選べるようにした。 レイステップ係数: 0.8 をデフォルトにした。1.0 だと少し飛びすぎてギザつくことがあり、0.5 以下だと滑らかだがステップ数が足りずに抜けることがある。0.8 なら 120 ステップで奥まで届きつつ、当たり判定(uHitEps を 3e-4 程度)と組み合わせて安定した。 全体の流れを一枚で 「形・カメラ・レイ・光・色・空気感」の 6 軸が、GLSL のどこで使われているかをボタンでたどれるようにしました。Shader Toy の作品を読むときも、この順で「いまどれをいじっているか」を当てると、何をしているコードか掴みやすくなります。 遊び:7色パレットの気分 セル ID から 7 色に割り振っているパレットは、位相(コサインのオフセット)を変えると雰囲気が変わります。 本番デモの「ガンマ」スライダーは、この色の見え方(明るさの曲線)を変えます。 まとめ 6つに分けると読める … GLSL のレイマーチは「形(SDF)・カメラ・レイの進め方・光・色・空気感」の 6 項目で整理すると、数式の塊でも何を決めているか追いやすくなる。 距離関数が中心 … 「点から表面までの距離」を返す関数さえあれば、レイを進めたり法線を近似したりできる。 パラメータで後から訂正 … 繰り返し幅・ボックス半径・フラクタル反復・光・霧・ガンマなどを uniform で渡し、スライダーで変えられるようにすると、同じコードで見え方をいじりやすい。 Shader Toy も同じ考え方 … 有名なシェーダーサイトの作品も、この 6 軸のどれを強調しているかで分解して読むと、挫折しにくくなった。 旧サイトのデモでは、この 6 軸に対応した 10 個のパラメータをその場で変えながら、無限に続く Menger 風の回廊を飛行できます。よければ デモを開く で全画面体験してみてください。 参考にしたもの Inigo Quilez - レイマーチングとSDF — 距離関数とレイマーチの解説 Shader Toy — ピクセルシェーダーだけで描く作品が多数。6 軸で分解して読むと構造が掴みやすい The Book of Shaders(日本語あり)— シェーダーの考え方の入門 関連して、マインドマップ型TODOアプリ では Canvas と SVG で別の表現を扱っています。データと描画を分けて考える点では、GLSL の「形と見た目を分けて決める」考え方と通じる部分があります。
• 記事タイトル 目次 記事を読み込み中... 🔗 関連する実行デモ この記事に関連する実行デモは旧サイトでご覧いただけます: 旧サイトのデモを見る この記事をシェア Twitter Facebook はてブ URLコピー 埋め込み 記事を埋め込む × 以下のコードをコピーして、あなたのサイトに貼り付けてください。 コードをコピー 関連記事