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
|
|
|
# function OrthogonalCamera(o, θ, s)
|
|
|
|
# new(o, RotXYZ(0.0, 0.0, 0.0), s)
|
|
|
|
# end
|
|
|
|
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
|
|
|
|
screen_pixel_position = [Vec3(x, y, 0.0) + camera.origin for y = LinRange(Y/2, -Y/2, Ny), x = LinRange(-X/2, X/2, Nx)]
|
|
|
|
[Ray(p, 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
|
|
|
|
u = cross(up, w) |> normalize
|
|
|
|
v = cross(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
|
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
|
|
|
|
[Ray(origin, normalize(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
|
|
|
|
|
2024-01-20 22:53:43 +01:00
|
|
|
|
|
|
|
|
2024-01-20 14:56:37 +01:00
|
|
|
struct Ray{T<:AbstractFloat}
|
|
|
|
origin::Vec3{T}
|
|
|
|
direction::Vec3{T}
|
|
|
|
# function Ray{T}(o, d) where {T<:AbstractFloat}
|
|
|
|
# new(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
|
|
|
|
a = dot(ray.direction, ray.direction)
|
|
|
|
b = 2 * dot(p, ray.direction)
|
|
|
|
c = dot(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
|
|
|
|
|
|
|
|
function trace_ray(world::World, ray::Ray)::HitInfo
|
|
|
|
mindist = Float64 |> typemax |> prevfloat
|
|
|
|
hit = false
|
|
|
|
color = world.background
|
|
|
|
for obj in world.objects
|
|
|
|
hit, dist = check_hit(ray, obj)
|
|
|
|
if hit && dist < mindist
|
|
|
|
mindist = dist
|
|
|
|
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
|