ゲームプログラミング/3Dグラフィック/非平面スクリーン投影
本ページの目的
編集本科目『ゲームプログラミング』は、ゲームクリエイターのための教材ではなくプログラマーのための教材です。
よって、単にゲーム用の3D-CGを作るだけなら、本ページの方法は不要です。また、ゲームで3D描画をしたいなら、Windowsの場合ならDirectXなど既存の3Dエンジンがあります。
しかし本ページでは、「今は自分は3Dエンジンを作れないが、将来的には自分で3Dエンジンを作りたいなあ」とか思ってるような人を念頭においています(ただし、本頁ページの水準では、到底ですが商用の3Dエンジンの質には及びません)。
本文まえがき
編集前提
編集- 円柱面投影の透視投影
本ページでは、まず、円柱面投影の透視投影を考えることにする。ここでいう「円柱」とは、正円の円柱とする(正円柱)。
楕円柱は考えないでおく。
なぜ非平面ディスプレイを考えるのか
編集人間の目の黒目(説明の簡単のため、読者は東洋人だとする)の部分は、それほど眼球に占める角度が大きくないのに、しかし視界の角度と、眼球の角度は一致しません。そして、視界の角度は広く、150度くらいはありそうなのです。
疑うなら、ためしに片目だけ開いて、その目の両方横に数センチ離れた位置で、両指をチョコチョコと動かしてみてください。
視界で150度くらいでは、同時に目に写ってしまいます。眼球に占める黒目の割合はもっと小さい数十度程度の角度なのに、しかし視界はそれよりもかなり大きいのです。
このため、人体の片目のセンサー配置は、球面の半円に近いと思われます。
しかし工業的には球面加工のディスプレイを作るのは経済的・工業的に困難なので、人間社会では、どうしてもリアルな視界を再現する必要のある場合、平面ディスプレイを3枚程度あわせた下記のような多面・複数面の配置によって、擬似的に球面を再現します。
_ / \
自動車教習所のシミュレーターのディスプレイが、そのように3枚の平面が並んだ配置です。
左右の2枚のディスプレイにより左右方向の視界をカバーしていますが、上下方向のカバーは無視しています。このため、足元および上空は見えません。ですが実際の自動車の運転では足元と真上を見ないのが安全運転なので、問題ありません。
さて、多面体ディスプレイの解析をするのが、自動車シミュレーターとしては一番正確ですが、しかしこれは1台のパソコンモニターはどうやっても再現できません(実験には3台ディスプレイが必要です)。
そこで、擬似的にですが、本ページでは、円柱に投影したものを展開したモデルを考えます。(というのは建前で、先に円柱投影のモデルを記述して、あとから自動車シミュレータの技術を元の版の編集者が発見し、それに合うように記事を再編集しただけなのが実情である。)
誤解のないようにいますが、円柱面に投影された映像を展開図にした平面は、実際の視界に写る景色ではありません。もし円柱展開図だとすると、顔の裏側まで映像が見えてしまいますので、不合理です。だから、円柱展開図すら擬似的なものです。円柱展開図のうち、人間の視界に写る前方だけを切り出したものによって、本ページでは擬似的に視界と見立ててシミュレーションしているに過ぎません。これでも視界側面での誤差はありますが、しかし平面ディスプレイ1枚における視界側面での誤差の無限大になる発散現象と比べたら、まだしも円柱展開図のほうが遥かにマシという、だけに過ぎません。
一見よさげに見える円柱モデルですが、致命的な欠点があり、それは方向によって誤差が変わるので(横方向と縦方向で誤差が異なる)、ころがる被写体などの回転運動をうまく表現できません。なので、円柱モデルに実用的な価値は、ほとんど、ありません。
実用性なら、平面ディスプレイのほうが遥かに上です。いっぽう円柱モデルは、あくまで平面ディスプレイの欠点を考えるための対比的なモデルに過ぎません。なので初学者はまず、一般の平面ディスプレイの理論に習熟すべきです。
さて、そもそも、人間の目のセンサー自体が、平面ではなく、やや球面です。そのくせ上述のように、眼球に占める黒目の角度と、視界の角度とが一致せず、とても複雑です。
なので、どうあがいても、平面ディスプレイに実際の景色を映すのは、無理です。
編集者は「たぶん、人間の片目の視界は、球面センサーに近いんじゃないのかな?」と考えていますが、それはあくまで仮説や予想にすぎず、本当に球面かどうかはわかりません。ただし、少なくとも、人間の片目の視界が、絶対に平面センサーの平面フィルム的な視界でないのだけは確実です。
なぜなら、もし視界が平面フィルム的な配置だとすると、何度も説明しているように、必要なディスプレイの幅が無限大になってしまうからです。なぜ無限大になるかというと、平面ディスプレイのページでも説明したと思いますが(版にも寄る)、計算式ではある被写体映像の投影先でのx座標やy座票を求める計算において、視界の側面では計算式の分母が0になってしまうのですが、片目の視界だけでも150度近くはあるので側面に近い場所を移すことになり、このため必要なディスプレイの幅が片目だけでもとても膨大になってしまい、家1件よりも大きなディスプレイが必要になりかねず、不合理です。
まして両目の180度をこえた視界をディスプレイ1枚で再現するとなると、真横の場所に対応するディスプレイ投影先のx座票が無限大になり、とても不合理です。
- なぜゲームでは問題にならないのか
実は、ゲームの3D映像は、リアルな3D計算ではなく望遠レンズぎみの構図を計算した映像です
なぜ望遠かというと、平面投影におけるカメラ側面付近での投影座標の無限大発散の問題を無くすために、そもそも視界そのものを狭めるために望遠の構図にしています。
さて、そのような望遠パースを採用しているゲームでは、ゲームプレイヤーを楽しませるための臨場感として、あたかも被写体がカメラ近くにあるかのような演出が必要であるので、なんらかの方法でゲームでは演出的なウソをついており、実際の視界に移る映像の再現ではありません。
ゲームとしては、それで合理的です。「リアルとリアリティは違う」というヤツです。
しかし運転シミュレーターでは、望遠テクニックによる視界の限定は許されないですし、視界の映像における演出的なウソも最小限にとどめなければなりません。このように運転シミュレーターには、ゲームにはない特有の難題があります。
円柱モデルで解析とシミュレーションしたときに得られる知見として、
側面 90° の位置での誤差の発散の問題があります。
なお平面モデルでは、そもそも最初から側面の描画をあきらめていますので、この知見が得られません。
数学で三角関数というのがあり、理系の大学で逆三角関数を習います。
円柱モデルにて、素直に正面向きのときの進行方向を基準に側面の映像を計算していくと、計算式中に
- 逆三角関数( 三角関数(90°+ 微小誤差A ) + 微小誤差B )
のような項が出てきます。
これは、数学的には問題が無いのですが、しかしコンピュータ計算では誤差が莫大になり発散してしまいやすいのです。(疑うなら、実際に後述の計算式のとおりにプログラミングして試してください。側面90°で描画が異常になります。)
誤差の発散の原因はおそらくですが、cos 90 ° が 0ですので、それぞれの微小誤差の影響が相対的に大きくなり無視できなくなり、発散してしまう空だろうと思います。
もし円柱モデルを使って、この発散を無くしたいなら、
側面の計算をするときは、けっして正面向きからの角度位置を基準にするのではなく、
最初から90°横を向いた向きを基準にして、角度を計算しなおすことです。
これは回転行列の計算で簡単に出来ます。
なお、偶然でしょうが運転シミュレーターの設計でも必然的に、側面ディスプレイでの被写体の計算には回転行列を使って計算することになるでしょうから、運転シミュレーターの3枚ディスプレイでは誤差発散の問題は回避されることになります。
あるいは、言い方を変えるなら、もしも正面パネルの中心向きを基準に極座標的にコンピュータ計算してしまうと、たとえ多面体モデルであっても、両側面での計算で誤差が発散してしまいます。
だから、多面体モデルにて誤差を発散させないための対策としては、
- 極座標的な計算をやめて直交座標で処理するか、
- どうしても極座標的な計算が必要ならば回転行列によって基準位置を各側面パネルの中心方向に取り直すことで90度に到達する何十度か手前で(たとえば60°くらいまでに留めるようにして)描画範囲を終わらすようにする、
などの必要があるでしょう。
ともかく上述のように極座標的なモデルや解析は、CGでは本ページで述べるように誤差の問題が生じるので、モデルが円柱モデルか多面体かに関わらず、プログラム中での数値計算の実用には極座標的なコードは適さない場合があります。
仕方無いので、直交座標では人間視点での数学的な角度関係の見通しや把握は悪くなってしまいますが、しかし誤差を防ぐためになるべく直交座標の考えにもとづくプログラミングが3D計算では必要になるでしょうか。
数学的な内容
編集円柱モデル
編集視界をしめる角度の割合の算出
編集実際の被写体の図形は3次元だが、説明の簡単化のため、二次元平面中での線分をもとに説明する。
円柱投影の場合での3D-CGの原理は余弦定理である。『高等学校数学I/図形と計量#余弦定理』
被写体の2点(仮にAおよびBとする)から、それぞれの点の位置をあらわす座標(直交座標)をもとに、カメラの位置(仮にK)から被写体の各点のベクトル(KA: および )を得ればいい。
そして、 および のなす角度θが求まる。
あらかじめ、視野の角度θ0を、決めておく。
あとは、θ0とθとの比率で、視界でどれだけの長さに投影されるかの比率をもつかが、求まる。
また、基準となる点や線を用意しとけば、それとの角度差から、被写体の表示される位置も求まる。
カメラ回転時の被写体の見える大きさの数学的事実
編集上記の計算で、仮にカメラを位置はそのままで、向きだけ変更させてみたとしましょう。つまり、カメラの向きだけ、右回りまたは左回りに回転した場合を考えます。
すると、じつは、ある被写体の全身が視界の内部にあるかぎり、カメラの水平方向の回転量にかかわらず、じつは(余弦定理で単純計算した場合の)物体の見える大きさは、不変です。
なぜなら、上記の条件でカメラの向きが変わっても、被写体の視野角に占める大きさが不変だからです。
このため、たとえば、カメラの水平で右回りに回転させても、実は被写体の大きさはそのままで、単に被写体が左側に移動するだけです。(近くにある被写体ほど、大きく移動する。)
被写体の見える大きさは、視野角に占める割合で決まるのですから、視界の中央にあろうが、視界のすみっこにあろうが、視野角に占める割合が変わらないので
しかし、みなさんは日常生活では、目の前にあるものは、なんとなく大きく見える感じがしていると体感していたでしょう。
同様に、日常生活では、視界のすみっこにある物体は、なんとなく小さく見える感覚があるでしょう。
つまり、皆さんの体感と、数学の幾何学的な事実とは、ちがいます。
この原因は、おおまかに、
- ・人間の首は、たとえ胴体が直立姿勢でも、首の向きはやや斜め前方に傾いている状態になっているので、なので、もし顔の向きを変えると、目の水平位置も変わってしまうこと。
- ・人間の肩の仕組みでは、たとえば左うでを左に伸ばした場合の左手は、左手を顔の前方の突き出した場合よりも、(左に伸ばした場合のほうが、手は)目から少しだけ遠ざかる。このため、左手にもった物体も、左手を左に伸ばすと小さく見える。これは実際に目からの距離が変化したのが原因であるが、日常生活では目からの距離の変化を意識しないので、あたかも「視界のすみっこにある物は小さく見える」という誤解を多くの人に起こしている原因のひとつであると考えられる。
- ・人間の脳の処理では、自動的に、視界のすみっこにあるものには、意識がむきづらいようになっていると思われる。
以上のことなどが、起因しているのでしょう。
さて、3Dグラフィック的には、この事が分かると、いろいろと便利です。
まず、被写体に奥行きの無い場合、つまり、被写体が平面物体の場合は、そもそも、いちいち三角関数をつかって計算をする3D計算の必要はありません。
どういうときに3D計算が必要かというと、被写体に奥行きがあり、その被写体をさまざまな角度から見たい場合に必要になるのです。
なので、もし被写体を一定の方向からしか見ないならば(たとえば、つねに南側にいる観測者が北側を見る視点ならば)、単なる、三角関数を使わない表現でも、充分にリアリティのある表現が可能なことが、数学的にも証明されることになります。
このような数学的な事実がありますので、3Dグラフィックのプログラムを自作する前に、はたして自分に三角関数による3D計算が必要かどうかを考えておきましょう。
なお、人間の首のかたむきや動き方の話の暗黙の前提として、人間が横を見たり振り返ったりする場合は、まず、肩や胴体はほとんど動かずに首(と頭)だけが動いて、そのあとに引きづられる形で、肩が動きます。これは、アニメーターの描く、人物の振り返りの作画の基本でもあります。
- 数学的な背景
さて、余弦定理でつくる角度が、回転しても変換というのは、中学~大学の数学で考えてみれば当然です。
中学生レベルで考えれば、まず、三角形を回転して別の場所に位置を移動しても、移動先の三角形はもとの三角形と合同なままです。
大学レベルで回転行列を使って考えれば、これは「回転行列は、2個のベクトルのつくる内積の値を不変に保つ」という定理へと一般化されます。
さらには、『直交行列』といわれる種類の行列は、2個のベクトルのつくる内積を不変に保つという定理が、すでに解明されています。
- 大切なこと
3D-CG製作で大切なことは、日常的な感覚と、上述の数学的な計算や定理との食い違いを、認識することでしょう。
もし、ある若者が、数学を勉強していて直交行列の内積不変の定理だけを知っていても、それをプログラミングの場で本書のような予備知識なしで初見で「カメラの位置をそのままで視点の向きを変えても、被写体の見える大きさは変わらない」という事実に気づける人は、そう多くないでしょう。
- ※ この考察結果をもし運転シミュレーター的な多面体モデルに当てはめるなら、つまり、たとえば自動車が向きを変えるなどして対応するカメラの向きが変わって、それまで中心パネルに投影されていた被写体が側面パネルに投影先が移動しても、その被写体の映像的な大きさは原則的には変わらないわけです。(ただし、各パネル内では平面モデルなので若干の大きさの変動はありうるが。)
- ※ wiki編集者たちが教習所シミュレーターを確認できる立場にはいないので、実際のシミュレーターがどうなってるかは知りません。またもし被写体の大きさが変わらないようになっていたとしても、その理由が果たして当ページで述べたような上述の理由によるものなのか、それとも別の理由なのかは知りません。
3D-CG そのもののビューワーなどのアプリケーション製作に必要な数学力とは、こういう「気づき」のできる能力のことでしょう。けっして単に、直交行列の公式だけを知っていても、それだけでは役立たずになってしまいます。
- 余弦定理で上手くいく理由
もし正円柱に投影するのではなく、楕円柱に投影する場合を考えると、算出すべきは角度ではなく楕円弧の弧長である。
正円の場合、角度のラジアンから弧の長さを三角関数によって簡単に算出できるので、角度さえ算出できれば、あとは高校レベルの簡単な計算で処理できるというワケである。
数学的には、高校3年~大学1年の微分積分で習う「楕円積分」という積分の公式で、コンピュータ数値計算の手法で楕円の弧の長さを求めることができる。だが、楕円積分の解析は難しい。
なおC言語では国際規格上ではC++などの規格で楕円積分を cmath ヘッダでサポートしているが[1]、だが実際のC言語界隈では情報が乏しく、教科書が乏しいので、あまり使わないほうがイイだろう。なおC++の規格では comp_ellint_1 などで楕円積分を定義している。その他の数学の特殊関数もC++では規格上は定義されている。)
なお、数学では、三角関数の楕円バージョンである楕円関数というのがある。楕円関数と、楕円積分の公式は、異なる。
円柱投影の場合の視界の特徴
編集円柱投影であっても、カメラから見て真横に近い方角にある被写体などは、ケタ落ちのため、不正確な表示になりますので、対策が必要です。
対策として、視界の中央から、±90°より大幅に未満(たとえば ±60°までなど)の一定範囲の角度内にある被写体だけを描画する必要があります。
このため角度計算が必要ですので、被写体が水平方向から見て、その方角にあるかの角度を保存しておく必要があり、その角度が視界内に相当する一定の範囲内にあるときだけ描画するようにプログラムを記述する必要があります。
どのみち、カメラの裏側にある物体の表示を隠したりするためにも、カメラから見た方角の角度の保存が必要になります。
なお、カメラの裏側にある物体が映るのを隠すなら、反対方向のベクトルどうしの内積がマイナスになることを利用すると、計算が簡略になります。
つまり、カメラの向きの単位ベクトルと、観察者から被写体への向きの単位ベクトルとの内積です。この内積が、カメラの裏側ではマイナスになります。
90°の直交する2つのベクトルどうしの内積は0ですし、90°に近い角度で交わるベクトルどうしの内積はゼロに近い数字です。
なので、たとえば「内積が 0.1以下の被写体は、描写を除外する」などのアルゴリズムを組めば、カメラ横にある被写体 と カメラ裏側の被写体 の描画を、同時に排除できます。
ただし、内積を計算できるようにする前提として、0ベクトルを排除する必要があります。なので、カメラから一定以上の距離を判定基準として、その距離よりも離れた位置にある被写体だけに、このアルゴリズムを適用する必要があります。この判定基準の距離には、投影面の円柱の半径に近い数字を判定基準にすればよいでしょう。
さて、この 横方向ケタ落ち 等の問題のために、円柱投影であっても、至近距離にある被写体などの描画では、平行投影に切り替えるなどの対策が必要になります。
カメラの横方向にある物体も、平行投影に切り替えるなどの対策が必要になります。
なぜなら、どんな被写体でも、カメラとの距離が0近くになれば、視界の±90°近くに入り込むようになってしまうからです。
ケタ落ちの問題のため、どんな形状の投影面であっても、透視投影を使うかぎり、スクリーンとカメラの距離は、けっして0にはできません。
このため、距離の遠近の判定をする必要があるので、判定基準としてカメラと投影面との距離の計算(これは投影面の円柱面の半径となる)が必要です。
結局、スクリーンとカメラとの間の距離は、0にはできず、大きさを持った値になります。
なので平面投影でも円柱投影でも、スクリーンとカメラの距離は、けっして0にはできません。
さて、円柱投影では、水平方向では投影面は円ですが、垂直方向では投影面は直線です。このため、垂直方向の倍率は、平面投影と同じ計算式になります。
もし、垂直方向の倍率も、水平方向の円投影の場合の倍率と同一にしてしまったら、それはもはや円柱投影ではなく球面投影ですので、地図の投影法(中学高校の社会科の地理で習うアレ)と同様の問題点に遭遇することになります。
自動車シミュイレーターとの関係
編集さて、私たちは円柱面の投影を考えましたが、しかし実際の自動車教習所にある3面ディスプレイは円柱面ではなく、
平面ディスプレイを3枚、
_ / \
の配置で並べているのでした。
円柱面の理論と照合してわかることは、なんと、教習所ディスプレイでは、実は、映像の精度が、3枚それぞれのディスプレイの中央部と両端部とで違います。
もし3面ディスプレイが一般的な平面ディスプレイ用のアルゴリズムを使って3D表示してるなら、実は、各ディスプレイの両端部は、映像の精度が少しだけ悪いのです(なぜならディスプレイ形状を円柱面にしない限り、観測者からの距離は一定ではないので、精度は悪くなる。)この文脈での「精度が悪い」とは、位置が微妙に違っていたり、精密な前後関係が信用できないということです。
おそらく、各ディスプレイの中央どうしに距離は、ほぼ一定になるように配置していると思われます。なので、私たちの考察では、各ディスプレイの中央から運転手の距離を基準にしましょう。
すると、ディスプレイ両端部と運転手の距離が、基準よりも長いので、(前後関係などの)精度はすこし悪くなります。
模式的に、下記の図に、拡大して、中央ディスプレイと右ディスプレイを抜粋して、精度の良否つきで書くと
悪 良 悪 ↓ ↓ ↓ _____ \← 悪 \ \← 良 \ \ ← 悪
のような感じになるはずです。
某社の運転シミュレーターのwebサイトの映像を確認したところ、ディスプレイ中央の映像と右ディスプレイの映像には、端部にすら重なりはありません。なので、2枚のディスプレイで精度を補強するようなことはしていません(もっとも、もし補強したらしたで、こんどは視界が狭まるという欠点もある)。
これはつまり、原理的には、各ディスプレイの端部には、中央部ほどには精度を高く要求するギミック教材は配置できない可能性があります。
ただし、ディスプレイが比較的に大きいことなどから、そんなに誤差は大きくないと思われます。なので、あまり気にしすぎる必要もないでしょう。
また、この誤差の問題はけっして3枚ディスプレイシミュレーターだけの問題ではなく、実は1枚ディスプレイシミュレーターでも同様に、前セクションなどで述べた数学的な定理から、両端部の精度は微妙に悪いことになります。
1枚シミュレーターと比べて3枚シミュレーターが向上している部分は、視界の広さです。残念ながら(映像オブジェクトの位置や前後関係などの)精度は向上できていないと思われます。
やはり、シミュレーターはけっして本物ではなく、あくまで模倣にすぎません。だからあまり映像を信用しすぎることなく、最終的には、教習所の屋外の運転コースでじっさいに運転練習をするしかありません。
なお、別ノセクションでも指摘してるかもしれませんが、実はCGでなく実写のビデオ映像だとしても、実は端部は誤差があります。レンズの仕組みやセンサーなどの仕組みにより、実写ですら、どうしても誤差が出てしまうのです。だから実写の撮影では、被写体をなるべく真ん中に近づける必要があるのです。
だから別に、けっして「CGだから誤差がある」とかいうわけではなく、そもそも機械による映像の再現には誤差があるのです。
よって、最終的には実際の運錬でなれるしかありません。
- 望遠テクニックが困難
一般の平面投影3Dでは、実はやや望遠ぎみの構図にすることで、両端部の誤差を少なくするテクニックがよく用いられます。
しかし運転シミュユレーターの場合、(なんとか可能だとしても)望遠は困難でしょう。
なぜなら、シミュレーターの用途である以上、実際の運転の視界をなるべく正確にシミュレートする必要があるのに、なのに実際の視界とは異なる望遠レンズのような構図にすることは許されないからです。
また3面デイスプレイの場合、仮に真ん中ディスプレイだけ望遠にできても、それを両脇のディスプレイ映像とどのように整合性を取るのか、難しいでしょう。
ともかく、3面運転シミュレーターでは、望遠テクニックは困難でしょう。
- 実写ビデオの誤差
なお、デイスプレイの両端部が誤差が大きいのは別に3D-CGに限ったことではなく、実は実写の映像も端部は誤差が比較的に大きいのです。
だからテレビ撮影などの映像は、大きめのビデオカメラで撮影しており、誤差の大きい上下左右の端部をあらかじめ捨てた映像が放映されていると言われています。テレビ編集用の機材が、すでにそうなっているらしいです。
だから教習所シミュレーターのディスプレイが大きいのも、そういった理由があるのでしょう。ディスプレイがなるべく大きいほうが(実写でいうビデオカメラのレンズが大きいこと相当するので、)誤差が減らせるはずです。
- ^ 『cpprefjp - C++日本語リファレンス』最終更新日時(UTC): 2018年01月22日 16時20分38秒 2022年1月21日に確認.