PathTracing
https://en.wikipedia.org/wiki/Path_tracing
// http://blog.weareglow.com/2011/11/path-tracing-in-flash/
/**
* Copyright YoupSolo ( http://wonderfl.net/user/YoupSolo )
* MIT License ( http://www.opensource.org/licenses/mit-license.php )
* Downloaded from: http://wonderfl.net/c/ljvD
*/
// https://en.wikipedia.org/wiki/Path_tracing
package
{
import flash.display.StageAlign;
import flash.display.StageQuality;
import flash.display.StageScaleMode;
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.Sprite;
import flash.events.Event;
[SWF(backgroundColor="#000000", frameRate="60", width="465", height="465")]
public class PathTracing extends Sprite
{
public static const AA:int = 1; // antialiasing
public static const WIDTH:int = 465 * AA;
public static const HEIGHT:int = 465 * AA;
public static const CAM_NEAR:Number = 2;
public static const CAM_FOV:Number = Math.PI * 0.3;
public static const MAX_DEPTH:int = 5;
private var scene_:Array;
private var bmd_:BitmapData;
private var buffer_:Vector.<Vec3>;
private var counts_:Vector.<int>;
public function PathTracing()
{
stage.align = StageAlign.TOP_LEFT;
stage.scaleMode = StageScaleMode.NO_SCALE;
//stage.quality = StageQuality.LOW;
stage.stageFocusRect = false;
stage.tabChildren = false;
var i:int;
const camera:Vec3 = new Vec3(0, 0, 2);
scene_ = Scenes.scene00;
// add random spheres
for (i = 0; i < 2; ++i)
{
scene_.push(new Sphere(0.1 + 0.5 * Math.random(), new Vec3(Math.random() * 2 - 1, Math.random() * 2 - 1, Math.random() * 2 - 1), new Vec3(Math.random(), Math.random(), Math.random()), new Vec3()));
}
// transform scene to camera space
for (i = 0; i < scene_.length; ++i)
{
var sphere:Sphere = scene_[i];
sphere.center.x -= camera.x;
sphere.center.y -= camera.y;
sphere.center.z -= camera.z;
}
// display
bmd_ = new BitmapData(WIDTH, HEIGHT, false, 0);
// holds accumulated color
buffer_ = new Vector.<Vec3>(WIDTH * HEIGHT);
// holds number of samples of that pixel
counts_ = new Vector.<int>(WIDTH * HEIGHT);
for (i = 0; i < WIDTH * HEIGHT; ++i)
{
buffer_[i] = new Vec3();
counts_[i] = 0;
}
// upside down (we're using OpenGl style coordinate system)
var b:Bitmap = new Bitmap(bmd_);
b.smoothing = true;
b.scaleY = -1;
b.y = b.height;
b.scaleX *= 1 / AA;
b.scaleY *= 1 / AA;
addChild(b);
addEventListener(Event.ENTER_FRAME, onEnterFrame);
}
private function onEnterFrame(event:Event):void
{
var x:int, y:int;
var c:int = 2000;
while (c--)
{
shootRay(Math.random() * WIDTH, Math.random() * HEIGHT);
}
bmd_.lock();
for (y = 0; y < HEIGHT; ++y)
{
for (x = 0; x < WIDTH; ++x)
{
const i:int = y * WIDTH + x;
if (counts_[i])
{
var color:Vec3 = buffer_[i];
const r:int = Utils.clampi(color.x * 255 / counts_[i], 0, 0xff);
const g:int = Utils.clampi(color.y * 255 / counts_[i], 0, 0xff);
const b:int = Utils.clampi(color.z * 255 / counts_[i], 0, 0xff);
bmd_.setPixel(x, y, (r << 16) | (g << 8) | b);
}
}
}
bmd_.unlock();
}
private function shootRay(x:Number, y:Number):void
{
var ray:Ray = new Ray();
ray.o.x = 0;
ray.o.y = 0;
ray.o.z = 0;
const nx:Number = (2 * (x / WIDTH) - 1) * WIDTH / HEIGHT;
const ny:Number = 2 * (y / HEIGHT) - 1;
ray.d.x = CAM_NEAR * Math.tan(CAM_FOV * 0.5) * nx;
ray.d.y = CAM_NEAR * Math.tan(CAM_FOV * 0.5) * ny;
ray.d.z = -CAM_NEAR;
ray.d = Utils.norm(ray.d);
const i:int = int(y) * WIDTH + int(x);
var c:Vec3 = radiance(ray, 0);
buffer_[i].x += c.x;
buffer_[i].y += c.y;
buffer_[i].z += c.z;
counts_[i]++;
}
private function radiance(ray:Ray, depth:int):Vec3
{
var i:int;
var sphere:Sphere;
var t:Number;
if (depth > MAX_DEPTH)
return new Vec3();
var nearest:Number = Infinity;
var collider:Sphere = null;
for (i = 0; i < scene_.length; ++i)
{
sphere = scene_[i];
t = sphere.intersect(ray);
if (t > 0 && t < nearest)
{
nearest = t;
collider = sphere;
}
}
if (collider == null)
return new Vec3();
// compute new ray
var out:Ray = new Ray();
out.o.x = ray.o.x + nearest * ray.d.x;
out.o.y = ray.o.y + nearest * ray.d.y;
out.o.z = ray.o.z + nearest * ray.d.z;
var normal:Vec3 = new Vec3();
normal.x = out.o.x - collider.center.x;
normal.y = out.o.y - collider.center.y;
normal.z = out.o.z - collider.center.z;
normal = Utils.norm(normal);
var tangent:Vec3 = new Vec3();
if (Math.abs(normal.x) < Utils.EPSILON)
{
tangent.x = 0;
tangent.y = -normal.z;
tangent.z = normal.y;
}
else
{
tangent.x = normal.y;
tangent.y = -normal.x;
tangent.z = 0;
}
tangent = Utils.norm(tangent);
const bitangent:Vec3 = normal.cross(tangent);
// random ray shooting from the normal hemisphere
const angle:Number = Math.random() * Math.PI * 2;
const radius2:Number = Math.random();
const radius:Number = Math.sqrt(radius2);
const cos:Number = Math.cos(angle) * radius;
const sin:Number = Math.sin(angle) * radius;
const sqrt:Number = Math.sqrt(1 - radius2);
out.d.x = tangent.x * cos + bitangent.x * sin + normal.x * sqrt;
out.d.y = tangent.y * cos + bitangent.y * sin + normal.y * sqrt;
out.d.z = tangent.z * cos + bitangent.z * sin + normal.z * sqrt;
out.d = Utils.norm(out.d);
// compute new color
var color:Vec3 = radiance(out, depth + 1);
color.x *= collider.color.x;
color.y *= collider.color.y;
color.z *= collider.color.z;
color.x += collider.emission.x;
color.y += collider.emission.y;
color.z += collider.emission.z;
return color;
}
}
}
class Vec3
{
public var x:Number;
public var y:Number;
public var z:Number;
public function Vec3(x:Number = 0, y:Number = 0, z:Number = 0)
{
this.x = x;
this.y = y;
this.z = z;
}
public function cross(v:Vec3):Vec3
{
var o:Vec3 = new Vec3();
o.x = y * v.z - z * v.y;
o.y = z * v.x - x * v.z;
o.z = x * v.y - y * v.x;
return o;
}
}
class Ray
{
public var o:Vec3 = new Vec3();
public var d:Vec3 = new Vec3();
}
class Sphere
{
public var radius:Number;
public var center:Vec3;
public var color:Vec3;
public var emission:Vec3;
public function Sphere(radius:Number, center:Vec3, color:Vec3, emission:Vec3)
{
this.radius = radius;
this.center = center;
this.color = color;
this.emission = emission;
}
public function intersect(ray:Ray):Number
{
const px:Number = ray.o.x - center.x;
const py:Number = ray.o.y - center.y;
const pz:Number = ray.o.z - center.z;
const pp:Number = px * px + py * py + pz * pz;
const pd:Number = px * ray.d.x + py * ray.d.y + pz * ray.d.z;
const A:Number = 1;
const B:Number = 2 * pd;
const C:Number = pp - radius * radius;
const det:Number = B * B - 4 * A * C;
if (det < 0)
return -1;
const det2:Number = Math.sqrt(det);
const sol:Number = (-B - det2) / (2 * A);
if (sol < 0)
return -1;
return sol;
}
}
class Utils
{
public static const EPSILON:Number = 0.0001;
public static function clamp(v:Number, min:Number, max:Number):Number
{
return Math.max(Math.min(v, max), min);
}
public static function clampi(v:int, min:int, max:int):int
{
return Math.max(Math.min(v, max), min);
}
public static function norm(v:Vec3):Vec3
{
var o:Vec3 = new Vec3(v.x, v.y, v.z);
const dd:Number = v.x * v.x + v.y * v.y + v.z * v.z;
if (dd == 0) return o;
const invd:Number = 1 / Math.sqrt(dd);
o.x *= invd;
o.y *= invd;
o.z *= invd;
return o;
}
}
class Scenes
{
public static const R:Number = 1e5;
public static const G:Number = 0.5;
public static const scene00:Array =
[
new Sphere(R, new Vec3(-R-1, 0, 0), new Vec3(G, G, G), new Vec3()),//left
new Sphere(R, new Vec3(R+1, 0, 0), new Vec3(G, G, G), new Vec3()),//right
new Sphere(R, new Vec3(0, 0, R+1), new Vec3(G, G, G), new Vec3()),//front
new Sphere(R, new Vec3(0, 0, -R-1), new Vec3(G, G, G), new Vec3()),//back
new Sphere(R, new Vec3(0, R+1, 0), new Vec3(G, G, G), new Vec3()),//top
new Sphere(R, new Vec3(0, -R-1, 0), new Vec3(G, G, G), new Vec3()),//bottom
new Sphere(0.5, new Vec3(0, 1+0.25, 0), new Vec3(), new Vec3(12,12,12))//light
];
}