「ゲームプログラミング/3Dグラフィック」の版間の差分

削除された内容 追加された内容
編集の要約なし
円柱面投影を後回しに。普及してない方式のため。
261 行
ただし、実際には、奥にある被写体は手前の被写体で隠れるので、zソート法などで隠面処理を考えた描画をする必要はある。
 
 
=== 円柱面投影の透視投影 ===
ここでいう円柱とは、正円の円柱とする(正円柱)。
 
楕円柱は考えないでおく。
 
 
==== 視界をしめる角度の割合の算出 ====
実際の被写体の図形は3次元だが、説明の簡単化のため、二次元平面中での線分をもとに説明する。
 
[[File:画角の角度関係図.png|thumb|400px|カメラなどの映像上の角について、被写体の位置と画面上の角度との関係図。<br>CGの原理では、図中の角度gをもとめればいい。]]
 
円柱投影の場合での3D-CGの原理は余弦定理である。『[[高等学校数学I/図形と計量#余弦定理]]』
 
被写体の2点(仮にAおよびBとする)から、それぞれの点の位置をあらわす座標(直交座標)をもとに、カメラの位置(仮にK)から被写体の各点のベクトル(KA:<math>
\vec{KA}
</math>および<math>
\vec{KB}
</math>)を得ればいい。
 
そして、<math>
\vec{KA}
</math>および<math>
\vec{KB}
</math>のなす角度θが求まる。
 
あらかじめ、視野の角度θ<sub>0</sub>を、決めておく。
 
あとは、θ<sub>0</sub>とθとの比率で、視界でどれだけの長さに投影されるかの比率をもつかが、求まる。
 
また、基準となる点や線を用意しとけば、それとの角度差から、被写体の表示される位置も求まる。
 
==== カメラ回転時の被写体の見える大きさの数学的事実 ====
上記の計算で、仮にカメラを位置はそのままで、向きだけ変更させてみたとしましょう。つまり、カメラの向きだけ、右回りまたは左回りに回転した場合を考えます。
 
すると、じつは、ある被写体の全身が視界の内部にあるかぎり、カメラの水平方向の回転量にかかわらず、じつは(余弦定理で単純計算した場合の)物体の見える大きさは、不変です。
 
なぜなら、上記の条件でカメラの向きが変わっても、被写体の視野角に占める大きさが不変だからです。
 
このため、たとえば、カメラの水平で右回りに回転させても、実は被写体の大きさはそのままで、単に被写体が左側に移動するだけです。(近くにある被写体ほど、大きく移動する。)
 
被写体の見える大きさは、視野角に占める割合で決まるのですから、視界の中央にあろうが、視界のすみっこにあろうが、視野角に占める割合が変わらないので
 
 
しかし、みなさんは日常生活では、目の前にあるものは、なんとなく大きく見える感じがしていると体感していたでしょう。
 
同様に、日常生活では、視界のすみっこにある物体は、なんとなく小さく見える感覚があるでしょう。
 
 
つまり、皆さんの体感と、数学の幾何学的な事実とは、ちがいます。
 
 
この原因は、おおまかに、
:・人間の首は、たとえ胴体が直立姿勢でも、首の向きはやや斜め前方に傾いている状態になっているので、なので、もし顔の向きを変えると、目の水平位置も変わってしまうこと。
:・人間の肩の仕組みでは、たとえば左うでを左に伸ばした場合の左手は、左手を顔の前方の突き出した場合よりも、(左に伸ばした場合のほうが、手は)目から少しだけ遠ざかる。このため、左手にもった物体も、左手を左に伸ばすと小さく見える。これは実際に目からの距離が変化したのが原因であるが、日常生活では目からの距離の変化を意識しないので、あたかも「視界のすみっこにある物は小さく見える」という誤解を多くの人に起こしている原因のひとつであると考えられる。
:・人間の脳の処理では、自動的に、視界のすみっこにあるものには、意識がむきづらいようになっていると思われる。
 
以上のことなどが、起因しているのでしょう。
 
 
さて、3Dグラフィック的には、この事が分かると、いろいろと便利です。
 
 
まず、被写体に奥行きの無い場合、つまり、被写体が平面物体の場合は、そもそも、いちいち三角関数をつかって計算をする3D計算の必要はありません。
 
どういうときに3D計算が必要かというと、被写体に奥行きがあり、その被写体をさまざまな角度から見たい場合に必要になるのです。
 
なので、もし被写体を一定の方向からしか見ないならば(たとえば、つねに南側にいる観測者が北側を見る視点ならば)、単なる、三角関数を使わない表現でも、充分にリアリティのある表現が可能なことが、数学的にも証明されることになります。
 
このような数学的な事実がありますので、3Dグラフィックのプログラムを自作する前に、はたして自分に三角関数による3D計算が必要かどうかを考えておきましょう。
 
 
なお、人間の首のかたむきや動き方の話の暗黙の前提として、人間が横を見たり振り返ったりする場合は、まず、肩や胴体はほとんど動かずに首(と頭)だけが動いて、そのあとに引きづられる形で、肩が動きます。これは、アニメーターの描く、人物の振り返りの作画の基本でもあります。
 
 
* 数学的な背景
さて、余弦定理でつくる角度が、回転しても変換というのは、中学~大学の数学で考えてみれば当然です。
 
中学生レベルで考えれば、まず、三角形を回転して別の場所に位置を移動しても、移動先の三角形はもとの三角形と合同なままです。
 
大学レベルで回転行列を使って考えれば、これは「回転行列は、2個のベクトルのつくる内積の値を不変に保つ」という定理へと一般化されます。
 
さらには、『直交行列』といわれる種類の行列は、2個のベクトルのつくる内積を不変に保つという定理が、すでに解明されています。
 
 
* 大切なこと
3D-CG製作で大切なことは、日常的な感覚と、上述の数学的な計算や定理との食い違いを、認識することでしょう。
 
もし、ある若者が、数学を勉強していて直交行列の内積不変の定理だけを知っていても、それをプログラミングの場で本書のような予備知識なしで初見で「カメラの位置をそのままで視点の向きを変えても、被写体の見える大きさは変わらない」という事実に気づける人は、そう多くないでしょう。
 
 
3D-CG そのもののビューワーなどのアプリケーション製作に必要な数学力とは、こういう「気づき」のできる能力のことです。けっして単に、直交行列の公式だけを知っていても、それだけでは役立たずになってしまいます。
 
* 余弦定理で上手くいく理由
もし正円柱に投影するのではなく、楕円柱に投影する場合を考えると、算出すべきは角度ではなく楕円弧の弧長である。
 
正円の場合、角度のラジアンから弧の長さを三角関数によって簡単に算出できるので、角度さえ算出できれば、あとは高校レベルの簡単な計算で処理できるというワケである。
 
高校3年~大学1年の微分積分で習う「楕円積分」という積分の公式で、コンピュータ数値計算の手法で楕円の弧の長さを求めることができる。
 
なおC言語では国際規格上ではC++などの規格で楕円積分を cmath ヘッダでサポートしているが、だが実際のC言語界隈では情報が乏しく、教科書が乏しいので、あまり使わないほうがイイだろう。なおC++の規格では comp_ellint_1 などで楕円積分を定義している。その他の数学の特殊関数もC++では規格上は定義されている。)
 
なお、数学では、三角関数の楕円バージョンである楕円関数というのがある。楕円関数と、楕円積分の公式は、異なる。
 
 
 
==== 円柱投影の場合の視界の特徴 ====
円柱投影であっても、カメラから見て真横に近い方角にある被写体などは、ケタ落ちのため、不正確な表示になりますので、対策が必要です。
 
対策として、視界の中央から、±90°より大幅に未満(たとえば ±60°までなど)の一定範囲の角度内にある被写体だけを描画する必要があります。
 
このため角度計算が必要ですので、被写体が水平方向から見て、その方角にあるかの角度を保存しておく必要があり、その角度が視界内に相当する一定の範囲内にあるときだけ描画するようにプログラムを記述する必要があります。
 
どのみち、カメラの裏側にある物体の表示を隠したりするためにも、カメラから見た方角の角度の保存が必要になります。
 
 
なお、カメラの裏側にある物体が映るのを隠すなら、反対方向のベクトルどうしの内積がマイナスになることを利用すると、計算が簡略になります。
 
つまり、カメラの向きの単位ベクトルと、観察者から被写体への向きの単位ベクトルとの内積です。この内積が、カメラの裏側ではマイナスになります。
 
 
90°の直交する2つのベクトルどうしの内積は0ですし、90°に近い角度で交わるベクトルどうしの内積はゼロに近い数字です。
 
なので、たとえば「内積が 0.1以下の被写体は、描写を除外する」などのアルゴリズムを組めば、カメラ横にある被写体 と カメラ裏側の被写体 の描画を、同時に排除できます。
 
ただし、内積を計算できるようにする前提として、0ベクトルを排除する必要があります。なので、カメラから一定以上の距離を判定基準として、その距離よりも離れた位置にある被写体だけに、このアルゴリズムを適用する必要があります。この判定基準の距離には、投影面の円柱の半径に近い数字を判定基準にすればよいでしょう。
 
 
 
さて、この 横方向ケタ落ち 等の問題のために、円柱投影であっても、至近距離にある被写体などの描画では、平行投影に切り替えるなどの対策が必要になります。
 
カメラの横方向にある物体も、平行投影に切り替えるなどの対策が必要になります。
 
なぜなら、どんな被写体でも、カメラとの距離が0近くになれば、視界の±90°近くに入り込むようになってしまうからです。
 
 
ケタ落ちの問題のため、どんな形状の投影面であっても、透視投影を使うかぎり、スクリーンとカメラの距離は、けっして0にはできません。
 
 
このため、距離の遠近の判定をする必要があるので、判定基準としてカメラと投影面との距離の計算(これは投影面の円柱面の半径となる)が必要です。
 
結局、スクリーンとカメラとの間の距離は、0にはできず、大きさを持った値になります。
 
なので平面投影でも円柱投影でも、スクリーンとカメラの距離は、けっして0にはできません。
 
 
さて、円柱投影では、水平方向では投影面は円ですが、垂直方向では投影面は直線です。このため、垂直方向の倍率は、平面投影と同じ計算式になります。
 
もし、垂直方向の倍率も、水平方向の円投影の場合の倍率と同一にしてしまったら、それはもはや円柱投影ではなく球面投影ですので、地図の投影法(中学高校の社会科の地理で習うアレ)と同様の問題点に遭遇することになります。
 
=== 中間まとめ ===
まとめると、平面投影にしろ円柱投影にしろ、
 
ゲーム映像の場合、原則を透視投影にしても結局、スクリーンよりも手前に来た被写体は、(投資投影でなく)平行投影など別の投影アルゴリズムで描写することになります。
 
また、スクリーンの奥側でも、真横の方向に近い位置にある被写体は、(アルゴリズムにもよりますが)透視投影ではケタ落ち等が起こりやすいので、平行投影などに切り替える必要があります。
 
 
このため、角度または内積を基準として、透視投影の描画の条件を満たした角度位置または内積となる被写体の場合にだけ、被写体を透視投影として描画することになるでしょう。
 
 
1,674 ⟶ 1,515行目:
 
</syntaxhighlight>
 
 
 
 
 
=== 円柱面投影の透視投影 ===
ここでいう円柱とは、正円の円柱とする(正円柱)。
 
楕円柱は考えないでおく。
 
 
==== 視界をしめる角度の割合の算出 ====
実際の被写体の図形は3次元だが、説明の簡単化のため、二次元平面中での線分をもとに説明する。
 
[[File:画角の角度関係図.png|thumb|400px|カメラなどの映像上の角について、被写体の位置と画面上の角度との関係図。<br>CGの原理では、図中の角度gをもとめればいい。]]
 
円柱投影の場合での3D-CGの原理は余弦定理である。『[[高等学校数学I/図形と計量#余弦定理]]』
 
被写体の2点(仮にAおよびBとする)から、それぞれの点の位置をあらわす座標(直交座標)をもとに、カメラの位置(仮にK)から被写体の各点のベクトル(KA:<math>
\vec{KA}
</math>および<math>
\vec{KB}
</math>)を得ればいい。
 
そして、<math>
\vec{KA}
</math>および<math>
\vec{KB}
</math>のなす角度θが求まる。
 
あらかじめ、視野の角度θ<sub>0</sub>を、決めておく。
 
あとは、θ<sub>0</sub>とθとの比率で、視界でどれだけの長さに投影されるかの比率をもつかが、求まる。
 
また、基準となる点や線を用意しとけば、それとの角度差から、被写体の表示される位置も求まる。
 
==== カメラ回転時の被写体の見える大きさの数学的事実 ====
上記の計算で、仮にカメラを位置はそのままで、向きだけ変更させてみたとしましょう。つまり、カメラの向きだけ、右回りまたは左回りに回転した場合を考えます。
 
すると、じつは、ある被写体の全身が視界の内部にあるかぎり、カメラの水平方向の回転量にかかわらず、じつは(余弦定理で単純計算した場合の)物体の見える大きさは、不変です。
 
なぜなら、上記の条件でカメラの向きが変わっても、被写体の視野角に占める大きさが不変だからです。
 
このため、たとえば、カメラの水平で右回りに回転させても、実は被写体の大きさはそのままで、単に被写体が左側に移動するだけです。(近くにある被写体ほど、大きく移動する。)
 
被写体の見える大きさは、視野角に占める割合で決まるのですから、視界の中央にあろうが、視界のすみっこにあろうが、視野角に占める割合が変わらないので
 
 
しかし、みなさんは日常生活では、目の前にあるものは、なんとなく大きく見える感じがしていると体感していたでしょう。
 
同様に、日常生活では、視界のすみっこにある物体は、なんとなく小さく見える感覚があるでしょう。
 
 
つまり、皆さんの体感と、数学の幾何学的な事実とは、ちがいます。
 
 
この原因は、おおまかに、
:・人間の首は、たとえ胴体が直立姿勢でも、首の向きはやや斜め前方に傾いている状態になっているので、なので、もし顔の向きを変えると、目の水平位置も変わってしまうこと。
:・人間の肩の仕組みでは、たとえば左うでを左に伸ばした場合の左手は、左手を顔の前方の突き出した場合よりも、(左に伸ばした場合のほうが、手は)目から少しだけ遠ざかる。このため、左手にもった物体も、左手を左に伸ばすと小さく見える。これは実際に目からの距離が変化したのが原因であるが、日常生活では目からの距離の変化を意識しないので、あたかも「視界のすみっこにある物は小さく見える」という誤解を多くの人に起こしている原因のひとつであると考えられる。
:・人間の脳の処理では、自動的に、視界のすみっこにあるものには、意識がむきづらいようになっていると思われる。
 
以上のことなどが、起因しているのでしょう。
 
 
さて、3Dグラフィック的には、この事が分かると、いろいろと便利です。
 
 
まず、被写体に奥行きの無い場合、つまり、被写体が平面物体の場合は、そもそも、いちいち三角関数をつかって計算をする3D計算の必要はありません。
 
どういうときに3D計算が必要かというと、被写体に奥行きがあり、その被写体をさまざまな角度から見たい場合に必要になるのです。
 
なので、もし被写体を一定の方向からしか見ないならば(たとえば、つねに南側にいる観測者が北側を見る視点ならば)、単なる、三角関数を使わない表現でも、充分にリアリティのある表現が可能なことが、数学的にも証明されることになります。
 
このような数学的な事実がありますので、3Dグラフィックのプログラムを自作する前に、はたして自分に三角関数による3D計算が必要かどうかを考えておきましょう。
 
 
なお、人間の首のかたむきや動き方の話の暗黙の前提として、人間が横を見たり振り返ったりする場合は、まず、肩や胴体はほとんど動かずに首(と頭)だけが動いて、そのあとに引きづられる形で、肩が動きます。これは、アニメーターの描く、人物の振り返りの作画の基本でもあります。
 
 
* 数学的な背景
さて、余弦定理でつくる角度が、回転しても変換というのは、中学~大学の数学で考えてみれば当然です。
 
中学生レベルで考えれば、まず、三角形を回転して別の場所に位置を移動しても、移動先の三角形はもとの三角形と合同なままです。
 
大学レベルで回転行列を使って考えれば、これは「回転行列は、2個のベクトルのつくる内積の値を不変に保つ」という定理へと一般化されます。
 
さらには、『直交行列』といわれる種類の行列は、2個のベクトルのつくる内積を不変に保つという定理が、すでに解明されています。
 
 
* 大切なこと
3D-CG製作で大切なことは、日常的な感覚と、上述の数学的な計算や定理との食い違いを、認識することでしょう。
 
もし、ある若者が、数学を勉強していて直交行列の内積不変の定理だけを知っていても、それをプログラミングの場で本書のような予備知識なしで初見で「カメラの位置をそのままで視点の向きを変えても、被写体の見える大きさは変わらない」という事実に気づける人は、そう多くないでしょう。
 
 
3D-CG そのもののビューワーなどのアプリケーション製作に必要な数学力とは、こういう「気づき」のできる能力のことです。けっして単に、直交行列の公式だけを知っていても、それだけでは役立たずになってしまいます。
 
* 余弦定理で上手くいく理由
もし正円柱に投影するのではなく、楕円柱に投影する場合を考えると、算出すべきは角度ではなく楕円弧の弧長である。
 
正円の場合、角度のラジアンから弧の長さを三角関数によって簡単に算出できるので、角度さえ算出できれば、あとは高校レベルの簡単な計算で処理できるというワケである。
 
高校3年~大学1年の微分積分で習う「楕円積分」という積分の公式で、コンピュータ数値計算の手法で楕円の弧の長さを求めることができる。
 
なおC言語では国際規格上ではC++などの規格で楕円積分を cmath ヘッダでサポートしているが、だが実際のC言語界隈では情報が乏しく、教科書が乏しいので、あまり使わないほうがイイだろう。なおC++の規格では comp_ellint_1 などで楕円積分を定義している。その他の数学の特殊関数もC++では規格上は定義されている。)
 
なお、数学では、三角関数の楕円バージョンである楕円関数というのがある。楕円関数と、楕円積分の公式は、異なる。
 
 
 
==== 円柱投影の場合の視界の特徴 ====
円柱投影であっても、カメラから見て真横に近い方角にある被写体などは、ケタ落ちのため、不正確な表示になりますので、対策が必要です。
 
対策として、視界の中央から、±90°より大幅に未満(たとえば ±60°までなど)の一定範囲の角度内にある被写体だけを描画する必要があります。
 
このため角度計算が必要ですので、被写体が水平方向から見て、その方角にあるかの角度を保存しておく必要があり、その角度が視界内に相当する一定の範囲内にあるときだけ描画するようにプログラムを記述する必要があります。
 
どのみち、カメラの裏側にある物体の表示を隠したりするためにも、カメラから見た方角の角度の保存が必要になります。
 
 
なお、カメラの裏側にある物体が映るのを隠すなら、反対方向のベクトルどうしの内積がマイナスになることを利用すると、計算が簡略になります。
 
つまり、カメラの向きの単位ベクトルと、観察者から被写体への向きの単位ベクトルとの内積です。この内積が、カメラの裏側ではマイナスになります。
 
 
90°の直交する2つのベクトルどうしの内積は0ですし、90°に近い角度で交わるベクトルどうしの内積はゼロに近い数字です。
 
なので、たとえば「内積が 0.1以下の被写体は、描写を除外する」などのアルゴリズムを組めば、カメラ横にある被写体 と カメラ裏側の被写体 の描画を、同時に排除できます。
 
ただし、内積を計算できるようにする前提として、0ベクトルを排除する必要があります。なので、カメラから一定以上の距離を判定基準として、その距離よりも離れた位置にある被写体だけに、このアルゴリズムを適用する必要があります。この判定基準の距離には、投影面の円柱の半径に近い数字を判定基準にすればよいでしょう。
 
 
 
さて、この 横方向ケタ落ち 等の問題のために、円柱投影であっても、至近距離にある被写体などの描画では、平行投影に切り替えるなどの対策が必要になります。
 
カメラの横方向にある物体も、平行投影に切り替えるなどの対策が必要になります。
 
なぜなら、どんな被写体でも、カメラとの距離が0近くになれば、視界の±90°近くに入り込むようになってしまうからです。
 
 
ケタ落ちの問題のため、どんな形状の投影面であっても、透視投影を使うかぎり、スクリーンとカメラの距離は、けっして0にはできません。
 
 
このため、距離の遠近の判定をする必要があるので、判定基準としてカメラと投影面との距離の計算(これは投影面の円柱面の半径となる)が必要です。
 
結局、スクリーンとカメラとの間の距離は、0にはできず、大きさを持った値になります。
 
なので平面投影でも円柱投影でも、スクリーンとカメラの距離は、けっして0にはできません。
 
 
さて、円柱投影では、水平方向では投影面は円ですが、垂直方向では投影面は直線です。このため、垂直方向の倍率は、平面投影と同じ計算式になります。
 
もし、垂直方向の倍率も、水平方向の円投影の場合の倍率と同一にしてしまったら、それはもはや円柱投影ではなく球面投影ですので、地図の投影法(中学高校の社会科の地理で習うアレ)と同様の問題点に遭遇することになります。
 
=== 中間まとめ ===
まとめると、平面投影にしろ円柱投影にしろ、
 
ゲーム映像の場合、原則を透視投影にしても結局、スクリーンよりも手前に来た被写体は、(投資投影でなく)平行投影など別の投影アルゴリズムで描写することになります。
 
また、スクリーンの奥側でも、真横の方向に近い位置にある被写体は、(アルゴリズムにもよりますが)透視投影ではケタ落ち等が起こりやすいので、平行投影などに切り替える必要があります。
 
 
このため、角度または内積を基準として、透視投影の描画の条件を満たした角度位置または内積となる被写体の場合にだけ、被写体を透視投影として描画することになるでしょう。
 
=== 隠面処理 ===