Ray Tracing in One Weekend on WebAssembly
A partial Ray Tracing in One Weekend running on the browser with WebAssembly, built by Zig.
Fat pointer interfaces in Zig
It is possible to implement the Go style interface values in Zig with the help of c_void
pointers.
Implementing interface pattern is wordier than in other languages (Go, Python, Rust, ...), but it is refreshing to see the mechanism that is hidden in other languages being trasparent when writing in Zig.
Following is an example of different material types implementing the Material interface type:
// Interface type
const Material = struct {
ptr: *const c_void,
vtable: *const VTable,
const VTable = struct {
scatter: fn (*const c_void, *Vec3, *Ray, Ray, HitRecord) bool,
};
fn scatter(self: @This(), out_attenuation: *Vec3, out_scattered: *Ray, in_ray: Ray, rec: HitRecord) bool {
return self.vtable.scatter(self.ptr, out_attenuation, out_scattered, in_ray, rec);
}
};
// Implementations
const Lambertian = struct {
albedo: Vec3,
const vtable = Material.VTable{
.scatter = scatter,
};
fn material(self: *const @This()) Material {
return Material{
.ptr = self,
.vtable = &vtable,
};
}
fn scatter(
ptr: *const c_void,
out_attenuation: *Vec3,
out_scattered: *Ray,
in_ray: Ray,
rec: HitRecord,
) bool {
_ = in_ray;
const self = @ptrCast(*const Lambertian, @alignCast(@alignOf(Lambertian), ptr));
var scatter_direction = vec3Add(rec.surface_normal, randomOnUnitSphere());
if (vec3NearZero(scatter_direction)) {
scatter_direction = rec.surface_normal;
}
out_scattered.* = Ray{ .origin = rec.p, .dir = scatter_direction };
out_attenuation.* = self.albedo;
return true;
}
};
const Metal = struct {
albedo: Vec3,
const vtable = Material.VTable{
.scatter = scatter,
};
fn material(self: *const @This()) Material {
return Material{
.ptr = self,
.vtable = &vtable,
};
}
fn scatter(
ptr: *const c_void,
out_attenuation: *Vec3,
out_scattered: *Ray,
in_ray: Ray,
rec: HitRecord,
) bool {
const self = @ptrCast(*const Metal, @alignCast(@alignOf(Metal), ptr));
const reflected = vec3Reflect(vec3Normalize(in_ray.dir), rec.surface_normal);
out_scattered.* = Ray{ .origin = rec.p, .dir = reflected };
out_attenuation.* = self.albedo;
return vec3Dot(out_scattered.dir, rec.surface_normal) > 0;
}
};
// Instantiate
const material_ground = Lambertian{ .albedo = vec3(0.8, 0.8, 0.0) };
var ground = Sphere{
.center = vec3(0, -100.5, -1),
.radius = 100.0,
.material = material_ground.material(),
};
// ...