depth-bufferingとカリング(Rust製ラスタライザ)
from 点を描くところから始めるRust製ソフトウェアラスタライザ
z-buffer testとカリング(Rust製ラスタライザ)
目次
目標
現状
z-buffer
culling
https://gyazo.com/2f4c59cbfc0998b608b51ec7f835e939
目標
これが正しく描画される。
https://gyazo.com/a9a3f8ac6c250fe625a6192db2bd62c4
code:desmos
\left(1,2,3\right),\left(1,-2,0\right),\ \left(-1,-1,-3\right)
\left(1,2,-3\right),\left(1,-2,0\right),\ \left(-1,-1,3\right)
現状
code:main.rs
let world:Vec<Actor> = vec![
Actor{
vertices: Vec4::newpoint(1., 2., 3.), Vec4::newpoint(1., -2., 0.), Vec4::newpoint(-1., -1., -3.),
position: Vec4::newpoint(0., 0., 0.),
axis: Vec4::newvec(0., 0., 0.),
theta: 0.0,
color: red.clone(), blue.clone(), red.clone()
},
Actor{
vertices: Vec4::newpoint(1., 2., -3.), Vec4::newpoint(1., -2., 0.), Vec4::newpoint(-1., -1., 3.),
position: Vec4::newpoint(0., 0., 0.),
axis: Vec4::newvec(0., 0., 0.),
theta: 0.0,
color: green.clone(), blue.clone(), green.clone()
},
];
while canvas.update()? {
let t = stopwatch.elapsed_as_sec();
camera.position = Vec4::newpoint(6.*f64::cos(k*t), 6.*f64::sin(k*t), 0.) ;
camera.look = Vec4::newvec(-f64::cos(k*t), -f64::sin(k*t), 0.) ;
camera.up = Vec4::newvec(0., 0., 1.);
camera.snapshot(canvas, &world);
}
https://gyazo.com/c64a7bd5e4ceef12e66177a3c1b8496e
WJ.icon depth-buffering
方針
投影面が映るピクセルごとに、現在描画した点の深度を保持しておく。
別の三角形を描画する際に、次の点とその点の深度を比較し、前側にあればその点を描画する。
draw_triangle三角形の引数
code:rs
&mut self,
points: Vec4Screen; 3,
color: &Color; 3
pointsのz値にはその点の透視深度が示されている。
従って、三角形面の各点ごとの透視深度を求めることができれば良い。
c.f. ○○くんのために一所懸命書いたものの結局○○くんの卒業に間に合わなかったGLFW による OpenGL 入門: 7.8.5 投影面上の線形補間
問題設定
いま、点$ p_1, p_2, p_3 (p_i = (x_i, y_i, z_i))を三角形の頂点とする。
点のスクリーン座標$ p'_i = (x'_i, y'_i)、各点の透視深度$ z'_i = f(z_i) = (\frac{C_1}{z_i} + C_2)、そしてw値$ 1が与えられるとしよう。
$ C_1, C_2はカメラパラメータによって定まる定数
この三角形上の点$ p = (x,y,z)において、透視投影後の$ p' = (x',y',z')の透視深度$ z'はいくつだろうか?appbird.icon
($ 'は投影後を示すとしよう)
わかるもの
スクリーン座標系のBarycentric座標$ (w'_1, w'_2, w'_3)がもとまる。
※ これはビュー空間のそれとは必ずしも一致しない
このBarycentric座標と透視深度から、$ z' = f(z)を求めなければならない。
求解
空間中の三角形$ v_1v_2v_3上における$ pのBarycentric座標$ (w_1, w_2, w_3)を考える。
$ p = w_1p_1 + w_2 p_2 + w_3p_3 = \sum_i w_ip_i ($ \sum_i w_i = 1)
二つのことに注目する。
準備:$ (w_1, w_2, w_3)と$ (w'_1, w'_2, w'_3)の関係は?
本題:$ f(z)を$ f(z_i)で表現するには?
準備:$ (w_1, w_2, w_3)と$ (w'_1, w'_2, w'_3)の関係は?
$ x = \sum_i w_i x_iが成立している。
透視投影変換の定義より、どの点に対しても$ x'_i = \frac{N}{W} \frac{x_i}{z_i}つまり$ x_i = \frac{W}{N}z_i x'_iが成立するため
$ \frac{W}{N}zx' = \sum_{i} \frac{W}{N}z_i w_i x'_i
$ x' = \sum_{i} \left(w_i\frac{z_i}{z}\right) x'_i
従って、$ w'_i = w_i\frac{z_i}{z}といえる。($ y方向を見ても同じ議論ができる)
仕切り直し本題
ビュー座標系において、$ z = \sum_i w_i z_iが成立する。
これをクリッピング座標の上に移そう
$ zf(z) = C_1 + C_2zが成り立つため$ z= (zf(z) - C_1)/(C_2)
$ zf(z) - C_1 = \sum_i w_i \cdot (z_if(z_i)- C_1)
$ zf(z) = \sum_i w_iz_if(z_i) ($ \sum_i w_i = 1)
$ f(z) = \sum_i w_i (z_i/z) f(z_i)
$ f(z) = \sum_i w'_if(z_i)
したがって、透視深度を採用した場合には、スクリーン上で線形補間をすることで深度が求まる。
ところで、$ C_1, C_2には何の制約もかけていないので、$ f(z) = 1/zとしても同様の関係が成立する。
従って、$ 1/z = \sum_i w_i' (1/z_i)であり、$ 1/z'_iの情報がわかってさえいれば$ z値の線形補間ができる。
つまり必ずしも$ z \in (-1, 1)に納める必要のないCPUラスタライザでは、ややこしい係数をつけずとも$ z値をそのまま座標値に突っ込んでさえいれば、$ z値そのものを簡単に得られる。
(カメラ近傍の精度を気にしなければ)
とほほ...appbird.icon
しょうがないので、今回はそのままdepthを使って比較できるようにしておく
depth-textureを計算したい時は、別途z値を渡してもらうようにしておくappbird.icon
code:triangle.rs
for y in &y_segment.and(&bound_y) {
let edge = if y < middle.y { &lines0 } else { &lines1 };
let i_edge1 = lines2.across_y(y);
let i_edge2 = edge.across_y(y);
let x_segment = i_edge1.or(i_edge2);
for x in &x_segment.and(&bound_x) {
let p = Vec4::newpixel(x, y);
let w = [
area(&points1, &points2, &p) * inv_abc,
area(&points2, &points0, &p) * inv_abc,
area(&points0, &points1, &p) * inv_abc,
];
let z = [
points0.z(), points1.z(), points2.z(),
];
let p = p.to_point2();
let depth = w0*z0 + w1*z1 + w2*z2;
let color = &color0*w0 + &color1*w1 + &color2*w2;
self.draw_pixel_with_depth(&p, &depth, &color);
}
}
https://gyazo.com/2f4c59cbfc0998b608b51ec7f835e939
AC.icon culling
三角形が裏表どちらを向いているかによって描画するかしないかを決めておく
今回はカメラからみて三角形の頂点が反時計回りに並んでいる場合を表向きとして計算する
スクリーン座標において、$ (p_1-p_0)\times(p_2 - p_0) \geq 0であれば反時計周りにあるといえる。
https://gyazo.com/7d6dddcc6ba1a4862393be6eb7fa54cc
これは、Barycentric座標を求める際に三角形$ p_0p_1p_2の符号付き面積の符号を見れば判定できる。
code:triangle.rs
// Barycentric座標
let area_abc = area(&points0, &points1, &points2);
// culling
if area_abc < 1e-6 { return; }
https://gyazo.com/3185c6a42377be72cdb2b7f3b8c9c8e5
一方の三角形が見えなくなったappbird.icon
回転すると緑色の三角形が見えてくる
うまくいかなかった導出 -->$ f(z)を$ f(z_i)で表現するには?
ビュー座標系において、$ z = \sum_i w_i z_iが成立する。
いま透視深度として$ z' = C_1/z + C_2が成り立つため
$ 1/z = \frac{z' - C_2}{C_1}
$ (z' - C_2)/C_1 = ( \sum_i w_i C_1/(z_i' - C_2))^{-1}
$ (z' - C_2) = ( \sum_i w_i/(z_i' - C_2))^{-1}
$ z'= ( \sum_i w_i/(z_i' - C_2))^{-1} + C_2
$ z'= \frac{1+C_2(\sum_i w_i/(z_i-C_2))}{\sum_i w_i/(z_i' - C_2)}
????