hibitの技術系メモ

数学とか3Dとか翻訳とか

2018年+αにやったこと(主に3D)

 4月から3DCGを初めて、いろいろと知見が溜まってきました。まだまだ勉強中で、走りながら考えるといった感じですが、それらをまとめて書き溜めておかないと、自分がどういうことをやってきたのか、またその過程でどのようなサイトにお世話になってきたのかということを忘れてしまい、技術の基礎が疎かになってしまう感じがあるので、書き記しておきたいと考えております。

 本当は、半年に1回ぐらいのペースでやるべきだったのかもしれませんが(半年前の記憶でさえすでにあやふや……)、もうすでにそのタイミングを逃してしまったので、やや中途半端な時期ではありますが、2018年+αにやったこと、という形でまとめておきたいと思います。

(以下、長いので敬体ではなく常体で記す)

VRChatへのアバターアップロード

 4月。HTC Viveを買ってVRChatに登録する。

 すべての始まり。この頃はトラストシステムがなく、登録したらすぐにアバターアップロードができた。アップロードにあたっては、サンオ (id:san_o)さんの以下のページに大変お世話になった。残念ながらこの記事だけで更新が止まってしまったようだが、この記事だけで千金に値するブログだと思う。

vr-san-o.hatenablog.com

f:id:hibit_at:20190128013636p:plain

 一応こんなものだが。この時期はまだ販売アバターが少なかったので、Unity Asset StoreにあるSuriyunさんのアバターを改変して遊んでいた。VTuberでこの子を使っている方もいたようだ(詳しくない)。下はそのボクセルバージョン。

f:id:hibit_at:20190128015714p:plain

 なお、ボクセルの作成方法についてはサンフィッシュ熊野さんの3DCG出来ない!でもVRChatでオリジナルアバターを作りたい人向けチュートリアルが詳しい。

フルスクラッチアバターの作成

f:id:hibit_at:20190211004556p:plain

 今なお現役のパンダもどき。二面図をモデル作っていくという作り方を日本VTRさんの以下のページから学んだ。

nvtrlab.jp

 元の二面図のパンダからは似ても似つかない謎の生物になったが、これはこれで愛嬌があってよいのではと思っている。

f:id:hibit_at:20190211004235p:plain

 私の外向けのメインアバターであるダークエルフちゃん(一応そういう名前)。見てわかる通り、服装はJamiroquaiVirtual Insanityのオマージュ。この子はJamiroquaiを聴きすぎてしまった結果、自分をアボリジニだと思い込んでしまった悲しい子である。

 特にそのために参考にしたという訳ではないが、高知工科大学が公開している以下のページは基礎的ながらもかなり内容が濃く、これからBlenderを始める人におすすめである。

krlab.info.kochi-tech.ac.jp

 この頃はVRChatで知り合った人達に、とりあえずその日できたアバターを見せては反応をもらったりアドバイスをもらったりという感じで、非常に恵まれた期間だった。あるフレンドのオリジナルアバターを見て「アマチュアでもここまで出来るのか……」という衝撃を受けたのが、アバター作りにチャレンジした一番のきっかけである。部屋にこもってBlenderをいじっていても、そういう気持ちは起きなかっただろう。

改変アバターの作成

f:id:hibit_at:20190211004704p:plain

 6月に作ったメインアバター。お気に入りではあるが、この子は露出度が高いしweebな感じがあるので、一応内向けアバターという位置づけ。この頃になると、フルスクラッチではなく販売素材や配布素材を活かして効率的に作っていく術を覚えるようになる。その時はタイミングが良いことにあるモデラーさんがVRChat向けに素体を配布してくれていたので、ありがたく改変させていただいた(今は広く公開はしていない)。服とか髪とか含めて1週間ぐらいで出来た気がする。

 今(2019年現在)では数百体のモデルがBOOTHを中心に売られている。もしVRChatを始めるのがあと半年遅かったら、自分はアバターを自作までする気になっただろうか? 今のコミュニティの人間と知り合えただろうか? 仮に知り合ったとして、同じような関係になれただろうか? そういうことを考えると不思議な気分になる。

数学の勉強を再開

 思うところがあり数学の勉強を再開した。図形の一般概念であるところの多様体のことを勉強したかったので、入門書として有名な「多様体の基礎」(通称:松本多様体)を購入し、一応一通り目を通した。

www.amazon.co.jp

 ラノベのように読めるということで有名だが、全然そんなことはなかった。位相と集合とか解析学とかあたりの基礎が抜けているっぽいので、そのあたりを補完してからリトライしようと思う。学部時代に数学を真面目に勉強しておけばよかったと後悔しているが、こういうことは思い立った時に始めるしかない。

 他に参考にしたものとして、東工大のサイトに上がっている川平准教授が公開されている多様体の基礎のキソというpdfは非常にわかりやすかった……が、もっと噛み砕いた資料、というか具体的な計算例や可視化例があってもいいかなと思う(というか私が欲しい)。私が多様体の気持ちをよく理解できるようになったら(cf. 多様体愛護協会)「多様体の基礎のキソのきそ」みたいな文を書こうと思う。

 全体的な理解とは別に、特に射影平面については面白い概念だと思ったのでUnityでビジュアライズできないかということに興味が移るが、これは後日レイマーチングを用いて一部成功した。

5次元立方体のバズ

 およそ1年前に投稿した5次元立方体のビジュアライズがなぜか1年経った頃になってバズる。phiさんがテセラクト(4次元立方体)シェーダを販売して話題になった頃だったから、皆様に耐性(?)のようなものがついたのかもしれない。ようやく時代が俺に追いついてきたということか……ということはなく、バズりは時の運の産物である。ただ、コーディングでこの動画を実装できたのはそれなりに面白いことだと思うので、それがある程度認められたようで嬉しかった。せっかくなのでQiitaに解説記事を書いた。

 5次元立方体についての動画はあまりなく、そこにある程度の私の新規性はあると思うが、YouTubeではJos Leysさんが超次元立体について非常に興味深い動画を多数投稿しているので、こういったレベルと比べるとまだ改善の余地がある。今後、UnityとVR/ARデバイスを用いてよりインタラクティブかつ、より体感的に幾何をビジュアライズすることにより新規性を出していきたい。

販売モデルリス

 6月頃から盛んになったアバター販売だが、すごい勢いで増え続けているので誰も整理できていないのが現状である。こりゃあ誰かがリスト化した方がいいぞ、ということでとりあえず作ってみた。最初に20体ぐらいのリストを作って(まだその頃は平和だった)Googleスプレッドシートで共同編集できるようにしたが、結論的にいうとこれは失敗した、というか更新停止をせざるを得なくなった。

f:id:hibit_at:20190129022704p:plain

 主に私とあるフレンドさんとで記入をしていたが、100体を超えたあたりで手が回らなくなってしまった。元から少数が人力管理するには無理のある量だったのだ。

 人力でダメなら機械じゃい、ということで正規表現Google Apps Scriptを勉強してBoothをWebスクレイピングすることを考えた。GASの扱いについてはこのページにお世話になった。ここら辺の経過はQiitaのここにまとめてある。

 しかしこれも結果的には失敗した。「3Dモデル」を機械的に抽出することはできても「どれがアバターで、どんな特徴を持つか」という情報を機械的に抽出するのが難しく、結局人力勝負になってしまうのだ。「3Dモデル」タグを持つ商品は2,000以上にのぼるが、人力で選別処理をするには馬鹿げた量だ。

 「大量のモデル群の整理がついていない」という懸念はたぶん誰もが感じている。例えば、ねこますさんは、作者個人が申請するWebプラットフォームのような構想が良いのではと言っている。これをやってみるのもアリかと思ったが、私がWeb周りに詳しくないのと、構築したところで周知浸透できるのかという点に不安があるので、今はまだチャレンジしていない。

 という訳でこれは未解決問題である。他に個人努力として考えられるアプローチとしては、キカイガクシューを頑張って、Boothの個別ページからアバターか否か・性別・外見をまとめる判別システムを作るとか(できるのか)?

シェーダ

 9月ぐらいからシェーダそのものをいじってみたくなる。最初は簡単な例から……ということで、いくつかのワンライナー改変を考えてQiitaに投稿したら、少し反響があって嬉しかった。その後、マンデルブロ集合固有ベクトルの図示などいろいろなシェーダを自作する。これらの勉強にあたってはちくわ (id:nn_hokuson)氏のおもちゃラボに非常にお世話になった。このサイトなくてはここまでスムーズにシェーダを勉強できなかったであろう。紛うことなき神サイトである。

 11月には、簡単なサーフェスシェーダだが、フルスクラッチで自作シェーダを作った。1行残らずすべてのコードの意味(少なくとも「変更したらどういう挙動を示すか」)を理解しているシェーダである。GitHubリポジトリここ、Qiitaの解説はここ

 また時期は前後するが、VRChatで見かけたFractal Explorerというワールドに感銘を受けて、ああいうのを再現できないかと思った。二次元の座標をもとに組むシェーダだと限界がありそうなので、レイマーチングに興味を持つようになる。その内GLSLとか読み書きできるようになりたい。

セル結合

 12月に書いた、セル結合について積年の恨みを書いた文章がなぜか異様にバズった。

deux-hibi.hatenablog.com

 これはまさに異様で、1,700ブクマ弱を獲得してその日のはてブロ総合トップになるとともに、Twitterのトレンドに「セル結合」が並ぶことになった。まさかここまで広がると思わず極端なキレ芸的文章を書いてしまったので、さすがにフォローした方が良いなと思って後日釈明記事を書いた。これで一躍人気ブログに……ということはなく、その後は細々とした感じに戻ったが、まあこれぐらいがちょうど良いんじゃなかろうか。それはともかく人類は(無意味な)セル結合をやめ……やめてくれると嬉しいな(遠慮)。

ビートセイバー(環境構築方面)

 12月にビートセイバー(Beat Saber)を始めた。単なる娯楽用のゲームを「始めた」と言うのも変だけど。ある晩、もののためしにコントローラーを逆手に持って数曲やってみたら「これは……”来る”……!」と謎の確信を感じ、その日から逆手でしかプレイをしなくなる(過言)。

 その思いを抑えきれず下のようなイラストツイートをしたところ、それを見かけたガチ勢の人から声をかけてもらった。これがきっかけとなってビートセイバーのディープな世界に突っ込んでいくことになる。

 このイラストは今年の初めに購入したiPad Proがなかったら描かなかったかもしれない。思いついた事柄をそのままApple Pencilで表現して数秒でtwitterに投稿できるのはすごいことだ。そう考えると今ビートセイバーを楽しめているのはiPad Proのおかげと言えるのか……?

 やったこととしては、LIVバーチャルモーションキャプチャー(VMC)の導入。このあたりは猫井ゆうなさんの解説ページがわかりやすくとても助かった。MODを用いたカスタムアバターの導入にあたってはのしろぐさんのサイトさんには非常にお世話になった。ビートセイバーに関する情報では、日本で一番まとまっているサイトではなかろうか。神サイトである。MODを使うかVMCを使うかどちらがいいのかというのは非常に難しい問題で、これについては別途記事を書くつもりである。

 なお動画撮影のためだけにトラッカーを買った。VR機器をフル活用するようになってからの方が、肉体の限界とか部屋の狭さとかが気になるようになった気がする。物理的制約から人を自由にするためのVRなのに、本末転倒では?

ビートセイバー(動画投稿方面)

 とりあえず逆手セイバーの可能性を示すために以下のようなマップ(譜面)を作成&撮影した。マップデータはここ

 あと、合同作譜というのもやった。サーバ上にあるマップを協同で編集するというやつで、わりとたのしい。こういうツール・MOD類がユーザー主導でどんどん公開されていくのもビートセイバーの良い文化である。

 せっかくみんなで作っているから、動画もリレー形式でプレイするものを作ってみてはどうかと呼びかけた。タイムシートを決めて、皆から素材動画をもらって動画編集をするということをやった。編集にはAVIUtlを用いた。非常に便利なフリーソフトだが、各種プラグインの導入やメモリ拡張が(ほぼ)必須なのでその点は注意。完成品は以下。

発信し続けること

 やりたいことや面白いと思ったことを発信し続けるのは大事だと思う。ふとしたことがきっかけで……というのはよくある話だが、結局そういうめぐり合わせがないと効率的に成長できないのが普通の人間だと思う。同時に、それと同じぐらい、頑張ったはずなのに上手くいかなかったことや自然消滅したこともあるというのが、普通の人間だと思う。こういうのは時の運が絡むので、こうすれば絶対に上手くいくということはない。

 だからうまい縁を引き当てるまではどんな形であれ、twitterの呟きでもブログでも飲み会の雑談でも何でもいいけど、可能な限りわかりやすく具体的にやりたいことのイメージや希望を発信し続けることが大事かなと思う。

2019年のこれから

 「最終的に何がやりたいの?」ということを簡潔に記すのは難しい(長期的には変わっていくものでもあるし)。現在考えていることを要約すると、世の中の美しいこと、複雑なことは突き詰めれば線形的な要素群に分解できて、いったん線形的に扱えるようにすればコンピュータに一括計算させられるので、プログラムを活かして上手くそれら(美しいことや複雑なこと)をビジュアライズしたい、ということになる。数学もプログラミングもそのためのツールに過ぎないと思っているが、まだまだ両者とも未熟である。

 それとは別に、3DとVR/AR(将来的にはXRの方がより総括的な名称になるのかな?)はまだまだ遊びの余地がたくさんあるので、そういう娯楽的な楽しみも追及していきたい。また、それらが一般化していく中で、編集や作業を効率化していきたいという需要は絶対出てくると思うので、そういった面でもプログラミング能力を活かせたらと思う。ニッチかつマイクロなOSS開発とかあったら参加してみたい(インターネットの詳しい人達おしえて!)。後は十分な食事と睡眠をとって健康に気を付けるぐらいだろうか。

Unityとレイマーチングを用いた射影平面上の二次曲線の可視化

Unityとレイマーチングを用いた射影平面上の二次曲線の可視化
Visualization of conic curve on projective plane using Unity and Raymarching
hibit

f:id:hibit_at:20190130081525p:plain

概要

 二次曲線(楕円,放物線,双曲線)は3次元空間上の円錐に対して投影方向を変えた断面であると解釈できる.筆者は,ゲーム用ミドルウェアであるUnity上で,レイマーチングと呼ばれる技術を用いることにより,3次元空間上の円錐断面がある平面上で二次曲線になる様子,そしてその角度が変化する様子をリアルタイムに観察できるVR空間を作成し,それをVRゲーム「VRChat」上のワールドとして公開した.これにより,それぞれ別の形をしているように見える二次曲線が,射影平面上では無限遠を介して1つの円に対応していることを可視化した.

緒言

 以下,射影平面と二次曲線についての概説を記す.

射影平面

 ユークリッド空間\mathbb{R}^{n}上において,原点を通る直線全体の集合(ただし原点を除く)は射影空間(Projective Space)と呼ばれ,\mathbb{P}^{n-1}と表現される[1].これは直感的には原点を通る直線の「傾き」だけを抽出した世界である.また,これら直線は原点を中心として球に対して対蹠点(反対側の点)を同一視した上で球面上の点に対応しており,それを平面に写したものは,ただの平面に加えて「無限遠で反対側にループする」性質を持つ平面であることがわかる[2].

f:id:hibit_at:20190130082656p:plain 射影平面の構造をシンプルに表現したもの.トポロジーへの招待 2 より引用)

 なお,\mathbb{P}^{2}\mathbb{R}^{3}に埋め込めないことが知られているが[1],自己交差した形では描画できる.そのような例として,例えば以下のようなCross-capと呼ばれる図形がある.

f:id:hibit_at:20190131234825p:plain
Cross-cap(Cross-Cap -- from Wolfram MathWorldより引用)

二次曲線

 楕円,放物線,双曲線はそれぞれ以下のような曲線である.

f:id:hibit_at:20190130081740p:plain (描画にはWolfram|Alpha: Computational Intelligenceを用いた)

 これらは,適当な定数a,b,c,dを用意することにより,xy空間上において, $$ ax^{2}+bxy+cy^{2}+d=0 $$  なる一般式により記述され,二次曲線(または円錐曲線,conic curve)と呼ばれる.

 これらは円錐の断面である正円を平面に写したものの,その角度を変えたものであるといえる.また,これらはすべてある円を異なる射影平面に投影したものであると言える.言い方を変えると,

  • 楕円は傾いた「正円の影」
  • 放物線は無限遠に伸びた「正円の影」
  • 双曲線は無限遠を介して反対側にループする「正円の影」

 であると言える.

 上の事実は円錐を任意の平面で切った断面を計算することにより計算できる.つまり,原理的には,適当な定数p,q,r,sを用意することにより,xyz空間上において,

$$x^{2}+y^{2}-z^{2}=0$$ $$px+qy+rz+s=0$$

 を満たす(x,y,z)を解析的に求めることにより,任意の二次曲線を生み出すことができる.

 しかし,実際にこれらの計算を解くことはかなり煩雑な作業であり,またそうして得られた二次方程式から平面と二次曲線との関係を直観することはやや困難である.そこで,ゲーム用ミドルウェアであるUnityを用いて「円錐が回転する様子」「円錐の延長と,ある平面の断面」を実際にゲーム画面上に描画することで,「任意の二次曲線が正円の影である」ことを可視化した.

 なお,楕円が円の影であることはほぼ自明であるが,放物線と双曲線が,無限遠を概念を加えた射影平面上においては円の影であると言えることについては,後の考察で説明する.

方法

 手法はレイマーチングと呼ばれる技術を用いた.これは,カメラポジションからレイ(探査線)を飛ばすことにより,レイがある条件を満たす時に座標を着色することにより,あたかも空間上にオブジェクトがあるかのような表現をするシェーダ技術である.

 本研究では,やぎり氏が公開しているコード[3]を一部改変し,以下の変更を加えた.

  • 形状関数を球ではなく円錐にした
  • 円錐を回転するようにした
  • 角度によって色に変化を出すようにした
  • 一定の距離を超えるとオブジェクトを描画しないようにした

 なお末尾の「付録」にソースコードを載せている.

結果

 実際に,VR空間上で円錐が回転し,それが平面に投影される様子を一人称視点で撮った動画が以下である.

 中心の球の中では赤・緑・青で着色された円錐が回転している.中心からの角度が同じであれば色相は同じになり,距離は問題にならない.つまり色相は空間上における線に対応する.これがある平面に投影される時,これは二次曲線となり色相は点に対応する.

 なお,見た目の都合上曲線だけではなくその内部も着色している.つまり元の正円における「円周」ではなく「円板」を描画しており,円板の中心に近ければ近いほど発光するような処理を加えている.

 このワールドは「Virtual Projective Plane」として,既にVRChat上にアップロードされているが,パフォーマンスの問題上パブリックワールドにはなっていない.いかに計算負荷を下げてパブリック申請を通すかというのもまた興味深いテーマであるが,それは今後の課題としたい.

考察

 上の現象を,射影平面の展開図上で考えてみる.射影平面は以下のようなトポロジーを持つ展開図を考えることができる[2].

f:id:hibit_at:20190203132331p:plain トポロジーへの招待 2 より引用)

 これらのつなぎ目を介して平面がループが連続する「大きな」平面を考える.家庭用RPG(つまりドラクエやその類)の世界であればこれは同一の平面が繰り返し続くだけだが(このような世界地図はトーラスと呼ばれるドーナツ型によって現実に再現できる),射影平面においてはループをする度に反対側に行く「ねじれ」が生じる.以下,射影平面の展開図にある同一の円が位置を変えながら投影される様子を示す.

f:id:hibit_at:20190203211808p:plain

 展開図の黄色の部分が観測者のいる世界であり,Unityの画面ではグリッドの刻まれた黒い床がそれにあたる.無限遠を越した後の世界では「ねじれ」が生じ,展開図ではそれを灰色の部分として示している(円のR→G→Bの方向が時計回りから反時計回りになっていることに注目されたい).射影平面の展開図はこのように「ねじれ」を繰り返しながら永遠にループするものであり,1方向に限ればトポロジーとしてはメビウスの輪と同一となる.

 さて,床に投影された影に着目したい.円錐の直下に投影される円は当然ながら円となる.

 円錐の回転が進んだ時の様子を見る.

f:id:hibit_at:20190203212300p:plain

 この時,影は放物線となり,展開図では円が無限遠で接している(乱暴な定義で恐縮である)ことがわかり,放物線は無限遠に伸びた円であると言える.ただしこれは何も数十万kmも移動する,というものではなく,歪んだレンズを通して平面に写した結果,あたかも無限遠まで伸びるように「見えてしまう」,という方が本質に近い.円錐はただマイペースに回転しているだけである.

 更に円錐が回転する様子を見る.

f:id:hibit_at:20190203212929p:plain

 この時,影は双曲線となる.双曲線は2つに分かれた曲線ではなく,無限遠を介してつながった1つの円が写されたものである(と解釈できる)ことがわかる.青と緑の部分は,それぞれ無限遠を介して「反対側」にループする.

f:id:hibit_at:20190203213605p:plain

 更に回転が進むとまた放物線が現れる.

 この後はまた円→放物線→双曲線→放物線→…という変化が続くが,周期が180°ずれると映される円の色相が違う(時計回りと反時計回りとが逆転する).この違いは単なる周期の違いでしかなく,メビウスの輪に表と裏がないようにどちらが正と言えるものではない.展開図においても,便宜上黄色を「表」,灰色を「裏」としているが,灰色側から見れば黄色こそが裏である.このように射影平面は向きつけ不可能なものである.

補足

 本記事はあたかも論文のような体裁を取ってはいるが,何の指導も査読も受けていないものである.記述内容に誤りがないか注意を払ってはいるが,内容(特にシェーダ,数学にかかわる部分)についてもし誤りがあれば,謹んで訂正したいので,筆者までお知らせ願いたい.このブログにはコメント欄がないので,お手数ながらtwitterのリプライかDMかで知らせていただきたい.

謝辞

 「方法」でも述べた通り,内容にクリティカルに関わる部分以外をやぎり氏のコードから流用させていただいている.また,レイマーチングを勉強し始める段階では,がとーしょこら氏には参考になるサイトをいくつも紹介いただいた.また,phi氏にはコード簡潔化のためのアドバイスをいただいた.この場を借りて感謝を申し上げたい.

参考文献

 著者名がハンドルネームしかわからない場合,はてなidを記す.

1, 松本幸夫(1988)「多様体の基礎」 東京大学出版会

2, 佐野岳人(2017)「トポロジーへの招待 〜 2. 切り貼りで作る色々な曲面」http://taketo1024.hateblo.jp/entry/topology/2

3 やぎり (id:yagiri000)(2018),「固定長進行レイマーチングやってみたので簡単なサンプルと解説」http://yagiri000.hatenablog.com/entry/2018/09/13/190006

付録

 以下がシェーダのコードである.ベースは[2]より引用したコードであるが,いくつか改変を加えた.

Shader "Custom/ProjectivePlane"
{
    Properties
    {
        _Threshold("Threshold", Range(-0.1,0.1)) = 0 // sliders
        _BaseColor("BaseColor", Color) = (.5,.5,.5,1)
   }
    SubShader
    {
        Tags{ "Queue" = "Transparent" }
        LOD 100

        Pass
        {
            ZWrite On
            Blend SrcAlpha OneMinusSrcAlpha

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            // make fog work
            #pragma multi_compile_fog

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 pos : POSITION1;
                float4 vertex : SV_POSITION;
            };

            v2f vert(appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.pos = mul(unity_ObjectToWorld, v.vertex);
                o.uv = v.uv;
                return o;
            }

            float _Threshold;
            float4 _BaseColor;
            float speed1;
            float speed2;

            float2x2 rotmat(float x){
                return float2x2(cos(x),-sin(x),sin(x),cos(x));
            }

            // 座標がオブジェクト内か?を返し,形状を定義する形状関数
            // 形状は原点を中心とした円錐.
            bool isInObject(float3 pos) {
                speed1 = _Time.x*3;
                speed2 = _Time.x*1.1;
                pos.xy = mul(rotmat(speed1),pos.xy);
                pos.zy = mul(rotmat(speed2),pos.zy);
                float keijou = pow(pos.x,2)+pow(pos.z,2)-pow(pos.y,2);
                return keijou < _Threshold;
            }

            fixed4 frag(v2f i) : SV_Target
            {
                fixed4 col;

                // 初期の色(黒)を設定
                col.xyz = _BaseColor;
                col.w = 1.0;

                // レイの初期位置
                float3 pos = i.pos.xyz; 

                // レイの進行方向
                float3 forward = normalize(pos.xyz - _WorldSpaceCameraPos); 

                // レイが進むことを繰り返す.
                // オブジェクト内に到達したら進行距離に応じて色決定
                // 当たらなかったらそのまま(今回は黒)
                const int StepNum = 1000;
                const float MarchingDist = 0.01;
                for (int i = 0; i < StepNum; i++) {
                    if (isInObject(pos)) {
                float3 original = pos.xyz;
                speed1 = _Time.x*3;
                speed2 = _Time.x*1.1;
                pos.xy = mul(rotmat(speed1),pos.xy);
                pos.zy = mul(rotmat(speed2),pos.zy);
                float light = saturate(1-(pow(pos.x,2)+pow(pos.z,2))*5);
                        if (pos.y>0){
                        col.xyz = (cos(atan2(pos.z,pos.x)+UNITY_PI*float3(0,2,4)/3)/2+light);
                        }
                        else{
                        col.xyz = (cos(atan2(pos.z,pos.x)+UNITY_PI*float3(3,5,7)/3)/2+light);
                        }
                        if (original.y < -.51){
                            col.xyz = _BaseColor;
                        }
                        if (pow(original.x,2) > pow(50.01,2)){
                            col.xyz = _BaseColor;
                        }
                        if (pow(original.z,2) > pow(50.01,2)){
                            col.xyz = _BaseColor;
                        }
                        break;
                    }
                    pos.xyz += MarchingDist * forward.xyz;
                }

                return col;
            }
            ENDCG
        }
    }
}

VRChatを目的にする人、VRChatを手段にする人

(どちらが優れているとかどちらが劣っているとか、そういう話ではないですよ。念の為。)

 どうも、hibitです。去年の12月~今年の1月にかけて今更ながらBeat Saberにハマっていました。と言っても、難譜面攻略や高ランクを目指す訳ではなく、人類がまだ経験していない芸術アートである(と勝手に思っている)逆手セイバーを確立・普及させるという使命(であると勝手に思っている)を果たすべく、

 などの環境構築・技術習得を主に行っておりました。という訳で動画観て。

 これらの活動をできたのは、えんぢょ氏がdiscord鯖に誘ってくれたことが大きくて、この場を借りて感謝の念を申し上げます。また、技術面においてはチャイロ氏(100万再生おめでとう!)にはいつも素早いお返事をいただきました。ほか個別に名前をあげていくとキリがないので割愛させていただきますが、皆様にはお世話になりました。ありがとうございました。

 そうそう、チャイロ氏の動画、100万再生超えたんですよね。すごい!

 先日、それをお祝いしてVRChat(VRC)上でバーチャルオフ会的なのが開催されました。

f:id:hibit_at:20190114013429p:plain ※バーチャルプライバシーに配慮してバーチャル目線を入れています(意味あるのか?)

 一瞬で集まれて、離席も気兼ねなくできて、それでいて空気はオフ会そのもの。いやあ実に便利な世の中になったものだ。

 実は上に書いたことはすべて報告・宣伝・前置きで、その時に感じたことが本記事の内容になります。VRCには、それを目的にする人と、それを手段にする人とがいるということです。

VRCを目的とする人

 私のVRキャリア(?)はVRChatから始まっていまして、2018年の4月からのものになります。その頃は、まだ販売アバターも豊富ではなく、アバターといえば改変(というかミコちゃん)か自作かという感じでした。当然、アバターの自作は、

  • ポリゴン編集
  • リグ付け
  • UV展開
  • テクスチャ付け
  • Unityでの調整

 といった長大な手間を要するものであり、その過程で自然にその人のこだわり・創造性、言ってしまえば「魂」が宿るものでした。

 当然、そのような状況では、VRCで出会った人との会話もアバターに関するものが多くなり、「アバターが自作かどうか」「シェーダは何を使っているか」というものはとっかかりとして定番でした。また、アバター以外の話題であっても、ワールド作成であるとか、どのようなVRバイスを使っているか、というものになりやすかったと記憶しています。

 このような状況は、VRCにいるだけでどんどん情報共有がなされ進化が起こりやすく、それがまたさらなる進化を……という好循環を生み出しやすく、そのおかげでVRCには活発なコミュニティ群がいくつも出来ていました(今も出来ているのかもしれませんが、あまり把握できていない……)。つまりVRCそのものがVRCにいる目的になっていた訳です。今のVRCにある豊富なアバターとワールドは、こういった空気の中で色々な人達が切磋琢磨してきたからこそ成り立ってきたものなのだと思います。私も、あの時期にVRCを初めていなかったら、自分でフルスクラッチアバターを作ったりすることはなかったかもな、と今でも思います。

 ただその一方で、「VRCで話すことが、本質的にはVRCに関わること(あとそれにまつわる人間関係とか)以外あまりない」という状況が長く続くことに、少し息苦しさを覚えていたのかもしれません。

VRCを手段とする人

 ここで話を冒頭で話したバーチャルオフ会に戻しますと、そこではVRCに慣れていない人がほとんどでした。joinてなに、フレンド申請ってどうやるの、というそういうレベル。当然、私含むVRC慣れてる勢が先導してアテンド的な働きをやっていました。と言っても、全然嫌ではなかったというか、むしろ新鮮な感じでした。

 アバターも、アバターワールドで借りてきたもので通して、特にそれで十分。VRCに入るのに、別にBlenderやUnityと格闘することは必要条件でも何でもない、という当たり前の事実を改めて感じることになりました(VRCのヘビーユーザーのグループにいると時々忘れそうになります)。こういう空気に触れると、VRC界隈で論争になっていたこと(自作アバター至上主義とかね)がばからしく思えてくる程ですが、こういう気軽な楽しみ方こそ、技術が本来目指すべきものではないのかな、とも思います。

 今後、このようなツールとしてのVRC(もしくは他のVR/ARプラットフォーム)の活用はどんどん進んでいくでしょう。そこでは、アバターもワールドも単なる手段であり、その人の魂を顕現させるものではありません。

それは良いことなのか悪いことなのか

 長期的には良いことだと思います。というのも、自己目的化した同質的な集団は、高い付加価値を生み出しやすい反面、過激化しやすい・排他的になりやすいという側面があります。ライト層の参入は、そういったタコツボ性を壊し、文化を一般化・長寿命化させるという働きがあります。

 そりゃ、いわゆる「古参」「懐古」側の気持ちはわからんでもないですよ。大衆化するとノイズが増えます。事実、インターネットは大衆化してゴミだらけの腐海になりました。パソコン通信の時代や、5ちゃんねる(旧2ch)の時代で時が止まって欲しいという人もいるでしょうが、やはり今のインターネットの利便性があるのは、色々なユーザーを取り込んで拡大してきたからです。それに、腐海の中でも情報を真摯に発信している人はいます。このブログもそうでありたいと願っています。全然関係ないけど、クソみたいなまとめサイトとかコタツ記事とか早く絶滅しないかな。

 いまだ発展途上にあるVR/AR界隈においても、ギーク層もライト層もお互いを尊重しつつ長く共存していけるといいですね、という超無難なまとめで本記事を締めたいと思います。

 そう言えば、Beat SaberもVRゲームのはずだけどあまりVR文化という感じがないですね。根本的に音ゲー文化だから?

アニメーションオーバーライドによって中腰になってしまったアバターを直す方法(Unity2017用)

 以前紹介していたアバターの中腰解除方法ですが、

deux-hibi.hatenablog.com

 なんと、VRChatに対応したUnityのバージョンが5.6.3から2017.4.15に上がったことにより、上記事の方法1が使えなくなってしまいました

 それに合わせて記事をリライトしようと思いましたが、タイミングがいいことにがとーしょこらさんがワンクリックで中腰を解除できるツール(VRCDeveloperTool)を公開してくれたので、その紹介を改めて行うような形で新記事としたいと思います。これを専門用語で他力本願と言います。

導入及び操作方法

 先程リンクを貼ったページからダウンロード。

f:id:hibit_at:20190107203050p:plain

 無料でもDLできますけど、当然お前らもブーストで投げ銭するよな?(私はしました)

f:id:hibit_at:20190107203409p:plain

 DLしたファイルをAssets欄にドラッグ&ドロップ。今からこの中腰になったAlcedoちゃんを救出します。

f:id:hibit_at:20190107203555p:plain

 ドロップした後は、Unityの上の方にあるメニューバーに「VRCDeveloperTool」という欄が出来ているはずなので、そこにある「HumanoidPose Resetter」をクリック。

f:id:hibit_at:20190107203742p:plain

 でてきたウィンドウにある「TargetObject」に、中腰を直したいアバターのオブジェクト(リグやメッシュではなく、親のオブジェクトです)をドラッグ&ドロップ。

f:id:hibit_at:20190107204022p:plain

「Reset Pose」を押すと、この通り。めでたしめでたし。

 …。

 ……。

 聴こえるぞ……ツールに頼らずに中腰を抹殺したいという怨嗟の叫びが……!

心を込めて中腰を抹殺する方法

 前記事の焼き直しになりますが、おさらい。

 Unityのアセット欄をたどって、

Assets > VRCSDK > Examples > Sample Assets > Animation

 の中にある「tpose-new」の横にある小さな再生ボタンを押します。

f:id:hibit_at:20180921214316p:plain

 そうしたらその横に「tpose-new」というアニメーションファイルが出てくると思うので、それを選択した状態でアニメーションウィンドウを押すと、   f:id:hibit_at:20180921214535p:plain

 このように、夥しい数のアニメーションコンポーネントが出てきます。これを全部コピーして、元々あった(つまり中腰の原因になっていた)アニメーションにペーストします。これで解決!

 ……と、前記事のコピペで終わるのもアレなので補足的なあれこれ。

Tポーズの動きを入れたら、アニメーション中も強制的にTポーズになったりしないの?

 なりません。VRCをプレイしている最中は、ボーンの動きはすべてトラッキングに準拠します。

全部のアニメーションにTポーズの動きを入れると煩雑なんだけど

 再生ボタンを押して発動するアニメーションは、Animatorの「Entry」から矢印が出ているオレンジ色のアニメーションになります。そこにTポーズの動きを入れていれば、再生中は(そして何か変なことをしなければ再生後も)Tポーズになります。

 更に、オーバーライド用のアニメーションに一切余計なプロパティを入れたくないということであれば、下の図ようにTポーズ用のアニメーションを用意して、Entryからそこに矢印を飛ばすようにしましょう。

f:id:hibit_at:20190107205556p:plain

 上の図でいうと「Wink」「Buku」がオーバーライドさせたいアニメーションになります。

再生中はTポーズにできても、再生を止めるとまた中腰になるんだけど

 Tポーズのアニメーションウィンドウで、

f:id:hibit_at:20190107205834p:plain

 この録画ボタンを2回押すと操作画面でもTポーズに戻ると思います。

 だが勇者よ油断するな、Unity2017を倒しても、Unityのアップデートが続く限り、第二第三の中腰がまたお前たちを襲うであろう……。

フェルマーの最終定理もどき

 先日、あるフォロワーさんがこのようなツイートをしていました。

f:id:hibit_at:20181225215109p:plain

(晒し上げる意図はないので、ID等は伏せています)

 nが自然数だったら有名なフェルマーの最終定理ですが、nが自然数じゃなかったら普通に成り立ちそうですね。という訳で私が考えた解答が以下の通り。

 本当の問題はこれからで、ドヤ顔であげたはいいものの、上の解答には数字の間違いがあります。よくやるんだこういうミス……。元ツイートのリプ欄には補足を入れていますが、\frac{7}{6}\piではなく\frac{7}{3}\piが正です。間違い正しついでに清書と解説を加えておきます。

清書

nが自然数であるとはどこにも書いていないので、n=\frac{1}{\log 2}\frac{7}{3}i\piとする。

(\log は自然対数)

この時、nは||n||\geq3を満たす。

ここで、(x,y,z)=(4,16,8)とすると、

4^{n}+16^{n}=4^{\frac{1}{\log 2}\frac{7}{3}i\pi}+16^{\frac{1}{\log 2}\frac{7}{3}i\pi}

=2^{2\frac{1}{\log 2}\frac{7}{3}i\pi}+2^{4\frac{1}{\log 2}\frac{7}{3}i\pi}

=e^{log 2\cdot2\frac{1}{\log 2}\frac{7}{3}i\pi}+e^{log 2\cdot4\frac{1}{\log 2}\frac{7}{3}i\pi}

 =e^{2\frac{7}{3}i\pi}+e^{4\frac{7}{3}i\pi}

 =e^{2\frac{1}{3}i\pi}+e^{4\frac{1}{3}i\pi}

 =e^{i\frac{2}{3}\pi}+e^{i\frac{4}{3}\pi}

 =(cos{\frac{2}{3}\pi}+i\cdot sin{\frac{2}{3}\pi})+(cos{\frac{4}{3}\pi}+i\cdot sin{\frac{4}{3}\pi})

 =(-\frac{1}{2}+\frac{\sqrt{3}}{2}i)+(-\frac{1}{2}-\frac{\sqrt{3}}{2}i)

 =-1

 =e^{i\frac{3}{3}\pi}

 =e^{3\frac{7}{3}i\pi}

=e^{log 2\cdot3\frac{1}{\log 2}\frac{7}{3}i\pi}

=2^{3\frac{1}{\log 2}\frac{7}{3}i\pi}

=8^{\frac{1}{\log 2}\frac{7}{3}i\pi}

=8^{n}

\therefore x^{n}+y^{n}=z^{n}を満たす自然数(x,y,z)は存在する。

証拠

 ホントかよ、と思う方へ。Wolframに実際に計算してもらいました。

f:id:hibit_at:20181225223425p:plain

f:id:hibit_at:20181225223501p:plain

f:id:hibit_at:20181225223537p:plain

解説

 上の解答を見ただけで納得できる人となんのこっちゃという人との両方がいると思うので、解説を加えます。上のトリック(?)は実数を虚数乗すると三角関数になるという仕組みを利用したものです。理由なんて聞かないで。今夜はずっと側にいて。もしくはマクローリン展開でぐぐって。式にすると以下になります。

e^{x+iy}=e^x(cos{y}+i\cdot sin{y})

 これを図示すると、下図のようなめかぶのバケモノみたいな曲面2種類(実部と虚部)、になりますが、x=0の断面でスパッと切るとおなじみの三角関数になります。

f:id:hibit_at:20181225223737p:plain

 しかも、この三角関数どのような実数をかけても振幅が同じであるという特性があります。振幅が変わらない代わりに周期が変わります。

 実数の虚数乗は実部と虚部とで単位円の座標になるので、単位円同士でa+b=cが成り立つような組み合わせを見つければ上記のフェルマーもどきを満たす等式は見つかるという訳です。そのようなものには、例えば\frac{2}{3}\pi,\frac{4}{3}\pi,\frac{3}{3}\pi(=\pi)があるので、それに対応した実数とnを用意すれば出来上がりです。

 ただ、\frac{1}{\log 2}\frac{1}{3}i\piだと||n||\geq3を満たさないので、三角関数の周期は2\piを足しても変わらない、という性質を利用して\frac{1}{\log 2}\frac{1}{3}i\pi+i2\pi=\frac{1}{\log 2}\frac{7}{3}i\piにしています。こうして見るとえらい強引だ……!

 ちなみに、上の計算に「オイラーの等式」として有名な下の式が出てきますが、

e^{i\pi}=-1

 要するに、\pi(=180°)回したら、単位円上の(-1,0)の位置になるよ、という幾何学的にはごく単純なことを述べているに過ぎないことがわかります。

冷静なつっこみ

 その後、あーあーあ~さんから冷静なつっこみが入る。

 ……。

 確かに、4^{3}+4^{3}>5^{3}であり、また 4^{4}+4^{4}<5^{4}なので、3 <n< 4のどこかで必ず4^{n}+4^{n}=5^{n}であるようなnは存在することになります。なお、これは容易に解けてn=\frac{log 2}{log 5-log 4} \fallingdotseq 3.11となります。

 ええーい、誰だ誰だ複素数なら成り立つ(ドヤ とか言ってたやつは。

 無駄に話を複雑にする男ってどこにでもいるよね~、という話でした。

f:id:hibit_at:20190104231916p:plain

Photoshop持ってなくてもpsdファイルがあるとテクスチャ改変する時に便利だという話

 今回はCGをやっている人からすると鼻で笑われるような話ですが、適当にぐぐった感触だと言及している記事がなかったので一応書いておきます。

アバターをテクスチャ改変したいとき

 アバターを改変するにあたって一番手頃な方法はテクスチャ改変(色変え)だと思います。だいたいの画像編集ソフトには色調補正(色相・輝度・彩度をいじる機能)がついているので、テクスチャ画像(pngファイル)のうち色を変えたい部分を範囲選択して色調補正する、というのがオーソドックスな方法かと思います。

 しかしこれには実は2つのデメリットがあります。

  • 範囲を選択するのがめんどくさい
  • ワークフローが直列かつ破壊的である

 レイヤー管理されたファイル(ほとんどの場合はPhotoshop用のデータであるpsdファイル)を使うことにより、これらのデメリットを解消することができます。でもワイPhotoshopなんてハイカラなモン持ってへんし……と思っているあなた、psdファイルはフリーソフトgimpでも開けるので大丈夫です。これから先は実際にgimpの操作画面を交えながら解説していきます。

範囲を選択しやすい

 これから先は、この間製作者であるみにさんの許可をいただけたので、実際の販売モデルである

miniscape.booth.pm

 みかちゃんのテクスチャで説明していこうと思います。みにさんありがとう! なななんとこのかわいいモデルが2,100円! 買うしかない! 時代の波に乗り遅れるな! マストバイ!

 で、これが実際のテクスチャ。psdファイルをgimpで開いています。

f:id:hibit_at:20181230021716p:plain

 右にあるレイヤーウィンドウで、このテクスチャ画像がいくつかのレイヤーに分かれていることがわかります。例えば、ハートの色をピンクから青に変えたい時は、

f:id:hibit_at:20181230022042p:plain

 ハートのレイヤーの色全体を変えれば、ハートの色だけが変わります。わざわざ範囲の選択や色域の選択で頑張る必要はなかった! というだけでなく、もう1つのメリットも紹介します。というか、こちらの方がより大事かなと。

直列かつ破壊的なワークフロー

 ある画像に対して

  1. 編集→その結果を反映
  2. 更にそれを編集→その結果を反映
  3. 更にそれを編集→その結果を反映

 という風に編集していくのは直列的なワークフローです。3番目の操作をした後で、2番目の操作だけを変えてみた結果を見たい、ということはアンドゥ等で遡らない限り不可能です。また、遡って2番目の操作を修正した後で3番目の操作を繰り返すのはひどく徒労感を伴います。

 そうでなくとも、色合いを変えた後で光沢もつけたいし、色合いもベースカラーと調整用の色を分けたいし……という状況が考えられますし、それぞれの効き具合も独立に確かめたいところです。

 また、pngで上書き保存してしまったら、もう元のファイルには二度と戻せません。そもそも緑色にしたいよね、となっても、もう青色になってしまったファイルしかないのです。元データや作業履歴を残すためには、別にファイルを用意してバージョン管理……とやる必要がありますが、まあスマートではないですよね。

f:id:hibit_at:20190103163757p:plain

 図で表すとこんな感じ。

並列かつ非破壊的なワークフロー

 レイヤーを使うと複数の操作を並列的に処理することができます。

f:id:hibit_at:20181230023257p:plain

 レイヤーウィンドウで右クリックして「新しいレイヤーグループ」を作成。

f:id:hibit_at:20181230023843p:plain

 でてきたグループ(例では「ハートG」と名付けています)にハートレイヤーを格納させた後、その中で「新しいレイヤーの追加」を行う。

f:id:hibit_at:20181230024112p:plain

 この時にレイヤーの描画モードを「標準」ではなく「乗算」にします。とりあえずは色を足すなら「乗算」、明るくするなら「加算」と覚えておいて、他のモードの細かい違いは慣れてきたら気にしていく感じでいいと思います。

f:id:hibit_at:20181230024519p:plain

 影や光を足していくとこんな感じ。それぞれの操作がレイヤーで分かれているのがわかるかと思います。

 図で表すとこんな感じ。

f:id:hibit_at:20190103163817p:plain

 各操作はレイヤーとして独立しているので、その調整にあたって順番を気にする必要はありません。また、元データはオリジナルレイヤーとして残されているので、いくら改変をしても元データに戻るのは容易です。例えるなら、電卓だと途中で1つでも打ち間違えたら終わりですけど、関数電卓だと途中の式を変えて計算結果を変えることができますよね。あんな感じです。余談になりますが私は以上のような理由により(破壊的編集プロセスの塊である)電卓が大嫌いです。

 テクスチャのごく簡単な改変でも、作業の途中で絶対にあ……これ戻りたいとか前やったあそこのパラメータだけ変えたいなとか思ったりすることはあります。元のデータには手を加えず、レイヤー管理ですべて済ませるという考え方を持つと、選択範囲を絞る労力も必要もないし、ムダなく作業ができますよという話でした。

漸化式は平面上の線形変換だということ、あるいは非線形を多次元で殴る話

 受験数学において三項間漸化式は頻出します。以下のようなやつ。

a_{n+2}=3a_{n+1}-2a_nの一般解を求めよ

 これを解くには、特性方程式を解いて式を比較して……というめんどくさい手続きをふまなければならないのですが、行列の対角化を使うことにより(計算量はともかく)手続き上はスムーズに解くことができます。

$$ \begin{bmatrix} a_{n+2} \\ a_{n+1}\\ \end{bmatrix}= \begin{bmatrix} 3 & -2 \\ 1 & 0 \\ \end{bmatrix} \begin{bmatrix} a_{n+1} \\ a_n\\ \end{bmatrix} $$

$$ \begin{bmatrix} a_{n+1} \\ a_n\\ \end{bmatrix}= \begin{bmatrix} 3 & -2 \\ 1 & 0 \\ \end{bmatrix}^{n-1} \begin{bmatrix} a_2 \\ a_1\\ \end{bmatrix} $$

$$ \begin{bmatrix} a_{n+1} \\ a_n\\ \end{bmatrix}= \begin{bmatrix} 1 & 2 \\ 1 & 1 \\ \end{bmatrix} \begin{bmatrix} 1 & 0 \\ 0 & 2 \\ \end{bmatrix}^{n-1} \begin{bmatrix} -1 & 2 \\ 1 & -1 \\ \end{bmatrix} \begin{bmatrix} a_2 \\ a_1\\ \end{bmatrix} $$

$$ a_n= \begin{bmatrix} 1 & 1 \\ \end{bmatrix} \begin{bmatrix} 1 & 0 \\ 0 & 2 \\ \end{bmatrix}^{n-1} \begin{bmatrix} -1 & 2 \\ 1 & -1 \\ \end{bmatrix} \begin{bmatrix} a_2 \\ a_1\\ \end{bmatrix} $$

$$ a_n= \begin{bmatrix} 1 & 2^{n-1} \\ \end{bmatrix} \begin{bmatrix} -a_1+2a_2 \\ a_1-a_2 \\ \end{bmatrix} $$

$$ a_n=-a_1+2a_2+2^{n-1}(a_1-a_2) $$

 めでたしめでたし。テクニックだけを知りたいのならば、ここから先は読む必要はありません。

行列の意味するところ

$$ A=\begin{bmatrix} 3 & -2 \\ 1 & 0 \\ \end{bmatrix} $$

 この行列が意味するところを考えていきます。2*2の行列は平面上における座標変換、それも線形変換を示しています。 つまり、上の行列はxy平面において(x,y)=(a_{n+1},a_n)という点がどこからどこへ移動するかを示しているということになります。変換の様子を図示したものが下の図になります*1

f:id:hibit_at:20190103170347p:plain

 Aを作用させ続けると、(1,0)がどのような動きをするかを図示したものが下になります。

f:id:hibit_at:20190103165856p:plain

 また、固有値を持つ線形変換は対角化によって直交基底の成分を抽出することができます。直交基底というとものものしいですが、要は「横方向にn倍、縦方向にm倍しているだけ」という状態です。見方を変えると、真の姿はそのようなシンプルな変換であるにも関わらず、何かの事情で、歪んだレンズを介してしか格子座標の世界に姿を現せないかわいそうな行列がAであるともいえます(向こうからしたら我々の世界こそA^{-1}に歪んだ世界でしょうが)。その証拠に歪んだ座標に合わせた初期値を与えてやると以下の通り

$$ \begin{bmatrix} 3 & -2 \\ 1 & 0 \\ \end{bmatrix} \begin{bmatrix} 1 \\ 1 \\ \end{bmatrix}= \begin{bmatrix} 1 \\ 1 \\ \end{bmatrix}=1* \begin{bmatrix} 1 \\ 1 \\ \end{bmatrix} $$


a_1=1,a_2=1の時、a_n=1,1,1,1,…

$$ \begin{bmatrix} 3 & -2 \\ 1 & 0 \\ \end{bmatrix} \begin{bmatrix} 2 \\ 1 \\ \end{bmatrix}= \begin{bmatrix} 4 \\ 2 \\ \end{bmatrix}=2* \begin{bmatrix} 2 \\ 1 \\ \end{bmatrix} $$


a_1=1,a_2=2の時、a_n=1,2,4,8,…

 1倍、2倍されています。なんという素直な変換! お気づきの方も多いと思いますが、「n倍、m倍」は行列の固有値、「歪んだ座標に合わせた初期値」は行列の固有ベクトルに相当します。

固有値が重複する場合

 漸化式の話に戻ると、特性方程式が重解を持つ場合があります。以下のようなやつ。

a_{n+2}=4a_{n+1}-4a_nの一般解を求めよ

 オーソドックスな手法だと、変数の数に対して方程式が足りなくなるため、2^{n}で割るなどの小細工が必要になりますが、行列を用いた手法ならばそのようなものは必要ありません。

$$ \begin{bmatrix} a_{n+2} \\ a_{n+1}\\ \end{bmatrix}= \begin{bmatrix} 4 & -4 \\ 1 & 0 \\ \end{bmatrix} \begin{bmatrix} a_{n+1} \\ a_n\\ \end{bmatrix} $$

$$ \begin{bmatrix} a_{n+1} \\ a_n\\ \end{bmatrix}= \begin{bmatrix} 4 & -4 \\ 1 & 0 \\ \end{bmatrix}^{n-1} \begin{bmatrix} a_2 \\ a_1\\ \end{bmatrix} $$

$$ \begin{bmatrix} a_{n+1} \\ a_n\\ \end{bmatrix}= \begin{bmatrix} 2 & 1 \\ 1 & 0 \\ \end{bmatrix} \begin{bmatrix} 2 & 1 \\ 0 & 2 \\ \end{bmatrix}^{n-1} \begin{bmatrix} 0 & 1 \\ 1 & -2 \\ \end{bmatrix} \begin{bmatrix} a_2 \\ a_1\\ \end{bmatrix} $$

 多少ジョルってますが(註:完全な対角化が不可能であり、ジョルダン標準形を持つこと)計算は依然としてシンプルです。

$$ a_n= \begin{bmatrix} 1 & 0 \\ \end{bmatrix} \begin{bmatrix} 2 & 1 \\ 0 & 2 \\ \end{bmatrix}^{n-1} \begin{bmatrix} 0 & 1 \\ 1 & -2 \\ \end{bmatrix} \begin{bmatrix} a_2 \\ a_1\\ \end{bmatrix} $$

$$ a_n= \begin{bmatrix} 1 & 0 \\ \end{bmatrix} \begin{bmatrix} 2^{n-1} & (n-1)2^{n-2} \\ 0 & 2^{n-1} \\ \end{bmatrix} \begin{bmatrix} 0 & 1 \\ 1 & -2 \\ \end{bmatrix} \begin{bmatrix} a_2 \\ a_1\\ \end{bmatrix} $$

$$ a_n= \begin{bmatrix} 2^{n-1} & (n-1)2^{n-2} \\ \end{bmatrix} \begin{bmatrix} a_1 \\ a_2-2a_1 \\ \end{bmatrix} $$

$$ a_n=2^{n-1}a_1+(n-1)2^{n-2}(a_2-2a_1) $$

次元を上げて行列で殴る…定数項編

 でもこの手法で扱えるのって線形変換だけじゃないの? と思った方へ。ご安心ください、以下のような非線形な式も次元を増やすことによって線形行列で殴ることができます。

a_{n+1}=2a_n+1の一般解を求めよ

 行列形及び回答は以下の通り。

$$ \begin{bmatrix} a_{n+1} \\ 1\\ \end{bmatrix}= \begin{bmatrix} 2 & 1 \\ 0 & 1 \\ \end{bmatrix} \begin{bmatrix} a_n \\ 1\\ \end{bmatrix} $$

$$ \begin{bmatrix} a_n \\ 1\\ \end{bmatrix}= \begin{bmatrix} 2 & 1 \\ 0 & 1 \\ \end{bmatrix}^{n-1} \begin{bmatrix} a_1 \\ 1\\ \end{bmatrix} $$

$$ \begin{bmatrix} a_n \\ 1\\ \end{bmatrix}= \begin{bmatrix} -1 & 1 \\ 1 & 0 \\ \end{bmatrix} \begin{bmatrix} 1 & 0 \\ 0 & 2 \\ \end{bmatrix}^{n-1} \begin{bmatrix} 0 & 1 \\ 1 & 1 \\ \end{bmatrix} \begin{bmatrix} a_1 \\ 1\\ \end{bmatrix} $$

$$ a_n= \begin{bmatrix} -1 & 1 \\ \end{bmatrix} \begin{bmatrix} 1 & 0 \\ 0 & 2 \\ \end{bmatrix}^{n-1} \begin{bmatrix} 0 & 1 \\ 1 & 1 \\ \end{bmatrix} \begin{bmatrix} a_1 \\ 1\\ \end{bmatrix} $$

$$ a_n= \begin{bmatrix} -1 & 2^{n-1} \\ \end{bmatrix} \begin{bmatrix} 1 \\ a_1+1 \\ \end{bmatrix} $$

$$ a_n=-1+2^{n-1}(a_1+1) $$

 なおこの変形は、幾何学的に見ればアフィン変換です。

 ただこの手法、見かけはシンプルですけど計算量は多いです。対角化さえすればあとは単純ですが、そもそも対角化をするのが結構めんどうくさい。これだったらオーソドックスな特性方程式で解いた方が早いです。オシャレではあるけど実用性はないやつ。

次元を上げて行列で殴る…階差数列編

 次元を上げれば非線形な漸化式も行列計算で解決できる! 今度はさらに発展させてみましょう。

a_{n+1}=a_n+nの一般解を求めよ

 いわゆる階差数列というやつ。

 これも漸化式を分解することによって以下のように整理できます。

$$ a_{n+1}=a_n+b_n $$

$$ b_{n+1}=b_n+1 $$

$$ \begin{bmatrix} a_{n+1} \\ b_{n+1} \\ 1 \\ \end{bmatrix}= \begin{bmatrix} 1 & 1 & 0\\ 0 & 1 & 1\\ 0 & 0 & 1\\ \end{bmatrix} \begin{bmatrix} a_n \\ b_n \\ 1 \\ \end{bmatrix} $$

$$ \begin{bmatrix} a_n \\ b_n \\ 1 \\ \end{bmatrix}= \begin{bmatrix} 1 & 1 & 0\\ 0 & 1 & 1\\ 0 & 0 & 1\\ \end{bmatrix}^{n-1} \begin{bmatrix} a_1 \\ 1 \\ 1 \\ \end{bmatrix} $$

※a_2=a_1+1よりb_1=1

$$ \begin{bmatrix} a_n \\ b_n \\ 1 \\ \end{bmatrix}= \begin{bmatrix} 1 & n-1 & \frac{(n-1)(n-2)}{2}\\ 0 & 1 & n-1\\ 0 & 0 & 1\\ \end{bmatrix} \begin{bmatrix} a_1 \\ 1 \\ 1 \\ \end{bmatrix} $$

$$ a_n= \begin{bmatrix} 1 & n-1 & \frac{(n-1)(n-2)}{2}\\ \end{bmatrix} \begin{bmatrix} a_1 \\ 1 \\ 1 \\ \end{bmatrix} $$

$$ a_n=a_1+(n-1)+\frac{(n-1)(n-2)}{2} $$

$$ a_n=a_1+\frac{n(n-1)}{2} $$

 このあたりになると、1次元直線における操作を3次元空間に写して計算していることになり、(この例では必要ないですが、大体の場合においては)逆行列の計算とかがエグいことになります。基本的に3次元以上の行列計算は人間のやることではないと考えています。GPUとかにやらせましょう。

補足1

 タイトルに「平面上」とありますが、正確には「\mathbb{R}^nユークリッド空間上」です。タイトルのわかりやすさを優先しました。

補足2

$$ a_{n+1}=a_n+b_n $$

$$ b_{n+1}=b_n+1 $$

 を変形すると以下のような三項間漸化式(+定数項)になります。

$$ a_{n+2} = a_{n+1}+b_{n+1} $$

$$ a_{n+2} = a_{n+1}+b_n+1 $$

$$ a_{n+2} = a_{n+1}+(a_{n+1}-a_n)+1 $$

$$ a_{n+2} = 2a_{n+1}-a_n+1 $$

 つまりこの2つの漸化式は、初期値の反映のされ方(つまり「歪み」)による見え方が違うだけで、本質的には同一のものであるといえます。実際に、得られるジョルダン標準形も同じです。

$$ \begin{bmatrix} 2 & -1 & 1\\ 1 & 0 & 0\\ 0 & 0 & 1\\ \end{bmatrix}= \begin{bmatrix} 1 & 1 & 0\\ 1 & 0 & 0\\ 0 & 0 & 1\\ \end{bmatrix} \begin{bmatrix} 1 & 1 & 0\\ 0 & 1 & 1\\ 0 & 0 & 1\\ \end{bmatrix} \begin{bmatrix} 0 & 1 & 0\\ 1 & -1 & 0\\ 0 & 0 & 1\\ \end{bmatrix} $$

 例えば、 a_{n+1}=a_n+nという条件を満たす初期値ベクトルは、前者では(a_1,1,1)、後者では(a_1+1,a_1,1)という形になります。

*1:この図示は、私が自作したシェーダ「EigenShader」によるものです。GitHubリポジトリここ