座標変換と三次元空間のポリゴン(Rust製ラスタライザ)
ただし、ここら辺はバグらせやすい要因になるので段階的に開発したいappbird.icon
参考資料
床井先生のこちらの資料を参考にさせていただく... 特に、p.114以降 (pdfビューア上ではp.122以降)
p.153
用語などはこちらの資料を参考にする。
WJ.icon まず、(0, 0, 0)から(0, 0, 1)の方向を向いて、(0, 1, 0)を上とするカメラを作る。
モデルの頂点座標とモデルの座標・角度を持つActorを定義する
モデル(三角ポリゴン)の中心座標$ \bm p
回転(回転軸と角度$ \bm \theta)
自身のモデル変換行列を返す
回転$ \bm\thetaを行った$ R後、$ \bm pだけ並行移動する$ T
行列$ TRを求めれば良い
今は単位行列としてよい
資料のPDFとしてはビューア表示においてp.161ぐらいappbird.icon
farを0.1 m, nearを100 mとして投影する。
視錐体
標準視体積に見える範囲の物体を持ってくるappbird.icon
せん断変形 --> 今回は視錐体はビュー変換された地点で直線$ (x, y) = (0, 0)を通っているものと仮定する ので、ここは単位行列でよい
同時座標系での透視変換を考える
画像乾板に映る像の大きさを考えることになる。
これは、幾何学的に相似を考えればすぐに求められる。
https://scrapbox.io/files/66faa3b9c371ca001ccd9eab.png
$ zで割る分に関しては、同次座標系の$ wへ$ z値を載せればいい
さらに、画像乾板の端っこにあたる座標値は$ -1, 1に正規化しておきたいので
画像乾板の幅$ Wと高さ$ Hで割っておく。
ということで、求める行列は、$ z座標ぶんを除いてここまでわかる。
code:tex
\begin{pmatrix}
x' \\ y' \\ z' \\ w'
\end{pmatrix}
=
\begin{pmatrix}
near/W & 0 & 0 & 0 \\
0 & near/H & 0 & 0 \\
* & * & * & * \\
0 & 0 & 1 & 0
\end{pmatrix}
\begin{pmatrix}
x \\ y \\ z \\ w
\end{pmatrix}
透視投影後の像の深度(カメラに対して、物体がどれぐらいの奥行き方向の距離に存在するか)のことを透視深度という
透視投影では、投影像の大きさが深度 (z 値) に反比例します。透視投影後の深度にも、元の深度の逆数を用います。
この深度は$ \lbrack -1, 1\rbrackの実数で表される。
https://scrapbox.io/files/66faa3f0bbbd59001dde1f67.png
要件
1. 前方面のz値($ z = N)が-1、後方面のz値($ z = F)が1になってほしい
2. $ zに反比例してほしい。
これはなぜ?appbird.icon
透視投影後の位置の精度は前方面に近いほど高く、前方面から離れるほど低くなります。このような深度を透視深度といいます。
なるほど、前方面まわりの深度の精度を確保したいからか
$ f(z) = \frac{C_1}{z} + C_2という関数の形を仮定して、このような関数を探してみる。
$ -1 = \frac{C_1}{N} + C_2, $ 1 = \frac{C_1}{F} + C_2
$ -N = C_1 + C_2N, $ F = C_1 + C_2F
$ -N - F = (N - F) C_2
$ \frac{F + N}{F - N} = C_2
$ -1 - \frac{ F + N }{ F - N } = \frac{C_1}{N}
$ - \frac{ F + N + F - N }{ F - N } = \frac{C_1}{N}
$ - \frac{ 2F }{ F - N } = \frac{C_1}{N}
$ - \frac{ 2FN }{ F - N } = C_1
従って、
$ f(z) = \left( -\frac{FN}{z} + \frac{F + N}{2}\right) \frac{2}{F - N}
同時座標系の$ w値に$ z値が乗ってるので、それに合わせると
$ zf(z) = \left( -FN + \frac{F + N}{2} z\right) \frac{2}{F - N}
ということで、正規化まで含めて求める行列はこうなる。
code:tex
\begin{pmatrix}
x' \\ y' \\ z' \\ w'
\end{pmatrix}
=
\begin{pmatrix}
N/W & 0 & 0 & 0 \\
0 & N/H & 0 & 0 \\
0 & 0 & \frac{F + N}{F - N} & -\frac{2FN}{F-N} \\
0 & 0 & 1 & 0
\end{pmatrix}
\begin{pmatrix}
x \\ y \\ z \\ w
\end{pmatrix}
ふつう、$ wは$ 1であることに注意したいappbird.icon
実装
モデルからモデル変換行列を提供する
code:rs
pub struct Actor {
position:Vec4,
axis:Vec4,
theta:f64,
}
impl Actor {
fn model_conversion(&self) -> Mat4x4 {
Mat4x4::translate(&self.position)
* Mat4x4::rotation(&self.axis, self.theta)
}
}
カメラから透視変換行列を提供する
code:rs
pub fn view_conversion(&self) -> Mat4x4 {
let n = self.near;
let f = self.far;
let w = self.width;
let h = self.height;
Mat4x4::from_array([
])
}
Actorの頂点をモデル変換行列・透視変換行列に通す。
code:rs
pub fn shot(&mut self, canvas:&mut Canvas, actors:Vec<Actor>) {
let perspective = self.view_conversion();
let view = Mat4x4::identity();
let pv = perspective*view;
for actor in actors {
let model = actor.model_conversion();
let pvm = &pv * &model;
Vec4Project(&pvm * &actor.vertices0), Vec4Project(&pvm * &actor.vertices1), Vec4Project(&pvm * &actor.vertices2), ];
self.draw_triangle(canvas, &projected0, &projected1, &projected2, &actor.color); }
}
テストappbird.icon
こんな感じの空間を撮ってみる
https://scrapbox.io/files/66fa0189b60f26001dc50d77.png
https://scrapbox.io/files/66fa0170bdf41c001c3cdcde.png
code:rs
let triangles = [
];
let blue = Vec4::new3d(0.2, 0.2, 0.9);
let green = Vec4::new3d(0.2, 0.9, 0.2);
WJ.icon $ \bm pから$ \bm Lの方向を向いて、$ \bm uを上とするカメラを作る。
テスト
$ \bm pを$ (\cos(2\pi t/5), \sin(2\pi t/5), 0)とする。
$ \bm pを$ (0, 0, \sin(2\pi t/5))とする。
遠近感が出ると良い
$ \bm Lを$ (\sin(2\pi t/5), 0, \cos(2\pi t/5))とする。
くるくるしてると良い
$ \bm uを$ (\cos(2\pi t/5), \sin(2\pi t/5), 0)とする。
画面がぐるぐるしていると良い