この記事はrogy Advent Calendar 2015の3日目の記事です.
修論シーズン真っ盛りのニックさんです.
AdventCalendarをロ技研でやるみたいなので,ふわっとした記事を書きます.
冷静に俺は修論を書くべき.



突然ですが皆さん,このような経験は無いですか?
ケース1:「ゲームのエフェクトを付ける時の透明度を0から255にして,また0に戻したい!
でもどんな関数にしよう…….」

ケース2:「二足歩行ロボットのモーションを作るぞ!
キーフレームでの姿勢とその間の補間関数はどうしようかな.」

ケース3:「音声を生成するプログラムを作ろう!
音量は最初は大きくて,だんだん減衰するようさせたいけど,どんな関数かな?」


何か数理的な処理の入るものづくりの場合には,必ず「関数」というものが出てきます.
数学的な難しい話ではなく「こういう引数の時こういう値が来る!」程度の話です.
その時どんな関数を使えば良いのかは知っていないとなかなか自分では設計できません.
気合を入れて3次スプライン曲線などを作ってフィッティングさせても良いですが,
それよりも簡単で便利な関数があれば計算コスト的にも実装コスト的にも嬉しいです.

今回の記事の内容は簡単で便利な関数をいくつかご紹介したいと思います.
特に僕自身ゲームを作る人間なので,ゲームでの利用例なんかも合わせて紹介できたらなと思います.
(ここで簡単とはできるだけ次数が小さく,初等関数程度で実装できるものを指します.)



ガウス関数
gauss
$$f(x) = \frac{1}{\sqrt{2\pi\sigma}}e^{-\frac{(x-\mu)^2}{2\sigma}}$$
 理学,工学ともによく使われる関数系です.正規分布なんて呼ばれたりもします.$\mu$を中心に$\sigma^2$程度の分散で山を作る関数です.
ちなみに山を作りたいだけならば頭に付いているいる定数項の$\frac{1}{\sqrt{2\pi\sigma}}$は要りません.定数項が付いていると確率の性質$\int dx f(x)=1$を満たすので,確率的な処理をしたい場合はガウス関数にしましょう.もっともプログラミング言語のライブラリでは正規分布に従った乱数を返す関数が用意されているので,自前の確率計算をするとき以外は使わないかもしれません.
 ゲームでキャラの与えるダメージをぶれさせたり,ぼーっと光る明かりの表現なんかをしたいときにはガウス関数はよくお世話になります.



シグモイド関数
sigmoid$$ f(x) = \frac{1}{1+e^{-ax}} $$
 続いてニューラルネットでの活性化関数としてもよく使われるシグモイド関数です.$f(-\infty)=0,f(\infty)=1$となる関数で,$a$の大きさによってその鋭さが変わってきます.特に$a\gg1$の時には殆どステップ関数($f(x)$が$x>0$の時のみ1という値を持つ関数)となるため,ステップ関数の拡張として考えることも出来ます.普通のステップ関数は$x=0$で微分が出来ないので性質が悪いですが,シグモイド関数はどこでも微分ができるので嬉しいです.
特に$x$の定義域なども無いので,何も考えずにこの関数に突っ込めば,0から1の値を返してくれるので精神的にも安定します(?).
ゲームならば敵AIでプレイヤーとの体力差を$x$として,その時の敵AIの攻撃積極度みたいなのをシグモイドにすると性質が良いかもしれませんね.
 ここで注意ですが,$x=\infty$にしないと$1$を返さない関数なので,モーションの補間などには使わないほうが良いでしょう.そういう場合は後述のease-inout(smoothstep)を使うと良いです.



ease-in,ease-out
ease
$$
  \begin{array}{ll}
    f(x) &= x^2 \\
    f(x) &= x(2-x)
  \end{array}
$$

 大げさな名前が付いていますが,本質はただの2次関数です.それぞれ$x=0,1$の場所で極小極大になるように係数が選ばれています.定義域はどちらも$0\leq x\leq1$です.
 主に使われる場所としては値の補間でしょうか.$x$が0から1で変化するときに,対応する値$y=f(x)$を滑らかに変化させるときに使います.$y=x$にするよりかは滑らかに繋がるので,動き始めや動き終わりの慣性みたいな物を表現できます.
 誰でも思いつきそうな関数ですが,お約束として知っておくと便利かもしれません.ゲームでの例ならば,キーフレームとして与えられているボーンのモーション間での補間で便利です.



ease-inout (smoothstep)
ease-inout$$ f(x) = x^2(3-2x) $$
 今度は3次関数です.動き始めと動き終わりで微分値が0となっている滑らかな形状です.これも$x=0,1$の場所で極小極大となるように係数が選ぶを自然とこの関数形になります.また,GLSLなどのシェーダー言語ではsmoothstep()という名前でこの関数が実装されています.その時は定義域$0\leq x\leq1$外では$f(x)=0,1$というように定数関数で滑らかに繋がるように実装されます.
 0から1までの間を滑らかに微分可能な形状で結ぶため,先ほど紹介したシグモイド関数と似ていますが,こちらは0や1に到達するので補間をするときには便利です.




impulse
inpulse$$ f(x) = kxe^{(1-kx)} $$
 この関数はIngo Quilezさんが自身のサイトで紹介している関数です.Ingo Quilezさんのサイトは凄いサイトなので,気になる方は是非とも参照してください.

 このimpluse関数は$f(1/k)=1$を満たす関数で,$x=1/k$までの間は急激に上昇,$x=1/k$付近でピークを持ち,
それ以降はゆっくりと0へと漸近していきます.最初にドカっと値を与えて,後はそこから緩和!みたいなエフェクトを作るときには重宝しそうです.




almost Identity

$$
  f(x) = \left\{ \begin{array}{ll}
    (2n-m)\big(\frac{x}{m}\big)^3 + (2m-3n)\big(\frac{x}{m}\big)^2 + n & (0\leq x \leq m) \\
    x & (m \le x)
  \end{array} \right.
$$

almostIdentity おなじくIngo Quilezさんが紹介している関数の1つがalmost Identityです.式は一見複雑に見えますが,実際は3次関数と1回の比較演算から成り立っています.
 $f(0)=n,f(m)=m$という関係を満たしていて,バイアスとして$n$,補間する範囲を決定する$m$という2つのパラメータを持ちます.
この関数は殆ど$y=x+n$ですが,原点付近で3次関数で滑らかに接続されている微分可能な関数です.

線形な補間をするときに立ち上がりのカクつきを減らしたい!って時には便利なのではないでしょうか?




レナード-ジョーンズ・ポテンシャル

LJ$$ U(r) = 4\epsilon\Big[ \Big(\frac{\sigma}{r}\Big)^p - \Big(\frac{\sigma}{r}\Big)^q \Big] $$
 ゲーム開発者の為のAI入門でも解説されていたポテンシャルです.元はと言えば分子動力学などで使われている原子間の相互作用ポテンシャルの経験的なモデルだそうです.ポテンシャルなので$F(r)=-\frac{dU}{dr}$をオブジェクトに働く力として与えてあげると,極小値$r=r_{\rm min}$付近へと近づいていく動きを実現出来ます.
$\epsilon$はポテンシャルの谷の深さを表すパラメータで,$p,q$は引力と斥力の次数,$\sigma$は距離のスケールです.
 よく使われる次数としては$(p,q)=(12,6)$でしょうか.目的に応じてパラメータチューニングされます.$r\rightarrow 0$に向かうと$U(r)\rightarrow\infty$となるので,「物体間の距離が0にはならない」という衝突を回避する動きができます.逆に物体が極端に遠いと$U(\infty)=0$より殆ど互いに力を与えること無く相関0となります.丁度良いスケールでオブジェクトが存在しているならばゲームのホーミング弾や,目的位置へのポテンシャルとして利用しても良いかもしれません.

レナード-ジョーンズ・ポテンシャルは優秀なポテンシャルに見えるのですが,明らかな欠点が2つほどあります.

  • チューニングすべきパラメータが多く,直感的で無い.
  • $r\rightarrow 0$のとき$U(r)\rightarrow\infty$となるので,値が発散する可能性がある.
これらの問題を解決できてレナード-ジョーンズ・ポテンシャルの代替として使えるポテンシャルが,
モース・ポテンシャルです(後述).




モース・ポテンシャル

morse$$ U(r) = \epsilon\Big[\Big(1- e^{-a(r-r_0)}\Big)^2 -1 \Big] $$
 モース・ポテンシャルも元は分子動力学で使われる近似的な分子間力のモデルです.$\epsilon$はポテンシャルの谷の深さ,$r_0$は極小値,$a$は谷の幅を表します.極小値や谷の幅などのパラメータが直感的で,使う側としてはチューニングがしやすいです.
 レナード-ジョーンズ・ポテンシャルとの大きな違いとしては$U(0)$が発散しない有限の値となることが挙げられます.これは距離0でも状態が許されるということを意味していて,シミュレーションの時間刻み幅が大雑把な場合でもオブジェクト間の距離が発散することを防げます.ゲームなどにおいては物理的な厳密な特性はあまり重要ではないので,良い性質ではないでしょうか?

またレナード-ジョーンズ・ポテンシャルに比べて緩やかで丸みを持った形状なので,柔らかな挙動をします.
僕の最近のオススメポテンシャルです.




フォンミーゼス分布

VM$$ f(\theta) = \frac{e^{\beta\cos{(\theta-\mu)}}}{2\pi I_0(\beta)} $$
 最後にとてもニッチな関数をご紹介します.フォンミーゼス分布は「円周上でのガウス関数」と言えるものです.
定義域は$0\leq \theta \leq 2\pi$で,$\mu$がピークの中心位置,$\beta$がピークの幅を表します.分母の$I_0(\beta)$は0次の第一種変形ベッセル関数と呼ばれる特殊関数です.
 こんな難しい関数を出してしまうと当初の主旨であった「簡単な関数」の範疇から外れてしまいますが,実用上は分母の$2\pi I_0(\beta)$の項は適当な係数で決めてもらって大丈夫かと思います.
 使い所としては,ゲームならば「敵が特定の方向を中心にある程度ランダムに登場させる」といった,
方向の絡んだ乱択処理の重みに使うと便利なのでは無いでしょうか?



------------------
今回ご紹介する関数は以上です.できるだけ簡単で使いやすいものを紹介したつもりです.
「三角関数とか$e$とか使ってて」全然簡単じゃないじゃん!って思うかもしれませんが,
今どきの計算機は早いのでゲーム内でリアルタイムに使っても問題ないと思います.
自作のオレオレスーパー関数を考える前に,初めに既存の関数を適当にフィットさせると時間的にも精神的にも良いプロトタイピングができるので,是非とも皆さん使って行きましょう.