func BloomFilter(img [][]geometry.Vec3, depth int) [][]geometry.Vec3 { data := make([][]geometry.Vec3, len(img)) for i, _ := range data { data[i] = make([]geometry.Vec3, len(img[0])) } const box_width = 2 factor := geometry.Float(1.0 / math.Pow(2*box_width+1, 2)) source := img for iteration := 0; iteration < depth; iteration++ { for y := box_width; y < len(img)-box_width; y++ { for x := box_width; x < len(img[0])-box_width; x++ { var colour geometry.Vec3 for dy := -box_width; dy <= box_width; dy++ { for dx := -box_width; dx <= box_width; dx++ { colour.AddInPlace(source[y+dy][x+dx]) } } data[y][x] = colour.Mult(factor) } } fmt.Printf("\rPost Processing %3.0f%% \r", 100*float64(iteration)/float64(depth)) source, data = data, source } return source }
func MonteCarloPixel(results chan Result, scene *geometry.Scene, diffuseMap /*, causticsMap*/ *kd.KDNode, start, rows int, rand *rand.Rand) { samples := Config.NumRays var px, py, dy, dx geometry.Float var direction, contribution geometry.Vec3 for y := start; y < start+rows; y++ { py = scene.Height - scene.Height*2*geometry.Float(y)/geometry.Float(scene.Rows) for x := 0; x < scene.Cols; x++ { px = -scene.Width + scene.Width*2*geometry.Float(x)/geometry.Float(scene.Cols) var colourSamples geometry.Vec3 if x >= Config.Skip.Left && x < scene.Cols-Config.Skip.Right && y >= Config.Skip.Top && y < scene.Rows-Config.Skip.Bottom { for sample := 0; sample < samples; sample++ { dy, dx = geometry.Float(rand.Float32())*scene.PixH, geometry.Float(rand.Float32())*scene.PixW direction = geometry.Vec3{ px + dx - scene.Camera.Origin.X, py + dy - scene.Camera.Origin.Y, -scene.Camera.Origin.Z, }.Normalize() contribution = Radiance(geometry.Ray{scene.Camera.Origin, direction}, scene, diffuseMap /*causticsMap,*/, 0, 1.0, rand) colourSamples.AddInPlace(contribution) } } results <- Result{x, y, colourSamples.Mult(1.0 / geometry.Float(samples))} } } }
func ClosestIntersection(shapes []*geometry.Shape, ray geometry.Ray) (*geometry.Shape, geometry.Float) { var closest *geometry.Shape bestHit := geometry.Float(math.Inf(+1)) for _, shape := range shapes { if hit := shape.Intersects(&ray); hit > 0 && hit < bestHit { bestHit = hit closest = shape } } return closest, bestHit }
func DiffusePhoton(scene []*geometry.Shape, emitter *geometry.Shape, ray geometry.Ray, colour geometry.Vec3, result chan<- PhotonHit, alpha geometry.Float, depth int, rand *rand.Rand) { if geometry.Float(rand.Float32()) > alpha { return } if shape, distance := ClosestIntersection(scene, ray); shape != nil { impact := ray.Origin.Add(ray.Direction.Mult(distance)) if depth == 0 && emitter == shape { // Leave the emitter first nextRay := geometry.Ray{impact, ray.Direction} DiffusePhoton(scene, emitter, nextRay, colour, result, alpha, depth, rand) } else { normal := shape.NormalDir(impact).Normalize() reverse := ray.Direction.Mult(-1) outgoing := normal if normal.Dot(reverse) < 0 { outgoing = normal.Mult(-1) } strength := colour.Mult(alpha / (1 + distance)) result <- PhotonHit{impact, strength, ray.Direction, uint8(depth)} if shape.Material == geometry.DIFFUSE { // Random bounce for color bleeding u := normal.Cross(reverse).Normalize().Mult(geometry.Float(rand.NormFloat64() * 0.5)) v := u.Cross(normal).Normalize().Mult(geometry.Float(rand.NormFloat64() * 0.5)) bounce := geometry.Vec3{ u.X + outgoing.X + v.X, u.Y + outgoing.Y + v.Y, u.Z + outgoing.Z + v.Z, } bounceRay := geometry.Ray{impact, bounce.Normalize()} bleedColour := colour.MultVec(shape.Colour).Mult(alpha / (1 + distance)) DiffusePhoton(scene, shape, bounceRay, bleedColour, result, alpha*0.66, depth+1, rand) } // Store Shadow Photons shadowRay := geometry.Ray{impact, ray.Direction} DiffusePhoton(scene, shape, shadowRay, geometry.Vec3{0, 0, 0}, result, alpha*0.66, depth+1, rand) } } }
func EmitterSampling(point, normal geometry.Vec3, shapes []*geometry.Shape, rand *rand.Rand) geometry.Vec3 { incomingLight := geometry.Vec3{0, 0, 0} for _, shape := range shapes { if !shape.Emission.IsZero() { // It's a light source direction := shape.NormalDir(point).Mult(-1) u := direction.Cross(normal).Normalize().Mult(geometry.Float(rand.NormFloat64() * 0.3)) v := direction.Cross(u).Normalize().Mult(geometry.Float(rand.NormFloat64() * 0.3)) direction.X += u.X + v.X direction.Y += u.Y + v.Y direction.Z += u.Z + v.Z ray := geometry.Ray{point, direction.Normalize()} if object, distance := ClosestIntersection(shapes, ray); object == shape { incomingLight.AddInPlace(object.Emission.Mult(direction.Dot(normal) / (1 + distance))) } } } return incomingLight }
func PhotonChunk(scene []*geometry.Shape, traceFunc RayFunc, shape *geometry.Shape, factor, start, chunksize int, result chan<- PhotonHit, done chan<- bool, rand *rand.Rand) { for i := 0; i < chunksize; i++ { longitude := (start*chunksize + i) / factor latitude := (start*chunksize + i) % factor //fmt.Println("Lo La:", longitude, latitude) sign := -2.0*float64(longitude%2.0) + 1.0 phi := 2.0 * math.Pi * float64(longitude) / float64(factor) theta := math.Pi * float64(latitude) / float64(factor) //fmt.Println("S, T, P:", sign, theta, phi) x, y, z := math.Sin(theta)*math.Cos(phi), sign*math.Cos(theta), math.Sin(theta)*math.Sin(phi) direction := geometry.Vec3{geometry.Float(x), geometry.Float(y), geometry.Float(z)} ray := geometry.Ray{shape.Position, direction.Normalize()} traceFunc(scene, shape, ray, shape.Emission, result, 1.0, 0, rand) } done <- true }
func CorrectColour(x geometry.Float) geometry.Float { return geometry.Float(math.Pow(float64(x), 1.0/Config.GammaFactor)*255 + 0.5) }
func Render(scene geometry.Scene) image.Image { img := image.NewNRGBA(image.Rect(0, 0, scene.Cols, scene.Rows)) pixels := make(chan Result, 128) workload := scene.Rows / Config.Chunks startTime := time.Now() globals /*, caustics*/ := GenerateMaps(scene.Objects) fmt.Println(" Done!") //fmt.Printf("Diffuse Map depth: %v Caustics Map depth: %v\n", globals.Depth(), caustics.Depth()) fmt.Printf("Diffuse Map depth: %v\n", globals.Depth()) fmt.Printf("Photon Maps Done. Generation took: ") stopTime := time.Now() PrintDuration(stopTime.Sub(startTime)) fmt.Println() startTime = time.Now() for y := 0; y < scene.Rows; y += workload { go MonteCarloPixel(pixels, &scene, globals /*caustics,*/, y, workload, rand.New(rand.NewSource(rand.Int63()))) } // Write targets for after effects data := make([][]geometry.Vec3, scene.Rows) peaks := make([][]geometry.Vec3, scene.Rows) for i, _ := range data { data[i] = make([]geometry.Vec3, scene.Cols) peaks[i] = make([]geometry.Vec3, scene.Cols) } // Collect results var so_far time.Duration var highest, lowest geometry.Vec3 highValue, lowValue := geometry.Float(0), geometry.Float(math.Inf(+1)) numPixels := scene.Rows * scene.Cols for i := 0; i < numPixels; i++ { // Print progress information every 500 pixels if i%500 == 0 { fmt.Printf("\rRendering %6.2f%%", 100*float64(i)/float64(scene.Rows*scene.Cols)) so_far = time.Now().Sub(startTime) remaining := time.Duration((so_far.Seconds()/float64(i))*float64(numPixels-i)) * time.Second fmt.Printf(" (Time Remaining: ") PrintDuration(remaining) fmt.Printf(" at %0.1f pps) \r", float64(i)/so_far.Seconds()) } pixel := <-pixels if low := pixel.colour.Abs(); low < lowValue { lowValue = low lowest = pixel.colour } if high := pixel.colour.Abs(); high > highValue { highValue = high highest = pixel.colour } data[pixel.y][pixel.x] = pixel.colour.CLAMPF() peaks[pixel.y][pixel.x] = pixel.colour.PEAKS(0.8) } fmt.Println("\rRendering 100.00%") bloomed := BloomFilter(peaks, Config.BloomFactor) for y := 0; y < len(data); y++ { for x := 0; x < len(data[0]); x++ { colour := data[y][x].Add(bloomed[y][x]) colour = CorrectColours(colour).CLAMP() img.SetNRGBA(x, y, color.NRGBA{uint8(colour.X), uint8(colour.Y), uint8(colour.Z), 255}) } } stopTime = time.Now() clearLine() fmt.Println("\rDone!") fmt.Printf("Brightest pixel: %v intensity: %v\n", highest, highValue) fmt.Printf("Dimmest pixel: %v intensity: %v\n", lowest, lowValue) // Print duration fmt.Printf("Rendering took ") PrintDuration(stopTime.Sub(startTime)) fmt.Println() return img }
func main() { flag.Parse() rand.Seed(*seed) gorender.Config.NumRays = *rays //gorender.Config.Caustics = *caustics gorender.Config.BloomFactor = *bloom gorender.Config.MinDepth = *mindepth gorender.Config.GammaFactor = *gamma gorender.Config.Skip.Top = *skipTop gorender.Config.Skip.Left = *skipLeft gorender.Config.Skip.Right = *skipRight gorender.Config.Skip.Bottom = *skipBottom wantedCPUs := *cores if wantedCPUs < 1 { wantedCPUs = 1 } fmt.Printf("Running on %v/%v CPU cores\n", wantedCPUs, runtime.NumCPU()) runtime.GOMAXPROCS(wantedCPUs) if wantedCPUs > *chunks { log.Print("Warning: chunks setting is lower than the number of cores - not all cores will be used") } if *rows%*chunks != 0 { log.Fatal("The images height needs to be evenly divisible by chunks") } gorender.Config.Chunks = *chunks if *cpuprofile != "" { cpupf, err := os.Create(*cpuprofile) fmt.Println("Writing CPU profiling information to file:", *cpuprofile) if err != nil { log.Fatal(err) } pprof.StartCPUProfile(cpupf) defer pprof.StopCPUProfile() } if *memprofile != "" { fmt.Println("Writing Memory profiling information to file:", *memprofile) } else { runtime.MemProfileRate = 0 } file, err := os.OpenFile(*output, os.O_CREATE|os.O_WRONLY, 0666) if err != nil { log.Fatal(err) } fmt.Printf("Rendering %vx%v sized image with %v rays per pixel to %v\n", *cols, *rows, *rays, *output) // "Real world" frustrum height := geometry.Float(2.0) width := height * (geometry.Float(*cols) / geometry.Float(*rows)) // Aspect ratio? angle := math.Pi * geometry.Float(*fov) / 180.0 scene := geometry.ParseScene(*input, width, height, angle, *cols, *rows) img := gorender.Render(scene) if err = png.Encode(file, img); err != nil { log.Fatal(err) } if *memprofile != "" { mempf, err := os.Create(*memprofile) if err != nil { log.Fatal(err) } pprof.WriteHeapProfile(mempf) defer mempf.Close() } }
func Radiance(ray geometry.Ray, scene *geometry.Scene, diffuseMap /*, causticsMap*/ *kd.KDNode, depth int, alpha float64, rand *rand.Rand) geometry.Vec3 { if depth > Config.MinDepth && rand.Float64() > alpha { return geometry.Vec3{0, 0, 0} } if shape, distance := ClosestIntersection(scene.Objects, ray); shape != nil { impact := ray.Origin.Add(ray.Direction.Mult(distance)) normal := shape.NormalDir(impact).Normalize() reverse := ray.Direction.Mult(-1) contribution := shape.Emission outgoing := normal if normal.Dot(reverse) < 0 { outgoing = normal.Mult(-1) } if shape.Material == geometry.DIFFUSE { var /*causticLight,*/ directLight geometry.Vec3 /*nodes := causticsMap.Neighbors(impact, 0.1) for _, e := range nodes { photon := causticPhotons[e.Position] dist := photon.Location.Distance(impact) light := photon.Photon.Mult(outgoing.Dot(photon.Incomming.Mult(-1 / math.Pi * (1 + dist)))) causticLight.AddInPlace(light) } if len(nodes) > 0 { causticLight = causticLight.Mult(1.0 / float64(len(nodes))) }*/ directLight = EmitterSampling(impact, normal, scene.Objects, rand) u := normal.Cross(reverse).Normalize().Mult(geometry.Float(rand.NormFloat64() * 0.5)) v := u.Cross(normal).Normalize().Mult(geometry.Float(rand.NormFloat64() * 0.5)) bounceDirection := geometry.Vec3{ u.X + outgoing.X + v.X, u.Y + outgoing.Y + v.Y, u.Z + outgoing.Z + v.Z, } bounceRay := geometry.Ray{impact, bounceDirection.Normalize()} indirectLight := Radiance(bounceRay, scene, diffuseMap /*causticsMap,*/, depth+1, alpha*0.9, rand) dot := outgoing.Dot(reverse) diffuseLight := geometry.Vec3{ (shape.Colour.X * (directLight.X + indirectLight.X) /*+ causticLight.X*/) * dot, (shape.Colour.Y * (directLight.Y + indirectLight.Y) /*+ causticLight.Y*/) * dot, (shape.Colour.Z * (directLight.Z + indirectLight.Z) /*+ causticLight.Z*/) * dot, } return contribution.Add(diffuseLight) } if shape.Material == geometry.SPECULAR { reflectionDirection := ray.Direction.Sub(normal.Mult(2 * outgoing.Dot(ray.Direction))) reflectedRay := geometry.Ray{impact, reflectionDirection.Normalize()} incomingLight := Radiance(reflectedRay, scene, diffuseMap /*causticsMap,*/, depth+1, alpha*0.99, rand) return incomingLight.Mult(outgoing.Dot(reverse)) } if shape.Material == geometry.REFRACTIVE { var n1, n2 float64 if normal.Dot(outgoing) < 0 { // Leave the glass n1, n2 = GLASS, AIR } else { n1, n2 = AIR, GLASS } factor := n1 / n2 cosTi := float64(normal.Dot(reverse)) sinTi := math.Sqrt(1 - cosTi*cosTi) // sinĀ² + cosĀ² = 1 sqrt := math.Sqrt(math.Max(1.0-math.Pow(factor*sinTi, 2), 0)) // Rs top := n1*cosTi - n2*sqrt bottom := n1*cosTi + n2*sqrt Rs := math.Pow(top/bottom, 2) // Rp top = n1*sqrt - n2*cosTi bottom = n1*sqrt + n2*cosTi Rp := math.Pow(top/bottom, 2) R := (Rs*Rs + Rp*Rp) / 2.0 // Approximate: R = math.Pow((n1-n2)/(n1+n2), 2) // SmallPT formula //R = R + (1 - R) * math.Pow(1 - cosTi, 5) T := 1.0 - R if math.IsNaN(R) { fmt.Printf("into: %v, sqrt: %v\n", n2 > n1, sqrt) fmt.Printf("cos: %v, sin: %v\n", cosTi, sinTi) fmt.Printf("n1: %v, n2: %v\n", n1, n2) fmt.Printf("Top: %v, Bottom: %v\n", top, bottom) fmt.Printf("Rs: %v, Rp: %v\n", Rs, Rp) fmt.Printf("R: %v, T: %v\n", R, T) panic("NAN!") } totalReflection := false if n1 > n2 { maxAngle := math.Asin(n2 / n1) actualAngle := math.Asin(sinTi) if actualAngle > maxAngle { totalReflection = true } totalReflection = totalReflection } if totalReflection { reflectionDirection := ray.Direction.Sub(outgoing.Mult(2 * outgoing.Dot(ray.Direction))) reflectedRay := geometry.Ray{impact, reflectionDirection.Normalize()} return Radiance(reflectedRay, scene, diffuseMap /*causticsMap,*/, depth+1, alpha*0.9, rand) } else { reflectionDirection := ray.Direction.Sub(outgoing.Mult(2 * outgoing.Dot(ray.Direction))) reflectedRay := geometry.Ray{impact, reflectionDirection.Normalize()} reflectedLight := Radiance(reflectedRay, scene, diffuseMap /*causticsMap,*/, depth+1, alpha*0.9, rand).Mult(geometry.Float(R)) nDotI := float64(normal.Dot(ray.Direction)) trasmittedDirection := ray.Direction.Mult(geometry.Float(factor)) term2 := factor * nDotI term3 := math.Sqrt(1 - factor*factor*(1-nDotI*nDotI)) trasmittedDirection = trasmittedDirection.Add(normal.Mult(geometry.Float(term2 - term3))) transmittedRay := geometry.Ray{impact, trasmittedDirection.Normalize()} transmittedLight := Radiance(transmittedRay, scene, diffuseMap /*causticsMap,*/, depth+1, alpha*0.9, rand).Mult(geometry.Float(T)) return reflectedLight.Add(transmittedLight).Mult(outgoing.Dot(reverse)) } } panic("Material without property encountered!") } return geometry.Vec3{0, 0, 0} }