#TokyoDemoFest 2018 の GLSL Graphics Compo で2位入賞しました

はじめに

2018/12/01〜12/02 に開催された Tokyo Demo Fest 2018 というデモパーティに初参加してきました。Tokyo Demo Fest をご存知ではない方のために、説明を公式サイトから引用します。

Tokyo Demo Fest は日本で唯一のデモパーティです。 デモパーティは、コンピュータを用いたプログラミングとアートに 興味のある人々が日本中、世界中から一堂に会し、 デモ作品のコンペティション(コンポ)やセミナーなどを行います。 また、イベント開催中は集まった様々な人たちとの交流が深められます。


私は GLSL Graphics Compo という GLSL のフラグメントシェーダのみで映像を作って競う部門に作品をエントリーし、2位に選んでいただきました。ありがとうございます!

f:id:setchi_q:20181214234241j:plain

作品は下記からご覧になれます。

Pouet
GitHub
Shadertoy

www.youtube.com

f:id:setchi_q:20181215133023p:plainf:id:setchi_q:20181215133044p:plainf:id:setchi_q:20181215133107p:plainf:id:setchi_q:20181215133053p:plainf:id:setchi_q:20181215133051p:plain

解説

「生命」をコンセプトとして、一つのキューブから増殖して複雑に変形してから、元の形に戻って消えていくという作品になっています。キューブから複雑な形状を作るところには、フラクタルの描画などに使われる IFS という考え方を使いました。

vec2 ifs(vec3 p) {
    float d1 = 999., d2 = 999.;
    float range = 0.8, radius = 0.5 * (1. + zoom);

    const float maxIter = 8.;
    for (int i = int(maxIter); i > 0; i--) {
        if (i <= iter) {
            break;
        }

        float ratio = float(i) / maxIter;
        float bx = box(p, radius * ratio);
        d1 = mix(d1, min(d1, bx), float(i > iter + 1));
        d2 = min(d2, bx);

        ratio *= ratio;

        p.xz = abs(p.xz) - range * ratio * 0.7;
        p.xz *= rot1;
        p.yz *= rot3;
        p.yx *= rot2;

        p.yz = abs(p.yz) - range * ratio * 0.7;
        p.xz *= rot1;
        p.yz *= rot4;
        p.yx *= rot2;
    }

    return vec2(d1, d2);
}

キューブの増減は IFS 反復数の変化

f:id:setchi_q:20181215134717g:plain
序盤・終盤のキューブがぽこぽこと増減していく動きは、距離関数の中で IFS の反復数が一つ違う形状を同時に求めておき、それを線形補間することで実現しています。

序盤は増殖する方向に、終盤は縮小する方向に動くため、一定の時間を超えたら補間の係数を反転させて逆方向に補間しています。

float map(vec3 p) {
    vec2 d = ifs(p);
    return mix(d.y, d.x, mix(a, 1. - a, step(time0, 5.5)));
}

今回、グラフのスケッチには GLSL Grapher というサイトを使わせていただきました。GLSL コードを使ってそのままグラフが書けるので捗りました。

f:id:setchi_q:20181215012453p:plain
IFS 反復数の遷移を制御する式(整理したらもっと簡単になりそう)

軽量に法線を求める

レイマーチングで素直に法線を求めようとすると map 関数を6回評価することになりますが、iq 氏の記事に4回の評価で法線を求める方法が紹介されています。これで map 関数2回分の計算を省くことができます。

vec3 normal(vec3 pos, float eps) {
    vec2 e = vec2(1.0, -1.0) * 0.5773 * eps;

    return normalize(e.xyy * map(pos + e.xyy) +
                     e.yyx * map(pos + e.yyx) +
                     e.yxy * map(pos + e.yxy) +
                     e.xxx * map(pos + e.xxx));
}

また、この記事を書いてる途中で見つけたので今回の作品には使っていませんが、下記のようにするとコンパイラが map 関数を4回インライン展開するのを防ぎ、コンパイル時間の節約になるようです。(WebGL ではシェーダコンパイルに時間がかかりすぎるとブラウザがクラッシュすることがある)

vec3 normal(vec3 pos, float eps) {
    vec3 n = vec3(0.0);

    // 0 に評価される定数以外の低コストな式
    #define ZERO (min(iFrame, 0))

    // for の展開を防ぎコンパイル時間短縮 & ブラウザクラッシュ防止
    for (int i = ZERO; i < 4; i++) {
        vec3 e = 0.5773 * (2.0 * vec3((((i + 3) >> 1) & 1), ((i >> 1) & 1), (i & 1)) - 1.0);
        n += e * map(pos + eps * e);
    }

    return normalize(n);
}

自分自身はまだコンパイル時間の長さに悩まされたことはありませんが、 GLSL Compo 1位の kaneta 氏ブラウザクラッシュと戦っていたので、コンパイル時間短縮のテクニックとして覚えておくと良さそうです。

追記: for の初期値に定数以外の値を入れるのは WebGL 2.0 からでないと使えないため、 GLSL Sandbox ではコンパイル時間削減のテクニックは使えませんでした。(2018/12/17 時点)

エッジ検出

f:id:setchi_q:20181215172821p:plain

vec3 nor = normal(pos, 0.008);
float edge = smoothstep(0., 0.01, length(nor - normal(pos, 0.015)));

作品中で多用しているオブジェクトのエッジを強調する表現です。異なる epsilon で求めた法線の差分を見ることでエッジかどうかを判定しています。オブジェクトにヒットしたら大体一回は法線を求めると思うので、片方はその値を流用すれば少しお得です。

カメラワーク

序盤と終盤は狙ったところへ Hermite 補間でつないでいます。そこは良いのですが、中盤の視点がパッパッと切り替わっているところは、なんと擬似乱数によるオフセットです。乱数です。。次の TDF までにはカメラワークの技術を学んで自分の意思でかっこいい動きを作りたいです。

開発環境

VSCode 上で GLSL 環境を探していたときに、ちょうど gam0022 先生が GLSL Sandbox 互換の VSCode 拡張を公開していたのでありがたく使わせていただきました!

途中で Eclipse の GLSL 開発環境である Synthclipse というのを見つけて少し気になっていたのですが、今回は余裕がなくて試すことができませんでした。
synthclipse.sourceforge.net

GLSL Sandbox でコンパイルエラー

VSCode や Shadertoy でのびのび GLSL を書いて、いざ TDF 本番環境である GLSL Sandbox で動かそうとしたらそもそもコンパイルが通らず冷や汗をかく経験をしました。

GLSL Sandbox は 2018/12/17 現在 WebGL 2.0 に対応していないので、違う環境で開発している場合はこまめに GLSL Sandbox と同じ環境で動作確認した方が良いです。

例えば身近な abs 関数一つとっても、genType abs(genType x) は使えるけど、genIType abs(genIType x) は使えないといった差があります。

// Shadertoy ではどちらも OK
int abs1 = int(abs(4.0));
int abs2 = abs(int(4.0)); // こちらは GLSL Sandbox でコンパイルエラー

おわりに

Tokyo Demo Fest 2018 (2日目)の様子を Twitch からタイムシフトで見ることができます。特にコンペ中のテンションがすごくて、みなさん「おおおおお!」「Foooooo!!!」とか言いながら作品をみています。
www.twitch.tv

大スクリーンに上映された自分の作品に対して生の反応を得るという貴重な経験ができるので、興味を持った方はぜひ Tokyo Demo Fest に作品をエントリーしてみてください!

Tokyo Demo Fest はコンペ以外にも DJ/VJ, セミナー, シェーダーライブコーディングバトルなど様々な催しが行われており、とても楽しいイベントでした。来年も何か作品を出そうと考えていますが、仮に作品が出せなくても是非また参加したいと思います。