数々の素晴らしい変態シェーダーが投稿されているShadertoyだが、私のような初心者は何をやっているかさっぱりわからず「すごいなあ」としか言えない。
今年からレイトレーシングを学び始めて、 「Shadertoyでもレイトレできないだろうか」と考えて調べてみると結構な量のレイトレを実装したシェーダーが投稿されいるのを見て、やれるんだったらやってみようとしました。
私も前の描画さえ持ち出せればそれっぽいのは作れるんじゃないかと考えていたのですが、肝心の描画持ち出しのやり方がわからず、また投稿されてるシェーダーを見ても中々何をやってるかがわからないものでしばらくどんづまっていました。
最近になってようやく色々調べていくうちにShadertoyには描画情報を保存できるチャンネルと別々の描画処理をできるBufferという機能があることを知って、何とかパストレーシングの実装に漕ぎつくことができました。 Bufferについての日本語記事がほとんどなかった(私が調べきれてないだけかも)ので折角なので私が実装したパストレーサーとともにBufferの使い方などをまとめておこうと思います。
※著者の勉強不足のため、間違っている記述があるかもしれないことに留意してください。
ShaderToyのiChannel
ShaderToyのコードの場所の下に何やらiChannel0などと書かれるものがあります。これがチャンネルと呼ばれるもので、ShaderToyでテクスチャなどを使用したいとなるとこのiChannelにそれらを設定し、そこから読み取るという形で行います。

チャンネルの場所をクリックすると色々と選択できるタブが出てくると思いますが、ここはShaderToyが用意しているテクスチャや動画、音声そしてBufferを選択することで、チャンネルにそれらを設定することができます。

チャンネルから設定したものの情報を受け取る方法についてはチャンネルに設定したものによって異なる。
- BufferやTextureの場合
BufferまたはTextureの場合、Textureという関数にチャンネル名とuvを与えることで情報を得ることができる。この場合ではTexture関数はvec4で色を返す。
vec4 col = Texture(iChannel0,uv)
- CubeMapの場合
CubeMapは背景用のテクスチャであり、上記のTextureとは異なり方向ベクトルを引数に取る。
vec4 col = Texture(iChannel0,dir)
このような形でチャンネルを使用することで色々な表現ができるようになる。
Buffer
ShaderToyのテキストエディタの上のほうに+のタブがある。このタブを押すことでBufferAなどを選択すると、Bufferと呼ばれるものを作ることができる。BufferではImage(最初にあるやつ)と同じようにかくことができ、最終的な出力をmainImage内でfragColorに入れる形となる。
最初にからあるImageというタブではShaderToyで出す「画像の色」を出力するものであり、最終的な画像を出す部分である。一方でBufferに書かれた処理というのはそのままでは画面に出てくることはない。
Bufferの出力というのはiChannelに設定し、Texture関数から得ることで使うことができる。 iChannelを開き、項目を見るとBufferAなどがあるはずです。それらを選択すると対応したBufferの出力をテクスチャとして得ることができます。チャンネルはBufferごとにそれぞれ4つあり、自らを選択することも可能なため、チャンネルに自らを設定することで自分の前の出力を得ることが可能である。
ずっとネックとなっていた「どうやって前の出力を得るのか」はこのBufferによって解決しました。私の実装ではBufferAにパストレーサー本体を記述し、出力に前の出力を受け取り、累計の輝度を出力する形となっています(後に平均を取る)。
実際に前の出力を使った簡単なサンプルを作りました。
ImageはBufferAの出力をそのまま表示するような形で動いており、BufferAでは本筋の処理が行われております。BufferAのiChannel0には自ら(BufferA)が設定されており、前回の出力をiChannelから得ることができます。実際にTexture(iChannel0,uv)という部分でiChannel0の中身を取っており、それを加算やらで色を作る処理をして画面を作っています。

このようにBufferとiChannelを使うことで前の出力を使うことができるが、これ以外にもテクスチャ生成と他の処理を分けるなどとImageだけでは出来なかった処理方法ができるようになります。
パストレーサーの実装
私が作ったパストレーサーは以下のような形で実装しました。
- BufferA
パストレーサー本体、自らのバッファをiChannel0に設定し前の処理内容を取れるようにしている。パストレーサーの処理を書き、最後の部分でiChannel0の値を取り、今回計算した輝度を加算したものを出力としている。 なので、このバッファは今までの輝度の合計を出力としており、Imageでは直接出力せずフレームで割って出力している。
- BufferB
BufferBではフレーム数を計算するバッファである。自らのバッファをiChannelに設定して、毎フレーム+1.0を加算していく処理を行っている。このため、バッファに記録されている値はフレーム数である。
- Image
表示を行う。チャンネルにBufferAとBufferBをそれぞれ設定しており、上記の通りBuffferAの値をBufferBのフレーム数で割った値を出力することで最終的な画像を表示することができる。

Bufferさえ使えれば、後はほとんどCPUパストレーサーと同様の処理をGLSLに翻訳してあげればこのように作ることができます。 ただし私が実装した例では途中のカメラ移動等を前提としていないため、処理中に数値を変えるなどをすると最初に戻る必要があります。これをどうにかするにはカメラの位置などが変わったことを確認できるような仕組みを作って、BufferAやBufferBがリセットされるようにしなくてはいけません。(どうにかしたい)
最後に
CPUパストレーサーでBRDFの実装をすると一々結果を確認するのに数10秒とか数分待たなくてはならず割と面倒でしたので、GPUでリアルタイムで見れるようにしたいということでShaderToyでGPUパストレーサーを作りました。
GPUを使用したパストレーサーを作るのはShaderToyにこだわる必要はなくOpenGLなどでもできますが、そちらを使用するとなるとそもそもライブラリの環境を整えることが少々大変だと思います。しかしながら、ShaderToyはそうした環境を整える必要もなく、Bufferの使い方さえ知っていれば結構簡単に作ることができるので他の方法に比べ簡単だと思います。
ですので、初めてGPUパストレーサー実装をしたいのであればShaderToyで実装するのが一番手っ取り早く、そこからOpenGLやWebGLなどに移植するといった手順でやると直接やるよりはすんなりいけるのではないのかと思います。
また、BufferはShaderToyですごいシェーダ―をみるとよく使われているのを見ます。どうやってるのだろうと思ってみたシェーダーで急にBufferAだとかが出てきて、そもそもBufferが何なのかよくわからなく結局全然読めんということがありました。初心者がこうした未知の機能にで会い、あまり資料がないとなんとも混乱してしまいます。 もし私のような初心者がこの記事を見て「よく見るBufferってそうゆうもんなのか」とBufferについて何かしら理解を得て、こうした混乱がなくなる手助けになれば幸いです。