2024-01-20 14:56:37 +01:00
|
|
|
|
module SimpleRayTracer
|
|
|
|
|
|
|
|
|
|
using LinearAlgebra
|
2024-01-20 22:53:43 +01:00
|
|
|
|
using StaticArrays
|
|
|
|
|
|
2024-01-20 14:56:37 +01:00
|
|
|
|
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}
|
2024-01-20 22:53:43 +01:00
|
|
|
|
rotation::RotXYZ{T}
|
2024-01-20 14:56:37 +01:00
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function get_rays(camera::OrthogonalCamera, resolution::Tuple{I, I}) where {I<:Integer}
|
2024-01-20 22:53:43 +01:00
|
|
|
|
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]
|
2024-01-20 22:53:43 +01:00
|
|
|
|
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
|
2024-01-20 22:53:43 +01:00
|
|
|
|
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
|
2024-01-20 14:56:37 +01:00
|
|
|
|
X, Y = camera.size
|
2024-01-20 22:53:43 +01:00
|
|
|
|
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)]
|
2024-01-20 14:56:37 +01:00
|
|
|
|
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
|
2024-01-20 14:56:37 +01:00
|
|
|
|
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
|
2024-01-20 14:56:37 +01:00
|
|
|
|
Δ = 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
|
|
|
|
|
|
|
|
|
|
|
2024-01-20 14:56:37 +01:00
|
|
|
|
function trace_ray(world::World, ray::Ray)::HitInfo
|
2024-01-23 22:51:29 +01:00
|
|
|
|
maxdist = Float64 |> typemax |> prevfloat
|
2024-01-20 14:56:37 +01:00
|
|
|
|
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
|
2024-01-20 14:56:37 +01:00
|
|
|
|
hit = true
|
|
|
|
|
color = obj.color
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
return HitInfo(hit, color)
|
|
|
|
|
end
|
|
|
|
|
|
2024-01-20 22:53:43 +01:00
|
|
|
|
function render(world, camera, resolution)
|
|
|
|
|
rays = get_rays(camera, resolution)
|
|
|
|
|
[trace_ray(world, ray).color for ray in rays];
|
|
|
|
|
end
|
|
|
|
|
|
2024-01-20 14:56:37 +01:00
|
|
|
|
end
|