From 96c21470a2064c07d8060de283812392e87c9150 Mon Sep 17 00:00:00 2001 From: Martin Lehoux Date: Sun, 5 Apr 2020 22:46:24 -0700 Subject: feat - Add Glass material --- hitable.go | 39 +++++++++++++++++++------------------- material.go | 63 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- vec3d.go | 7 +++++++ 3 files changed, 88 insertions(+), 21 deletions(-) diff --git a/hitable.go b/hitable.go index ef5822f..9f612d9 100644 --- a/hitable.go +++ b/hitable.go @@ -2,6 +2,7 @@ package main import "math" +// HitRecord contains data of a ray hiting a hitable type HitRecord struct { time float64 point Vec3D @@ -9,44 +10,44 @@ type HitRecord struct { material Material } +// Hitable is a physical volume a Ray can hit type Hitable interface { - Hit(ray Ray, t_min float64, t_max float64) HitRecord + Hit(ray Ray, tmin float64, tmax float64) HitRecord Normal(point Vec3D, ray Ray) Vec3D } +// HitableList ... type HitableList []Hitable -func (hitable_list HitableList) Hit(ray Ray, t_min float64, t_max float64) HitRecord { - closest_hit := HitRecord{T_MAX, Vec3D{}, Vec3D{}, nil} - for _, hitable := range hitable_list { - hit := hitable.Hit(ray, t_min, closest_hit.time) +// Hit finds the closest hit of all hitables +func (hitableList HitableList) Hit(ray Ray, tmin float64, tmax float64) HitRecord { + closestHit := HitRecord{TMax, Vec3D{}, Vec3D{}, nil} + for _, hitable := range hitableList { + hit := hitable.Hit(ray, tmin, closestHit.time) if hit.time > 0 { - closest_hit = hit + closestHit = hit } } - if closest_hit.time < T_MAX { - return closest_hit - } else { - return HitRecord{} + if closestHit.time < TMax { + return closestHit } + return HitRecord{} } +// Sphere ... type Sphere struct { center Vec3D radius float64 material Material } +// Normal ... func (sphere Sphere) Normal(point Vec3D, ray Ray) Vec3D { - out_normal := SubVec3D(point, sphere.center).Unit() - if DotProduct(ray.direction, out_normal) < 0.0 { - return out_normal - } else { - return out_normal.Invert() - } + return SubVec3D(point, sphere.center).Unit() } -func (sphere Sphere) Hit(ray Ray, t_min float64, t_max float64) HitRecord { +// Hit ... +func (sphere Sphere) Hit(ray Ray, tmin float64, tmax float64) HitRecord { oc := SubVec3D(ray.origin, sphere.center) a := DotProduct(ray.direction, ray.direction) b := 2.0 * DotProduct(oc, ray.direction) @@ -55,12 +56,12 @@ func (sphere Sphere) Hit(ray Ray, t_min float64, t_max float64) HitRecord { if discriminant > 0 { root := math.Sqrt(discriminant) time := (-b - root) / (2.0 * a) - if t_min < time && time < t_max { + if tmin < time && time < tmax { point := ray.PointAt(time) return HitRecord{time, point, sphere.Normal(point, ray), sphere.material} } time = (-b + root) / (2.0 * a) - if t_min < time && time < t_max { + if tmin < time && time < tmax { point := ray.PointAt(time) return HitRecord{time, point, sphere.Normal(point, ray), sphere.material} } diff --git a/material.go b/material.go index a36fb52..4667bd6 100644 --- a/material.go +++ b/material.go @@ -1,27 +1,86 @@ package main +import ( + "math" + "math/rand" +) + +// Material generate color when hitable is hit type Material interface { Scatter(ray Ray, hit HitRecord) (Ray, Color) } +// Lambertian is diffuse material type Lambertian struct { albedo Color } +// Scatter for Lambertians func (lambertian Lambertian) Scatter(ray Ray, hit HitRecord) (Ray, Color) { // TODO: Use the correct distribution - direction := AddVec3D(hit.normal, RandomInUnitSphere()) + var normal Vec3D + if DotProduct(ray.direction, hit.normal) < 0.0 { + normal = hit.normal + } else { + normal = hit.normal.Invert() + } + direction := AddVec3D(normal, RandomInUnitSphere()) scattered := Ray{hit.point, direction} return scattered, lambertian.albedo } +// Metal material type Metal struct { albedo Color fuzz float64 } +// Scatter for Metal func (metal Metal) Scatter(ray Ray, hit HitRecord) (Ray, Color) { - reflected := AddVec3D(ray.direction.Unit().Reflect(hit.normal), ScalarProduct(metal.fuzz, RandomInUnitSphere())) + var normal Vec3D + if DotProduct(ray.direction, hit.normal) < 0.0 { + normal = hit.normal + } else { + normal = hit.normal.Invert() + } + reflected := AddVec3D(ray.direction.Unit().Reflect(normal), ScalarProduct(metal.fuzz, RandomInUnitSphere())) scattered := Ray{hit.point, reflected} return scattered, metal.albedo } + +// Dielectric is glass for instance +type Dielectric struct { + eta float64 +} + +// Scatter for Dielectric +func (dielectric Dielectric) Scatter(ray Ray, hit HitRecord) (Ray, Color) { + var etaI, etaT float64 + var normal Vec3D + attenuation := Color{1.0, 1.0, 1.0} + if DotProduct(ray.direction, hit.normal) < 0.0 { + etaI, etaT = 1.0, dielectric.eta + normal = hit.normal + } else { + etaI, etaT = 1.0, dielectric.eta + normal = hit.normal.Invert() + } + vecI := ray.direction.Unit() + cosTheta := FMin(DotProduct(vecI.Invert(), normal), 1.0) + sinTheta := math.Sqrt(1.0 - cosTheta*cosTheta) + if etaI/etaT*sinTheta > 1.0 || rand.Float64() < Schlick(cosTheta, etaI, etaT) { + reflected := ray.direction.Unit().Reflect(normal) + scattered := Ray{hit.point, reflected} + return scattered, attenuation + } + refracted := Refract(vecI, normal, etaI, etaT) + scattered := Ray{hit.point, refracted} + return scattered, attenuation +} + +// Schlick glass refraction probability +func Schlick(cosine float64, etaI float64, etaT float64) float64 { + r0 := (1.0 - etaI/etaT) / (1.0 + etaI/etaT) + r0 *= r0 + return r0 + (1.0-r0)*math.Pow(1.0-cosine, 5) +} diff --git a/vec3d.go b/vec3d.go index 9e4ced0..5b2a691 100644 --- a/vec3d.go +++ b/vec3d.go @@ -29,6 +29,13 @@ func (vec Vec3D) Reflect(normal Vec3D) Vec3D { return SubVec3D(vec, ScalarProduct(2*DotProduct(vec, normal), normal)) } +func Refract(vecI Vec3D, normal Vec3D, etaI float64, etaT float64) Vec3D { + cosTheta := FMin(DotProduct(vecI.Invert(), normal), 1.0) + vecTPar := ScalarProduct(etaI/etaT, AddVec3D(vecI, ScalarProduct(cosTheta, normal))) + vecTPer := ScalarProduct(-math.Sqrt(1.0-DotProduct(vecTPar, vecTPar)), normal) + return AddVec3D(vecTPar, vecTPer) +} + func AddVec3D(vec1 Vec3D, vec2 Vec3D) Vec3D { return Vec3D{vec1.x + vec2.x, vec1.y + vec2.y, vec1.z + vec2.z} } -- cgit v1.3