自作フラクタルレンダラとシェーダの取り回しについて

WebGL Advent Calendar 2017 - Qiita
WebGL Advent Calendar 2017,19日目の記事です.

WebGLを用いたフラクタルレンダラを開発しています.そのコンセプトと中身を簡単に紹介します.今回紹介するのは二種類です.どちらも開発途上で実験段階ではありますが,それなりに面白い図を得ることが出来ます.

フラクタルレンダラ

どちらもWebGL2を要求します.また,新しめのGPUでないと動かないかもしれません.

SchottkyLink

f:id:soma_arc:20171216175254p:plain
f:id:soma_arc:20171216175713p:plain
円や球を元にしたフラクタルを描画することができます.これらの図はリアルタイムに描画され,生成される図を見ながら図形を配置していくことができます.

例えば,下図,左側の図のように,円を配置すると右図のような模様が描かれます.

三次元の場合も同様に,球を配置することでフラクタルが生成されます.
f:id:soma_arc:20171217164052p:plain

しかし,より複雑な絵を描画するためにはアルゴリズム的に困難な部分がでてきてしまい,現在は開発が止まっています.
高速に描画する話や,これを描画するための数学的な話はこちらの論文や記事に書きました.

Experimental Sphairahedron-based Fractal Renderer

f:id:soma_arc:20171216180453p:plain
最近開発を進めているものです.自由度は少ないですが,非常に面白い形状を描画することができます.画像の上部にある二つのパネルのうち,左側に描画されている赤と緑の球によって削られている立体は球面体と呼ばれる立体です.この立体をベースにして右側のパネルに描画されているフラクタルを生成することが出来ます.画像下部の左から二番目のパネルに描画されている赤い点を操作することで,このフラクタルを変形することが出来ます.

このフラクタルの構成方法は以下の動画をみるとなんとなくわかるかもしれません.
www.youtube.com


残念ながら,このフラクタルの描き方に関する文献はほとんどありません.これから論文等を書いていく予定です.また,より自由度を高いパラメータ化も考える予定です.

コンセプト

フラクタル図形の描画というと,マンデルブロ集合をご存知の方は多いかと思います.フラクタルアート界隈ではマンデルブロ集合のようなフラクタルの計算方法*1を用いて三次元のフラクタル図形を描画する式が開発されています.これらのフラクタルのいくつかはFractal Lab - Interactive WebGL Fractal Explorerレンダリングすることができます.
f:id:soma_arc:20171217183255p:plain
これらのフラクタル形状を自分で描画することは,決まった式があるので,さほど難しくはありません.しかし,その式をうまく改変したり,パラメータを操作して自分の思うような形状を得ることは難しいです.

そこで,開発中のフラクタルレンダラでは円や球,直線,平面といった幾何学形状によって定義される操作を組み合わせることでフラクタル形状を生成するアプローチをとっています.パラメータの操作は,図形の位置や大きさを変化させることで行い,レンダリング結果はリアルタイムで表示します.そうすることである程度複雑なフラクタル形状を直観的に生成することができるようになりました.

最終的にはこれらのフラクタルに関わる数学的な性質を理解し,パラメータを直観的に操作できるようなレンダラを作ることで,自由自在にフラクタル形状を生成することを目指しています.

設計

ここで紹介したレンダラでは,GLSLを用いて図を描画しています.GLSLのフラグメントシェーダを用いてレンダリングを行う際には,シーン毎にシェーダファイルを用意しておく必要があります.例えば,上述のFractal Labでも,各フラクタルのシェーダファイルが予め用意してあり,パラメータがUniform変数で渡されて形状が制御されます.しかし,SchottkyLinkではユーザの操作によって様々なフラクタルのジェネレータが追加されるため,あらかじめシェーダファイルを用意しておくことは難しいです.

そこで,ユーザーの操作に応じてシェーダを動的に生成し,シェーダをコンパイルしなおしています.シェーダの生成にはテンプレートエンジンのNunjucksを使用しています.

例えば,下図のような四つの円から構成されるフラクタルを描画することを考えてみます.

この四つの円による反転という操作を繰り返すことで描画されます.この処理のGLSLコードの例は以下のようになります.

for(int i = 0; i < MAX_ITERATIONS; i++) {
    if(distance(pos, u_circle0.center) < u_circle0.radius){
        pos = circleInvert(pos, u_circle0);
        invNum++;
    } else if(distance(pos, u_circle1.center) < u_circle1.radius){
        pos = circleInvert(pos, u_circle1);
        invNum++;
    } else if(distance(pos, u_circle2.center) < u_circle2.radius){
        pos = circleInvert(pos, u_circle2);
        invNum++;
    } else if(distance(pos, u_circle3.center) < u_circle3.radius){
        pos = circleInvert(pos, u_circle3);
        invNum++;
    }
}

Uniform変数で円や球の位置,半径といったパラメータが制御されます.この円の数はユーザの操作によって減ったり増えたりします.そこで,以下のようなテンプレートを作成しておきます.

for(int i = 0; i < MAX_ITERATIONS; i++) {
{% for n in range(0,  numCircle ) %}
    if(distance(pos, u_circle{{ n }}.center) < u_circle{{ n }}.radius){
        pos = circleInvert(pos, u_circle{{ n }}, dr);
        invNum++;
    }
{% endfor %}
}

numCircleパラメータを渡すことで,このテンプレートから円の数に応じた新たなシェーダが生成できます.実際のシェーダはこのような感じになっています.
SchottkyLink/2dCircles.njk.frag at master · soma-arc/SchottkyLink · GitHub
少々汚いですがマクロ等を使えば綺麗に書けるかと思います.シェーダのモジュール化もこのテンプレートエンジンで行っています.

この方法の欠点はデバッグが面倒なことと,一度に複数のシェーダを再生成するとシェーダのリンクあたりに非常に時間がかかってしまうことです.あらかじめ大きめのUniform配列を用意しておく手もありだとは思いますが,まだ試せてはいません.実行速度にどの程度影響があるかが問題です.

おわりに

フラクタルレンダラとそのシェーダの取り回しについて書きました.
GPUへ依存はありますが)このような複雑なレンダラもブラウザで手軽に動かしてデモできるというのは非常に便利です.特に,こういったある意味マニアックなソフトウェアでも触ってもらいやすいという利点もあります.
完成にはほど遠いですが,数学的な部分も含め,粛々と開発を続けていきます.

*1:Escape-time アルゴリズムとも呼ばれ,特定の式を画面上の点に繰り返し作用させ,その点の収束,発散を見る.