SBWO/SimpleRayTracer.jl

130 lines
3.1 KiB
Julia
Raw Normal View History

module SimpleRayTracer
using LinearAlgebra
using StaticArrays
using Images
using GeometryBasics
using Rotations
export OrthogonalCamera
export World, Ray
export Sphere
abstract type AbstractCamera end
abstract type AbstractObject end
struct OrthogonalCamera{T<:AbstractFloat} <: AbstractCamera
origin::Vec3{T}
size::Tuple{T, T}
rotation::RotXYZ{T}
end
function get_rays(camera::OrthogonalCamera, resolution::Tuple{I, I}) where {I<:Integer}
R = camera.rotation
X, Y = camera.size
Nx, Ny = resolution
2024-01-21 14:34:28 +01:00
screen_pixel_position = [R*Vec3(x, y, 0.0) + camera.origin for y = LinRange(Y/2, -Y/2, Ny), x = LinRange(-X/2, X/2, Nx)]
[Ray(p, R*Vec3(0.0, 0.0, 1.0)) for p in screen_pixel_position]
end
struct PinHoleCamera{T<:AbstractFloat} <: AbstractCamera
origin::Vec3{T}
distance::T
base::SMatrix{3, 3, T}
size::Tuple{T, T}
end
function PinHoleCamera(origin, lookAt, up, distance, size)
w = origin - lookAt |> normalize
2024-01-21 14:34:28 +01:00
u = up × w |> normalize
v = w × u
B = [u v w]
PinHoleCamera(origin, distance, B, size)
end
function get_rays(camera::PinHoleCamera, resolution::Tuple{I, I}) where {I<:Integer}
origin = camera.origin
d = camera.distance
X, Y = camera.size
B = camera.base
Nx, Ny = resolution
2024-01-23 22:50:51 +01:00
[Ray(origin, B*Vec3(x, y, -d)) for y = LinRange(Y/2, -Y/2, Ny), x = LinRange(X/2, -X/2, Nx)]
end
struct Ray{T<:AbstractFloat}
origin::Vec3{T}
direction::Vec3{T}
2024-01-21 14:34:28 +01:00
function Ray(o::Vec3{T}, d::Vec3{T}) where {T<:AbstractFloat}
new{T}(o, d |> normalize)
end
end
struct HitInfo
hit::Bool
color::RGB{N0f8}
end
struct World
objects::Vector
background::RGB{N0f8}
function World(color)
new([], color)
end
end
Base.push!(w::World, x::AbstractObject) = push!(w.objects, x)
struct Sphere{T<:AbstractFloat} <: AbstractObject
origin::Vec3{T}
radius::T
color::RGB{N0f8}
end
function check_hit(ray::Ray{T}, sphere::Sphere{T})::Tuple{Bool, T} where {T <: AbstractFloat}
p = ray.origin - sphere.origin
2024-01-21 14:34:28 +01:00
a = ray.direction ray.direction
b = 2 * (p ray.direction)
c = p p - sphere.radius^2
Δ = b^2 - 4a*c
Δ < 0.0 && return false, -1.0
x, z = -b/(2a), Δ/(2a)
t = x - z
t = t < eps() ? x + z : t
return t < eps() ? (false, -1.0) : (true, t)
end
2024-01-23 22:51:29 +01:00
struct Plane{T<:AbstractFloat} <: AbstractObject
point::Vec3{T}
normal::Vec3{T}
color::RGB
end
function check_hit(ray::Ray{T}, plane::Plane{T})::Tuple{Bool, T} where {T <: AbstractFloat}
t = (plane.point - ray.origin) plane.normal / (ray.direction plane.normal)
return t > eps() ? (true, t) : (false, -1.0)
end
function trace_ray(world::World, ray::Ray)::HitInfo
2024-01-23 22:51:29 +01:00
maxdist = Float64 |> typemax |> prevfloat
hit = false
color = world.background
for obj in world.objects
hit, dist = check_hit(ray, obj)
2024-01-23 22:51:29 +01:00
if hit && dist < maxdist
maxdist = dist
hit = true
color = obj.color
end
end
return HitInfo(hit, color)
end
function render(world, camera, resolution)
rays = get_rays(camera, resolution)
[trace_ray(world, ray).color for ray in rays];
end
end