0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Rustでポリゴンの基礎を学ぶ その4

0
Last updated at Posted at 2026-08-04

フルコードはgithubにあります。

めっちゃ間が空きました。コードだけ先に書いて文章を書くのを後回しにするとサボってしまう。いかん。

Zバッファの実装

複数のポリゴンを扱うときにどちらが前面かを扱うためにZバッファ法を導入する。

まずzbuffer.rsモジュールを作成する。構造体は幅と高さの情報と、そのサイズ分のピクセルの配列をもつ。データの初期値は無限遠。インデックス経由で配列にアクセスする。分かりやすく2次元配列にできないものかは後で考える。

pub struct ZBuffer {
    pub width: u32,
    pub height: u32,
    pub data: Vec<f64>,
}

impl ZBuffer {
    pub fn new(width: u32, height: u32) -> Self {
        Self {
            width,
            height,
            data: vec![f64::INFINITY; (width * height) as usize],
        }
    }

    #[inline]
    pub fn index(&self, x: u32, y: u32) -> usize {
        (y * self.width + x) as usize
    }
}

ポリゴンの頂点に、Z深度フィールドを追加。

--- a/src/triangle.rs
+++ b/src/triangle.rs
@@ -3,6 +3,7 @@ use crate::color::Color;
 #[derive(Copy, Clone)]
 pub struct Vertex {
     pub pos: Vec2,
+    pub z: f64,
     pub color: Color,
 }

polygon_fill()で、ピクセル描画時にZ深度をチェックして、手前だった時だけピクセルを更新する。これで手前のポリゴンが見えるようになる。

pub fn polygon_fill(
    fb: &mut ImageBuffer<image::Rgb<u8>, Vec<u8>>,
    transform: &Transform,
+    zbuffer: &mut ZBuffer,
    triangle: &Triangle,
) {
     let min_x = triangle.v0.pos.x.min(triangle.v1.pos.x.min(triangle.v2.pos.x)) as i32;
     let max_x = triangle.v0.pos.x.max(triangle.v1.pos.x.max(triangle.v2.pos.x)) as i32;
     let min_y = triangle.v0.pos.y.min(triangle.v1.pos.y.min(triangle.v2.pos.y)) as i32;
@@ -60,7 +66,13 @@ pub fn polygon_fill(fb: &mut ImageBuffer<image::Rgb<u8>, Vec<u8>>, transform: &T
                 ]);

                 if let Some((sx, sy)) = transform.to_screen(i, j) {
-                    fb.put_pixel(sx, sy, rgb);
+                    let idx = zbuffer.index(sx, sy);
+                    let z: f64 = w0 * triangle.v0.z + w1 * triangle.v1.z + w2 * triangle.v2.z;
+
+                    if z < zbuffer.data[idx] {
+                        zbuffer.data[idx] = z;
+                        fb.put_pixel(sx, sy, rgb);
+                    }
                 }
             }
         }

境界値計算の改善

スクリーンを切り出すときに、四隅の座標を浮動小数点から整数に変換するが、単純にキャストせずきちんと切り上げ(.floor())、切り捨て(.ceil())処理することで、境界値を正確に算出した。
見た目では違いが分からんけど。

-    let min_x = triangle.v0.pos.x.min(triangle.v1.pos.x.min(triangle.v2.pos.x)) as i32;
-    let max_x = triangle.v0.pos.x.max(triangle.v1.pos.x.max(triangle.v2.pos.x)) as i32;
-    let min_y = triangle.v0.pos.y.min(triangle.v1.pos.y.min(triangle.v2.pos.y)) as i32;
-    let max_y = triangle.v0.pos.y.max(triangle.v1.pos.y.max(triangle.v2.pos.y)) as i32;
+    let (min_x, max_x, min_y, max_y) = triangle.bounding_box();
impl Triangle {
    pub fn bounding_box(&self) -> (i32, i32, i32, i32) {
        let v0 = self.v0.pos;
        let v1 = self.v1.pos;
        let v2 = self.v2.pos;

        let min_x = v0.x.min(v1.x.min(v2.x)).floor();
        let max_x = v0.x.max(v1.x.max(v2.x)).ceil();
        let min_y = v0.y.min(v1.y.min(v2.y)).floor();
        let max_y = v0.y.max(v1.y.max(v2.y)).ceil();

        (min_x as i32, max_x as i32, min_y as i32, max_y as i32)
    }
}

3Dへの道

今まで2次元座標を扱っていたが、vec3.rsを作って3次元に拡張する。zはとりあえず0に固定するので、動作的には変更無し。

use crate::triangle::Pixel;
use std::fmt;
use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub};

#[derive(Debug, Copy, Clone, PartialEq)]
pub struct Vec3 {
    pub x: f64,
    pub y: f64,
    pub z: f64,
}

pub type Point3 = Vec3;

impl Vec3 {
    pub fn new(x: f64, y: f64, z: f64) -> Self {
        Self { x, y, z }
    }

impl From<Pixel> for Vec3 {
    fn from(item: Pixel) -> Self {
        Vec3 {
            // Convert to center pixel by adding 0.5
            x: item.x as f64 + 0.5,
            y: item.y as f64 + 0.5,
            z: 0.0,
        }
    }
}

ゴールは3Dのポリゴンを扱うことですが、先に2.5D化する。2.5DとはZ深度を使ってポリゴンを描くという意味。

下記のように二次元のまま各頂点のZ深度を変えて、二つの三角形が交わるようにする。

let tri = Triangle {
    v0: Vertex {
        pos: Vec3 {
            x: 129.7,
            y: -26.4,
            z: 0.0,
        },
        z: 1.0,
        color: Color::new(0.0, 0.0, 1.0),
    },
    v1: Vertex {
        pos: Vec3 {
            x: 1327.3,
            y: 480.1,
            z: 0.0,
        },
        z: 1.0,
        color: Color::new(0.0, 1.0, 0.0),
    },
    v2: Vertex {
        pos: Vec3 {
            x: 124.0,
            y: 841.0,
            z: 0.0,
        },
        z: 1.0,
        color: Color::new(0.5, 0.0, 0.0),
    },
};
polygon_fill(&mut imgbuf, &transform, &mut zbuf, &tri);

let tri = Triangle {
    v0: Vertex {
        pos: Vec3 {
            x: 1412.7,
            y: -26.4,
            z: 0.0,
        },
        z: 1.5,
        color: Color::new(0.0, 0.0, 0.5),
    },
    v1: Vertex {
        pos: Vec3 {
            x: -55.3,
            y: 280.1,
            z: 0.0,
        },
        z: 0.1,
        color: Color::new(1.0, 1.0, 0.0),
    },
    v2: Vertex {
        pos: Vec3 {
            x: 1033.0,
            y: 841.0,
            z: 0.0,
        },
        z: 1.3,
        color: Color::new(1.0, 0.0, 0.0),
    },
};
polygon_fill(&mut imgbuf, &transform, &mut zbuf, &tri);

出力された画像。

output.png

続く。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?