One By One Design

Starling ‘God Ray’ Filter

While cruising the internet today looking for interesting things to try out, I ran across this fun little GPU Gem about creating a post-process volumetric lighting effect. With a little bit of work I quickly ported it over to an AGAL shader (you can see it on Wonderfl here). Then I figured what the hell, why not make it into a Starling filter.

I should say right off the bat that this won’t work in ‘baseline constrained’ mode (i.e. no mobile apps), but if you’re working on a web or desktop project, this filter will give you an effect like this.

The entire filter is below. Enjoy.

 

/**
 *	Copyright (c) 2013 Devon O. Wolfgang
 *
 *	Permission is hereby granted, free of charge, to any person obtaining a copy
 *	of this software and associated documentation files (the "Software"), to deal
 *	in the Software without restriction, including without limitation the rights
 *	to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 *	copies of the Software, and to permit persons to whom the Software is
 *	furnished to do so, subject to the following conditions:
 *
 *	The above copyright notice and this permission notice shall be included in
 *	all copies or substantial portions of the Software.
 *
 *	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 *	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 *	AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 *	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 *	OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 *	THE SOFTWARE.
 */

package starling.filters
{
	import flash.display3D.Context3D;
	import flash.display3D.Context3DBlendFactor;
	import flash.display3D.Context3DProgramType;
	import flash.display3D.Program3D;
	import starling.textures.Texture;
    
	/**
	 * Creates a 'God Rays' / fake volumetric light filter effect. Only use with Context3DProfile.BASELINE (not compatible with constrained profile)
	 * @author Devon O.
	 */
	
    public class GodRaysFilter extends FragmentFilter
    {
        
		private var shaderProgram:Program3D;
		
		private var numSteps:int;
		
		// lightx, lighty
		private var lightPos:Vector. = Vector.( [ .5, .5, 1, 1 ]);
		
		// numsamples, density, numsamples * density, 1 / numsamples * density
		private var values1:Vector. = Vector.( [ 1, 1, 1, 1 ]);
		
		// weight, decay, exposure
		private var values2:Vector. = Vector.( [ 1, 1, 1, 1 ]);
		
		
		private var _lightX:Number 		= 0;
		private var _lightY:Number		= 0;
		private var _weight:Number		= .50;
		private var _decay:Number		= .87;
		private var _exposure:Number	= .35;
		private var _density:Number		= 2.0;
        
        public function GodRaysFilter(numSteps:int = 30)
        {
			this.numSteps = numSteps;
        }
        
        public override function dispose():void
        {
            if (this.shaderProgram) this.shaderProgram.dispose();
            super.dispose();
        }
        
        protected override function createPrograms():void
        {
            var frag:String = "";
			
			// Calculate vector from pixel to light source in screen space.
			frag += "sub ft0.xy, v0.xy, fc0.xy \n";
			
			// Divide by number of samples and scale by control factor.  
			frag += "mul ft0.xy, ft0.xy, fc1.ww \n";
			
			// Store initial sample.  
			frag += "tex ft1,  v0, fs0 <2d, clamp, linear, mipnone> \n";
			
			// Set up illumination decay factor.  
			frag += "mov ft2.x, fc0.w \n";
			
			// Store the texcoords
			frag += "mov ft4.xy, v0.xy \n";
			
			for (var i:int = 0; i < this.numSteps; i++)
			{
				// Step sample location along ray. 
				frag += "sub ft4.xy, ft4.xy, ft0.xy \n";
				
				// Retrieve sample at new location.  
				frag += "tex ft3,  ft4.xy, fs0 <2d, clamp, linear, mipnone> \n";
				
				// Apply sample attenuation scale/decay factors.  
				frag += "mul ft2.y, ft2.x, fc2.x \n";
				frag += "mul ft3.xyz, ft3.xyz, ft2.yyy \n";
				
				// Accumulate combined color.  
				frag += "add ft1.xyz, ft1.xyz, ft3.xyz \n";
				
				// Update exponential decay factor.  
				frag += "mul ft2.x, ft2.x, fc2.y \n";
			}
			
			// Output final color with a further scale control factor. 
			frag += "mul ft1.xyz, ft1.xyz, fc2.zzz \n";
			frag += "mov oc, ft1";
				
            this.shaderProgram = assembleAgal(frag);
        }
        
        protected override function activate(pass:int, context:Context3D, texture:Texture):void
        {		
			// light position
			this.lightPos[0] = this._lightX / texture.width;
			this.lightPos[1] = this._lightY / texture.height;
			
			// numsamples, density, numsamples * density, 1 / numsamples * density
			this.values1[0] = this.numSteps;
			this.values1[1] = this._density;
			this.values1[2] = this.numSteps * values1[1];
			this.values1[3] = 1 / values1[2];
			
			// weight, decay, exposure
			this.values2[0] = this._weight;
			this.values2[1] = this._decay;
			this.values2[2] = this._exposure;
			
			context.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT, 0, this.lightPos, 1 );	
			context.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT, 1, this.values1,  1 );
			context.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT, 2, this.values2,  1 );
			
            context.setProgram(this.shaderProgram);
			
        }
		
		public function set lightX(value:Number):void { this._lightX = value; }
		public function get lightX():Number { return this._lightX; }
		
		public function set lightY(value:Number):void { this._lightY = value; }
		public function get lightY():Number { return this._lightY; }
		
		public function set decay(value:Number):void { this._decay = value; }
		public function get decay():Number { return this._decay; }
		
		public function set exposure(value:Number):void { this._exposure = value; }
		public function get exposure():Number { return this._exposure; }
		
		public function set weight(value:Number):void { this._weight = value; }
		public function get weight():Number { return this._weight; }
		
		public function set density(value:Number):void { this._density = value; }
		public function get density():Number { return this._density; }
		
    }
}

Posted by

Post a comment

Your email address will not be published. Required fields are marked *