From c86e8c7637a7368950add912b9bce244bbf9e689 Mon Sep 17 00:00:00 2001 From: Tim Vaughan Date: Mon, 20 Feb 2023 00:28:36 +0100 Subject: [PATCH] Initial commit. --- Primitives.lua | 16 ++++++++ Textures.lua | 27 +++++++++++++ Vector.lua | 107 +++++++++++++++++++++++++++++++++++++++++++++++++ raymarch.lua | 79 ++++++++++++++++++++++++++++++++++++ 4 files changed, 229 insertions(+) create mode 100644 Primitives.lua create mode 100644 Textures.lua create mode 100644 Vector.lua create mode 100644 raymarch.lua diff --git a/Primitives.lua b/Primitives.lua new file mode 100644 index 0000000..88a3849 --- /dev/null +++ b/Primitives.lua @@ -0,0 +1,16 @@ +-- Geometric primitives for raymarching +require "Vector" +local V = Vector + +local function make_sphere(centre, radius, texture) + return function(p) + return {dist = V.norm(p-centre) - radius, + texture = texture} + end +end + +Primitives = { + make_sphere = make_sphere +} + +return Primitives diff --git a/Textures.lua b/Textures.lua new file mode 100644 index 0000000..b1d1cb3 --- /dev/null +++ b/Textures.lua @@ -0,0 +1,27 @@ +-- Textures for raymarcher +require "Vector" +local V = Vector + +local function make_phong(lights, colour, amb, spec, shiny) + return function (location, ray_dir, normal, count) + local thiscol = {0, 0, 0} + local reflected_ray = V.normalize(normal*(ray_dir*normal*2) - ray_dir) + for _,light in ipairs(lights) do + local light_dir = V.normalize(light-location) + local Iamb = math.max(normal*light_dir, 0) + local Ispec = math.pow(math.max(light_dir*reflected_ray,0),shiny) + + thiscol[1] = thiscol[1] + colour[1]*Iamb*amb + Ispec*spec + thiscol[2] = thiscol[2] + colour[2]*Iamb*amb + Ispec*spec + thiscol[3] = thiscol[3] + colour[3]*Iamb*amb + Ispec*spec + end + + return thiscol + end +end + +Textures = { + make_phong = make_phong +} + +return Textures diff --git a/Vector.lua b/Vector.lua new file mode 100644 index 0000000..3f35b64 --- /dev/null +++ b/Vector.lua @@ -0,0 +1,107 @@ +-- Small package of 3D vector functions + +local mt = {} + +local function new(v) + local vec = {v[1], v[2], v[3]} + setmetatable(vec, mt) + return vec +end + +local function addvec(v, w) + return new{v[1]+w[1], v[2]+w[2], v[3]+w[3]} +end + +local function subvec(v, w) + return new{v[1]-w[1], v[2]-w[2], v[3]-w[3]} +end + +local function addnum(v, n) + return new{v[1] + n, v[2] + n, v[3] + n} +end + +local function subnum(v, n) + return new{v[1] - n, v[2] - n, v[3] - n} +end + +local function addany(v, x) + if type(x) == "table" then + return addvec(v, x) + else + return addnum(v, x) + end +end + +local function subany(v, x) + if type(x) == "table" then + return subvec(v, x) + else + return subnum(v, x) + end +end + +local function scale(v, n) + return new{v[1]*n, v[2]*n, v[3]*n} +end + +local function dot(v, w) + return v[1]*w[1] +v[2]*w[2] + v[3]*w[3] +end + +local function mulany(v, x) + if type(x) == "table" then + return dot(v, x) + else + return scale(v, x) + end +end + +local function cross(v, w) + return new {v[2]*w[3] - w[2]*v[3], + w[1]*v[3] - v[1]*w[3], + v[1]*w[2] - w[1]*v[2]} +end + +local function scaleinv(v,n) + return new{v[1]/n, v[2]/n, v[3]/n} +end + +local function neg(v) + return v*(-1) +end + +local function norm2(v) + return dot(v,v) +end + +local function norm(v) + return math.sqrt(norm2(v)) +end + +local function normalize(v) + return v/norm(v) +end + +local function vectostring(v) + return "{" .. v[1] .. ", " .. v[2] .. ", " .. v[3] .. "}" +end + +mt.__add = addany +mt.__sub = subany +mt.__mul = mulany +mt.__div = scaleinv +mt.__unm = neg +mt.__tostring = vectostring + +Vector = { + new = new, + norm = norm, + norm2 = norm2, + cross = cross, + normalize = normalize, + x = new{1,0,0}, + y = new{0,1,0}, + z = new{0,0,1} +} + +return Vector diff --git a/raymarch.lua b/raymarch.lua new file mode 100644 index 0000000..d296c23 --- /dev/null +++ b/raymarch.lua @@ -0,0 +1,79 @@ +require "Vector" +local V = Vector + +require "Primitives" +local P = Primitives + +require "Textures" +local T = Textures + +local eps = 0.01 +local max_dist = 50 +local bg_col = {1,0,1} + +local function calculate_normal(sdf, l) + local delta = 1e-3; + return -V.new{sdf(l+V.x*delta).dist-sdf(l-V.x*delta).dist, + sdf(l+V.y*delta).dist-sdf(l-V.y*delta).dist, + sdf(l+V.z*delta).dist-sdf(l-V.z*delta).dist}/(2*delta) +end + +local function march(location, ray_dir, sdf, count) + local p = sdf(location) + if p.dist < eps then + return p.texture(location, ray_dir, calculate_normal(sdf, location-(ray_dir*eps)), count) + elseif p.dist > max_dist then + return bg_col + else + return march(location + ray_dir*p.dist, + ray_dir, sdf, count+1) + end +end + +local function render(scene, width, height, filename) + print("Rendering to file " .. filename .. "...") + + local f = io.open(filename, "w") + f:write("P3 ", width, " ", height, " 255\n") + + local c = scene.camera + local cam_dir = V.normalize(c.point_at - c.location) + local right = V.normalize(c.right) + local up = V.cross(right,cam_dir) + local aspect = height/width; + + for y=1,height do + + if y % math.floor(height/10) == 0 then + print(y/math.floor(height/10) * 10 .. "%") + end + + local rayy = cam_dir + up*((y/height - 0.5)*c.fov*aspect) + + for x=1,width do + local ray_dir = V.normalize(rayy + right*((x/width - 0.5)*c.fov)) + local col = march(c.location, ray_dir, scene.sdf, 0) + col = {math.min(col[1]*255, 255), + math.min(col[2]*255, 255), + math.min(col[3]*255, 255)} + + f:write(math.floor(col[1]), " ", math.floor(col[2]), " ", math.floor(col[3]), " ") + end + f:write("\n") + end + f:write("\n") + + f:close() + + print("done") +end + +local scene = { + sdf = P.make_sphere(V.new{0,0,0}, 1, + T.make_phong({V.new{2,0,2}}, {0,1,0}, 1.0, 0.5, 100)), + camera = {location = V.new{0,-5,0}, + point_at = V.new{0,0,0}, + right = V.new{1,0,0}, + fov = 1}} + +render(scene, 640, 480, "test.ppm") -- 2.20.1