経緯
サークル内で開催されたクリエイタソンに参加し,cone marchingを作ってみたのですが,分野が何でもありのイベントだったためそもそもraymarchingについて知っている人がほとんどいない,という状況でした.
その結果,
cone marchingについて説明する前にraymarchingの説明が必要
→ どう考えても話が長くなりそう
→ 大した説明もせず「フラクタル図形いいよね!!」で押し切った
→ せっかく作ったから解説したい欲がわく
ということで今書いております.
cone marchingについて
そもそもraymarchingが何をやっているのかの簡単なおさらいをしてみましょう.
raymarchingではスクリーンのピクセル一個一個にオブジェクトとの距離を求めさせることで3次元空間の表現をしています.(距離の求め方は一度置いておきます)
これが,cone marchingでは下のように変化します.
このように,cone marchingでは最初から全てのrayを一斉に飛ばしていません.飛ばすrayの数を限定し,途中でrayを分散させることで計算量を抑えることができます.
具体的例として,1024×1024のスクリーンで距離関数を100回使ってraymarchingをすることを考えます.この場合距離関数を使う回数が
1024×1024×100 = 104,857,600
という膨大な計算量になってしまいます(まあ,それこそがraymarching,と言えますが)
これを,70回と30回に分け,最初の70回では解像度を4分の1にした256×256で行います.すると距離関数を使う回数は
256×256×70 + 1024×1024×30 = 36,044,800
となります.
計算誤差をなくす
このままだとcone marchingは致命的なアーティファクト(rayの超過)が発生します.
この図ではrayの密度を小さくしたことで本来なら見つけられたはずのオブジェクトを通り過ぎています.
これを解決するために通常よりもmarchingを止める条件を緩める必要があります.
どうすればいいかというと,距離関数の値が「隣のrayとの二分線」との距離を下回った時にmarchingを止めるようにします.
こうすることでrayという一本の線ではなく円錐(cone)をmarchingできます.(伏線回収成功!)
「隣のrayとの二分線」との距離,についてはバッファサイズとfocusの値で求めることができます.(dirを決めるとき,xyにuv座標,zに1~10ぐらいの数値を入れますよね.あれです)
vec3 dir = nomarize(vec3(uv, 3.0)); //この場合focusは3.0
実装方法
環境はVisualStudio2022, openGL3.2, shaderのバージョンは150で行いました.
最初にフレームバッファを用意するのですが,
今回はmarchingを70回→30回に移行する際,70回分のmarchingで進んだ分の距離を継承する必要があります.
これをdepthbufferで行おうとすると入れた数値が強制的に0~1にされてしまいます.なのでcolorbufferに新たに32bitのfloat型変数を格納できるものを作ってやり,そこに記録していきます.
fragmentshaderではまず普通のraymarchingを作ってやり,最後のgl_fragcolorに入れる値だけraymarchingで求めたトータルの距離に変えてあげます.
cone_R=(1./min(Resoution.x, Resolution.y)) / 3.0;
for(float i=0.0; i++<70.;){
//距離を求めてrayを進める
if(d < cone_R * total_d)break;
}
また,marchingの終了条件も変えてやります.
gl_fragcolor = vec3(tota_d, 0., 0., 1.0);
次に2回目に呼び出されるfragmentshaderですが,こちらも最初に普通のraymarchingを作ってやります.texture()で先ほど格納した距離を受け取り,raymarchingでの最初の一回分はその距離を進むようにしてあげれば大丈夫です.
float depth = texture(Chanenl, uv);
pos += dir*depth;
float total_d = depth;
終わりに
cone marching,今回は何段階に分けるかとか回数の割合とか深く考えずにしましたが,その辺も突き詰めていけばより高度に,遠くを見られるようになると思います.
今回は70-30で分けてしまったため,早期にmarchingを終了してしまいアーティファクトが発生してしまいます.(きれいな直線ができていない)
また,反射や屈折などのraymarchiingだからこそできた技のいくつかが使えなくなってしますのは考え物です.そういうのは使わない場面でなら計算量を抑えつつ遠くを見る,ということができるのですが.
ともあれ,距離関数であればすべての形に対応できると思うので,とにかく見える範囲を広げたい!という時は活躍するでしょう.
最後に今回サンプルで使った距離関数はshadertoyにあげておきます.(最初に見せたほう)