ホーム < ゲームつくろー! < DirectX技術編 < スプライン曲線上をおおよそ等速で移動する(丸み不均一スプライン)
その34 スプライン曲線上をおおよそ等速で移動する(丸み不均一スプライン)
スプライン曲線は空間内にある点を滑らかに結んでいく平滑化法の一種です。これについては世の中に山ほど資料がありますので今更感もあるのですが、いざ実装しようとなると案外大変だったりします。その理由は、ゲームを対象としてそれをまとめている資料が少ないと言うのもありますが、スプライン関数(均一3次スプライン)が持つ微妙な性質がゲームに適用しにくいという点にもあります。均一スプライン関数の「均一」というのは2点間を通る時間が一緒であるという意味です。これは2点間に限って言えば嬉しい性質です。しかし、次の区間になったとたん区間長が異なるためにオブジェクトのスピードが不連続に変わってしまいます。これがゲームに適用しにくい性質なのです。また、ゲームへの適用としては、点を与えるだけでらしい曲線を描いてくれるのが理想です。
それらの性質を改善したスプライン曲線が世の中にはあります。それが「丸み不均一スプライン」です。この章では、ゲームとの相性が良いこの丸み不均一スプラインの理屈と実装についてじっくりと見ていくことにしましょう。
(尚、この章の内容は「Game Programming Gems 4 不均一スプライン」を参照にしております)
@ 均一スプライン曲線の基本式
丸み不均一スプラインもスプライン関数であることには変わりありません。そこで、まずはスプライン曲線そのものについてざっと見ていきます。均一3次スプライン曲線は2つの点の間を「3次関数」で結びます。これは色々な定義方法があるのですが、比較的扱いやすいのは「2点と各点の速度ベクトル」で定義する方法です。そのイメージ図はこんな感じになります:
(ちなみにこれはスプライン曲線ではなくてベジェ曲線です。イメージですから(^-^;;)
スプライン曲線を定義する式は、四の五の言わず次式を利用して下さい。
(Game Programming Gems4, p168参照)
これは非常に有名な式でして、3つの行列の掛け算でスプライン曲線の3次関数を表しています。tは「媒介変数」と言いまして0から1の値を取ります。P1は「始点」、P2は「終点」の点の座標を表します。v1、v2は各点上の方向ベクトルで、ここでは「速度ベクトル」と考えます。点と速度ベクトルを固定した時、tの値に対応するこの区間内での一つの座標が出てきます。t=0の時はP1、t=1の時はP2です。tの値とP1からの長さに比例関係はありません。
均一スプラインは、どんな区間長であっても、t=0でP1、t=1でP2です(均一の意味はここにあります)。ですから、tを等間隔に進めていくと、点が詰まっている箇所ではゆっくりと、点の間が広いところは高速に移動してしまいます。このため、普通にスプラインを使ってしまうと移動速度が不連続に変わるギクシャクとした動きになってしまいます。これを改善し、どの区間でもだいたい等速移動になるように工夫したのが丸み不均一スプラインです。
A だいたい等速度で進行できる不均一スプライン
丸み不均一スプライン(Rounded Nonuniform Spline:RNS)は、均一3次スプライン関数が持つ「区間移動時間の不変」の欠点を改良したスプラインです。このスプラインの最大の利点は、区間の平均移動速度が一定である点にあります。「不均一」の意味するところは、区間が長いところはその分通過までに時間がかかるという点です。これにより、キャラクタやカメラの速度をほぼ同様に保ちながらスプライン曲線上を移動できます。またこのスプラインは、点上の速度ベクトルを違和感の無い丸みを帯びたカーブになるよう自動的に決めてくれます。これが「丸み」の示す部分です。丸み不均一スプラインには以上の「不均一」と「丸み」という2つの要素が含まれています。そこで、まずは不均一スプラインについてじっくりとその意味を見ていくことにしましょう。
不均一スプラインの式自体は自然3次スプラインと全く同じです。異なるのは先ほども説明したとおり時間tを各区間で可変にするという扱いです。それがどういうカラクリなのか、まずは以下の図をご覧下さい。
上の図は均一スプラインの曲線をまっすぐに伸ばした状態を表しています。仮に区間長は5mだとしましょう。キャラクタをこの区間において秒速1mで進ませたいと考えます。始点からの実際の経過時間を実時間Tと定義すると、例えばT=1秒後にキャラクタは1m進んでいることになります。その時、媒介変数tの値は、上の図からもわかると思いますがおおよそt=0.2になります。もし速度が倍であったら、同じ実時間Tでキャラクタは倍の2m進みまして、それに該当するのはt=0.4となります。このことから、実時間Tに該当する媒介変数tの値を算出できれば区間内でキャラクタを思う速度で移動させることが出来るようになるわけです。tを可変にするとはそういう意味です。
上の図で、T=1.0秒の時t=0.2、T=2.0の時はt=0.4です。明らかに、Tに0.2を掛けるとtになりますね。この0.2というのは、「速度を区間の長さで割った値」になっています。速度や区間の長さを色々と変えて計算してみると、そうなっていることが分かると思います。
試しに、上と同じ条件で短い区間の場合を見てみましょう。
区間の長さは2mです。速度は1m/s。この時、キャラクタはこの区間を2秒で走り抜けることになります。実時間T秒の時の媒介変数tの値はやはり速度/区間長で求められます(=0.5)。例えば、実時間0.6秒後にはt=0.6*0.5=0.3となります。より実用的な例としては、1フリップ=0.016秒としますと、上のスプライン曲線の区間で与えるtは0.016*0.5=0.008になるわけです。媒介変数tを実時間Tと移動速度vで表す、これが不均一スプラインの核です。
ただし、重要な注意があります。媒介変数tとスプライン曲線の長さには比例関係はありません。上で「ほぼ」とか「だいたい」などの言葉でお茶を濁しているのはそのためです。必ずと言うわけではないのですが、節点の近くではゆっくりになり、t=0.5辺りで速くなります。スプライン曲線のカーブが急なほどその格差は大きくなります。速度を常に一定にできないかと、実は4〜5日近くも考えたのですが、スマートで良い案は浮かびませんでした(泥臭すぎる方法ならありますが…)。この点については今のところ妥協せざるを得ないようです。
B 「丸み」を出すために
次は「丸み」を出す工夫です。丸みというのは何とも曖昧な表現ですが、これは、「点上の速度ベクトルの方向を自動的に決めて、自然な丸みを持ったスプライン曲線にする」という事を指しています。これがどういうことか、次の図をご覧下さい。
今、左点から真ん中下の点、そして右点とキャラクタを歩かせたいとします。上図の場合、真ん中の速度ベクトルの方向が明らかに不適切であるため、キャラクタはうねうねと曲がりくねったパスの上を歩く事になります。とても自然な行動とは思えません。
真ん中の速度ベクトルを調節して適切な方向に合わせると、曲線のうねりが取れ、薄緑色で示した領域が感覚的に心地良い形になるはずです。こうなるための方向の決め方については幾つかの考え方があると思いますが、丸み不均一スプラインでは「角の二等分線に垂直なベクトル」と定めています。何を言っているかは図を見れば一目瞭然です。
方向を修正したい点Oから前後の点A、Bに向けて線を引き、そこに出来た角AOBの二等分線(紫のベクトルOP)を引き、それに垂直で次の点の方向を向くように速度ベクトルを定めます。こうすることで、丸みを持った自然なカーブが描かれることになります。これが「丸み」の定義です。
角の二等分線の方向ベクトルOPを求めるのは面倒に思えますが、驚くほど簡単です。ベクトルOAとベクトルOBを標準化して同じ長さにそろえるとと、その足し算がちょうど二等分線の方向になります。
ただ、実際にベクトルOPは求めません。と言うのは、図からわかりますように求めたい方向は上図のベクトルA'B'に平行なためです。これ以上でも以下でもありません。方向さえ定まれば、それを標準化して速度vを掛け算擦れは、欲しい速度ベクトルが出来上がります。式で書きますと、
v = v * A'B'/|A'B'|
です。外積も内積も使いません。とても簡単ですよね。
スプライン曲線の始点から検索を始めていけば、速度ベクトルの方向はどんとん決定していけます。そしてこれは「スプラインを追加していける」というとてもありがたい機能でもあるわけです。ただし、最初の点の方向だけは決めておく必要があります。通常、これは次の点へまっすぐ向かうベクトルになるでしょうね。また、最後の点の方向ベクトルは自動で決まりませんので、ユーザが与える事になるでしょう。これで、丸み不均一スプラインは自由に扱えるようになります。
C スプライン曲線の長さはどうしよう
スプライン曲線の長さは解析的に解くことが出来ません(その昔mathematicaというソフトで解かせたらえらい事になりました(^-^;)。ですから、何らかの形で近似する必要があります。解析的に近似する一つの方法としては「マクローリン展開」があります(高校生のみなさんには何のことやらと思うかもしれませんが、駄目だったという話しをするので気にしないで下さい)。曲線の長さを表す積分式をこれで近似するわけです。しかし検討した結果、スプライン曲線のマクローリン展開近似は「収束が異常に遅い」ことがわかりました。たぶん100回微分をしてもまともになりません。この方法は駄目です。
結論としては、古典的ではありますが区間長を適当に分けて直線近似するのが最も手っ取り早いと思われます。どれだけの厳密性が必要かによってこの区分は分かれると思いますが、5〜10分割もすれば実用上は十分ではないでしょうか。Game Programming Gems 4には大胆にも「点の間の直線距離でいい」と書いてあります。ゲームの本質は「らしさの演出」ですから、実はそれでも良いのかもしれません。
E 直線状に並んでも方向ベクトルがゼロベクトルでも大丈夫
媒介変数で表されたスプライン関数は、点がどのように並んでも問題ありません。つまり直線区間を表現できます。また、節点の速度ベクトルがゼロベクトルであっても大丈夫です。この場合、スプライン曲線はその点で折れる事になります。これにより折れ線も表現できるようになります。点が重なっても実は大丈夫なんです。丸み不均一スプラインも同様の頑健性を保持しますが、点が重なると区間長が0になってしまうのでエラーとなります。
スプライン関数は工夫次第でもっと色々な拡張ができます。その詳細は「Game Programming Gems4」に掲載されていますので、気になる方は一読されてみると良いでしょう。工夫の殆どは媒介変数tと点上の速度の与え方の問題です。よって、オリジナルの拡張もわりと簡単に出来ます。適用場面が数限りなくあるスプライン曲線。うまく使っていきたいものです。