demoscene.jp Japanese demoscene portal

912月/120

レイマーチンを増やせ!

Hi!
こんにちは。gyaboと申します。
2012年のメガデモアドベントカレンダーの一つ「レイマーチン(raymarching)を増やせ!」と題して簡単なTipsをご紹介します。

◆前提マシン

こちらでレイマーチングで遊んだり記事書いたりしたときに使った環境は以下の通りです。
CPU : core i7 860
グラボ : HD5870(radeonさん)
API : DirectX9(HLSL)
DX11でも使えるTipsです。多分…

◆何を増やすの?

レイマーチングで遊んでると、作ったのは良いけれどどうやって膨らまそう…という感じになるかもしれません。しかもレイマーチングは最初の方は大体やる事決まってて作ってるうちにウワーまた同じレイ投げちゃう関数作っちゃったよ!などが発生してモチベーションが下がったり気が狂ったりする可能性があります。
そこで、簡単にレイマーチングを増やすマクロをご紹介します。
TDF2012の時に気付いたのですが音楽とザクザクあわせれば膨らみまくりなのですがここでは割愛します

◆参考文献やintro/demo

この記事は以下のサイトやdemoを参考にして作成しました。

http://www.pouet.net/prod.php?which=51449
receptor by TBC
★本記事は上記のintroを基に作りました。
exe実行するとやばすぎる!

www.iquilezles.org
既にアドベントカレンダーの記事に掲載されているかもしれませんが、
ここでraymarchingで描ける基本的なprimitiveが分かります。
もしかしたら他に面白いprimitiveがあるかもしれません。

Raymarching Beginners' Thread(pouet)
raymarchingの切磋琢磨道場的な感じです。
ビギナーとなってますが割と上級者向け。細かいTipsが投稿されています。

◆シェーダを増やすマクロを作ってあげる

iq氏のプレゼン資料にある通りの仕組みでレイマーチングする場合、四角形書いたりなどのフレームワーク的なのは既存のコピペで済んだりします。

で、面倒なのが描画したいシーンに応じて反復回数やらを調整していちいち書き分けたりパラメータ別に別途distance functionを作ってあげなければならないところだと思っています(レイマーチング、使ってくると他にも面倒な事が一杯ありますが…)

そこで、HLSL限定かもですがシェーダ書くときにトークン結合が使えます。以下みたいなマクロ定義してサッサと増やしてあげます。

//増やすマクロ

#define dup(name, prep, map, ite, ret) float name(float3 p) { map; } float4 p##name(float2 vp : texcoord0) : color { float2 uv = -1 + 2 * vp; if(abs(uv.y) > 0.75)
return 0; float3 dir = normalize(float3(uv * float2(1.25, 1), 1)); float3 start = float3(0, 0, cnt.x); prep; float3 pos = start; for(int i = 0 ; i < ite; i++) { pos += 0.75*name(pos) * dir; } float2 k = float2(0.01, 0); float3 N = normalize(float3( name(pos + k.xyy), name(pos + k.yxy), name(pos + k.yyx)) - name(pos));ret;}

さて、↑のマクロを展開すると以下みたいに展開されます(インデントつけてコメントつけてます)
##ってなんじゃい!って方はこちらを参照(トークンを結合せよ)


//レイマーチングする際に使われる距離関数
float name(float3 p) {
    map;
}

float4 p##name(float2 vp : texcoord0) : color {
    float2 uv = -1 + 2 * vp;

    //上と下の黒帯(フィルレート稼ぐ意味もある…)
    if(abs(uv.y) > 0.75) return 0;

    //レイを投げる方向ベクトル
    float3 dir = normalize(float3(uv * float2(1.25, 1), 1));

    //視点位置
    float3 start = float3(0, 0, cnt.x);

    //レイマーチング前処理
    prep;

    //レイマーチング
    float3 pos = start;

    //ite回反復する
    for(int i = 0 ; i < ite; i++) {
        pos += 0.75*name(pos) * dir;
    }

    //法線っぽいのを取る
    float2 k = float2(0.01, 0);
    float3 N = normalize(
        float3(name(pos + k.xyy), name(pos + k.yxy), name(pos + k.yyx))
            - name(pos));

    //色をつけてreturnするように書く
    ret;
}

上記のようにマクロで2つ関数(distance functionと反復回数指定raymarching)を作るように仕込みます。

◆使ってみよう(書いてみよう)

こんな感じでレイマーチングの関数共をピクセルシェーダ内部に書けます。


//rotate
float2 rot(float2 d, float t) { return float2(cos(t) * d.x - sin(t) * d.y, sin(t) * d.x + cos(t) * d.y); }

dup(
    //関数名。ここではa(), pa()という関数が作られる
    a,
    //レイ前処理
    float t = cnt.x * 0.1;
    dir.yz = rot(dir.zy, t);
    dir.xy = rot(dir.xy, t),
    
    //オブジェクト距離計算
    float3 k = p;
    k += (sin(p.z * 15) + sin(p.x * 15) + sin(p.y * 15)) * 0.02;
    k += (sin(p.z * 25) + sin(p.x * 32) + sin(p.y * 35)) * 0.01;
    return length(abs(k % 2) - 1) - 0.7,
    
    //反復回数
    50,
    
    //distance functionで求めた位置を使って色を計算する
    float d = length(pos - start);
    float D = dot(N, normalize(float3(1, 1, 0.7)));
    return float4((pow(D,16)+D*(D * dir * 0.2)) + d * 0.05, 1);
)

    

raymarching basic

raymarching basic

Oh!書き方が直感的でラクデスネー!
なぜなら出力したい事の前準備とdistance functionの本体、反復回数を直接バチバチ書いていけば良いだけだからデス!

で、もうちょっと膨らましてみましょうと描いてみたのが以下です。

// http://www.iquilezles.org/www/articles/distfunctions/distfunctions.htm
float cyl(float3 p, float3 s) { return length(p.xz) - s.xz; }
float ibox(float3 p, float3 scale) { return length(max(abs(p) - scale, 0).xz) - scale.xz * 0.2;}

//rotate
float2 rot(float2 d, float t) { return float2(cos(t) * d.x - sin(t) * d.y, sin(t) * d.x + cos(t) * d.y); }

dup(
    //ここではd(), pd()という関数が展開される
    d,
    
    //レイ前処理
    float t = cnt.x * 0.2;
    start.z += t*2;
    dir.yz = rot(dir.zy, t*0.3);
    dir.xy = rot(dir.xy, t*0.7),
    
    //オブジェクト距離計算
    if( (cnt.x % 60) > 45) {
        p.x += -cos(p.y+cnt.x*9) * 0.3;
        p.y +=  sin(p.z+cnt.x*7) * 0.3;
        p.z += -cos(p.y+cnt.x*7) * 0.3;
    }
    float3 tp = float3(p.x + sin(p.z * 0.3) * 1.3, p.y + cos(p.z * 0.2) * 1.2, p.z);
    float   k = dot(2-p, float3(0, 1, 0));
    
    //fragment.ここでオブジェクトに大量の穴を開けます
    k = max(k, -(length(abs(p.xz   % 0.20)-0.10)-0.08));
    k = max(k, -(length(abs(p.xz   % 1.00)-0.50)-0.45));
    k = min(k,  ibox(   abs(tp     % 4.00)-2.00, 0.2)); //穴空き柱
    k = max(k, -(length(abs(tp.yx  % 5.00)-3.00)-2.0));
    k = max(k, -(length(abs(p.xy   % 0.20)-0.10)-0.08));
    k = max(k, -(length(abs(p.xy   % 0.04)-0.02)-0.008));
    k = max(k, -(length(abs(p.xz   % 0.04)-0.02)-0.008));
    k = max(k, -(length(abs(p.zy   % 0.04)-0.02)-0.008));
    return k,
    
    //重いのでもう少し小さくしても良いかも
    70,
    
    //色作る
    float  d = length(pos - start);
    float  D = dot(N, normalize(float3(1, -2, -0.7)));
    return float4((pow(D,32)+D*(0.4*float3(5, 2, 1))) + d * 0.04, 1);
)


で、上記で作成した感じの画像がこちら(グラボがスーパーマンなら60FPSで動きます)

raymarching膨らまし

raymarching膨らまし

raymarching膨らまし2

raymarching膨らまし2

ほら、なんか単純な同じ処理適当に書いてよさげなのできましたよ!
crinklerの圧縮も何となく期待できる(ホントかよ)

◆楽なの?

HAHAHA, 単に書き方の問題だろ、と思うかもしれませんが、実際にレイマーチングで遊んでると、調整周りや、関数定義周りでだんだんとだるくなるので、とっさにスケッチできるようなものを用意しておくと良さそうです。
尚、上記の書き方、割と4kintro周りで見られるっぽいです(自分調べ)
亜種は大量にあるみたいです…みんな工夫してるんだね…

ここにmakeが不要な適当なフレームワークを置いておきました。
宜しければ使ってみてください。

huyase.zip(EXE詰まってます。セキュリティソフト周りで怒られたらごめんなさい)

◆0)好きな解像度(マシンパワー低いならちょっと小さめの)huyase_WWWWxHHH.exeを起動して、
◆1)カーソルキーで好きなshader8つを選んで、
◆2)main.fx(只のテキストです。notepadでも開けます)をお好きなテキストエディタで編集して、
◆3)F5で書いたシェーダを映ってる画面に適用!

して、遊んでみてください。
※vcとDirectXSDK持っている場合はpath通してmake.bat実行で本体も好きに直せます
※尚、F6で選択しているもの以外、F7で全部更新します

シェーダコンパイラに怒られた時は見えているウィンドウの他にコマンドラインが出ているので、エラーメッセージを確認して直してみてください。

◆huyase.exeの本記事と関連するところ

scene.cppのwork_compile_shaderを見てください。_beginthread経由でシェーダをコンパイルしているところなのですが、char str_ps[] = "pa"という名前で関数の初期値を決めています。

str_ps[1] += num(シェーダの’a~z’ - ’a’までの差分)という感じにして、関数名のASCIIに直接足してコンパイルすれば’z’までシェーダを増やせます。
(4kintro周りだと結構な割合でdemoがこの方法使ってます(自分調べ)。GLSLも。)

増やす場合、shaderごとに分けちゃった方が毎回コンパイルしなくてもガッツリ切り替えるだけで良さそうです。
いい加減にthread立ててますが、大抵の4kなどは前処理一発で作るようです。

あとサンプルがやたらだだっ広い(ground系)のが多いのは私の趣味です。
狭いの苦手なんです!(なのでdot(p, float3(0, 1, 0))みたいに平面を切り刻んだりする)

皆さんもぜひ、


Enjoy raymarching!(Christmas!)

おまけ

pouetの昔の4kintroなどですさまじいレイマーチングなものがあるのですが、safeなexeが提供されていない場合、exe再生すると怒られる場合があります(特にwin7になってから)

使用されているpackerにも依るのですが、windows環境でcrinklerで圧縮したものなら、以下のようにexeを処理すると、fixed.exeというのができるので、それを実行すれば再生する事ができるかもしれません(何のpackerで圧縮されているか分からない場合でもとりあえず処理してみるとうまくいく場合があります)

crinkler sugoi_demo.exe /RECOMPRESS

実行するとfixed.exeというのが新しくできる


デバッガ使ってぽちぽち再生するよりお手軽です。上記でも特定のOSの内部リソース使うものは再生できないかもしれませんが…
あとRECOMPRESSする際は環境変数のtempとexeを処理するフォルダをセキュリティソフトで除外対象フォルダにしないとうまくfixed.exeが作れないかもしれません。ちょっと前のwin4kintroを再生したい時の参考にしてみてください。

Comments (0) Trackbacks (0)

No comments yet.


Leave a comment

Trackbacks are disabled.