1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
|
const std = @import("std");
const debug = std.debug;
const math = std.math;
const Ray = @import("ray.zig").Ray;
const vec = @import("vec.zig");
const Vec3 = vec.Vec3;
const rgb = vec.rgb;
const Color = vec.Color;
const HitRecord = @import("hit_record.zig").HitRecord;
const Texture = @import("texture.zig").Texture;
const rand = @import("rand.zig");
const Random = rand.Random;
const randomPointInUnitSphere = rand.randomPointInUnitSphere;
const randomUnitVector = rand.randomUnitVector;
const randomReal01 = rand.randomReal01;
const MaterialTag = enum {
diffuse,
metal,
dielectric,
};
pub const Material = union(MaterialTag) {
diffuse: DiffuseMaterial,
metal: MetalMaterial,
dielectric: DielectricMaterial,
pub fn scatter(mat: Material, r_in: Ray, record: HitRecord, attenuation: *Color, scattered: *Ray, rng: Random) bool {
return switch (mat) {
MaterialTag.diffuse => |diffuse_mat| diffuse_mat.scatter(r_in, record, attenuation, scattered, rng),
MaterialTag.metal => |metal_mat| metal_mat.scatter(r_in, record, attenuation, scattered, rng),
MaterialTag.dielectric => |dielectric_mat| dielectric_mat.scatter(r_in, record, attenuation, scattered, rng),
};
}
};
pub const DiffuseMaterial = struct {
albedo: Texture,
fn scatter(mat: DiffuseMaterial, r_in: Ray, record: HitRecord, attenuation: *Color, scattered: *Ray, rng: Random) bool {
var scatter_direction = record.normal.add(randomUnitVector(rng));
if (scatter_direction.near_zero()) {
scatter_direction = record.normal;
}
scattered.* = .{ .origin = record.p, .dir = scatter_direction, .time = r_in.time };
attenuation.* = mat.albedo.value(record.u, record.v, record.p);
return true;
}
};
pub const MetalMaterial = struct {
albedo: Color,
fuzz: f64,
fn scatter(mat: MetalMaterial, r_in: Ray, record: HitRecord, attenuation: *Color, scattered: *Ray, rng: Random) bool {
debug.assert(mat.fuzz <= 1.0);
const reflected = reflect(r_in.dir.normalized(), record.normal);
scattered.* = .{ .origin = record.p, .dir = reflected.add(randomPointInUnitSphere(rng).mul(mat.fuzz)), .time = r_in.time };
attenuation.* = mat.albedo;
return reflected.dot(record.normal) > 0.0;
}
};
pub const DielectricMaterial = struct {
// index of refraction.
ir: f64,
fn scatter(mat: DielectricMaterial, r_in: Ray, record: HitRecord, attenuation: *Color, scattered: *Ray, rng: Random) bool {
const refraction_ratio = if (record.front_face) 1.0 / mat.ir else mat.ir;
const unit_dir = r_in.dir.normalized();
const cos_theta = @min(unit_dir.mul(-1.0).dot(record.normal), 1.0);
const sin_theta = @sqrt(1.0 - cos_theta * cos_theta);
// sin(theta') = refraction_ratio * sin(theta) <= 1
const can_refract = refraction_ratio * sin_theta <= 1.0;
const dir = if (can_refract and reflectance(cos_theta, refraction_ratio) < randomReal01(rng)) refract(unit_dir, record.normal, refraction_ratio) else reflect(unit_dir, record.normal);
scattered.* = .{ .origin = record.p, .dir = dir, .time = r_in.time };
attenuation.* = rgb(1.0, 1.0, 1.0);
return true;
}
fn reflectance(cos: f64, refraction_idx: f64) f64 {
const r0 = (1.0 - refraction_idx) / (1.0 + refraction_idx);
const r1 = r0 * r0;
return r1 + (1.0 - r1) * math.pow(f64, 1.0 - cos, 5.0);
}
};
fn reflect(v: Vec3, n: Vec3) Vec3 {
return v.sub(n.mul(2 * v.dot(n)));
}
fn refract(uv: Vec3, n: Vec3, etai_over_etat: f64) Vec3 {
const cos_theta = @min(uv.mul(-1.0).dot(n), 1.0);
const r_out_perpendicular = uv.add(n.mul(cos_theta)).mul(etai_over_etat);
const r_out_parallel = n.mul(-@sqrt(@fabs(1.0 - r_out_perpendicular.normSquared())));
return r_out_perpendicular.add(r_out_parallel);
}
|