Basic blue-to-white gradient

This commit is contained in:
k 2026-03-25 19:29:35 -04:00
commit 67fc7ea21d
7 changed files with 275 additions and 0 deletions

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
/target
*.ppm

7
Cargo.lock generated Normal file
View file

@ -0,0 +1,7 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "rust-ray"
version = "0.1.0"

6
Cargo.toml Normal file
View file

@ -0,0 +1,6 @@
[package]
name = "rust-ray"
version = "0.1.0"
edition = "2024"
[dependencies]

8
shell.nix Normal file
View file

@ -0,0 +1,8 @@
{ pkgs ? import <nixpkgs> {} }:
pkgs.mkShell {
buildInputs = with pkgs; [
cargo
rust-analyzer
];
}

67
src/image.rs Normal file
View file

@ -0,0 +1,67 @@
use std::fs::File;
use std::io::{BufWriter, Write};
#[derive(Clone)]
pub struct Pixel {
red: u8,
green: u8,
blue: u8,
}
impl Pixel {
pub fn set_color(&mut self, r: f64, g: f64, b: f64) {
self.red = (r * 255.999) as u8;
self.green = (g * 255.999) as u8;
self.blue = (b * 255.999) as u8;
}
}
pub struct Image {
data: Vec<Pixel>,
pub width: u16,
pub height: u16,
}
impl Image {
pub fn new(width: u16, height: u16) -> Self {
let total_pixels = (width as usize) * (height as usize);
let data = vec![
Pixel {
red: 0,
green: 0,
blue: 0
};
total_pixels
];
Image {
data,
width,
height,
}
}
pub fn save(&self, path: &str) -> std::io::Result<()> {
let file = File::create(path)?;
let mut writer = BufWriter::new(file);
writeln!(writer, "P3")?;
writeln!(writer, "{} {}", self.width, self.height)?;
writeln!(writer, "255")?;
self.data.iter().try_for_each(|pixel| {
writeln!(writer, "{} {} {}", pixel.red, pixel.green, pixel.blue)
})?;
writer.flush()?;
Ok(())
}
pub fn get_pixel(&mut self, x: u16, y: u16) -> Option<&mut Pixel> {
if x >= self.width || y >= self.height {
return None;
}
let index = (y as usize * self.width as usize) + x as usize;
Some(&mut self.data[index])
}
}

48
src/main.rs Normal file
View file

@ -0,0 +1,48 @@
mod image;
mod vec3;
use image::Image;
use vec3::{Point3, Ray, Vec3};
fn main() {
let aspect_ratio = 16.0 / 9.0;
let image_width = 800;
let image_height = (image_width as f64 / aspect_ratio) as i32;
let focal_length = 1.0;
let viewport_height = 2.0;
let viewport_width = (viewport_height * (image_width / image_height) as f64) as i32;
let camera_center = Point3::new(0.0, 0.0, 0.0);
let viewport_u = Vec3::new(viewport_width as f64, 0.0, 0.0);
let viewport_v = Vec3::new(0.0, -viewport_height as f64, 0.0);
let pixel_delta_u = viewport_u / image_width as f64;
let pixel_delta_v = viewport_v / image_height as f64;
let viewport_upper_left = camera_center
- (Vec3::new(0.0, 0.0, focal_length) - viewport_u / 2.0 - viewport_v / 2.0).to_point();
let pixel00_loc = viewport_upper_left + ((pixel_delta_u + pixel_delta_v) * 0.5).to_point();
let mut img = Image::new(800, 600);
for y in 0..img.height {
for x in 0..img.width {
if let Some(px) = img.get_pixel(x, y) {
let pixel_center = pixel00_loc
+ ((pixel_delta_u * x as f64) + (pixel_delta_v * y as f64)).to_point();
let ray_direction = (pixel_center - camera_center).to_vec();
let ray = Ray::new(camera_center, ray_direction);
let (r, g, b) = color_ray(ray);
px.set_color(r, g, b);
} else {
}
}
}
img.save("./foo.ppm").unwrap();
}
fn color_ray(ray: Ray) -> (f64, f64, f64) {
let unit_direction = ray.direction().unit();
let a = 0.5 * unit_direction.y() + 1.0;
let b = 1.0 - a;
(b + 0.5 * a, b + 0.7 * a, b + 1.0 * a)
}

137
src/vec3.rs Normal file
View file

@ -0,0 +1,137 @@
use std::ops::{Add, Div, Mul, Sub};
#[derive(Clone, Copy)]
pub struct Vec3 {
x: f64,
y: f64,
z: f64,
}
#[derive(Clone, Copy)]
pub struct Point3 {
x: f64,
y: f64,
z: f64,
}
#[derive(Clone, Copy)]
pub struct Ray {
direction: Vec3,
origin: Point3,
}
impl Ray {
pub fn new(origin: Point3, direction: Vec3) -> Ray {
Ray { direction, origin }
}
pub fn at(self, t: f64) -> Point3 {
(self.origin.to_vec() + Vec3 { x: t, y: t, z: t } * self.direction).to_point()
}
pub fn direction(self) -> Vec3 {
self.direction
}
pub fn origin(self) -> Point3 {
self.origin
}
}
macro_rules! impl_op {
($trait:ident, $func:ident, $op:tt, $who:ident) => {
impl $trait for $who {
type Output = Self;
fn $func(self, other: Self) -> Self {
Self {
x: self.x $op other.x,
y: self.y $op other.y,
z: self.z $op other.z,
}
}
}
impl $trait<f64> for $who {
type Output = Self;
fn $func(self, value: f64) -> Self {
Self {
x: self.x $op value,
y: self.y $op value,
z: self.z $op value,
}
}
}
};
}
impl_op!(Add, add, +,Vec3);
impl_op!(Sub, sub, -,Vec3);
impl_op!(Mul, mul, *,Vec3);
impl_op!(Div, div, /,Vec3);
impl_op!(Add, add, +,Point3);
impl_op!(Sub, sub, -,Point3);
impl_op!(Mul, mul, *,Point3);
impl_op!(Div, div, /,Point3);
impl Point3 {
pub fn new(x: f64, y: f64, z: f64) -> Point3 {
Point3 { x, y, z }
}
pub fn to_vec(self) -> Vec3 {
Vec3 {
x: self.x,
y: self.y,
z: self.z,
}
}
}
impl Vec3 {
pub fn new(x: f64, y: f64, z: f64) -> Vec3 {
Vec3 { x, y, z }
}
pub fn full(v: f64) -> Vec3 {
Vec3 { x: v, y: v, z: v }
}
pub fn to_point(self) -> Point3 {
Point3 {
x: self.x,
y: self.y,
z: self.z,
}
}
pub fn dot(self, other: Vec3) -> f64 {
self.x * other.x + self.y * other.y + self.z * other.z
}
pub fn x(self) -> f64 {
self.x
}
pub fn y(self) -> f64 {
self.y
}
pub fn z(self) -> f64 {
self.z
}
pub fn cross(self, other: Vec3) -> Vec3 {
let x = self.y * other.z - self.z * other.y;
let y = self.y * other.x - self.x * other.y;
let z = self.x * other.y - self.y * other.x;
Vec3 { x, y, z }
}
pub fn length_squared(self) -> f64 {
self.x * self.x + self.y * self.y + self.z * self.z
}
pub fn length(self) -> f64 {
self.length_squared().sqrt()
}
pub fn unit(self) -> Vec3 {
self / self.length()
}
}