さやちゃんぐbotブログ

シェーダーで遊んだりするブログです。

他の人が描いたレイマーチングを魔改造する。(カメラ編)

f:id:sayachang_bot:20191128163118p:plain

こんにちはこんにちは!さやちゃんぐbotだよ。

今年は軽率にシェーダーアドベントカレンダー Advent Calendar 2019なんてものを作ってしまいました。

qiita.com

言い出しっぺの法則に従って、初日のエントリは自分で書かなきゃな…ということでやって参ります!

 

この記事ではレイマーチングにおけるカメラをどう扱うかということを中心に考えていきますが、レイマーチングに限らず撮影においてカメラの構図や動きはどうするのかということについて、ライティング&レンダリング*1も読みつつやって参ります。

さて、本題に入る前にちょっとレイマーチングでのお絵描きについて考えてみましょう。

レイマーチングで絵を描くということは3Dを描くということです。必要になる要素をざっと挙げていくと、座標、色、形状を作る距離関数、ライティング、カメラの姿勢制御といったものがあります。そしてこれらのすべてを数式でコーディングしなければなりません!

そのため、レイマーチングはたいへん敷居が高く、著者は何か月もまったく絵が出せないという時期を経験しました。

 

幸い、最近は良い入門記事の数も増えています。それを読みながら手を動かすというのはレイマーチングの習得に役に立ちますが、誰かが完成させたレイマーチングを自分なりに改造するというのも良い方法です。

このエントリでは、レイマーチングでカメラをいじくるやり方について見ていきます。

 

カメラの構図と動き

まずは写真や映像を撮影する世界を少し覗いてみましょう。ショット全体のレイアウトを構図、オブジェクトとキャラクターの配置をステージングと呼びます

構図とステージングについて、ざっと用語をまとめたページを書いてあります。以下リンクをご参照ください。

scrapbox.io

 

ここからいくつか話題を取り上げてみましょう。

カメラの位置を被写体に対して近くに配置するか、遠くに配置するかということで、おおざっぱにクロースアップワイドショットにわけられます。

遠くにカメラを置くと見る人が全体を把握でき、オブジェクトの近くにカメラを移動すればディテールが確認できます。

これはシンプルな話ですが、同時に重要なことでもあります。画面の情報量が視覚に与えるインパクトを変えることを意識しておくべきでしょう。レイマーチングにおいても、カメラ座標を変えるだけで地味でつまらない絵にもなりますし、びっくりするような面白い絵に化けることが多いのです

 

次はイマジナリーラインという考え方です。

サッカーの中継をする場合、一般的にカメラは競技場の片側に配置されています。ゴールからゴールまでの直線に対して、カメラ視点を反対側に切り替えてしまうと視聴者が混乱します。この直線をイマジナリーラインと呼び、原則的にイマジナリーラインの片側からのみ撮影することが映像のわかりやすさに繋がります。

この話はドラマ、映画、アニメでも同様です。もし、急にイマジナリーラインの反対側にカメラ位置を切り替えてしまったら、キャラクターが反対を向いたように誤った印象を与えたり、それまでの人や建物やオブジェクトなどの位置関係が混乱してシーンを把握しづらくなります。このようなことを「180度ルールを破る」と呼びます。

さて、レイマーチングではどうでしょうか。これは完全に私見ですが、レイマーチングではイマジナリーラインはそれほど意識しない絵作りでも問題は少ないだろうと思われます。現状、レイマーチングでキャラクターをモデリングして動かすのは、まだそれほど扱われていない分野です。*2いったんはルールを忘れ、ありえないカメラのチーティングを実装し、自由な発想で面白い絵作りを目指すのが楽しさに繋がるのではないでしょうか。

ただし、複数のキャラクターをレイマーチングで動かすのであれば、勿論こういったルールは重要になります。守るべきことと、あえて破ることとを考えてみるのも面白いでしょう。

 

カメラの動きについて、パン、ティルト、ズーム、ドリー、トラックといった用語があることを知っておくことも役に立つかもしれません。

パンはカメラの横方向、ティルトは上下方向の回転です。ズームはカメラの視野を拡大・縮小します。ドリーとトラックはカメラから被写体に向けて、それぞれ位置を前後させること、横方向に水平移動することです。

これらはいずれも座標をトランスフォームするやり方で実装できます。

 

最後に構図の1/3ルールについておさえておきます。

ショットにはメインの被写体があり、これがポジティブスペースと呼ばれる空間を作り出します。それ以外の背景や余白はネガティブスペースです。ポジティブスペースで左右の2/3を占めるようにしたり、地平線が画面の下側1/3の位置になるように調整することで、画面にメリハリを作り出すことができます。

 

レイマーチングでのカメラ実装

前書きが長くなりました。いよいよ実装を見ていきましょう。

まずお手本として、Kanetaaaaa神のEnergyLabを取り上げます。

neort.io

 

ここからは実践としてカメラに関わる箇所を探し、どのように手を入れることができるのかを見ていきます。カメラ操作の実装がわかれば、レイマーチングの絵に対して動きの追加が容易になり、魔改造がはかどります

まずスクリーン座標の扱いです。main関数の最初の部分で座標を定義します。

vec2 p = (fragCoord.xy * 2.0 - iResolution.xy) / min(iResolution.x, iResolution.y);

これはレイマーチングでは典型的な書き方です。これで画面左下の座標が(-1.0,-1.0)、右上が(1.0,1.0)となります。modやabsで反復表示をする際、座標に負の値になるところも含めると都合が良いのです。

 

シンプルなカメラの定義はrordという変数で扱うのが良く見るパターンです。それぞれ意味はRayOrigin、RayDirectionで、実装は以下のようになります。

vec3 ro = vec3(0.0, 0.0, -3.0);

vec3 rd = normalize(vec3(p, 1.0));

roは原点を含むxy平面よりz座標が手前となり、rdはこのxy平面に向けてレイを飛ばす方向を定義します。レイが前に進み過ぎないよう、normalizeして進む距離を抑えます。

EnergyLabではこのroに相当するものはorigin、rdはrayという変数名になっています。

rdでz方向に進む大きさをFoVと表現して実装されたレイマーチングが世の中にはいくつかあります。

float FoV = 0.8;

vec3 rd = normalize(vec3(p, FoV));

これは上で出てきたズームです。試しにEnergyLabに適用してみましょう。コード内ではfoという変数に追加で掛け算を書き加えると良いです。

 

ズームの変更

0.2 * fo

f:id:sayachang_bot:20191128144905p:plain

2.8 * fo

f:id:sayachang_bot:20191128145018p:plain

レイマーチングでは、ちょっとだけ掛け算を書き足すとクロースアップとワイドショットを描き分けることができるのです。

 

カメラをもっと動かしやすくするため、姿勢制御できるように書き換えてみましょう。

vec3 ro = vec3(0.0, 0.0, -3.0);

vec3 ta = vec3(0.0, 0.0, 0.0);

vec3 fwd = normalize(ta - ro);

vec3 up = vec3(0.0, 1.0, 0.0);

vec3 side = normalize(cross(fwd, up));

up = normalize(cross(side, fwd));

float FoV = 1.0;

vec3 rd = normalize(p.x * side + p.y * up + fwd * FoV));

コードが込み入ってきましたが、それぞれの変数の意味からいきましょう。

taはtargetでカメラがどこを向くかを指すLookAtです。最初は原点にしておいて、後からルック調整する時に数値を変えていくと良いでしょう。

fwdはforwardでカメラからターゲットへの方向です。大きさではなく方向としてのベクトルが欲しいのでnormalizeします。

upではy方向の上を指し、これとfwdの外積からsideを求めます。これはふたつのベクトルの両方に直交するベクトルを求めるという、外積の典型的な使い方です。カメラを数学的に表現するのに、外積はなくてはならないツールです。sideを求めた後にupを再度外積で求め直します。

 

パンやティルトの実装はとてもシンプルです。taのxかyの要素を変えるだけです。

vec3 ta = vec3(sin(time), 0.0, 0.0);

ドリーとトラックは、roとtaを同じ量だけ移動すれば実現できます。

vec3 ro = vec3(0.0, sin(time), -3.0);

vec3 ta = vec3(0.0, sin(time), 0.0);

 

他のプラットフォームでカメラの姿勢制御を実装する時、クォータニオンを使ってもっと厳密にした方が良い場合がありますが、レイマーチングやシェーダーアートの世界では不正確でもルックが出ますし、逆に数学的に間違っているからこそ面白いルックが天から降りてきてくれることすらあります。ですので、数学がわからないといってしり込みすることはなく、もっと軽率にシェーダーを楽しむのが良いです*3

 

次に1/3ルールを試してみましょう。先ほどのズームに加え、roとtaの座標を調整してみます。

f:id:sayachang_bot:20191128155747p:plain

意図的にネガティブスペースを作り出し、床を画面の下側1/3くらいの位置に調整します。全体の印象がずいぶん変わったのではないでしょうか。

 

私見ですが、レイマーチングでの画面のインパクトは主にふたつの要素によるものだと考えています。

  • 形状の複雑さ
  • カメラの動きの面白さ

形状についてはさまざまな図形の組み合わせと、空間の折り畳みの妙が重要となります。特に、ノイズで作られたトンネルを進むものと、フラクタルで構成された空間を浮遊するレイマーチングはインパクトを出しやすいと感じます。

 

カメラの動きについてはもう少し手を入れてみましょう。平行移動と回転を組み合わせるだけで面白い動きになっていきます。

一番やりやすい動きは、z方向で前に進む実装です。

vec3 ro = vec3(1.0, 1.0, time);

roかtaに対して任意の平面で回転を加えるには、以下のような実装になります。

ro.xz *= rot(ro.xz, time); // rotはcosとsinから成る回転行列

ここでxとzの要素がゼロだと動きませんので、値をゼロ以外にしておくことに注意します。roとtaの両方で複数の平面の回転を加えると、カメラの動きが複雑化してたいへん面白いルックになります。

 

カメラの動きで特に面白いと感じたのは、Revision2019のShader Showdownでevvvvilさんが実装していたコードです。

www.youtube.com

これを先ほどのroとtaにあてはめて、わかりやすいコードに書き換えてみます。

vec3 ro = vec3(1.0, 1.0, 3.0 * sin(time));

vec3 ta = vec3(0.0, 0.0, 0.0);

vec3 fwd = normalize(ta - ro);

何が起きるのでしょうか?カメラはz方向に向かって前後に平行移動します。ところで、カメラは常にターゲットの方向を向きます。カメラが動き、zがゼロの境目を行き来するたびに、カメラがぐるりと動いて画面に強いインパクトを与えます。

同様のことをEnergyLabに書き加えてみましょう。変数originのzにある定数にsin(time)を掛けてあげるだけです。このシンプルな実装で、画面が急に派手な動きになります。180度ルールを破るというのは、とても楽しい行為なのです。

f:id:sayachang_bot:20191128163118p:plain

 

参考用に、手を入れたこのコードを以下の場所に置いておきました。元ソースから本当にほとんど変わっていないにも関わらず、ルックが激変していることを確認してみてください。

glslfan.com

 

おわりに

いかがでしたでしょうか。

レイマーチングにはさまざまなテクニックがありますが、本記事を元にカメラ実装に関わる知見を得る一助となれたら幸甚です。続編としてライティングについての記事も後ほど出しますので、興味のある方はぜひご覧ください。

 

シェーダーアドベントカレンダー次回12/2の記事は、mokoさんによる「UE4 格子付きモザイクマテリアル | Mosaic Material with Checker Pattern」です。え!楽しそう!!これは見逃せない!!

effect.hatenablog.com

 

引き続きシェーダーアドベントカレンダー2019をお楽しみください!!

ではでは。

*1:https://www.borndigital.co.jp/book/9563.html

*2:iq神のHappy Jumpingは、キャラクターが飛び跳ねるとても面白いレイマーチングです。

https://www.shadertoy.com/view/3lsSzf

*3:私見ですが、「ラッキーシェーダー」にこそシェーダーアートの本当においしい部分があると思っております。