Basic blue-to-white gradient
This commit is contained in:
commit
67fc7ea21d
7 changed files with 275 additions and 0 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
/target
|
||||
*.ppm
|
||||
7
Cargo.lock
generated
Normal file
7
Cargo.lock
generated
Normal 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
6
Cargo.toml
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
[package]
|
||||
name = "rust-ray"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
8
shell.nix
Normal file
8
shell.nix
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
{ pkgs ? import <nixpkgs> {} }:
|
||||
|
||||
pkgs.mkShell {
|
||||
buildInputs = with pkgs; [
|
||||
cargo
|
||||
rust-analyzer
|
||||
];
|
||||
}
|
||||
67
src/image.rs
Normal file
67
src/image.rs
Normal 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
48
src/main.rs
Normal 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
137
src/vec3.rs
Normal 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()
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue