二次元ベクトルに垂直なベクトル

必要だったのでメモ

二次元ベクトルであれば、90度回転させると垂直なベクトルになります(当たり前)

90度の回転であれば、行列計算の結果がコンポーネントと符号の入れ替えになるため、わざわざ回転行列を用意する必要はなく

vertical = vec2(-vec.y, vec.x);

とするだけで良いです

www.shadertoy.com

なんとあのShadertoyのShader Of the Weekに作品が掲載された!!

憧れのサイトのトップページを飾れて嬉しさしかないです!!

学生時代は「Shadertoy = 神が集まるサイト」で平民の自分は全く理解できなかったので、神シェーダーを眺めているだけでした

まさか自分が作品を投稿するようになって、ましてやトップページの一番大きいところに載せて頂けるとは当時は想像すらしていなかったです...

記念撮影

f:id:kaneta1011:20181228003431p:plain

f:id:kaneta1011:20181228003649p:plain

Tokyo Demo Fest 2018のGLSL Graphics Compoで優勝した!作品の解説等

2018年の12/1と12/2の2日間に渡って、Tokyo Demo Fest 2018という日本唯一のデモパーティが開催され、そこに初参加してきました。

僕はそのイベントの、GLSL Graphics CompoCombined Graphics Compoに作品をエントリーして1位と4位に選んで頂きました!ありがとうございます!

f:id:kaneta1011:20181212200638p:plain
GLSL Graphics Compo 優勝!

GLSL Graphics Compo作品の動画やソースは以下から見ることができます。

www.youtube.com

Graphics Compoの作品は当日会場に到着してから、友人と話しているときに思いついて、GLSL Graphics Compo用のネタでお蔵入りしていたシェーダーを流用して、iq氏の4kテンプレートで大急ぎで作りました。

僕がGLSL Graphics CompoにエントリーしたTraveler 2は、偉大な先人の方々の解説記事や作品に多大な影響を受けています。誰の作品にどんな影響を受けたかはこちらの記事に全てまとめているのでご参照ください。 qiita.com

この記事では、先人達への感謝の気持ちを込めて、さらに深堀して作品の解説をしたいと思います。

Traveler 2の解説

コンセプト

過去の入賞作品や、TDF2017のGLSL Graphics CompoのYoutube動画を何度も見て、以下の条件に当てはまるものを作ろうと考えました。

  • 過去の作品にはないような見た目
    • 単純に新しい体験の方が楽しめる
  • 過去の作品に比べて尺が長め
    • 上映時間が長い方が印象に残るかも?
    • ほかの作品が大体30秒ほどなので、差別化できるかもしれない
    • あまり長くすると退屈するかもしれないので2分を目安
  • 上映中に退屈になる時間が無い
    • GLSL作品は任意の音楽を再生できないので、常に画面に変化を加える
    • Traveler 2では大体8beat毎に新しい変化を加えた
  • 小気味のよい動き
    • 2017年優勝作品がぬるぬる動いていた

結果、「1パスGLSLだけで作られているとは思えないようなメガデモ風の作品」というコンセプトの元にTraveler 2が誕生しました。

「これが1パスGLSLだけで作られてるの!?」と一人にでも思っていただけていればうれしいです。

時系列

今年の6月からレイマーチングの勉強を初めると同時に、Traveler 2の元となるtraveler.という作品を2週間で作りました。

グラフィックの知識が多少あったので、割とすんなりレイマーチングを習得することができました。

Shadertoy BETA

f:id:kaneta1011:20181209161413p:plain
traveler.

その後10月の下旬までの4ヵ月間、シェーダー芸のポキャブラリーを増やすために、Shadertoyや既存の作品のコードを読んだり、小ネタを実装したりしていました。

10月下旬からTDF本番までは、できるようになったことをtraveler.に詰め込んでTraveler 2が完成しました。

traveler.からTraveler 2までの履歴はすべてこちらのPRに残っているので、興味がある方はご覧ください! github.com

ロード画面について

f:id:kaneta1011:20181209234852p:plain 当日の上映時は緊張で回りの音が聞こえてなかったんですけど、アーカイブを見ると、最初のロード画面に困惑されている方が多そうでした。

皆さんお察しの通りだと思いますが、GLSL Sandboxでは事前にロードする処理等は書けないので、実際にロードしているわけではありません

ではなぜ、30秒もロード画面を表示したのかといいますと、シェーダーのコンパイルに20秒近く掛かってしまうからなんです。

GLSL Sandboxはコンパイル中も時間が進み続けてしまい、20秒もコンパイルをしていると、作品が途中から再生されてしまうという可能性があったんです。

なので最初の30秒間に待機画面を用意して、必ず作品を最初から再生してもらう、というのが狙いでした。

しかし上映時はブラウザに、リハーサルした際のキャッシュが残っていたのか一瞬でコンパイルが完了していましたね...

皆さんには30秒待ってもらうことになってしまいましたが、逆にインパクトがあったのかなぁとも思います。

背景

f:id:kaneta1011:20181209234935p:plain

f:id:kaneta1011:20181209235143p:plain 背景の3Dオブジェクトは、すべてメンガーのスポンジを改造したものを2つ重ねて作りました。

序盤のシーンでは重ねたもののうち、一つをbeatに合わせてIFS中のイテレーション毎に回転させています

後半は、追加でz軸を中心に回転foldをして複雑な形状を作りました。

ポストエフェクト

f:id:kaneta1011:20181209234720p:plain 今回は描画結果に対して3種類のポストエフェクトを使用しました。

グレアエフェクト

中央の球体と進行方向にグレアエフェクトを使用しました。

といっても1パスなので、よく使われるような描画結果をボカして合成するといった手法は使えないので、レイの進行方向とオブジェクトの角度を光量とすることにしました。

float flare = pow(max(0.0, dot(vec3(0.0, 0.0, 1.0), ray)), stageFlareExp * 1.25);
float flare2 = pow(max(0.0, dot(vec3(0.0, 0.0, 1.0), ray)), stageFlareExp);
vec3 f = flare * stageFlareCol + flare2 * di * stageFlareCol * 0.05;
    
float sflare = pow(max(0.0, dot(normalize(sp - ro), ray)), travelerFlareExp * 1.25);
float sflare2 = pow(max(0.0, dot(normalize(sp - ro), ray)), travelerFlareExp);
vec3 s = sflare * travelerFlareCol + sflare2 * di * travelerFlareCol * 0.05;

かなりいい加減な方法ですが、割とよい雰囲気を出せたように思います。

レンズダート

レンズに埃のようなものが付いているときにでるあれです。

バトルフィールドか何かのポストエフェクトで見て、かっこよかったので入れてみました。

実装はとても単純で、画面をいくつかのセルに分けてそれぞれに、ダートエフェクトを表示します。

その時にセル毎の乱数でダートエフェクトの位置を変えることで、不規則に並べることができます

それだけでは、密度が足りないのでいくつかのレイヤーに分けてリアルタイムでダートマスクを生成しました。

vec3 dirt(vec2 uv, float n)
{
    vec2 p = fract(uv * n);
    vec2 st = (floor(uv * n) + 0.5) / n;
    vec2 rnd = hash(st);
    float c = Bokeh(p, vec2(0.5, 0.5) + vec2(0.3) * rnd, 0.2, abs(rnd.y * 0.4) + 0.3, 0.25 + rnd.x * rnd.y * 0.2);
    
    return vec3(c) * exp(rnd.x * 4.0);
}

vec3 di = dirt(uv, 3.5);
di += dirt(uv - vec2(0.17), 3.0);
di += dirt(uv- vec2(0.41), 2.75);
di += dirt(uv- vec2(0.3), 2.5);
di += dirt(uv - vec2(0.47), 3.5);
di += dirt(uv- vec2(0.21), 4.0);
di += dirt(uv- vec2(0.6), 4.5);

ダートマスクのみを切り出したものをこちらに用意しているのでご参照ください。

Shadertoy BETA

f:id:kaneta1011:20181212183932p:plain
ダートマス

周辺減光

本来はレンズに発生する光学現象ですが、今回はいい感じに画面端を暗くすることでそれっぽい雰囲気を出しました。

お手軽に画面のクオリティアップを狙えるのでオススメです。

vec2 uv = fragCoord.xy / iResolution.xy;
uv *=  1.0 - uv.yx;
float vig = uv.x*uv.y * 200.0;
vig = pow(vig, 0.1);
col = saturate(pow(col, vec3(1.0 / 2.2))) * vig;

疑似パーティクル

f:id:kaneta1011:20181209235104p:plain

途中のシーンから表示されるパーティクルのようなものは、ポストエフェクトで紹介したレンズダートとほぼ同じことをしています。

レンズダートは2Dでしたが、こちらは3D空間でmodして増やした球やボックスをセル毎に乱数で間引く + 中央から乱数で少し位置を変えるということをした上で、全体を真上にスクロールしました。

また球は、セルの中央を起点にランダムに回転させることで複雑な動きをしているように見せかけています

疑似パーティクルの部分のみを切り出したものをこちらに用意したのでご参照ください。

Shadertoy BETA

f:id:kaneta1011:20181212181806p:plain
疑似パーティクル

2DのIFSで模様

f:id:kaneta1011:20181209235524p:plain

球体や背景の模様はとても単純で、2DのIFSを使用してボックスの輪郭のみを描画しています

実際に使用しているコードがこちらにありますが、とても簡潔ですね。

vec3 tex(vec2 p, float z)
{
    vec2 q = (fract(p / 10.0) - 0.5) * 10.0;
    float d = 9999.0;
    for (int i = 0; i < 5; ++i) {
        q = abs(q) - 0.5;
        q *= rot(0.785398);
        q = abs(q) - 0.5;
        q *= rot(z * 0.5);
        float k = sdRect(q, vec2(1.0, 0.55 + q.x));
        d = min(d, k);
    }
    float f = 1.0 / (1.0 + abs(d));
    return vec3(smoothstep(0.8, 0.9, f));
}

ボックスを軸で折り畳みして平行移動・回転するという動作を繰り返すことで複雑な模様が生まれます。

この時引数のzを変えることでいろいろなバリエーションの模様ができるのですが、Traveler 2の模様を作るにあたって、パラメータをtimeにして眺めながら、好みの模様ができるtimeをメモしてコードに直打ちしました。

模様部分のみを切り出したものを以下に用意していますのでご参照ください。

Shadertoy BETA

f:id:kaneta1011:20181212155309p:plain
紋様

Shadertoy BETA

f:id:kaneta1011:20181212155501p:plain
球体にマッピングしたもの

モーションブラー

f:id:kaneta1011:20181209235346p:plain

モーションブラーを愚直に実装してしまうと、1フレーム内で過去のレンダリング結果をいくつか計算しなおしてブレンドするという方法になります。

Traveler 2も最初はそうしていたんですが、後半になるにつれて処理負荷が無視できないものになってきました...

そこで、最終的に採用したのはピクセル毎に時間をずらすという単純な手法です。

多少見た目がノイジーになってしまいましたが、フルHDで再生する分には許容範囲でした。

実装はこのようになっています。

beat = (t + hash(p).x * 0.0065 * (1.0 - saturate((orgBeat - 230.0) / 4.0)) * step(12., orgBeat)) * BPM / 60.0;

tはtimeです、 * (1.0 - saturate((orgBeat - 230.0) / 4.0)) * step(12., orgBeat)) こんなよく分からないものが付いていますが、これは、最初と最後にカメラが大きく揺れるシーンがあり、その二か所のみノイズが許容できなかったのでモーションブラーをしないようにする処理です。

カメラの手振れ

これはかなりオススメなんですが、カメラの手振れにfbm(fractal brownian motion)を使用しました。

最初はカメラの位置と視点を揺らすつもりだったんですが、元々結構ギリギリなカメラワークをしていたりしたので、一部のシーンでカメラがめり込んでしまいました...

Traveler 2ではカメラをめり込ませたくなかったので、レイを飛ばす前にスクリーン座標をfbmでオフセットすることで、手振れさせても絶対にめり込まないようにしました。

vec2 pp = p + (vec2(fbm(vec2(beat * 0.1), 1.0), fbm(vec2(beat * 0.1 + 114.514), 1.0)) * 2.0 - 1.0) * .65;
vec3 col =  scene(pp) * glitchColor;

カメラに手振れを追加すると無機質な映像が、一気に臨場感ある映像になって感動しました!

苦労話

コンパイルに時間がかかりすぎてChromeがクラッシュする...

最終提出時に手元のPC(i7 2.7GHz/GTX1050)ではコンパイル時間18秒ぐらいで、コンパイル時に6回に1回ぐらいクラッシュする状態でした...

最強のコンポマシンならきっと動く!と神に祈る勢いで提出したんですが、ちゃんと上映されてホッとしています。

コンパイル時間の問題はWindows限定で、おそらくAngleを経由してOpenGLを動かしているからだと推測していますが、正しいことはわかりません。

ちなみにChromeのuse-angleオプションを使用してOpenGLを直接動かすと、Windowsでもコンパイルが爆速になります。詳しい話は以下の記事を見てください。

nanka.hateblo.jp

シェーダーがクラッシュして、もう機能追加できない!という状態が制作中に3度ほどありましたが、品質を落としたり、ifを撲滅したりすることで何とか納得のいく状態まで作り終えることができました。

コンパイル時間改善にもっとも効果があったのがifの撲滅で、以下のPRを適用すると、20秒だったコンパイル時間が12秒になりました

github.com

まだまだTraveler 2に入れたいものがあった...

コンパイル時間の問題が無ければ追加したいものがまだまだありました...

事前に用意していて入らなかったものを2つ紹介します。

タイトルアニメーション

一番最初に球体が飛んでいくシーンの直前に入れようとこんなものを用意していました。

残念ながらコンパイル時間が長すぎて入れることができませんでした...

Shadertoy BETA

f:id:kaneta1011:20181212195115p:plain
タイトルアニメーション

TDFロゴ

Graphics Compoに提出したGLSL Compoでお蔵入りになったと言った奴です。

これは3Dの距離関数として作っていて、Traveler 2序盤の不自然なほど激しいカメラワークが2箇所あるのですが、そこで一瞬TDFの3Dロゴを表示しようと考えていました。

Shadertoy BETA

f:id:kaneta1011:20181212195736p:plain
TDFの3Dロゴ

作品の最終シーンがなかなか決まらない...

提出版最終シーンは、紆余曲折あって、画面に徐々にノイズが現れて、最後にシャットダウンするようなシーンになっています。

最初は別プランでやっていましたが、終わり感が出せずいまいちしっくりこなかったため、同僚の@amanatsu_nit氏に相談したところ、シャットダウン演出を提案してもらい、モックまで作ってもらいました!

f:id:kaneta1011:20181212205817g:plain
シャットダウンエフェクトのモック

ここにたどり着くまでになかなか迷走しましたが、本番の前日ぐらいまでモックを見ながら、良しなに調整できたので、盛り上がる最終シーンに仕上がったと思います。

さいごに

Tokyo Demo Festは、同僚のgam0022氏が2016年のGLSL Graphics Compoで3位入賞して、そこで初めて存在を知りました。

同僚の活躍を見て、僕も作品を出したい!と常々思っていたのですが、時間があまりとれずに、今年の開催が12月にずれたこともあって、ようやく初参加することができました!

僕はコミュニケーションが苦手なので知人以外とあまり喋ることができませんでしたが、後半はtwitterでいつも見かけるような有名人の方とも沢山お話しできてとても楽しかったです。

特に印象に残っているのは、2nd Stage Boss等の4k作品を手掛けたよっしんさんが「この量のコードなら4kに収まる」と仰っていたことで、驚きすぎて耳を疑いました...(25000charsもあるのに...)

個人的に話してみたいなぁと思っていた方も見かけたのですが、声を掛けることができなかったので、来年は勇気を出して声を掛けたいと思います!

今後は4kbに収まるようにコードを書く技術や、音楽を作る練習をして、次回作品をエントリーするときは4k作品をエントリーしたいです!(作品が間に合わなくても、来年もTDFを見に行きます!)

最後になりましたが、Tokyo Demo Fest 関係者の皆様、最高のイベントありがとうございました!!

おまけ

誰も見ていないだろうと、最初からパブリックなリポジトリでTraveler 2を作っていたんですが。

突然レイトレ若人にマサカリを投げられてしまいました...

github.com

心が折れたので、来年からはプライベートリポジトリで作業しようと思います!

windowsノートPC(XPS15)でイヤホンで聞いた時の音が急におかしくなった

設定をいじった記憶もないし、元に戻す設定を探しても無いしで困り果てたので

結局ドライバーを入れ直して解決した

f:id:kaneta1011:20181123011659p:plain

サウンドのドライバをアンインストール

f:id:kaneta1011:20181123011737p:plain

ハードウェア変更のスキャンで再インストール

Tokyo Demo Fest 2018

今年の6月ごろからTDFに参加するためにいろいろ勉強してましたが、あっという間に開催一か月前です...

当初は4k introに出すために頑張っていましたが、どうにも音楽のセンスが壊滅的でうまくいかないので、グラフィック方面に絞ってGLSL Graphics Compoに出すことになりそうです

webgl-waterのコースティクスをshadertoyで実装した

ちょっと前にtwitterでバズっていたwebgl-waterのコースティクス部分をフラグメントシェーダーで実装した

https://www.shadertoy.com/view/MldfDn f:id:kaneta1011:20181017142952p:plain

せっかくなのでqiitaで解説記事を書いた qiita.com

6年ぐらい前から存在は知っていたんですけど、当時は全然理解できなかったんですよね...

6年越しに理解することができて成長を感じました。