package starling.extensions { import flash.display.BitmapData; import starling.animation.IAnimatable; import starling.display.Quad; import starling.textures.Texture; import starling.utils.MathUtil; /** A quad that efficiently renders a 2D light ray effect on its surface. * * <p>This class is useful for adding atmospheric effects, like the typical effects you see * underwater or in a forest. Add it to a juggler or call 'advanceTime' so that the effect * becomes animated.</p> * * <p>Play around with the different settings to make it suit the style you want. In addition * to the class-specific properties, you can also assign an overall color or different colors * per vertex.</p> */ public class GodRayPlane extends Quad implements IAnimatable { private static const TEXTURE_HEIGHT:int = 32; private static const TEXTURE_WIDTH:int = 512; private var _bitmapData:BitmapData; private var _speed:Number; private var _size:Number; private var _skew:Number; private var _fade:Number; /** Create a new instance with the given size. Using a "packed" texture format produces * a slightly different effect with visible gradient steps. */ public function GodRayPlane(width:Number, height:Number, textureFormat:String="bgra") { super(width, height); _speed = 0.1; _size = 0.1; _skew = 0.0; _fade = 1.0; _bitmapData = new BitmapData(TEXTURE_WIDTH, TEXTURE_HEIGHT, false); texture = Texture.empty(TEXTURE_WIDTH, TEXTURE_HEIGHT, true, false, false, 1.0, textureFormat, true); updateTexture(); textureRepeat = true; style = new GodRayStyle(); } /** Disposes the internally used texture. */ override public function dispose():void { super.dispose(); _bitmapData.dispose(); texture.dispose(); } private function updateTexture():void { _bitmapData.perlinNoise(TEXTURE_WIDTH * _size, TEXTURE_HEIGHT * 0.2, 2, 0, true, true, 0, true); texture.root.uploadBitmapData(_bitmapData); } private function updateVertices():void { vertexData.setPoint(2, "texCoords", -_skew, 1.0); vertexData.setPoint(3, "texCoords", -_skew + 1.0, 1.0); vertexData.setAlpha(2, "color", 1.0 - _fade); vertexData.setAlpha(3, "color", 1.0 - _fade); setRequiresRedraw(); } /** @inheritDoc */ public function advanceTime(time:Number):void { if (_speed > 0 && time > 0) godRayStyle.offsetY += time * _speed; } private function get godRayStyle():GodRayStyle { return style as GodRayStyle; } /** The speed with which the effect is animated. A value of '1.0' causes the pattern * to repeat exactly after one second. Range: 0 - infinite. @default 0.1 */ public function get speed():Number { return _speed; } public function set speed(value:Number):void { _speed = MathUtil.max(0, value); } /** Determines up the angle of the light rays. * Range: -5 - 5. @default: 0.0 */ public function get skew():Number { return _skew; } public function set skew(value:Number):void { _skew = MathUtil.clamp(value, -5, 5); updateVertices(); } /** Determines the change in the light ray's angles over the width of the plane. * Range: -1 - 10. @default: 0.0 */ public function get shear():Number { return godRayStyle.shear; } public function set shear(value:Number):void { godRayStyle.shear = value; } /** The width of the rays. As a rule of thumb, one divided by this value will yield the * approximate number of rays. Range: 0.0001 - 1. @default: 0.1 */ public function get size():Number { return _size; } public function set size(value:Number):void { _size = MathUtil.clamp(value, 0.0001, 1); updateTexture(); } /** Indicates how the light rays fade out towards the bottom. Zero means no fading, * one means that the rays will become completely invisible at the bottom. * Range: 0 - 1, @default: 1 */ public function get fade():Number { return _fade; } public function set fade(value:Number):void { _fade = MathUtil.clamp(value, 0, 1); updateVertices(); } /** The distinctiveness and brightness of the light rays. * Range: 0 - infinite, @default: 1 */ public function get contrast():Number { return godRayStyle.contrast; } public function set contrast(value:Number):void { godRayStyle.contrast = value; } /** The current offset used when sampling the noise-texture. * This is the value that is animated via the 'speed' property. * If you want to animate the value yourself, set 'speed' to zero. * Range: 0 - 1, @default: 0 */ public function get textureOffset():Number { return godRayStyle.offsetY; } public function set textureOffset(value:Number):void{ godRayStyle.offsetY = value; } } } import flash.display3D.Context3D; import flash.display3D.Context3DProgramType; import starling.display.Mesh; import starling.rendering.MeshEffect; import starling.rendering.Program; import starling.rendering.VertexDataFormat; import starling.styles.MeshStyle; import starling.utils.MathUtil; class GodRayStyle extends MeshStyle { public static const VERTEX_FORMAT:VertexDataFormat = MeshStyle.VERTEX_FORMAT.extend("settings:float3"); private var _offsetY:Number; private var _shear:Number; private var _contrast:Number; public function GodRayStyle() { _offsetY = 0.0; _shear = 0.0; _contrast = 1.0; } override public function copyFrom(meshStyle:MeshStyle):void { var godRayStyle:GodRayStyle = meshStyle as GodRayStyle; if (godRayStyle) { _offsetY = godRayStyle._offsetY; _shear = godRayStyle._shear; _contrast = godRayStyle._contrast; } super.copyFrom(meshStyle); } override public function createEffect():MeshEffect { return new GodRayEffect(); } override public function get vertexFormat():VertexDataFormat { return VERTEX_FORMAT; } override protected function onTargetAssigned(target:Mesh):void { updateVertices(); } private function updateVertices():void { if (target) { vertexData.setPremultipliedAlpha(false, true); var numVertices:int = vertexData.numVertices; for (var i:int=0; i<numVertices; ++i) vertexData.setPoint3D(i, "settings", _offsetY, _shear, _contrast); setRequiresRedraw(); } } public function get shear():Number { return _shear; } public function set shear(value:Number):void { _shear = MathUtil.clamp(value, -1, 10); updateVertices(); } public function get offsetY():Number { return _offsetY; } public function set offsetY(value:Number):void { while (value > 1.0) value -= 1.0; _offsetY = value; updateVertices(); } public function get contrast():Number { return _contrast; } public function set contrast(value:Number):void { _contrast = MathUtil.max(0, value); updateVertices(); } } class GodRayEffect extends MeshEffect { private static const sConstants:Vector.<Number> = new <Number>[0, 1, 2, 0.5]; public function GodRayEffect() { } override protected function createProgram():Program { var vertexShader:String = [ "m44 op, va0, vc0", // 4x4 matrix transform to output clip-space "mov v0, va1 ", // pass texture coordinates to fragment program "mov v1.xyz, va2.xyz", // copy color to v1.xyz "mul v1.w, va2.w, vc4.w", // copy combined alpha to v1.w "mov v2, va3 " // pass settings to fp ].join("\n"); var fragmentShader:String = [ // offset "mov ft0, v0", "mov ft0.y, v2.x", // texture coordinates: v = offset // shear "mul ft2.x, v0.y, v2.y", // shear *= v "add ft2.x, ft2.x, fc5.y", // shear = 1 + v * shear "div ft0.x, ft0.x, ft2.x", // texture coordinates: divide 'u' by shear // texture lookup tex("ft1", "ft0", 0, texture), // contrast "mul ft1.xyz, ft1.xyz, v2.zzz", // tex color *= contrast "sub ft2.xyz, fc5.yyy, v2.zzz", // ft2 = 1 - contrast "add ft1.xyz, ft1.xyz, ft2.xyz", // tex color += ft2 // alpha + tinting "mul ft1.w, ft1.x, v1.w", // multiply with vertex alpha "mul ft1.xyz, ft1.xxx, v1.xyz", // tint with vertex color "mul ft1.xyz, ft1.xyz, ft1.www", // premultiply alpha // copy to output "mov oc, ft1" ].join("\n"); return Program.fromSource(vertexShader, fragmentShader); } override public function get vertexFormat():VertexDataFormat { return GodRayStyle.VERTEX_FORMAT; } override protected function beforeDraw(context:Context3D):void { super.beforeDraw(context); context.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT, 5, sConstants); vertexFormat.setVertexBufferAt(3, vertexBuffer, "settings"); } override protected function afterDraw(context:Context3D):void { context.setVertexBufferAt(3, null); super.afterDraw(context); } }