diff --git a/src/main.zig b/src/main.zig --- a/src/main.zig +++ b/src/main.zig @@ -28,8 +28,8 @@ // Setting up the world var material_ground = Material.lambertian(zm.f32x4(0.8, 0.8, 0.0, 1.0)); - var material_center = Material.lambertian(zm.f32x4(0.7, 0.3, 0.3, 1.0)); - var material_left = Material.metal(zm.f32x4(0.8, 0.8, 0.8, 1.0), 0.0); + var material_center = Material.lambertian(zm.f32x4(0.1, 0.2, 0.5, 1.0)); + var material_left = Material.dielectric(1.0 / 1.33); var material_right = Material.metal(zm.f32x4(0.8, 0.6, 0.2, 1.0), 1.0); var world = HittableList.init(allocator); diff --git a/src/material.zig b/src/material.zig --- a/src/material.zig +++ b/src/material.zig @@ -7,6 +7,7 @@ pub const Material = union(enum) { lambertian: Lambertian, metal: Metal, + dielectric: Dielectric, pub fn lambertian(albedo: zm.Vec) Material { return .{ .lambertian = .{ .albedo = albedo } }; @@ -16,10 +17,15 @@ return .{ .metal = .{ .albedo = albedo, .fuzz = if (fuzz < 1) fuzz else 1.0 } }; } + pub fn dielectric(refraction_index: f32) Material { + return .{ .dielectric = .{ .refraction_index = refraction_index } }; + } + pub fn scatter(self: *Material, r: *Ray, rec: *hittable.HitRecord, attenuation: *zm.Vec) ?Ray { return switch (self.*) { .lambertian => |*lambert| lambert.scatter(rec, attenuation), .metal => |*met| met.scatter(r, rec, attenuation), + .dielectric => |*die| die.scatter(r, rec, attenuation), }; } }; @@ -47,5 +53,29 @@ const scattered = Ray.init(rec.p, reflected + zm.f32x4s(self.fuzz) * util.randomUnitVec()); attenuation.* = self.albedo; return if (zm.dot3(scattered.dir, rec.normal)[0] > 0) scattered else null; + } +}; + +pub const Dielectric = struct { + refraction_index: f32, + + pub fn scatter(self: *Dielectric, r: *Ray, rec: *hittable.HitRecord, attenuation: *zm.Vec) ?Ray { + attenuation.* = zm.f32x4s(1.0); + const ri = if (rec.front_face) (1.0 / self.refraction_index) else self.refraction_index; + + const unit_direction = zm.normalize3(r.dir); + const cos_theta = @min(zm.dot3(-unit_direction, rec.normal)[0], 1.0); + const sin_theta = @sqrt(1.0 - cos_theta * cos_theta); + + const cannot_refract = ri * sin_theta > 1.0; + const direction = blk: { + if (cannot_refract) { + break :blk util.reflect(unit_direction, rec.normal); + } else { + break :blk util.refract(unit_direction, rec.normal, ri); + } + }; + + return Ray.init(rec.p, direction); } }; diff --git a/src/util.zig b/src/util.zig --- a/src/util.zig +++ b/src/util.zig @@ -61,6 +61,13 @@ return (@abs(e[0]) < s) and (@abs(e[1]) < s) and (@abs(e[2]) < s); } -pub fn reflect(v: zm.Vec, n: zm.Vec) zm.Vec { +pub inline fn reflect(v: zm.Vec, n: zm.Vec) zm.Vec { return v - zm.f32x4s(2 * zm.dot3(v, n)[0]) * n; +} + +pub inline fn refract(uv: zm.Vec, n: zm.Vec, etai_over_etat: f32) zm.Vec { + const cos_theta = @min(zm.dot3(-uv, n)[0], 1.0); + const r_out_perp = zm.f32x4s(etai_over_etat) * (uv + zm.f32x4s(cos_theta) * n); + const r_out_parallel = zm.f32x4s(-@sqrt(@abs(1.0 - zm.lengthSq3(r_out_perp)[0]))) * n; + return r_out_perp + r_out_parallel; }