Ambient Occlusion Rendering

by keim_at_Si
Ambient Occlusion Bench Flash10 porting
Original version of AO bench was written by Syoyo Fujita.
http://lucille.atso-net.jp/aobench/
In original Flash10 porting, it takes 7 times slower than the Proce55ing.
(refer from http://lucille.atso-net.jp/blog/?p=638).
And now, it seems to be same speed as the Proce55ing does.
----------------------------------------------------------------------
webpage; http://soundimpulse.sakura.ne.jp/ambient-occlusion-rendering/
♥17 | Line 230 | Modified 2009-02-18 22:09:25 | MIT License
play

ActionScript3 source code

/**
 * Copyright keim_at_Si ( http://wonderfl.net/user/keim_at_Si )
 * MIT License ( http://www.opensource.org/licenses/mit-license.php )
 * Downloaded from: http://wonderfl.net/c/7W2s
 */

// Ambient Occlusion Bench Flash10 porting
//   Original version of AO bench was written by Syoyo Fujita.
//     http://lucille.atso-net.jp/aobench/
//   In original Flash10 porting, it takes 7 times slower than the Proce55ing.
//   (refer from http://lucille.atso-net.jp/blog/?p=638).
//   And now, it seems to be same speed as the Proce55ing does.
//----------------------------------------------------------------------
package {
    import flash.display.*;
    import flash.text.*;
    import flash.events.*;
    
    [SWF(frameRate = "1000", backgroundColor = "#808080")]
    public class main extends Sprite {
        private var _tf:TextField = new TextField();
        private var _screen:BitmapData = new BitmapData(WIDTH, HEIGHT, false, 0xffffff);
        private var _renderY:int;
        
        function main() { 
            _tf.autoSize = 'left';
            _tf.htmlText = "<font color='#ffffff'>Rendering...</font>";
            addChild(_tf);
            with(addChild(new Bitmap(_screen))){ x = y = 104.5; }
            
            _renderY = 0;
            
            addEventListener("enterFrame", _startRender);
        }
        
        private function _startRender(e:Event) : void {
            // rendering
            render.rasterRendering(_renderY);
            
            // copy pixels
            _screen.lock();
            for (var x:int=0; x<WIDTH; x++) _screen.setPixel(x, _renderY, render.output[x]);
            _screen.unlock();
            
            // increment
            _renderY++;
            if (_renderY == 256) {
                // finish rendering
                _tf.htmlText = "<font color='#ffffff'>Rendering time : " + String(render.renderingTime/1000) + "[sec]</font>";
                removeEventListener("enterFrame", _startRender);
            }
        }
    }
}




import flash.display.*;
import flash.geom.*;
import flash.utils.getTimer;


// global variables
//--------------------------------------------------------------------------------
const WIDTH :int = 256;
const HEIGHT:int = 256;
const NSUBSAMPLES:int = 2;
const NAO_SAMPLES:int = 8;

var render:Render = new Render();


// structures
//--------------------------------------------------------------------------------
class Isect {
    public var t:Number = 0;
    public var p:Vector3D = new Vector3D();
    public var n:Vector3D = new Vector3D();
    public var hit:int = 0;
}

class Ray {
    public var org:Vector3D = new Vector3D();
    public var dir:Vector3D = new Vector3D();
}

class Sphere {
    public var center:Vector3D;
    public var radius2:Number;
    function Sphere(center:Vector3D, radius:Number) {
        this.center = center;
        this.radius2 = radius*radius;
    }
}

class Plane {
    public var p:Vector3D;
    public var n:Vector3D;
    public var pn:Number;
    function Plane(p:Vector3D, n:Vector3D) {
        this.p = p;
        this.n = n;
        this.pn = n.x*p.x + n.y*p.y + n.z*p.z;
    }
}




// render
//--------------------------------------------------------------------------------
class Render {
// variables
//--------------------------------------------------
    public var output:Vector.<uint>;
    public var renderingTime:int;

    private var spheres:Vector.<Sphere> = new Vector.<Sphere>(3, true);
    private var plane:Plane;
    
    
// constructor
//--------------------------------------------------
    function Render() {
        _initScene();
        renderingTime = 0;
        output = new Vector.<uint>(WIDTH, true);
    }
    
    
// rendering
//--------------------------------------------------
    public function rasterRendering(y:int) : void {
        var t:int = getTimer();
        _render(output, WIDTH, HEIGHT, y);
        renderingTime += getTimer() - t;
    }

    
// privates
//--------------------------------------------------
    // initialize scene
    private function _initScene() : void {
        spheres[0] = new Sphere(new Vector3D(-2.0, 0.0, -3.5), 0.5);
        spheres[1] = new Sphere(new Vector3D(-0.5, 0.0, -3.0), 0.5);
        spheres[2] = new Sphere(new Vector3D( 1.0, 0.0, -2.2), 0.5);
        plane      = new Plane(new Vector3D(0.0, -0.5, 0.0), new Vector3D(0.0, 1.0, 0.0));
    }
    
    
    // render
    private var ray:Ray = new Ray();
    private var isect:Isect = new Isect();
    private function _render(line:Vector.<uint>, w:int, h:int, y:int) : void {
        var idx:int, x:int, u:int, v:int, du:Number, dv:Number, pixel:uint, ao:Vector3D, 
            hw :Number = w*0.5, hh :Number = h*0.5, 
            ihw:Number = 1/hw,  ihh:Number = 1/hh,
            step:Number = 1/NSUBSAMPLES, 
            occlusionPerPixel:Number = 1/(NSUBSAMPLES*NSUBSAMPLES),
            color:Vector3D = new Vector3D();

        // scan all pixels
        idx = 0;
        for (x = 0; x < w; x++) {
            // initialize pixel color
            color.x = 0;
            color.y = 0;
            color.z = 0;
            
            // sub samplings
            for (v = 0, dv = 0; v < NSUBSAMPLES; v++, dv += step) {
                for (u = 0, du = 0; u < NSUBSAMPLES; u++, du += step) {
                    // initialize ray vectors
                    ray.org.x = 0.0;
                    ray.org.y = 0.0;
                    ray.org.z = 0.0;
                    ray.dir.x =  (x + du - hw) * ihw;
                    ray.dir.y = -(y + dv - hh) * ihh;
                    ray.dir.z = -1.0;
                    ray.dir.normalize();

                    // check ray intersections
                    isect.p.x = isect.p.y = isect.p.z = 0.0;
                    isect.n.x = isect.n.y = isect.n.z = 0.0;
                    isect.t   = 1.0e+17;
                    isect.hit = 0;
                    _intersectBySphere(isect, ray, spheres[0]);
                    _intersectBySphere(isect, ray, spheres[1]);
                    _intersectBySphere(isect, ray, spheres[2]);
                    _intersectByPlane (isect, ray, plane);
                    // when the ray is intersected, calculate ambient occlusion color.
                    if (isect.hit) {
                        color.incrementBy(_ambientOcclusion(isect));
                    }
                }
            }
            
            // calculate gray scale
            color.scaleBy(occlusionPerPixel);
            pixel = 0xff000000;
            pixel |=  (color.x >= 1) ? 255 : (color.x <= 0) ? 0 : int(color.x * 255);
            pixel |= ((color.y >= 1) ? 255 : (color.y <= 0) ? 0 : int(color.x * 255)) << 8;
            pixel |= ((color.z >= 1) ? 255 : (color.z <= 0) ? 0 : int(color.x * 255)) << 16;
            output[idx] = pixel;
            
            // increment index
            idx++;
        }
    }
    
    
    // calculate ambient occlusion
    private var aoRay:Ray = new Ray();
    private var aoIsect:Isect = new Isect();
    private var aoColor:Vector3D = new Vector3D();
    private var basis0:Vector3D = new Vector3D();
    private var basis1:Vector3D = new Vector3D();
    private var basis2:Vector3D = new Vector3D();
    private function _ambientOcclusion(isect:Isect) : Vector3D {
        var i:int, j:int, th:Number, ph:Number,
            x:Number,  y:Number,  z:Number,
            rx:Number, ry:Number, rz:Number,
            ntheta:int = NAO_SAMPLES,
            nphi:int   = NAO_SAMPLES,
            occlusionPerSample:Number = 1 / (ntheta*nphi),
            eps:Number = 0.0001,
            occlusion:Number = 1.0;
        
        // calculate transform matrix from local to global.
        // the "local" coordinate is based on the normal vector at intersected point.
        basis2 = isect.n;
        basis1.x = 0.0;
        basis1.y = 0.0;
        basis1.z = 0.0;
             if ((isect.n.x < 0.6) && (isect.n.x > -0.6)) basis1.x = 1.0;
        else if ((isect.n.y < 0.6) && (isect.n.y > -0.6)) basis1.y = 1.0;
        else if ((isect.n.z < 0.6) && (isect.n.z > -0.6)) basis1.z = 1.0;
        else                                              basis1.x = 1.0;
        vcross(basis0, basis1, basis2).normalize();
        vcross(basis1, basis2, basis0).normalize();

        // calculate the origin of the second ray
        aoRay.org.x = isect.p.x + eps * isect.n.x;
        aoRay.org.y = isect.p.y + eps * isect.n.y;
        aoRay.org.z = isect.p.z + eps * isect.n.z;
        
        // calculate the ambient occlusion at intersected point
        for (j = 0; j < ntheta; j++) {
            for (i = 0; i < nphi; i++) {
                // calculate the direction of the second ray
                th = Math.sqrt(Math.random());
                ph = 6.283185307179586 * Math.random();
                x = Math.cos(ph) * th;
                y = Math.sin(ph) * th;
                z = Math.sqrt(1.0 - th * th);
                // transform second ray vector from local to global
                aoRay.dir.x = x * basis0.x + y * basis1.x + z * basis2.x;
                aoRay.dir.y = x * basis0.y + y * basis1.y + z * basis2.y;
                aoRay.dir.z = x * basis0.z + y * basis1.z + z * basis2.z;

                // check second ray intersections 
                aoIsect.p.x = aoIsect.p.y = aoIsect.p.z = 0.0;
                aoIsect.n.x = aoIsect.n.y = aoIsect.n.z = 0.0;
                aoIsect.t   = 1.0e+17;
                aoIsect.hit = 0;
                _intersectBySphere(aoIsect, aoRay, spheres[0]);
                _intersectBySphere(aoIsect, aoRay, spheres[1]);
                _intersectBySphere(aoIsect, aoRay, spheres[2]);
                _intersectByPlane (aoIsect, aoRay, plane);
                // when the second ray is intersected, increase the occlusion.
                if (aoIsect.hit) occlusion -= occlusionPerSample;
            }
        }

        // return result
        aoColor.x = occlusion;
        aoColor.y = occlusion;
        aoColor.z = occlusion;
        return aoColor;
        
        // cross product
        function vcross(c:Vector3D, v0:Vector3D, v1:Vector3D) : Vector3D {
            c.x = v0.y * v1.z - v0.z * v1.y;
            c.y = v0.z * v1.x - v0.x * v1.z;
            c.z = v0.x * v1.y - v0.y * v1.x;
            return c;
        }
    }
    
    
    // check ray intersection by sphere
    private function _intersectBySphere(isect:Isect, ray:Ray, sph:Sphere) : void {
        var rsx:Number = ray.org.x - sph.center.x,
            rsy:Number = ray.org.y - sph.center.y,
            rsz:Number = ray.org.z - sph.center.z,
            B:Number = rsx*ray.dir.x + rsy*ray.dir.y + rsz*ray.dir.z,
            C:Number = rsx*rsx + rsy*rsy + rsz*rsz - sph.radius2,
            D:Number = B * B - C;

        // when the ray is intersected by the sphere,
        if (D > 0.0) {
            var t:Number = -B - Math.sqrt(D);
            
            if ((t > 0.0) && (t < isect.t)) {
                // calculate cross point and normal vector.
                isect.t = t;
                isect.hit = 1;
                
                isect.p.x = ray.org.x + ray.dir.x * t;
                isect.p.y = ray.org.y + ray.dir.y * t;
                isect.p.z = ray.org.z + ray.dir.z * t;

                isect.n.x = isect.p.x - sph.center.x;
                isect.n.y = isect.p.y - sph.center.y;
                isect.n.z = isect.p.z - sph.center.z;
                isect.n.normalize();
            }
        }
    }
    

    // check ray intersection by plane
    private function _intersectByPlane(isect:Isect, ray:Ray, pln:Plane) : void {
        var v:Number = pln.n.x*ray.dir.x + pln.n.y*ray.dir.y + pln.n.z*ray.dir.z;
        
        // when parallel with plane
        if (v>-1.0e-17 && v<1.0e-17) return;
        
        var t:Number = -(pln.n.x*ray.org.x + pln.n.y*ray.org.y + pln.n.z*ray.org.z - pln.pn) / v;

        // when the ray is intersected by the plane,
        if ((t > 0.0) && (t < isect.t)) {
            // calculate cross point and normal vector.
            isect.t = t;
            isect.hit = 1;
            
            isect.p.x = ray.org.x + ray.dir.x * t;
            isect.p.y = ray.org.y + ray.dir.y * t;
            isect.p.z = ray.org.z + ray.dir.z * t;

            isect.n.x = pln.n.x;
            isect.n.y = pln.n.y;
            isect.n.z = pln.n.z;
        }
    }
}

Forked