5. 3次元の変換と投影

第4章では、平らな2次元の世界で図形を動かし、回し、伸ばしました。
此の章では、第2章の仮想カメラが実際に住んでいる3次元に進み、其の後、全てのCG画像が最後に答えなければならない問いに答えます:3次元のシーンはどうやって平らな絵になるのか?
此の2つ目の段階を投影と呼びます。

此の章は出題範囲の分野2(座標変換とパイプライン)の3次元変換と投影の2項目を扱います。
行列自体は線型代数シリーズの第8章〜第13章で導いたので、全てをもう一度導く事はしません。
其の代わりに、各道具を手短に復習して作った場所へのリンクを貼り、残りの時間をCGの視点と検定の視点に使います:規約、分類、各変換や投影が何を保ち何を壊すのか、そして沢山の数値例です。

此の章の流れは次の通りです:

  • 3次元の座標系:右手系と左手系、そして頂点が通る空間。
  • 4×4行列で表す基本的な3次元変換、任意軸周りの回転、オイラー角。
  • 何故法線ベクトルには別の行列が必要なのか。
  • 視点・注視点・上方向から作るビュー変換。
  • 投影の系統図:平行投影(正投影、軸測投影、斜投影)と透視投影(1点・2点・3点透視)。
  • 式で見る透視投影:相似な三角形、4×4の投影行列、透視除算、視錐台。

第6章では、此れら全てをクリッピングとビューポートを含むビューイングパイプライン全体に繋げます。

5.1 3次元の座標系

右手系と左手系

2次元では賢い選択肢は1つだけでした:xは右、yは上です。
3次元では、xとyを決めた後でも、zは手前に向く事も奥に向く事も出来、其の為2種類の座標系が生まれます:

+zが手前を向く右手系の軸と、+zが画面の奥を向く左手系の軸

  • 右手系:右手の指を+x方向に伸ばし、+yの方へ曲げると、親指が+zを指します。xが右、yが上なら、+zは画面から見る人の方へ出てきます。此のシリーズ、OpenGL流の数学、Blender及びMayaが此方を使います。
  • 左手系:同じ事を左手でやるので、+zは画面の奥へ向かいます。Unity、Unreal Engine(z軸が上)及び伝統的なDirect3Dの規約が此方を使います。

どちらも「正しい」訳ではなく、只の規約です。
変換の仕方は第1章で既に見ました:𝐒=diag(1,1,−1)でzを反転し、変換𝐌は𝐒𝐌𝐒になります。
此処では、数字で見ておく価値の有る結果を1つ紹介します。
y軸周りの回転を取って、𝐒𝐑y(θ)𝐒を計算してみましょう。
左から𝐒を掛けると3行目の符号が反転し、右から掛けると3列目の符号が反転するので、角(3行3列)の要素は2回反転して元に戻ります:

𝐒[cosθ0sinθ010−sinθ0cosθ]𝐒=[cosθ0−sinθ010sinθ0cosθ]=𝐑y(−θ)

xでも同じ事が起こります:𝐒𝐑x(θ)𝐒=𝐑x(−θ)。
しかし𝐒𝐑z(θ)𝐒=𝐑z(θ)です。𝐑zの3行目と3列目には1以外何も無いからです。
従って、右手系のツールから左手系のエンジンへシーンを移す時は、zの位置の符号が変わり、x軸とy軸(鏡映面の中に有る軸)周りの回転角の符号が変わり、z軸周りの角度は変わりません。
行列𝐑x,𝐑y,𝐑zの書き方はどちらの座標系でも同じで、変わるのは「正の向き」の見え方です。
右手系では正の回転は右手の法則(親指を軸に沿わせ、指が曲がる向きに回る)に従い、左手系では左手の法則に従います。
軸の正の端から原点の方を見ると、右手系では反時計回り、左手系では時計回りになります。

ローカル座標・ワールド座標・カメラ座標

2次元の時(第4章)と同じく、3次元のシーンも幾つもの座標系を同時に使います:

  • ローカル座標系(モデリング座標系、物体座標系とも):モデルを作った時の座標。普通は原点がモデルの中心か足元に有ります。
  • ワールド座標系:全ての物体、光源、カメラを配置する、共通の1つの空間。
  • カメラ座標系(視点座標系、ビュー空間とも):カメラから見た世界。カメラが原点に有り、−z方向を見ます。

モデリング変換はローカルからワールドへ、ビュー変換(視野変換とも)はワールドからカメラへ座標を移します。
其の後に此の章の最後で扱う投影が来て、更にクリッピングと画面への変換は第6章です。
線型代数シリーズの第11章では、1つの頂点が此の旅をする様子を追い掛けました。

5.2 基本的な3次元変換

4×4の同次座標行列

3次元では4つの数による同次座標を使います:点は(x,y,z,1)、方向は(x,y,z,0)です。
余分な座標のお陰で平行移動も行列に出来ます。第4章の3×3行列が2次元でやった事と全く同じです(線型代数 第9章)。
5つの基本変換は次の通りです:

𝐓(tx,ty,tz)=[100tx010ty001tz0001],𝐒(sx,sy,sz)=[sx0000sy0000sz00001]
𝐑x(θ)=[10000cosθ−sinθ00sinθcosθ00001],𝐑y(θ)=[cosθ0sinθ00100−sinθ0cosθ00001],𝐑z(θ)=[cosθ−sinθ00sinθcosθ0000100001]

座標軸周りの回転は、どれも第4章の2次元の回転を残りの2つの座標に当てはめた物で、軸自身の座標は其のままです。
注意が必要なのは𝐑yです:マイナス記号が右上ではなく左下に有ります。
理由はx → y → z → xの循環です:z軸周りではxがyの方へ、x軸周りではyがzの方へ、そしてy軸周りではzがxの方へ回ります。
線型代数 第8章で全ての符号を右手の法則で確かめたので、此処では頭の中で出来る簡単なテストを1つだけ紹介します:𝐑y(90°)は+z(手前)を+x(右)に移さなければなりません。上から見て反時計回りに4分の1回すと、物体の正面が右側面に来るからです。
𝐑y(90°)の3列目は(sin90°,0,cos90°)=(1,0,0)です。合っていますね。

3次元にも、2次元と同じ考え方の基本的な線型変換が後2つ有ります:

  • 鏡映は座標の1つの符号を反転します。例えばdiag(1,1,−1)はxy平面に関して映します。行列式が−1なので、物体を「裏返し」にし、三角形の頂点の順番を逆にします。
  • せん断は、ある座標の定数倍を別の座標に足します。例えばx′=x+ky。3次元では此の組み合わせが6通り有ります。

此れらは全てアフィン変換なので、第4章でアフィン変換について言った事は其のまま成り立ちます:直線は直線のまま、平行線は平行のまま、直線上の比は保たれます。
回転と平行移動を合わせた物が剛体変換で、長さと角度を保ちます。

順番が大事:数値例

行列の積は、2次元と同じく3次元でも交換出来ません。
点𝐩=(1,0,2)と、2つの変換𝐑x(90°)及び𝐓(0,1,0)を考えます。
cos90°=0、sin90°=1なので、𝐑x(90°)は(x,y,z)を(x,−z,y)に移します。

先に回転、次に平行移動(𝐌=𝐓𝐑x、右から左へ読む):

𝐑x(1,0,2)=(1,−2,0),𝐓(1,−2,0)=(1,−1,0)

先に平行移動、次に回転(𝐌=𝐑x𝐓):

𝐓(1,0,2)=(1,1,2),𝐑x(1,1,2)=(1,−2,1)

結果が違います。
2つ目の順番では、回転は物体ではなくワールドの原点を中心に回るので、平行移動した分まで一緒に振り回してしまいます。
其の為、普通のモデル行列は𝐌=𝐓𝐑𝐒です:物体がまだ原点に居る間に先ず拡大縮小、次に回転し、最後に所定の位置へ動かします。

逆変換

逆変換は2次元(4.3節)と全く同じです:𝐓(−tx,−ty,−tz)、𝐒(1/sx,1/sy,1/sz)、𝐑x(−θ)=𝐑x(θ)⊤(y、zも同様)、鏡映は自分自身が逆で、積は逆の順番で取り消します:(𝐀𝐁)−1=𝐁−1𝐀−1。
回転の逆行列が転置になるのは、列が互いに垂直な単位ベクトルだからです(線型代数 第10章)。
此の章では此れを2回使います:法線とビュー変換です。

5.3 任意軸周りの回転

ある点を通る軸:ドアの蝶番

ドアはワールドのy軸ではなく、蝶番の周りに回ります。
コツは第4章のある点を中心とする回転と同じです:軸を原点へ動かし、回し、元に戻します。
点𝐜を通りy軸に平行な軸なら:

𝐌=𝐓(𝐜)𝐑y(θ)𝐓(−𝐜)

ドアを90°開けてみましょう。
蝶番は𝐜=(2,0,1)を通り、ドアの端は𝐩=(3,0,1)に有ります。

ドアの例を上から見た図:端pが3, 0, 1から蝶番c 2, 0, 1の周りに90度回り、2, 0, 0に来る

一歩ずつ計算します:

  1. 𝐓(−𝐜):(3,0,1)−(2,0,1)=(1,0,0)。蝶番が原点に来ました。
  2. 𝐑y(90°)は(x,y,z)を(z,y,−x)に移すので、(1,0,0)→(0,0,−1)。
  3. 𝐓(𝐜):(0,0,−1)+(2,0,1)=(2,0,0)。

従って𝐩′=(2,0,0)です:ドアの端は蝶番の右側から奥側へ振れました。上から見て反時計回りの4分の1回転です。
3つの行列を掛け合わせると、ドア全体の為の1つの行列になります:

𝐌=[00110100−10030001]

蝶番自身で確かめましょう:𝐌(2,0,1,1)=(0+1+1,0,−2+0+3,1)=(2,0,1,1)。
軸上の点は、当然ながら動きません。

任意の向きの軸:合成による方法

軸の向きが斜めの時は、基本的な回転を組み合わせて作る古典的な方法が有ります。
軸が𝐜を通り、単位方向ベクトルが𝐤=(kx,ky,kz)だとして、d=ky2+kz2と置きます:

  1. 𝐓(−𝐜)で軸を原点へ動かす。
  2. cosα=kz/d、sinα=ky/dの𝐑x(α)で𝐤をxz平面に倒す:(kx,0,d)になる。
  3. cosβ=d、sinβ=−kxの𝐑y(β)でz軸に重ねる:(0,0,1)になる。
  4. 𝐑z(θ)で本来の回転をする。
  5. 手順3、2、1を逆の順番で元に戻す。

𝐌=𝐓(𝐜)𝐑x(−α)𝐑y(−β)𝐑z(θ)𝐑y(β)𝐑x(α)𝐓(−𝐜)

(d=0なら、軸は既にx軸に沿っているので、直接𝐑xを使えます。)
長いですが、どの部品も既に知っている物ばかりです。「問題を簡単な場所へ移し、解き、元に戻す」という型はCGの至る所に出てきます。

任意の向きの軸:ロドリゲスの回転公式

直接的な方法はロドリゲスの回転公式で、線型代数 第13章で導きました。
原点を通る単位軸𝐤と角度θに対して:

𝐯′=𝐯cosθ+(𝐤×𝐯)sinθ+𝐤(𝐤⋅𝐯)(1−cosθ)

言葉で言うと:𝐯の軸方向の成分は動かず、軸に垂直な成分は其の平面の中で2次元の回転の様に回り、𝐤×𝐯が2本目の軸の役をします。

𝐯=(1,2,0)を𝐤=(0,0.6,0.8)の周りに90°回してみましょう。
先ず𝐤が単位ベクトルか確かめます:0.36+0.64=1。大丈夫です。
cos90°=0、sin90°=1なので、1つ目の項は消え、1−cosθ=1です。

𝐤⋅𝐯=0⋅1+0.6⋅2+0.8⋅0=1.2
𝐤×𝐯=(0.6⋅0−0.8⋅2,0.8⋅1−0⋅0,0⋅2−0.6⋅1)=(−1.6,0.8,−0.6)
𝐤(𝐤⋅𝐯)(1−cosθ)=1.2⋅(0,0.6,0.8)=(0,0.72,0.96)
𝐯′=(0,0,0)+(−1.6,0.8,−0.6)+(0,0.72,0.96)=(−1.6,1.52,0.36)

回転なら必ず通るべき確認が2つ有ります:

  • 長さが保たれる:‖𝐯‖2=1+4=5、‖𝐯′‖2=2.56+2.3104+0.1296=5。
  • 軸方向の成分が保たれる:𝐤⋅𝐯′=0.6⋅1.52+0.8⋅0.36=0.912+0.288=1.2で、前と同じ。

行列で書くと𝐑=cosθ𝐈+sinθ𝐊+(1−cosθ)𝐤𝐤⊤で、𝐊は𝐤の外積行列です。
今回の軸と角度では𝐊+𝐤𝐤⊤になります:

𝐑=[0−0.80.60.800−0.600]+[00000.360.4800.480.64]=[0−0.80.60.80.360.48−0.60.480.64]

そして𝐑(1,2,0)=(0−1.6+0,0.8+0.72+0,−0.6+0.96+0)=(−1.6,1.52,0.36)で、同じ答えです。
原点を通らない軸なら、ドアと同じ様に𝐓(𝐜)⋯𝐓(−𝐜)で挟みます。

オイラー角とジンバルロック

姿勢を表す一番人間らしい方法は、軸ごとに1つ、3つの角度を使う事です:オイラー角です。
カメラや乗り物では、ヨー(上方向の軸周り)、ピッチ(横方向の軸周り)、ロール(前方向の軸周り)と呼ばれます。
3Dツールの3つの回転の入力欄の様に、入力としては優秀ですが、問題が3つ有ります:

  • 順番が大事。 𝐑y(a)𝐑x(b)𝐑z(c)と𝐑z(c)𝐑x(b)𝐑y(a)は違う姿勢で、ソフトによって選ぶ順番が違うので、順番も分からないと3つの数字には意味が有りません。
  • 固定軸か、動く軸か。 同じ積を2通りに読めます。右から左へ読むと、𝐑y(a)𝐑x(b)𝐑z(c)は固定されたワールドの軸z、x、yの順に回します。左から右へ読むと、物体自身の軸の周りに回します:先ず自分のy、次に新しいx、最後に新しいzです。どちらの読み方でも同じ行列です。
  • ジンバルロック。 真ん中の回転が90°になると、最初と最後の軸が重なって同じ直線の周りに回る様になります。自由度が1つ失われ、其の姿勢の近くでは、少し向きを変えるだけで角度が大きく跳ぶ様になります。

線型代数 第13章ではジンバルロックを数字で見せ、其の治療法であるクォータニオン(四元数)を紹介しました。クォータニオンはジンバルロック無しで姿勢を保存出来、slerpで2つの姿勢の間を滑らかに補間出来ます。
アニメーションの話として第13章で又取り上げます。

5.4 法線ベクトルの変換

法線ベクトルは面から真っ直ぐ外へ向くベクトルで、第10章以降の全ての章で照明計算は此れに依存します。
其の為、モデルを変換したら法線も変換しなければなりません。
すぐ思い付くのは同じ行列を掛ける事です(方向なのでw=0)。
回転と均等な拡大縮小なら其れで上手く行きますが、非均等な拡大縮小やせん断では、法線が面に垂直でなくなります。

3次元の数値例

三角形A=(1,0,0)、B=(0,1,0)、C=(0,0,1)を考えます。
此れは平面x+y+z=1上に有るので、法線は𝐧=(1,1,1)です(正規化していませんが、角度を調べるだけなら問題有りません)。
モデルを𝐒=diag(2,1,0.5)で拡大縮小します:

A′=(2,0,0),B′=(0,1,0),C′=(0,0,0.5)

新しい三角形の本当の法線は、2本の辺の外積です(線型代数 第5章):

B′−A′=(−2,1,0),C′−A′=(−2,0,0.5)
(B′−A′)×(C′−A′)=(1⋅0.5−0⋅0,0⋅(−2)−(−2)⋅0.5,(−2)⋅0−1⋅(−2))=(0.5,1,2)

素朴な法線は𝐒𝐧=(2,1,0.5)です。
辺に垂直でしょうか?

(2,1,0.5)⋅(−2,1,0)=−4+1+0=−3≠0

垂直ではありません。
では逆転置行列(𝐒−1)⊤を試します。
𝐒−1=diag(0.5,1,2)で、対角行列は自分自身の転置なので:

(𝐒−1)⊤𝐧=(0.5⋅1,1⋅1,2⋅1)=(0.5,1,2)

本当の法線とぴったり一致しました。
図は、両方の三角形を丁度1本の辺の方向から見た物です。此の向きだと三角形は線分に見え、正しい法線は其の線分と目に見える直角を作ります:

両方の三角形を辺の方向から見た図:逆転置行列による法線は拡大縮小後の三角形に垂直だが、S nは垂直ではない

三角形をx方向に伸ばすと面が寝るので、法線はxの方へ傾く量が減るべきで、2で割る事が丁度其れをしています。

規則

左上3×3部分が𝐀のモデル行列に対しては、法線を法線行列で変換します:

𝐍=(𝐀−1)⊤

𝐍は長さを保たないので、後で正規化します。
理由を1行で言うと:法線は面の全ての接線𝐭に対して𝐧⋅𝐭=0となるベクトルとして定義され、接線は𝐀で変換され、(𝐍𝐧)⋅(𝐀𝐭)=𝐧⊤𝐍⊤𝐀𝐭は𝐍⊤𝐀=𝐈なら全ての接線で0になるからです。
詳しい導出は線型代数 第10章に有ります。

省略出来る場合:

  • 回転だけ:(𝐑−1)⊤=𝐑なので、法線行列はモデル行列其の物です。
  • 回転と均等な拡大縮小s:法線行列は1s𝐑で、s𝐑と同じ向きなので、正規化すれば普通の行列で十分です。
  • 平行移動は法線に影響しません。方向はw=0だからです。

実際には、エンジンは物体ごとに1回CPUで法線行列を計算して頂点シェーダーに渡し、頂点シェーダーは位置と法線を並べて変換します:

struct Transformed {
  float4 clipPos;
  float3 worldNormal;
};

Transformed transformVertex(float4x4 model, float4x4 view, float4x4 proj,
                            float3x3 normalMatrix, float3 pos, float3 normal) {
  Transformed o;
  float4 world = mul(model, float4(pos, 1.0));
  o.clipPos = mul(proj, mul(view, world));
  o.worldNormal = normalize(mul(normalMatrix, normal));
  return o;
}
struct Transformed {
  vec4 clipPos;
  vec3 worldNormal;
};

Transformed transformVertex(mat4 model, mat4 view, mat4 proj,
                            mat3 normalMatrix, vec3 pos, vec3 normal) {
  Transformed o;
  vec4 world = model * vec4(pos, 1.0);
  o.clipPos = proj * view * world;
  o.worldNormal = normalize(normalMatrix * normal);
  return o;
}
struct Transformed {
  float4 clipPos;
  float3 worldNormal;
};

Transformed transformVertex(float4x4 model, float4x4 view, float4x4 proj,
                            float3x3 normalMatrix, float3 pos, float3 normal) {
  Transformed o;
  float4 world = mul(model, float4(pos, 1.0));
  o.clipPos = mul(proj, mul(view, world));
  o.worldNormal = normalize(mul(normalMatrix, normal));
  return o;
}

此れの完全な版は、OpenGLシリーズで自分で書く事になります。

5.5 ビュー変換

カメラではなく世界を動かす

ビュー変換はワールド座標をカメラ座標に移します。カメラ座標ではカメラが原点に有り、+yを上にして−z方向を見ます。
カメラが剛体変換𝐂(回してから視点の位置𝐞へ動かす)でワールドに置かれたなら、ビュー行列は其の逆行列です:

𝐕=𝐂−1=𝐑⊤𝐓(−𝐞)

4.3節の規則の3次元版です:座標系を𝐌で動かす事は、物体を𝐌−1で動かす事と同じです。
カメラを1m右に動かすと、世界全体を1m左に滑らせたのと全く同じに見えます。

視点・注視点・上方向から軸を作る

カメラの回転行列を手で打ち込む人は居ません。
其の代わりに3つの物を与えます:視点の位置𝐞、見る先の注視点𝐭、大まかな上方向𝐮𝐩(普通は(0,1,0))です。
此れらから外積でカメラの3本の軸を作ります(各段階の説明は線型代数 第11章):

𝐟=𝐭−𝐞‖𝐭−𝐞‖,𝐫=𝐟×𝐮𝐩‖𝐟×𝐮𝐩‖,𝐮=𝐫×𝐟

前𝐟は注視点を指し、右𝐫は前と上方向のヒントの両方に垂直で、本当の上𝐮は其の両方に垂直です。
𝐑⊤の行は𝐫、𝐮、−𝐟(カメラは−zを見るのでマイナス)なので:

𝐕=[rxryrz−𝐫⋅𝐞uxuyuz−𝐮⋅𝐞−fx−fy−fz𝐟⋅𝐞0001]

此れが有名なlookAt関数です。
カメラが丁度上方向のヒントに沿って見ると𝐟×𝐮𝐩が0になって作れないので、真下を見るカメラには別の上方向のヒントが必要です。

数値例:斜め下を見るカメラ

カメラは𝐞=(0,3,4)に有り、原点を見て、上方向のヒントは(0,1,0)です。
此のカメラは原点の上かつ手前に居て、原点を見下ろしています。

前。 𝐭−𝐞=(0,−3,−4)、長さは0+9+16=5なので、𝐟=(0,−0.6,−0.8)。

右。 𝐮𝐩=(0,1,0)なら外積は𝐟×𝐮𝐩=(−fz,0,fx)と簡単になります:

𝐟×𝐮𝐩=(0.8,0,0),𝐫=(1,0,0)

前に傾くだけのカメラなので、予想通りカメラの右はワールドの+xです。

本当の上。

𝐮=𝐫×𝐟=(0⋅(−0.8)−0⋅(−0.6),0⋅0−1⋅(−0.8),1⋅(−0.6)−0⋅0)=(0,0.8,−0.6)

カメラが下を向いているので、カメラの上は注視点の側、つまり前に傾いています(zが負)。頷くと頭のてっぺんが前を向くのと同じです。

平行移動の列。

−𝐫⋅𝐞=0,−𝐮⋅𝐞=−(0.8⋅3−0.6⋅4)=−(2.4−2.4)=0,𝐟⋅𝐞=−0.6⋅3−0.8⋅4=−5

組み立て。

𝐕=[100000.8−0.6000.60.8−50001]

確認。 注視点(原点)は距離5の真正面に来る筈です:𝐕(0,0,0,1)=(0,0,−5,1)。正解です。
視点は原点に来る筈です:𝐕(0,3,4,1)=(0,2.4−2.4,1.8+3.2−5,1)=(0,0,0,1)。正解です。
そして単位立方体の角(1,1,1)は:

𝐕(1,1,1,1)=(1,0.8−0.6,0.6+0.8−5,1)=(1,0.2,−3.6,1)

右に1、視界の中心より0.2上、カメラの3.6前です。

検定のポイント

  • 列ベクトルでは合成を右から左へ読みます:𝐓𝐑𝐒では拡大縮小が最初です。行ベクトルを使う本では、転置した行列を逆の順番で書きます(4.6節)。
  • 𝐑yのマイナス記号は左下です。右手系では正の回転は右手の法則に従います。
  • 点𝐜を通る軸の周りの回転:𝐓(𝐜)𝐑𝐓(−𝐜)。
  • 回転の逆行列は転置。積の逆行列は、逆行列を逆の順番に掛けた物。
  • オイラー角は順番に依存し、ジンバルロックが起こります。クォータニオンでは起こりません。
  • 法線は(𝐀−1)⊤で変換し、正規化します。回転と均等な拡大縮小だけなら、モデル行列其のままで構いません。
  • ビュー変換はカメラの配置の逆変換です。

5.6 投影の全体像

投影は3次元の点を2次元の投影面、つまり仮想のフィルムや画面に写します。
各点は投影線と呼ばれる直線に沿って投影面へ運ばれます。
投影線の並び方で投影の種類が決まります:

平行投影は平行な投影線を使い大きさを保つ。透視投影は全ての投影線が投影中心を通るので、遠い物体程小さく見える

  • 平行投影では、全ての投影線が1つの投影方向に平行です。図の2つの物体は同じ高さで、距離に関係無く像も同じ高さです。
  • 透視投影では、全ての投影線が1つの点、投影中心を通ります。此れは目、又は第2章のカメラのピンホールです。遠い物体程、像が小さくなります。

平行投影は、投影中心を無限に遠くへ動かした透視投影だと考える事も出来ます:其の時、投影線は平行になります。

どちらの系統も更に枝分かれします。
全体の系統図は次の通りで、検定対策として押さえておきたい図です:

投影の分類:平行投影は正投影と斜投影に、正投影は多面投影と軸測投影に、軸測投影は等角・二等角・不等角投影に、斜投影はカバリエ投影とキャビネット投影に、透視投影は1点・2点・3点透視に分かれる

次の2つの節で、此の木を左の枝から順に見ていきます。

5.7 平行投影

正投影と三面図

投影線が投影面に垂直な平行投影を正投影と呼びます。
一番簡単な物は、zを忘れてxy平面に投影します:

𝐏ortho=[1000010000000001],𝐏ortho(x,y,z,1)=(x,y,0,1)

行列式は0です:空間を平らに潰すので元に戻せません。1本の投影線上の全ての点が同じ場所に落ちるからです。
(GPUの投影行列は深度バッファの為に奥行きの値を残すので、逆行列が有ります。線型代数 第12章と5.9節を参照。)

投影面が物体の1つの面に平行なら、其の面は実形・実寸で描かれ、此れは設計者に必要な性質です。
其の為、製図では此の様な図を幾つも使う多面投影を使います:正面図、平面図、側面図で、合わせて三面図と呼びます。
次はL字型のブロックで、JISやアメリカの図面と同じ並べ方(第三角法:正面図の上に平面図、右に右側面図)にしてあります:

L字型ブロックの正面図・平面図・右側面図を揃えて並べ、同じブロックの等角図を添えた図

各図は、選んだ面を見る人の方へ向ける回転の後に𝐏orthoを掛けただけの物で、図同士は寸法を共有します:正面図の幅は平面図の幅と、正面図の高さは側面図の高さと等しくなります(破線)。
弱点は、どの1枚の図も物体を立体的に見せない事です。複数の図を合わせて読む必要が有ります。

軸測投影:等角・二等角・不等角

物体(又は投影面)を回して、物体の主な面がどれも投影面に平行でない様にすると、1枚の正投影で3つの面が同時に見えます。
此れが軸測投影です。
此の時、物体の各軸は本当の長さより短く見え、其の割合を短縮率と呼びます。
単位方向𝐝^=(dx,dy,dz)に沿った正投影では、x軸は長さの1−dx2倍に縮みます(軸の𝐝^方向の成分が失われるから)。y、zも同様です。
軸測投影は、3つの短縮率の内いくつが等しいかで名前が付いています:

  • 等角投影(アイソメトリック):3つ全て等しい。
  • 二等角投影(ダイメトリック):2つが等しい。
  • 不等角投影(トリメトリック):3つ全て異なる。

等角投影・二等角投影・不等角投影で描いた立方体と、其々の下に書いたx・y・z軸の短縮率

数値例:lookAtで作る等角投影。
等角投影とは、立方体の対角線方向𝐝^=13(1,1,1)に沿って見る事です。
従って各短縮率は1−13=23≈0.816です。
実際の図は5.5節のカメラで作れます:視点を(1,1,1)に置き、原点を見て、上方向のヒントを(0,1,0)にし、奥行きを捨てます。
すると𝐟=−13(1,1,1)、𝐟×𝐮𝐩=(−fz,0,fx)=13(1,0,−1)で、正規化してもう1回外積を取ると:

𝐫=12(1,0,−1)≈(0.7071,0,−0.7071),𝐮=16(−1,2,−1)≈(−0.4082,0.8165,−0.4082)

点𝐩は図の上の(𝐫⋅𝐩,𝐮⋅𝐩)に描かれます。
3本の単位軸の行き先は:

x→(0.7071,−0.4082),y→(0,0.8165),z→(−0.7071,−0.4082)

3本とも長さは0.5+0.1667=0.8165で、向きは−30°、90°、−150°なので、互いに120°離れています。
此れが見慣れた等角投影の見た目です:縦の線は縦のまま、他の2本の軸は左右に1本ずつ、水平から30°の向きに描かれます。
製図では普通0.816を省いて3本の軸を実長で描き、此れを等角図と呼びます。等角投影を1/0.816≈1.22倍に拡大しただけの物です。

便利な確認方法:dx2+dy2+dz2=1なので、3つの短縮率の2乗の和は常に3−1=2です。
等角投影では3⋅23=2、上の二等角投影の立方体では0.792+0.872+0.792≈2です。

実際の使われ方。
CADソフトには等角・二等角・不等角の視点がプリセットとして用意されていて、ストラテジーゲームやパズルゲームも此の見た目が大好きです。
「アイソメトリック」と呼ばれるドット絵のゲームの殆どは、実は二等角投影を使っています:床の線が横2ピクセルに対して縦1ピクセル上がり、角度は30°ではなくarctan0.5≈26.57°です。2対1の段差なら綺麗なドットの線になるからです。
此れは水平から30°見下ろす事に相当し、床の2軸の短縮率が約0.79、縦軸が約0.87になります。丁度上の二等角投影の立方体です。

斜投影:カバリエ投影とキャビネット投影

斜投影では、投影線は平行ですが投影面に垂直ではありません。
投影面に平行な正面は正面図と同じく実形を保ちますが、奥行きの軸は角度αの斜めの線として、倍率Lで描かれます。
奥行き方向は此のシリーズの座標では−zなので、正面より1単位奥の点は方向αにLだけずれます:

x′=x−Lzcosα,y′=y−Lzsinα

名前の付いた2種類はLだけが違います:

  • カバリエ投影:L=1。奥行きを実長で描くので、立方体が奥に長過ぎる様に見えます。投影線は投影面と45°で交わります。
  • キャビネット投影:L=12。奥行きを半分の長さで描くので、ずっと自然に見えます。投影線は投影面とarctan2≈63.4°で交わります。

立方体を正投影の正面図、カバリエ投影、キャビネット投影で描き、奥行きの辺を角度アルファで描いた図

角度αは普通45°か30°です。
行列で書くと、斜投影はせん断の後にzを捨てる事です:

𝐏oblique=[10−Lcosα001−Lsinα000000001]

数値例。 α=45°のキャビネット投影なので、Lcosα=Lsinα=0.5⋅0.7071=0.3536です。
正面より2単位奥の点(1,1,−2)は:

x′=1−0.3536⋅(−2)=1.7071,y′=1−0.3536⋅(−2)=1.7071

カバリエ投影(L=1)なら(2.4142,2.4142)で、(1,1)からのずれが2倍になります。
斜投影は方眼紙に手で描き易いので、教科書、家具のカタログ、多くの2Dゲームの背景で使われています。

全ての平行投影が保つ物

平行投影は全てアフィン変換(回転又はせん断の後にzを捨てる)なので、アフィン変換が保つ物を保ちます:

  • 直線は直線のまま、平行線は平行のまま。
  • 直線上の比が保たれる:辺の中点は、描いた辺の中点に描かれる。
  • 物体の大きさは距離に依存しない。
  • 投影面に平行な面は実形を保つ(正投影と斜投影)。

失うのは透視投影の奥行きの手掛かりです:絵から、物がどれだけ遠いのか分かりません。

5.8 透視投影

短縮と投影中心

透視投影は、目やピンホールカメラがする事です。
遠い物程小さく描かれ、奥へ向かう線は実際より短く見えます。此の効果を短縮(フォアショートニング)と呼びます。
1枚の絵の中では一番強い奥行きの手掛かりで、其の為殆ど全ての3Dゲームと映画が透視投影を使っています。

消失点

透視投影の一番目立つ特徴は、平行線が交わる事が有る事です。
線路はずっと平行に続きますが、写真では地平線上の1点に集まります。
其の点が消失点です。

何故そうなるのか見てみましょう。
投影中心を原点に置き、−z方向を見て、投影面を距離dに置きます(投影がx′=dx/(−z)になる理由は5.9節で説明します)。
点𝐩0を通り方向𝐯の直線は𝐩(t)=𝐩0+t𝐯です。
此れを投影し、tを限り無く大きくします:

x′=dp0x+tvx−(p0z+tvz)→dvx−vz(t→∞),y′→dvy−vz

始点𝐩0が消えました!
同じ方向𝐯の直線は全て同じ像の点に向かい、其の点は方向だけで決まります。
そしてvz=0なら、つまり直線が投影面に平行なら、消失点は有りません:其の直線は絵の中でも平行のままです。
纏めると:

  • 投影面に平行でない方向には、其々1つの消失点が有る。
  • 投影面に平行な方向には消失点が無く、其の直線は平行のまま。
  • 水平な方向は全て、目の高さの1本の線、水平線の上で消える。

1点透視・2点透視・3点透視

箱には3つの辺の方向が有ります。
其の内いくつが投影面に平行かで、箱に見える消失点の数が決まります:

同じ箱を1点透視、2点透視、3点透視で描き、辺を消失点まで延長した図

  • 1点透視:箱がカメラに真正面を向けています。幅と高さの辺は投影面に平行なので平行のまま、奥行きの辺だけが真正面の1つの消失点に集まります。正面から見た廊下や道路が此れです。
  • 2点透視:箱が縦軸の周りに回っていて、カメラは水平です。2つの水平な辺の方向が水平線上の2点で消え、縦の辺は縦のままです。建物が真っ直ぐ立って見えるので、建築パースでよく使われます。
  • 3点透視:カメラが更に上か下に傾いています。今度はどの辺の方向も投影面に平行でなく、縦の辺も遥か下(又は上)の3つ目の点に集まります。高層ビルを見上げた時の迫力の有る見た目です。

上の3枚の絵は全て、同じ箱をx′とy′の式で実際に投影した物で、目分量で描いた物ではありません。

数値例:2つの消失点

d=3とし、箱を縦軸の周りにφ=30°回し、カメラを水平にします。
2つの水平な辺の方向は𝐚=(cos30°,0,−sin30°)=(0.866,0,−0.5)と𝐛=(−sin30°,0,−cos30°)=(−0.5,0,−0.866)です(どちらもカメラから遠ざかる向き。𝐚⋅𝐛=−0.433+0.433=0なので互いに垂直)。

VPa=(3⋅0.8660.5,0)=(5.196,0),VPb=(3⋅−0.50.866,0)=(−1.732,0)

どちらもy′=0なので、目の高さの水平線上に有ります。
縦の方向(0,1,0)はvz=0なので消失点が無く、縦の辺は縦のままです:2点透視です。

此処には綺麗な規則が隠れています。
一般に2つの点はdcotφと−dtanφに有るので、其の積は常に

xaxb=−d2(5.196⋅(−1.732)=−9=−32)

です。
箱を回すと、一方の消失点は中心に近付き、もう一方は横へ逃げていきますが、積は変わりません。
実践課題で此れを自己チェックに使います。

透視投影が保つ物と保たない物

  • 直線は直線のまま。
  • 平行線は、投影面に平行な物を除いて平行のままではない。
  • 直線上の比は保たれない:奥へ続く長い壁の真ん中は、手前の端より奥の端に近い所に描かれます。其の為、透視補正されたテクスチャ補間が必要になります(第6章と第2部)。
  • 大きさは1/距離で小さくなる。

第4章の言葉で言うと、透視投影はアフィン変換ではなく射影変換です。

5.9 式で見る透視投影

相似な三角形

カメラを横から見てみましょう。
投影中心は原点、投影面は距離dに有り、点(x,y,z)はカメラの前の距離−zに有ります(カメラは−zを見るので、前に有る点のzは負です)。

横から見た図:高さ1.5で距離6と12に有る2つの点が、距離3の投影面の高さ0.75と0.375に投影される

小さい三角形(目、投影面、像)と大きい三角形(目、点)は角度が同じなので、辺の比が等しくなります:

y′d=y−z⇒y′=dy−z,x′=dx−z

透視投影とは距離で割る事です。
図ではd=3で、2つの点はどちらも高さy=1.5です(ラベルは(y,z)):

z=−6:y′=3⋅1.56=0.75,z=−12:y′=3⋅1.512=0.375

2倍遠いと、高さは半分です。
本によっては+z方向を見るカメラを使い、其の場合式はx′=dx/zになります。zの符号を反転しただけで、考え方は同じです。

4×4行列で書く:透視除算

行列は掛け算と足し算しか出来ず、zで割る事は出来ません。
同次座標が此れを解決します:w≠0の4次元ベクトル(x,y,z,w)は、3次元の点(x/w,y/w,z/w)を表します。
そこで行列に距離をwへ入れさせ、後で割ります:

𝐏d=[10000100001000−1d0],𝐏d[xyz1]=[xyz−z/d]

w=−z/dで割ると(dx−z,dy−z,−d)になります:相似な三角形の式其の物で、全ての点が平面z=−dに落ちます。
此の割り算を透視除算と呼び、投影をアフィンではなく射影的にしているのは此の段階です。
GPUでは頂点シェーダーの後に自動で行われます。

数値例。 d=3、点(2,1.5,−6):

𝐏d(2,1.5,−6,1)=(2,1.5,−6,2)→(22,1.52,−62)=(1,0.75,−3)

像は平面z=−3上の(1,0.75)で、先程のx′=3⋅2/6=1及びy′=0.75と一致します。
結果のzが常に−dになる事に注目して下さい:此の行列は奥行きを捨ててしまい(3行目と4行目が互いの定数倍なので行列式は0)、どの面が手前かという情報も一緒に失われます。
其の為、実際のレンダラーは少し違う行列を使います。

視体積と視錐台

本物のカメラは全てを見る訳ではありません。
見えるのは限られた角度、視野角θの範囲で、画像にはアスペクト比a = 幅 / 高さが有ります。
レンダラーは更に距離nのニア平面(前方クリップ面とも)と距離fのファー平面(後方クリップ面とも)を決め、其れより近い物と遠い物は切り取られます。
画像に写り得る領域を視体積と呼びます。
透視投影では先端を切り落とした角錐、視錐台になり、正投影では直方体になります。

視野角90度、ニア平面2、ファー平面6の視錐台と、高さ1.5・距離4の例の点を横から見た図

GPUの透視投影行列は視錐台を立方体、正規化デバイス座標(NDC)に写します。OpenGLでは−1から1です。
c=1/tan(θ/2)と置くと、OpenGL版は:

𝐏=[ca0000c0000−f+nf−n−2fnf−n00−10]

𝐏dと比べると、xとyの行は視野角に合わせて拡大縮小し(視錐台の縁が±1に来る様に)、zの行は奥行きを捨てずに残し、ニア平面を−1に、ファー平面を+1に写します。
一番下の行は相変わらず透視除算の為に−zをwへ写します。
線型代数 第12章で全ての要素を導き、Vulkan版(yが反転し、奥行きが0から1)も紹介しました。

数値例。 視野角90°、アスペクト比4:3、n=2、f=6:

c=1tan45°=1,ca=0.75,−f+nf−n=−84=−2,−2fnf−n=−244=−6
𝐏=[0.75000010000−2−600−10]

カメラ座標の点(1,1.5,−4)を取ります:

𝐏(1,1.5,−4,1)=(0.75,1.5,−2⋅(−4)−6,4)=(0.75,1.5,2,4)

w=4で透視除算すると:

𝐩ndc=(0.1875,0.375,0.5)

何故yndc=0.375なのかは視錐台の図を見ると分かります:視野角90°では、距離4での視錐台はy=−4からy=4まで広がり、1.5/4=0.375の所が丁度点の位置です。
NDCを画素の位置に変える段階はビューポート変換で、第6章で扱います。

奥行きは線型ではない

其の例の奥行きを見て下さい。
距離4の点はニア平面(2)とファー平面(6)の丁度真ん中ですが、NDCのzは0.5で、−1から1の真ん中ではありません。
除算の後の奥行きはf+nf−n−2fn(f−n)(−z)で、距離其の物ではなく1距離に従います。
今の数字では、範囲の真ん中(NDCのz = 0)は距離4ではなく2fnf+n=248=3に有ります。
次のグラフはn=1、f=10で、0から1の深度バッファに保存される値を描いた物です:

距離に対する深度値:透視投影の曲線はカメラの近くで急に上がり、平行投影は直線になる

透視投影は奥行きの精度の殆どをカメラの近くで使ってしまい、遠くの面はほんの僅かな精度を分け合う事になります。
ビット数が限られていると、遠くの2つの面が同じ深度値になり、互いにちらつきます(Zファイティング)。
実践的な規則:ニア平面は出来るだけ遠くに置きましょう。
正投影の奥行きは線型で、直線になります。
深度バッファ自体は第10章で、奥行きの精度は第2部で更に深く扱います。

検定のポイント

  • 平行投影と透視投影:平行な投影線(投影中心が無限遠)か、1つの投影中心を通る投影線か。
  • 正投影:投影線が投影面に垂直。斜投影:平行だが斜め。カバリエ投影は奥行きを実長、キャビネット投影は半分で描く。
  • 軸測投影の名前は等しい短縮率の数:等角(3つ等しい、軸同士は120°、短縮率は2/3≈0.816)、二等角(2つ)、不等角(等しい物無し)。
  • 平行投影は平行線と直線上の比を保つ。透視投影は直線を保つが平行性と比は保たず、像の大きさは距離と共に小さくなる。
  • 透視投影の箱の消失点の数は、投影面に平行でない辺の方向の数:1、2又は3。投影面に平行な線には消失点が無い。
  • x′=dx/(−z)は相似な三角形から。行列で書くと、割り算はwによる透視除算。
  • 透視投影の視体積は視錐台(ニア平面、ファー平面、視野角、アスペクト比)。保存される奥行きは距離に対して線型ではない。
  • 全体の順番は𝐏𝐕𝐌:モデル、ビュー、投影の順。

5.10 コード

線型代数シリーズのヘッダーlinalg.hhを使って、C++で一連の流れを全部書いてみます:モデル行列としてのロドリゲスの回転、5.5節のカメラ、5.9節の投影です。
後半は5.4節の法線の例を確かめます。

#include <cstdio>
#include <numbers>
#include "linalg.hh"

int main() {
  const float pi = std::numbers::pi_v<float>;

  // 1. model: 90° about the axis (0, 0.6, 0.8)
  Mat4 model = rotate({0.0f, 0.6f, 0.8f}, pi / 2.0f);
  // 2. view: eye (0, 3, 4) looking at the origin
  Mat4 view = lookAt({0.0f, 3.0f, 4.0f}, {0.0f, 0.0f, 0.0f}, {0.0f, 1.0f, 0.0f});
  // 3. projection: 90° vertical fov, aspect 4:3, near 2, far 6
  Mat4 proj = perspectiveGl(pi / 2.0f, 4.0f / 3.0f, 2.0f, 6.0f);

  Vec4 p{1.0f, 2.0f, 0.0f, 1.0f};
  Vec4 world = model * p;
  Vec4 eye = view * world;
  Vec4 clip = proj * eye;
  Vec4 ndc = (1.0f / clip.w) * clip; // perspective division

  printf("world (%.4f, %.4f, %.4f)\n", world.x, world.y, world.z);
  printf("view  (%.4f, %.4f, %.4f)\n", eye.x, eye.y, eye.z);
  printf("clip  (%.4f, %.4f, %.4f, %.4f)\n", clip.x, clip.y, clip.z, clip.w);
  printf("ndc   (%.4f, %.4f, %.4f)\n", ndc.x, ndc.y, ndc.z);

  // normal of the triangle x + y + z = 1 after scale(2, 1, 0.5)
  Mat4 s = scale({2.0f, 1.0f, 0.5f});
  Mat4 normalMatrix = transpose(inverse(s));
  Vec4 n{1.0f, 1.0f, 1.0f, 0.0f};
  Vec4 wrong = s * n;
  Vec4 right = normalMatrix * n;
  printf("S n            (%.4f, %.4f, %.4f)\n", wrong.x, wrong.y, wrong.z);
  printf("(S^-1)^T n     (%.4f, %.4f, %.4f)\n", right.x, right.y, right.z);
  return 0;
}

world (-1.6000, 1.5200, 0.3600)
view  (-1.6000, 1.0000, -3.8000)
clip  (-1.2000, 1.0000, 1.6000, 3.8000)
ndc   (-0.3158, 0.2632, 0.4211)
S n            (2.0000, 1.0000, 0.5000)
(S^-1)^T n     (0.5000, 1.0000, 2.0000)

ワールド座標の位置は5.3節のロドリゲスの結果です。
ビューの行は5.5節の行列𝐕で手計算出来ます:y=0.8⋅1.52−0.6⋅0.36=1.216−0.216=1、z=0.6⋅1.52+0.8⋅0.36−5=−3.8。
点はカメラの3.8前、ニア平面(2)とファー平面(6)の間に有るので、NDCの3つの座標は全て−1と1の間に入り、画面に写ります。

5.11 まとめ

  • 3次元の座標系は右手系(+zが手前、此のシリーズ)か左手系(+zが奥)。変換はzの反転𝐒𝐌𝐒で、x軸とy軸周りの回転角の符号が変わる。
  • 点はローカル座標からワールド座標(モデリング変換)、カメラ座標(ビュー変換)へと移り、其の後投影される。
  • 平行移動、拡大縮小、x・y・z軸周りの回転、鏡映、せん断は4×4の同次座標行列。合成は右から左へ読み、交換出来ない。
  • 𝐜を通る軸の周りの回転は𝐓(𝐜)𝐑𝐓(−𝐜)。斜めの軸には合成による方法かロドリゲスの公式を使う。
  • オイラー角は順番に依存し、ジンバルロックが起こる。クォータニオンなら避けられる。
  • 法線は逆転置行列(𝐀−1)⊤で変換する。非均等な拡大縮小やせん断で重要。
  • ビュー行列はカメラの配置の逆で、視点・注視点・上方向から外積で作る(lookAt)。
  • 投影は平行投影(正投影:多面投影と軸測投影、斜投影:カバリエとキャビネット)か透視投影(1点・2点・3点透視)。
  • 平行投影は平行線と大きさを保つ。透視投影は距離で割るので、平行線は消失点に集まり、遠い物は小さく見える。
  • GPUの透視投影行列は−zをwに入れ、視錐台をNDCに写し、距離に対して線型でない奥行きを残す。

5.12 用語

English 日本語 備考
right-handed system 右手系 +zが手前(此のシリーズ)
left-handed system 左手系 +zが画面の奥
local coordinates ローカル座標系(モデリング座標系) モデル自身の空間
world coordinates ワールド座標系 シーン共通の空間
camera coordinates カメラ座標系(視点座標系) カメラが原点で−zを見る
modeling transform モデリング変換 ローカル → ワールド
view transform ビュー変換(視野変換) ワールド → カメラ
homogeneous coordinates 同次座標 (x, y, z, w)
translation 平行移動
scaling 拡大縮小 均等又は非均等
rotation 回転 x・y・z軸又は任意軸の周り
reflection 鏡映 行列式は−1
shear せん断
rotation about an arbitrary axis 任意軸周りの回転 合成による方法又はロドリゲス
Rodrigues' rotation formula ロドリゲスの回転公式
Euler angles オイラー角 順番が大事
gimbal lock ジンバルロック 2つの軸が重なる
quaternion クォータニオン(四元数) ジンバルロックが無い
normal vector 法線ベクトル
normal matrix 法線行列 逆転置行列
projection 投影 3次元 → 2次元
projection plane 投影面
projector 投影線 点が運ばれる直線
center of projection 投影中心 目
parallel projection 平行投影 平行な投影線
orthographic projection 正投影 投影線が投影面に垂直
multi-view projection, three-view drawing 多面投影、三面図 正面図・平面図・側面図
axonometric projection 軸測投影 1枚で3つの面を見せる
isometric projection 等角投影 短縮率が3つ等しい、120°
dimetric projection 二等角投影 短縮率が2つ等しい
trimetric projection 不等角投影 等しい短縮率が無い
oblique projection 斜投影 斜めの平行な投影線
cavalier projection カバリエ投影 奥行きを実長で
cabinet projection キャビネット投影 奥行きを半分で
perspective projection 透視投影 投影線が1点を通る
vanishing point 消失点 平行線が集まる点
one-, two-, three-point perspective 1点透視、2点透視、3点透視
foreshortening 短縮(フォアショートニング)
perspective division 透視除算 wで割る
view volume 視体積 カメラに見える領域
frustum 視錐台 透視投影の視体積
near plane, far plane ニア平面、ファー平面(前方・後方クリップ面)
field of view 視野角
aspect ratio アスペクト比 幅 / 高さ
normalized device coordinates 正規化デバイス座標 −1〜1(OpenGL)

5.13 課題

  1. ★ 点(1,1,1)を𝐌=𝐓(1,−2,3)𝐑x(90°)𝐒(2,2,0.5)で一段ずつ変換し、次に3つの行列の順番を逆にして変換して下さい。
  2. ★ ロドリゲスの公式で(0,0,2)を軸𝐤=(0.6,0.8,0)の周りに90°回し、長さと𝐤⋅𝐯が変わらない事を確かめて下さい。
  3. ★ d=2として、点(3,1,−4)と(3,1,−8)をx′=dx/(−z)、y′=dy/(−z)で投影し、方向(1,0,−1)の消失点を求めて下さい。
  4. ★★ 視野角90°、アスペクト比1、n=1、f=3のOpenGLの透視投影行列を作り、距離1、1.5、2、3の点のNDCの奥行きを計算して下さい。NDCの奥行きが0になるのはどの距離ですか?
  5. ★★ モデルを𝐒(1,3,1)で拡大縮小します。ある面の法線は(0,1,1)、接線は(1,0,0)と(0,1,−1)です。接線を𝐒で、法線を2通り(𝐒𝐧と(𝐒−1)⊤𝐧)で変換し、内積を使ってどちらの法線が正しいか示して下さい。
  6. ★★★ 簡単な投影行列𝐏dの行列式は0ですが、GPUの行列𝐏には逆行列が有ります。此の違いが深度バッファと「マウスピッキング」(マウスカーソルの下の3次元の点を求める事)にとって何を意味するか説明して下さい。

選択問題

問1 ★ 物体の3本の座標軸を同じ短縮率で、互いに120°の角度で描く投影はどれか。

あ 等角投影
い 2点透視
う キャビネット投影
え 二等角投影
お カバリエ投影

問2 ★★ 物体を、左上3×3部分𝐀が非均等な拡大縮小を含むアフィン行列で変換する。法線ベクトルを面に垂直なまま保つ為に使う行列はどれか。

あ 𝐀
い 𝐀⊤
う (𝐀−1)⊤
え 𝐀−1
お −𝐀

問3 ★ 透視投影について、次のうち正しい物はどれか。

あ 空間で平行な直線は、画像でも常に平行のままである。
い 物体の消失点の数は、其の物体の辺の数に等しい。
う 画像上の物体の大きさは、投影中心からの距離に依存しない。
え 箱の2点透視では、縦の辺は箱の下に有る消失点に集まる。
お 投影面に平行な平行線は画像でも平行のままであり、其れ以外の平行線は消失点に集まる。

5.14 実践課題

此の課題では、1つの立方体を4種類の投影で描きます。全て手で計算し、各図が満たすべき性質で確かめます。
方眼紙、定規、鉛筆、電卓を用意して下さい。
1単位 = 2cm(透視図は4cm。消失点が中心から右に約21cm、左に約7cmの所に来るので、A3の紙を横長に使うか、A4を2枚横に貼り合わせて下さい)とし、角が0と1の全ての組み合わせに有る単位立方体を使います。角にはA = (0, 0, 0)からH = (1, 1, 1)まで好きな様に名前を付けて下さい。

  1. 三面図。 立方体の正面図・平面図・右側面図を第三角法の並べ方(正面図の上に平面図、右に右側面図)で描いて下さい。次に、上・手前・右の角に0.5 × 0.5 × 0.5の切り欠きを入れ(x > 0.5、y > 0.5、z > 0.5の部分を取り除く)、三面図を描き直して下さい。
  2. 等角投影。 5.7節の𝐫=(0.7071,0,−0.7071)と𝐮=(−0.4082,0.8165,−0.4082)を使って8つの角の図上の位置(𝐫⋅𝐩,𝐮⋅𝐩)を計算し、点を打って見える辺を結んで下さい。
  3. キャビネット投影。 x′=x−0.3536z、y′=y−0.3536zを使って、α=45°のキャビネット投影で立方体を描いて下さい。
  4. 2点透視。 8つの角を𝐑y(30°)で回し(x′=0.866x+0.5z、z′=−0.5x+0.866z)、(0,−1.5,−5)だけ動かします。其れをd=3で投影します:x′′=3x/(−z)、y′′=3y/(−z)。点を打って辺を結んで下さい。
  5. 透視図に、回した後のxとzの辺の方向の2つの消失点を5.8節の式で計算して書き込み、其れらを通る水平線を引いて下さい。

最後に出来上がる物:同じ立方体の4枚の図(と切り欠き付きの三面図)で、角の横に其々の数値を書いた物。
自己チェック:

  • 三面図では、正面図と平面図の幅、正面図と側面図の高さが正確に一致する事。
  • 等角図では、12本の辺が全て同じ長さ0.816×2≈1.63cmで、1つの角に集まる3本の辺が120°ずつ離れている事。
  • キャビネット図では、4本の奥行きの辺が他の辺の半分の長さで、丁度45°傾いている事。
  • 透視図では、4本の縦の辺が縦のままである事。各水平方向の4本の辺を定規で延長すると、其の方向の消失点で交わる事。2つの消失点が水平線上の同じ高さに有る事。そして其のx座標の積が−d2=−9になる事(センチメートルに直す前の単位で)。

どれかの確認が上手く行かない時は、殆どの場合符号の間違いです:−zのマイナスと回転の向きを見直して下さい。


コメント(0)

まだコメントはありません。