2024-01-25 19:41:11 +00:00
|
|
|
package main
|
|
|
|
|
2024-01-29 16:25:29 +00:00
|
|
|
import "fmt" // for IO and standard library
|
|
|
|
import "os" // for handling the progress bar
|
|
|
|
import "math" // for maths
|
2024-02-28 14:20:31 +00:00
|
|
|
import "unsafe" // for fast inverse square pointers
|
|
|
|
import "math/rand" // for random
|
|
|
|
import "time" // for random
|
|
|
|
|
|
|
|
|
|
|
|
func init() {
|
|
|
|
rand.Seed(time.Now().UnixNano())
|
|
|
|
}
|
2024-01-25 19:41:11 +00:00
|
|
|
|
2024-01-28 13:32:04 +00:00
|
|
|
// ================ VEC3 CLASS =====================
|
|
|
|
|
|
|
|
type Vec3 struct {
|
|
|
|
E [3]float32
|
|
|
|
}
|
|
|
|
|
|
|
|
//Basic vector functions
|
|
|
|
|
|
|
|
func NewVec3(e0, e1, e2 float32) Vec3 {
|
|
|
|
return Vec3{E: [3]float32{e0, e1, e2}}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (v Vec3) X() float32 {
|
|
|
|
return v.E[0]
|
|
|
|
}
|
|
|
|
|
|
|
|
// can be executed as v.X() in main
|
|
|
|
|
|
|
|
func (v Vec3) Y() float32 {
|
|
|
|
return v.E[1]
|
|
|
|
}
|
|
|
|
|
|
|
|
func (v Vec3) Z() float32 {
|
|
|
|
return v.E[2]
|
|
|
|
}
|
|
|
|
|
|
|
|
func (v Vec3) Neg() Vec3 {
|
|
|
|
return NewVec3(-v.E[0], -v.E[1], -v.E[2])
|
|
|
|
}
|
|
|
|
|
|
|
|
func (v Vec3) Get(i int) float32 {
|
|
|
|
return v.E[i]
|
|
|
|
}
|
|
|
|
|
|
|
|
func (v *Vec3) Set(i int, val float32) {
|
|
|
|
v.E[i] = val
|
|
|
|
}
|
|
|
|
|
2024-02-03 21:48:01 +00:00
|
|
|
func (v Vec3) Add(v2 Vec3) Vec3 {
|
|
|
|
return NewVec3(v.E[0]+v2.E[0], v.E[1]+v2.E[1], v.E[2]+v2.E[2])
|
2024-01-28 13:32:04 +00:00
|
|
|
}
|
|
|
|
|
2024-02-03 21:48:01 +00:00
|
|
|
func (v Vec3) Mult(t float32) Vec3 {
|
|
|
|
return NewVec3(v.E[0]*t, v.E[1]*t, v.E[2]*t)
|
2024-01-28 13:32:04 +00:00
|
|
|
}
|
|
|
|
|
2024-02-03 21:48:01 +00:00
|
|
|
func (v Vec3) Div(t float32) Vec3 {
|
2024-01-28 13:32:04 +00:00
|
|
|
if t != 0 {
|
2024-02-03 21:48:01 +00:00
|
|
|
return NewVec3(v.E[0] / t, v.E[1] / t, v.E[2] / t)
|
2024-01-28 13:32:04 +00:00
|
|
|
}
|
2024-02-03 21:48:01 +00:00
|
|
|
return v
|
2024-01-28 13:32:04 +00:00
|
|
|
}
|
|
|
|
|
2024-02-03 21:48:01 +00:00
|
|
|
|
2024-01-28 13:32:04 +00:00
|
|
|
func (v Vec3) Length() float32 {
|
|
|
|
return float32(math.Sqrt(float64(v.E[0]*v.E[0] + v.E[1]*v.E[1] + v.E[2]*v.E[2])))
|
|
|
|
}
|
|
|
|
|
2024-02-27 13:01:12 +00:00
|
|
|
func (v Vec3) Length_squared() float32 {
|
|
|
|
return v.E[0]*v.E[0] + v.E[1]*v.E[1] + v.E[2]*v.E[2]
|
|
|
|
}
|
|
|
|
|
2024-03-02 09:48:53 +00:00
|
|
|
func (v Vec3) Near_zero() bool {
|
|
|
|
var s float64 = 1e-8
|
|
|
|
return (math.Abs(float64(v.E[0])) < s && math.Abs(float64(v.E[1])) < s && math.Abs(float64(v.E[2])) < s)
|
|
|
|
}
|
|
|
|
|
2024-01-28 13:32:04 +00:00
|
|
|
// Vector utility functions
|
|
|
|
|
|
|
|
func (v Vec3) String() string {
|
|
|
|
return fmt.Sprintf("%v %v %v", v.E[0], v.E[1], v.E[2])
|
|
|
|
}
|
|
|
|
|
2024-02-03 21:48:01 +00:00
|
|
|
func (v Vec3) Sub(v2 Vec3) Vec3 {
|
|
|
|
return NewVec3(v.E[0]-v2.E[0], v.E[1]-v2.E[1], v.E[2]-v2.E[2])
|
2024-01-28 13:32:04 +00:00
|
|
|
}
|
|
|
|
|
2024-02-03 21:48:01 +00:00
|
|
|
func (v Vec3) MultVec(v2 Vec3) Vec3 {
|
|
|
|
return NewVec3(v.E[0]*v2.E[0], v.E[1]*v2.E[1], v.E[2]*v2.E[2])
|
2024-01-28 13:32:04 +00:00
|
|
|
}
|
|
|
|
|
2024-02-03 21:48:01 +00:00
|
|
|
func (v Vec3) DivVec(v2 Vec3) Vec3 {
|
|
|
|
if v2.E[0] != 0 && v2.E[1] != 0 && v2.E[2] != 0 {
|
|
|
|
return NewVec3(v.E[0]/v2.E[0], v.E[1]/v2.E[1], v.E[2]/v2.E[2])
|
|
|
|
}
|
|
|
|
return v
|
2024-01-28 13:32:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func Dot(v1 Vec3, v2 Vec3) float32 {
|
|
|
|
return (v1.E[0]*v2.E[0] + v1.E[1]*v2.E[1] + v1.E[2]*v2.E[2])
|
|
|
|
}
|
|
|
|
|
|
|
|
func Cross(v1 Vec3, v2 Vec3) Vec3 {
|
|
|
|
return NewVec3(v1.E[1]*v2.E[2] - v1.E[2]*v2.E[1],
|
|
|
|
v1.E[2]*v2.E[0] - v1.E[0]*v2.E[2],
|
|
|
|
v1.E[0]*v2.E[1] - v1.E[1]*v2.E[0])
|
|
|
|
}
|
|
|
|
|
2024-02-12 12:05:10 +00:00
|
|
|
func q_rsqrt(v Vec3) float32 {
|
|
|
|
var x float32 = v.E[0]*v.E[0] + v.E[1]*v.E[1] + v.E[2]*v.E[2]
|
|
|
|
i := *(*int32)(unsafe.Pointer(&x)) // evil floating point bit level hacking
|
|
|
|
i = 0x5f3759df - (i >> 1) // what the fuck?
|
|
|
|
y := *(*float32)(unsafe.Pointer(&i))
|
|
|
|
return y
|
|
|
|
}
|
|
|
|
|
2024-01-28 13:32:04 +00:00
|
|
|
func Unit_vector(v Vec3) Vec3 {
|
2024-03-04 23:05:12 +00:00
|
|
|
//new_v := v.Mult(q_rsqrt(v))
|
2024-03-06 12:08:24 +00:00
|
|
|
new_v := v.Mult(1.0/v.Length())
|
2024-02-10 17:34:15 +00:00
|
|
|
return new_v
|
2024-01-28 13:32:04 +00:00
|
|
|
}
|
|
|
|
|
2024-02-28 20:35:39 +00:00
|
|
|
func RandomInUnitSphere() Vec3 {
|
|
|
|
for true {
|
|
|
|
p := RandomVec3(-1, 1)
|
|
|
|
if (p.Length_squared() < 1) {
|
|
|
|
return p
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return NewVec3(0,0,0)
|
|
|
|
}
|
|
|
|
|
|
|
|
func RandomUnitVector() Vec3 {
|
|
|
|
return Unit_vector(RandomInUnitSphere())
|
|
|
|
}
|
|
|
|
|
|
|
|
func RandomOnHemisphere(normal Vec3) Vec3 {
|
|
|
|
var on_unit_sphere Vec3 = RandomUnitVector()
|
|
|
|
if (Dot(on_unit_sphere, normal) > 0.0) {
|
|
|
|
return on_unit_sphere
|
|
|
|
} else {
|
|
|
|
return on_unit_sphere.Neg()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-03-02 09:48:53 +00:00
|
|
|
func Reflect(v Vec3, n Vec3) Vec3 {
|
|
|
|
return v.Sub(n.Mult(2*Dot(v,n)))
|
|
|
|
}
|
|
|
|
|
2024-03-06 12:08:24 +00:00
|
|
|
func Refract(uv, n Vec3, etai_over_etat float32) Vec3 {
|
|
|
|
cos_theta := float32(math.Min(float64(Dot(uv.Neg(), n)), 1.0))
|
|
|
|
r_out_perp := uv.Add(n.Mult(cos_theta)).Mult(etai_over_etat)
|
|
|
|
r_out_parallel := n.Mult(-float32(math.Sqrt(math.Abs(1.0 - float64(r_out_perp.Length_squared())))))
|
|
|
|
return r_out_perp.Add(r_out_parallel)
|
2024-03-03 19:07:26 +00:00
|
|
|
}
|
|
|
|
|
2024-02-27 13:01:12 +00:00
|
|
|
const pi float32 = 3.1415926535897932385
|
|
|
|
|
2024-03-06 12:08:24 +00:00
|
|
|
func Degrees_to_radians(degrees float32) float64 {
|
|
|
|
return float64((degrees * pi) / 180.0)
|
2024-02-27 13:01:12 +00:00
|
|
|
}
|
|
|
|
|
2024-02-29 14:16:04 +00:00
|
|
|
func Linear_to_gamma(linear_component float32) float32 {
|
|
|
|
return float32(math.Sqrt(float64(linear_component)))
|
|
|
|
}
|
|
|
|
|
2024-02-28 14:20:31 +00:00
|
|
|
func RandomDouble() float32 {
|
|
|
|
// Returns a random real in [0,1).
|
|
|
|
return rand.Float32()
|
|
|
|
}
|
|
|
|
|
|
|
|
func RandomDoubleInRange(min, max float32) float32 {
|
|
|
|
// Returns a random real in [min,max).
|
|
|
|
return min + (max-min)*RandomDouble()
|
|
|
|
}
|
|
|
|
|
2024-02-28 20:35:39 +00:00
|
|
|
func RandomVec3(borders ...float32) Vec3 {
|
|
|
|
if (len(borders) == 2) {
|
|
|
|
return NewVec3(RandomDoubleInRange(borders[0], borders[1]),RandomDoubleInRange(borders[0], borders[1]),RandomDoubleInRange(borders[0], borders[1]))
|
|
|
|
} else {
|
|
|
|
return NewVec3(RandomDouble(),RandomDouble(),RandomDouble())
|
|
|
|
}
|
|
|
|
}
|
2024-01-28 13:32:04 +00:00
|
|
|
// ============== COLOUR CLASS ==============
|
|
|
|
|
2024-02-28 14:20:31 +00:00
|
|
|
func Write_color(v Vec3, samples_per_pixel int) {
|
|
|
|
// Averaging
|
|
|
|
|
|
|
|
r := v.X() / float32(samples_per_pixel)
|
|
|
|
g := v.Y() / float32(samples_per_pixel)
|
|
|
|
b := v.Z() / float32(samples_per_pixel)
|
|
|
|
|
2024-02-29 14:16:04 +00:00
|
|
|
r = Linear_to_gamma(r)
|
|
|
|
g = Linear_to_gamma(g)
|
|
|
|
b = Linear_to_gamma(b)
|
|
|
|
|
2024-02-28 14:20:31 +00:00
|
|
|
// Write the translate [0, 255] value of each color component
|
|
|
|
intensity := NewInterval(0.000, 0.999)
|
|
|
|
fmt.Println(int(intensity.Clamp(r)* 256.0), int(intensity.Clamp(g)* 256.0), int(intensity.Clamp(b)* 256.0))
|
2024-01-28 13:32:04 +00:00
|
|
|
}
|
|
|
|
|
2024-02-03 21:48:01 +00:00
|
|
|
func NewColor(e0, e1, e2 float32) Vec3 {
|
|
|
|
return Vec3{E: [3]float32{e0, e1, e2}}
|
|
|
|
}
|
|
|
|
|
2024-01-29 16:25:29 +00:00
|
|
|
// ============== RAY CLASS =================
|
|
|
|
|
2024-02-03 21:48:01 +00:00
|
|
|
func NewPoint3(e0, e1, e2 float32) Vec3 {
|
|
|
|
return Vec3{E: [3]float32{e0, e1, e2}}
|
|
|
|
}
|
2024-01-29 16:25:29 +00:00
|
|
|
type Ray struct {
|
2024-02-03 21:48:01 +00:00
|
|
|
Orig Vec3
|
2024-01-29 16:25:29 +00:00
|
|
|
Dir Vec3
|
|
|
|
}
|
|
|
|
|
2024-02-03 21:48:01 +00:00
|
|
|
func NewRay(orig Vec3, dir Vec3) *Ray {
|
2024-01-29 16:25:29 +00:00
|
|
|
return &Ray{Orig: orig, Dir: dir}
|
|
|
|
}
|
|
|
|
|
2024-02-03 21:48:01 +00:00
|
|
|
func (r *Ray) Origin() Vec3 {
|
2024-01-29 16:25:29 +00:00
|
|
|
return r.Orig
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *Ray) Direction() Vec3 {
|
|
|
|
return r.Dir
|
|
|
|
}
|
|
|
|
|
2024-02-03 21:48:01 +00:00
|
|
|
func (r *Ray) At(t float32) Vec3 {
|
|
|
|
return Vec3{
|
|
|
|
E: [3]float32{
|
|
|
|
r.Orig.X() + t*r.Dir.X(),
|
|
|
|
r.Orig.Y() + t*r.Dir.Y(),
|
|
|
|
r.Orig.Z() + t*r.Dir.Z(),
|
|
|
|
},
|
2024-01-29 16:25:29 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-02-27 18:01:28 +00:00
|
|
|
// =============== INTERVAL =================
|
|
|
|
|
|
|
|
type Interval struct {
|
|
|
|
min, max float32
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewInterval(borders ...float32) *Interval {
|
|
|
|
if (len(borders) == 2) {
|
|
|
|
return &Interval{
|
|
|
|
min: borders[0],
|
|
|
|
max: borders[1],
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return &Interval{
|
|
|
|
min: float32(math.Inf(1)),
|
|
|
|
max: float32(math.Inf(-1)),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (i *Interval) Contains(x float32) bool {
|
|
|
|
return i.min <= x && x <= i.max
|
|
|
|
}
|
|
|
|
|
|
|
|
func (i *Interval) Surrounds(x float32) bool {
|
|
|
|
return i.min < x && x < i.max
|
|
|
|
}
|
|
|
|
|
2024-02-28 14:20:31 +00:00
|
|
|
func (i *Interval) Clamp(x float32) float32 {
|
|
|
|
if (x < i.min) {
|
|
|
|
return i.min
|
|
|
|
} else if (x > i.max) {
|
|
|
|
return i.max
|
|
|
|
}
|
|
|
|
return x
|
|
|
|
}
|
|
|
|
|
2024-02-27 18:01:28 +00:00
|
|
|
var Empty *Interval = NewInterval()
|
|
|
|
var Universe *Interval = NewInterval(float32(math.Inf(-1)), float32(math.Inf(1)))
|
|
|
|
|
2024-02-10 17:34:15 +00:00
|
|
|
|
2024-02-27 13:01:12 +00:00
|
|
|
// =============== HIT ======================
|
|
|
|
|
|
|
|
type Hit_record struct {
|
|
|
|
p Vec3
|
|
|
|
normal Vec3
|
|
|
|
t float32
|
|
|
|
front_face bool
|
2024-03-02 09:48:53 +00:00
|
|
|
mat Material
|
2024-02-27 13:01:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (rec *Hit_record) Set_face_normal(r *Ray, outward_normal Vec3) {
|
|
|
|
if (Dot(r.Direction(), outward_normal) < 0) {
|
|
|
|
rec.front_face = true
|
|
|
|
}
|
|
|
|
if (rec.front_face) {
|
|
|
|
rec.normal = outward_normal
|
|
|
|
} else {
|
|
|
|
rec.normal = outward_normal.Neg()
|
|
|
|
}
|
2024-02-03 21:48:01 +00:00
|
|
|
}
|
|
|
|
|
2024-02-10 17:34:15 +00:00
|
|
|
// =============== SPHERE ===================
|
|
|
|
|
2024-02-27 13:01:12 +00:00
|
|
|
type Sphere struct {
|
|
|
|
center Vec3
|
|
|
|
radius float32
|
2024-03-02 09:48:53 +00:00
|
|
|
mat Material
|
2024-02-27 13:01:12 +00:00
|
|
|
}
|
|
|
|
|
2024-02-27 18:01:28 +00:00
|
|
|
func (s Sphere) Hit_sphere(r *Ray, ray_t *Interval, rec *Hit_record) bool {
|
2024-02-27 13:01:12 +00:00
|
|
|
oc := r.Origin().Sub(s.center)
|
|
|
|
a := r.Direction().Length_squared()
|
|
|
|
half_b := Dot(oc, r.Direction())
|
|
|
|
c := oc.Length_squared() - s.radius*s.radius
|
|
|
|
|
|
|
|
discriminant := half_b*half_b - a*c
|
2024-02-12 12:05:10 +00:00
|
|
|
if (discriminant < 0) {
|
2024-02-27 13:01:12 +00:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
sqrtd := float32(math.Sqrt(float64(discriminant)))
|
|
|
|
|
|
|
|
// find the nearest root that lies in the acceptable range
|
|
|
|
root := (-half_b - sqrtd) / a
|
2024-02-27 18:01:28 +00:00
|
|
|
if (!ray_t.Surrounds(root)) {
|
2024-02-27 13:01:12 +00:00
|
|
|
root = (sqrtd-half_b) / a
|
2024-02-27 18:01:28 +00:00
|
|
|
if (!ray_t.Surrounds(root)) {
|
2024-02-27 13:01:12 +00:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
rec.t = root
|
|
|
|
rec.p = r.At(rec.t)
|
|
|
|
outward_normal := rec.p.Sub(s.center).Div(s.radius)
|
|
|
|
rec.Set_face_normal(r, outward_normal)
|
2024-03-02 09:48:53 +00:00
|
|
|
rec.mat = s.mat
|
2024-02-27 13:01:12 +00:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
// =============== HITTABLE ==================
|
|
|
|
type Hittable struct {
|
|
|
|
spheres []Sphere
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewHittable() *Hittable {
|
|
|
|
return &Hittable{}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (hl *Hittable) Add(s Sphere) {
|
|
|
|
hl.spheres = append(hl.spheres, s)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (hl *Hittable) Clear() {
|
|
|
|
hl.spheres = []Sphere{}
|
|
|
|
}
|
|
|
|
|
2024-02-27 18:01:28 +00:00
|
|
|
func (hl Hittable) Hit(r *Ray, ray_t Interval, rec *Hit_record) bool {
|
2024-02-27 13:01:12 +00:00
|
|
|
var temp_rec Hit_record
|
|
|
|
var hit_anything bool = false
|
2024-02-27 18:01:28 +00:00
|
|
|
var closest_so_far float32 = ray_t.max
|
2024-02-27 13:01:12 +00:00
|
|
|
|
|
|
|
for _, sphere := range hl.spheres {
|
2024-02-27 18:01:28 +00:00
|
|
|
if (sphere.Hit_sphere(r, NewInterval(ray_t.min, closest_so_far), &temp_rec)) {
|
2024-02-27 13:01:12 +00:00
|
|
|
hit_anything = true
|
|
|
|
closest_so_far = temp_rec.t
|
|
|
|
*rec = temp_rec
|
|
|
|
}
|
2024-02-10 17:34:15 +00:00
|
|
|
}
|
2024-02-27 13:01:12 +00:00
|
|
|
return hit_anything
|
|
|
|
}
|
|
|
|
|
2024-03-02 09:48:53 +00:00
|
|
|
// =============== MATERIAL =================
|
|
|
|
type Material struct {
|
2024-03-03 19:07:26 +00:00
|
|
|
material int
|
2024-03-02 09:48:53 +00:00
|
|
|
albedo Vec3
|
|
|
|
fuzz float32
|
2024-03-03 19:07:26 +00:00
|
|
|
ir float32
|
2024-03-02 09:48:53 +00:00
|
|
|
}
|
|
|
|
|
2024-03-03 19:07:26 +00:00
|
|
|
func NewMaterial(m int, a Vec3, f float32, i float32) Material {
|
2024-03-02 09:48:53 +00:00
|
|
|
if f >= 1.0 {
|
|
|
|
return Material{
|
2024-03-03 19:07:26 +00:00
|
|
|
material: m,
|
2024-03-02 09:48:53 +00:00
|
|
|
albedo : a,
|
|
|
|
fuzz: 1.0,
|
2024-03-03 19:07:26 +00:00
|
|
|
ir: i,
|
2024-03-02 09:48:53 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return Material{
|
2024-03-03 19:07:26 +00:00
|
|
|
material: m,
|
2024-03-02 09:48:53 +00:00
|
|
|
albedo : a,
|
|
|
|
fuzz: f,
|
2024-03-03 19:07:26 +00:00
|
|
|
ir: i,
|
2024-03-02 09:48:53 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2024-03-04 23:05:12 +00:00
|
|
|
func Reflectance(cosine float32, ref_idx float32) float32 {
|
|
|
|
// Use Schlick's approximation for reflectance
|
|
|
|
r0 := (1.0-ref_idx) / (1.0+ref_idx)
|
|
|
|
r0 = r0*r0
|
2024-03-06 12:08:24 +00:00
|
|
|
return r0 + (1.0-r0)*float32(math.Pow(1.0 - float64(cosine), 5))
|
2024-03-04 23:05:12 +00:00
|
|
|
}
|
|
|
|
|
2024-03-02 09:48:53 +00:00
|
|
|
func (mat Material) Scatter(r_in *Ray, rec *Hit_record, attenuation *Vec3, scattered *Ray) bool {
|
2024-03-03 19:07:26 +00:00
|
|
|
if (mat.material == 0) { //Lambertian
|
2024-03-02 09:48:53 +00:00
|
|
|
scatter_direction := rec.normal.Add(RandomUnitVector())
|
|
|
|
// Catch degenerate scatter direction
|
|
|
|
if scatter_direction.Near_zero() {
|
|
|
|
scatter_direction = rec.normal
|
|
|
|
}
|
|
|
|
|
|
|
|
*scattered = *NewRay(rec.p, scatter_direction)
|
|
|
|
*attenuation = mat.albedo
|
2024-03-03 19:07:26 +00:00
|
|
|
} else if (mat.material == 1) { //Metall
|
2024-03-02 09:48:53 +00:00
|
|
|
var reflected Vec3 = Reflect(Unit_vector(r_in.Direction()), rec.normal)
|
|
|
|
*scattered = *NewRay(rec.p, reflected.Add(RandomUnitVector().Mult(mat.fuzz)))
|
|
|
|
*attenuation = mat.albedo
|
|
|
|
if !(Dot(scattered.Direction(), rec.normal) > 0) {
|
|
|
|
return false
|
|
|
|
}
|
2024-03-03 19:07:26 +00:00
|
|
|
} else if (mat.material == 2) { //Dielectric
|
|
|
|
*attenuation = NewColor(1.0, 1.0, 1.0)
|
|
|
|
var refraction_ration float32
|
|
|
|
if rec.front_face {
|
2024-03-06 12:08:24 +00:00
|
|
|
refraction_ration = 1.0/mat.ir
|
2024-03-03 19:07:26 +00:00
|
|
|
} else {
|
|
|
|
refraction_ration = mat.ir
|
|
|
|
}
|
|
|
|
unit_direction := Unit_vector(r_in.Direction())
|
2024-03-04 23:05:12 +00:00
|
|
|
|
|
|
|
cos_theta := float32(math.Min(float64(Dot(unit_direction.Neg(), rec.normal)), 1.0))
|
|
|
|
sin_theta := float32(math.Sqrt(float64(1.0 - cos_theta*cos_theta)))
|
|
|
|
var cannot_refract bool
|
|
|
|
var direction Vec3
|
|
|
|
if (refraction_ration * sin_theta > 1.0) {
|
|
|
|
cannot_refract = true
|
|
|
|
}
|
|
|
|
|
|
|
|
if (cannot_refract || Reflectance(cos_theta, refraction_ration) > RandomDouble()) {
|
|
|
|
direction = Reflect(unit_direction, rec.normal)
|
|
|
|
} else {
|
|
|
|
direction = Refract(unit_direction, rec.normal, refraction_ration)
|
|
|
|
}
|
|
|
|
|
|
|
|
*scattered = *NewRay(rec.p, direction)
|
2024-03-02 09:48:53 +00:00
|
|
|
}
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2024-02-27 19:04:26 +00:00
|
|
|
// =============== CAMERA ===================
|
|
|
|
|
|
|
|
type Camera struct {
|
|
|
|
aspect_ratio float32
|
|
|
|
image_width int
|
|
|
|
image_height int
|
|
|
|
center Vec3
|
|
|
|
pixel00_loc Vec3
|
|
|
|
pixel_delta_u Vec3
|
|
|
|
pixel_delta_v Vec3
|
2024-02-28 14:20:31 +00:00
|
|
|
samples_per_pixel int
|
2024-02-29 09:08:28 +00:00
|
|
|
max_depth int
|
2024-03-06 12:08:24 +00:00
|
|
|
vfov float32
|
|
|
|
lookfrom Vec3
|
|
|
|
lookat Vec3
|
|
|
|
vup Vec3
|
|
|
|
u Vec3
|
|
|
|
v Vec3
|
|
|
|
w Vec3
|
2024-02-27 19:04:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func NewCamera() *Camera {
|
|
|
|
return &Camera{}
|
|
|
|
}
|
|
|
|
|
2024-02-29 09:08:28 +00:00
|
|
|
func Ray_color(r Ray, depth int, world *Hittable) Vec3 {
|
2024-02-27 13:01:12 +00:00
|
|
|
var rec Hit_record
|
2024-02-29 09:08:28 +00:00
|
|
|
|
|
|
|
if (depth <= 0) {
|
|
|
|
return NewColor(0,0,0)
|
|
|
|
}
|
|
|
|
if (world.Hit(&r, *NewInterval(0.001, float32(math.Inf(1))), &rec)) {
|
2024-03-02 09:48:53 +00:00
|
|
|
var scattered Ray
|
|
|
|
var attenuation Vec3
|
|
|
|
if ((rec.mat).Scatter(&r, &rec, &attenuation, &scattered)) {
|
|
|
|
return attenuation.MultVec(Ray_color(scattered, depth-1, world))
|
|
|
|
}
|
|
|
|
return NewColor(0.0,0.0,0.0)
|
2024-02-27 13:01:12 +00:00
|
|
|
}
|
|
|
|
unit_direction := Unit_vector(r.Direction())
|
|
|
|
a := (unit_direction.Y() + 1.0)*0.5
|
|
|
|
return NewColor(1.0,1.0,1.0).Mult(float32(1.0-a)).Add(NewColor(0.5,0.7,1.0).Mult(a))
|
2024-02-27 19:04:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (cam *Camera) Initialize() {
|
|
|
|
// Calculate the image height
|
|
|
|
cam.image_height = int(float32(cam.image_width) / cam.aspect_ratio)
|
|
|
|
if cam.image_height < 1 {
|
|
|
|
cam.image_height = 1
|
|
|
|
}
|
2024-03-06 12:08:24 +00:00
|
|
|
cam.center = cam.lookfrom
|
|
|
|
|
|
|
|
|
2024-02-27 19:04:26 +00:00
|
|
|
// Viewport
|
2024-03-06 12:08:24 +00:00
|
|
|
var focal_length float32 = (cam.lookfrom.Sub(cam.lookat)).Length()
|
|
|
|
theta := Degrees_to_radians(cam.vfov)
|
|
|
|
h := float32(math.Tan(theta/2.0))
|
|
|
|
var viewport_height float32 = 2.0 * h * focal_length
|
2024-02-27 19:04:26 +00:00
|
|
|
var viewport_width float32 = viewport_height * float32(cam.image_width)/float32(cam.image_height)
|
2024-03-06 12:08:24 +00:00
|
|
|
|
|
|
|
// Calculate the u,v,w unit basis vectors for the camera coordinate frame
|
|
|
|
cam.w = Unit_vector(cam.lookfrom.Sub(cam.lookat))
|
|
|
|
cam.u = Unit_vector(Cross(cam.vup, cam.w))
|
|
|
|
cam.v = Cross(cam.w, cam.u)
|
2024-02-27 19:04:26 +00:00
|
|
|
|
|
|
|
// Calculate the vectors across the horizontal and down the vertical viewport edges
|
2024-03-06 12:08:24 +00:00
|
|
|
viewport_u := cam.u.Mult(viewport_width)
|
|
|
|
viewport_v := cam.v.Mult(-viewport_height)
|
2024-02-27 13:01:12 +00:00
|
|
|
|
2024-02-27 19:04:26 +00:00
|
|
|
//Calculate the horizontal and vertical delta vectors from pixel to pixel
|
|
|
|
cam.pixel_delta_u = viewport_u.Div(float32(cam.image_width))
|
|
|
|
cam.pixel_delta_v = viewport_v.Div(float32(cam.image_height))
|
|
|
|
|
|
|
|
// Calculate the location of the upper left pixel
|
2024-03-06 12:08:24 +00:00
|
|
|
viewport_upper_left := cam.center.Sub(cam.w.Mult(focal_length)).Sub(viewport_u.Div(2)).Sub(viewport_v.Div(2))
|
2024-02-27 19:04:26 +00:00
|
|
|
cam.pixel00_loc = viewport_upper_left.Add((cam.pixel_delta_u.Add(cam.pixel_delta_v)).Div(2))
|
|
|
|
}
|
|
|
|
|
|
|
|
func (cam *Camera) Render(world *Hittable) {
|
|
|
|
cam.Initialize()
|
|
|
|
|
|
|
|
// Rendering
|
|
|
|
fmt.Println("P3")
|
|
|
|
fmt.Println(cam.image_width, " ", cam.image_height, "\n255")
|
|
|
|
for j := 0; j < cam.image_height; j++ {
|
|
|
|
fmt.Fprintf(os.Stderr, "\rScanlines remaining: %d ", cam.image_height-j)
|
|
|
|
for i := 0; i < cam.image_width; i++ {
|
2024-02-28 14:20:31 +00:00
|
|
|
pixel_color := NewColor(0,0,0)
|
|
|
|
for sample := 0; sample < cam.samples_per_pixel; sample++ {
|
|
|
|
r := cam.GetRay(i, j)
|
2024-02-29 09:08:28 +00:00
|
|
|
pixel_color = pixel_color.Add(Ray_color(*r, cam.max_depth, world))
|
2024-02-28 14:20:31 +00:00
|
|
|
}
|
|
|
|
Write_color(pixel_color, cam.samples_per_pixel)
|
2024-02-27 19:04:26 +00:00
|
|
|
}
|
|
|
|
}
|
2024-02-10 17:34:15 +00:00
|
|
|
}
|
|
|
|
|
2024-02-28 14:20:31 +00:00
|
|
|
func (cam *Camera) GetRay(i, j int) *Ray {
|
|
|
|
// Get a randomly sampled camera ray for the pixel at location i,j
|
|
|
|
pixel_center := cam.pixel00_loc.Add(cam.pixel_delta_u.Mult(float32(i))).Add(cam.pixel_delta_v.Mult(float32(j)))
|
|
|
|
pixel_sample := pixel_center.Add(cam.Pixel_sample_square())
|
|
|
|
|
|
|
|
ray_direction := pixel_sample.Sub(cam.center)
|
|
|
|
return NewRay(cam.center, ray_direction)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (cam *Camera) Pixel_sample_square() Vec3 {
|
|
|
|
// Returns a random point in the square surrounding a pixel at the origin
|
|
|
|
px := -0.5 + RandomDouble()
|
|
|
|
py := -0.5 + RandomDouble()
|
|
|
|
return (cam.pixel_delta_u.Mult(px).Add(cam.pixel_delta_v.Mult(py)))
|
|
|
|
}
|
|
|
|
|
2024-02-27 19:04:26 +00:00
|
|
|
|
|
|
|
|
2024-01-28 13:32:04 +00:00
|
|
|
// =============== MAIN =====================
|
2024-01-25 19:41:11 +00:00
|
|
|
func main() {
|
2024-03-06 12:08:24 +00:00
|
|
|
|
2024-03-02 09:48:53 +00:00
|
|
|
// Materials
|
2024-03-03 19:07:26 +00:00
|
|
|
material_ground := NewMaterial(0, NewColor(0.8, 0.8, 0.0), 0.0, 0.0)
|
2024-03-06 12:08:24 +00:00
|
|
|
material_center := NewMaterial(0, NewColor(0.1, 0.2, 0.5), 0.0, 0.0)
|
|
|
|
material_left := NewMaterial(2, NewColor(1.0, 1.0, 1.0), 0.0, 1.5)
|
2024-03-04 23:05:12 +00:00
|
|
|
material_right := NewMaterial(1, NewColor(0.8, 0.6, 0.2), 0.0, 0.0)
|
2024-03-02 09:48:53 +00:00
|
|
|
|
2024-02-27 19:04:26 +00:00
|
|
|
// World
|
|
|
|
|
|
|
|
world := NewHittable()
|
|
|
|
world.Add(Sphere{
|
2024-03-06 12:08:24 +00:00
|
|
|
center: NewVec3(0.0, -100.5, -1.0),
|
|
|
|
radius: 100.0,
|
2024-03-02 09:48:53 +00:00
|
|
|
mat: material_ground,
|
|
|
|
})
|
|
|
|
world.Add(Sphere{
|
2024-03-06 12:08:24 +00:00
|
|
|
center: NewVec3(0, 0, -1),
|
2024-02-27 19:04:26 +00:00
|
|
|
radius: 0.5,
|
2024-03-02 09:48:53 +00:00
|
|
|
mat: material_center,
|
2024-02-27 19:04:26 +00:00
|
|
|
})
|
|
|
|
world.Add(Sphere{
|
2024-03-06 12:08:24 +00:00
|
|
|
center: NewVec3(-1, 0, -1),
|
2024-03-02 09:48:53 +00:00
|
|
|
radius: 0.5,
|
|
|
|
mat: material_left,
|
|
|
|
})
|
2024-03-04 23:05:12 +00:00
|
|
|
world.Add(Sphere{
|
2024-03-06 12:08:24 +00:00
|
|
|
center: NewVec3(-1, 0, -1),
|
2024-03-04 23:05:12 +00:00
|
|
|
radius: -0.4,
|
|
|
|
mat: material_left,
|
|
|
|
})
|
2024-03-02 09:48:53 +00:00
|
|
|
world.Add(Sphere{
|
|
|
|
center: NewVec3(1.0, 0, -1.0),
|
|
|
|
radius: 0.5,
|
|
|
|
mat: material_right,
|
2024-02-27 19:04:26 +00:00
|
|
|
})
|
|
|
|
// Image
|
|
|
|
|
|
|
|
cam := NewCamera()
|
|
|
|
cam.aspect_ratio = 16.0 / 9.0
|
|
|
|
cam.image_width = 400
|
2024-02-29 09:08:28 +00:00
|
|
|
cam.samples_per_pixel = 100
|
|
|
|
cam.max_depth = 50
|
2024-03-06 12:08:24 +00:00
|
|
|
cam.vfov = 20
|
|
|
|
cam.lookfrom = NewVec3(-2,2,1)
|
|
|
|
cam.lookat = NewVec3(0,0,-1)
|
|
|
|
cam.vup = NewVec3(0,1,0)
|
2024-02-27 19:04:26 +00:00
|
|
|
cam.Render(world)
|
|
|
|
// INFO: The pixels are written out in rows.
|
|
|
|
// Image file can be created with
|
|
|
|
// go run main.go > image.ppm
|
2024-01-25 19:41:11 +00:00
|
|
|
}
|