Selaa lähdekoodia

review Blur implementation: radius, quality, gain, linear parameters
added Blur sample
added emissive and bloom to pbr

ncannasse 7 vuotta sitten
vanhempi
commit
06a7253318

+ 2 - 2
h2d/RenderContext.hx

@@ -103,8 +103,8 @@ class RenderContext extends h3d.impl.RenderContext {
 		textures.begin();
 	}
 
-	public function allocTarget(name, filter = false, size = 0) {
-		var t = textures.allocTarget(name, scene.width >> size, scene.height >> size, false);
+	public function allocTarget(name, filter = false) {
+		var t = textures.allocTarget(name, scene.width, scene.height, false);
 		t.filter = filter ? Linear : Nearest;
 		return t;
 	}

+ 19 - 31
h2d/filter/Blur.hx

@@ -3,65 +3,53 @@ package h2d.filter;
 class Blur extends Filter {
 
 	/**
-		Gives the blur quality : 0 for disable, 1 for 3x3, 2 for 5x5, etc.
+		See [h3d.pass.Blur.radius]
 	**/
-	public var quality(get, set) : Int;
+	public var radius(get, set) : Float;
 
 	/**
-		The amount of blur (gaussian blur value).
+		See [h3d.pass.Blur.linear]
 	**/
-	public var sigma(get, set) : Float;
+	public var linear(get, set) : Float;
 
 	/**
-		The number of blur passes we perform (default = 1)
-	**/
-	public var passes(get, set) : Int;
-
-	/**
-		How much the blur lighten/darken the image (default = 1)
+		See [h3d.pass.Blur.gain]
 	**/
 	public var gain(get, set) : Float;
 
 	/**
-		Increment to use a reduced size temporary texture and make use of hardware blur.
+		See [h3d.pass.Blur.quality]
 	**/
-	public var reduceSize : Int = 0;
+	public var quality(get, set) : Float;
 
 	var pass : h3d.pass.Blur;
 
-	public function new( quality = 1, passes = 1, sigma = 1. ) {
+	public function new( radius = 1., gain = 1., quality = 1. ) {
 		super();
 		smooth = true;
-		pass = new h3d.pass.Blur(quality, passes, sigma);
+		pass = new h3d.pass.Blur(radius, quality, gain);
 	}
 
 	inline function get_quality() return pass.quality;
 	inline function set_quality(v) return pass.quality = v;
-	inline function get_sigma() return pass.sigma;
-	inline function set_sigma(v) return pass.sigma = v;
-	inline function get_passes() return pass.passes;
-	inline function set_passes(v) return pass.passes = v;
+	inline function get_radius() return pass.radius;
+	inline function set_radius(v) return pass.radius = v;
 	inline function get_gain() return pass.gain;
 	inline function set_gain(v) return pass.gain = v;
+	inline function get_linear() return pass.linear;
+	inline function set_linear(v) return pass.linear = v;
 
 	override function sync( ctx : RenderContext, s : Sprite ) {
-		boundsExtend = (quality * 2) * passes;
+		boundsExtend = radius * 2;
 	}
 
 	override function draw( ctx : RenderContext, t : h2d.Tile ) {
 		var out = t.getTexture();
-		if( reduceSize > 0 ) {
-			out = ctx.textures.allocTarget("blurOut", t.width >> reduceSize, t.height >> reduceSize, false);
-			h3d.pass.Copy.run(t.getTexture(), out);
-		}
-		var tex = ctx.textures.allocTarget("blurTmp", out.width, out.height, false);
-		tex.filter = smooth ? Linear : Nearest;
-		pass.apply(out, tex);
-		if( reduceSize <= 0 )
-			return t;
-		var tt = h2d.Tile.fromTexture(out);
-		tt.scaleToSize(t.width, t.height);
-		return tt;
+		var old = out.filter;
+		out.filter = Linear;
+		pass.apply(ctx, out);
+		out.filter = old;
+		return t;
 	}
 
 }

+ 4 - 5
h2d/filter/DropShadow.hx

@@ -7,8 +7,8 @@ class DropShadow extends Glow {
 	public var angle : Float;
 	var alphaPass = new h3d.mat.Pass("");
 
-	public function new( distance : Float = 4., angle : Float = 0.785, color : Int = 0, alpha = 1., quality = 1, passes = 1, sigma = 1. ) {
-		super(color, alpha, quality, passes, sigma);
+	public function new( distance : Float = 4., angle : Float = 0.785, color : Int = 0, alpha = 1., radius : Float = 1., gain : Float = 1, quality = 1. ) {
+		super(color, alpha, radius, gain, quality);
 		this.distance = distance;
 		this.angle = angle;
 		alphaPass.addShader(new h3d.shader.UVDelta());
@@ -23,12 +23,11 @@ class DropShadow extends Glow {
 		setParams();
 		var save = ctx.textures.allocTarget("glowSave", t.width, t.height, false);
 		h3d.pass.Copy.run(t.getTexture(), save, None);
-		var glowTmpTex = (quality == 0) ? null : ctx.textures.allocTarget("glowTmp", t.width, t.height, false);
-		pass.apply(save, glowTmpTex);
+		pass.apply(ctx, save);
 		var dx = Math.round(Math.cos(angle) * distance);
 		var dy = Math.round(Math.sin(angle) * distance);
 		alphaPass.getShader(h3d.shader.UVDelta).uvDelta.set(dx / t.width, dy / t.height);
-		h3d.pass.Copy.run(t.getTexture(), save, Alpha, alphaPass );
+		h3d.pass.Copy.run(t.getTexture(), save, Alpha, alphaPass);
 		var ret = h2d.Tile.fromTexture(save);
 		ret.dx = dx;
 		ret.dy = dy;

+ 3 - 3
h2d/filter/Glow.hx

@@ -7,8 +7,8 @@ class Glow extends Blur {
 	public var knockout : Bool;
 	public var smoothColor : Bool;
 
-	public function new( color : Int = 0xFFFFFF, alpha = 1., quality = 1, passes = 1, sigma = 1., smoothColor = false ) {
-		super(quality, passes, sigma);
+	public function new( color : Int = 0xFFFFFF, alpha = 1., radius = 1., gain = 1., quality = 1., smoothColor = false ) {
+		super(radius, gain, quality);
 		this.color = color;
 		this.alpha = alpha;
 		this.smoothColor = smoothColor;
@@ -25,7 +25,7 @@ class Glow extends Blur {
 		setParams();
 		var save = ctx.textures.allocTarget("glowSave", t.width, t.height, false);
 		h3d.pass.Copy.run(t.getTexture(), save, None);
-		pass.apply(t.getTexture(), ctx.textures.allocTarget("glowTmp", t.width, t.height, false));
+		pass.apply(ctx, t.getTexture());
 		if( knockout )
 			h3d.pass.Copy.run(save, t.getTexture(), Erase);
 		else

+ 8 - 3
h3d/mat/PbrMaterial.hx

@@ -18,7 +18,8 @@ typedef PbrProps = {
 	var blend : PbrBlend;
 	var shadows : Bool;
 	var culling : Bool;
-	var alphaKill : Bool;
+	@:optional var alphaKill : Bool;
+	@:optional var emissive : Float;
 }
 
 class PbrMaterial extends Material {
@@ -45,7 +46,6 @@ class PbrMaterial extends Material {
 				blend : Alpha,
 				shadows : false,
 				culling : false,
-				alphaKill : false,
 			};
 		case "ui":
 			props = {
@@ -61,7 +61,6 @@ class PbrMaterial extends Material {
 				blend : None,
 				shadows : true,
 				culling : true,
-				alphaKill : false,
 			};
 		}
 		return props;
@@ -112,6 +111,7 @@ class PbrMaterial extends Material {
 		if( shadows ) getPass("shadow").culling = mainPass.culling;
 
 		// get values from specular texture
+		var emit = props.emissive == null ? 0 : props.emissive;
 		var spec = mainPass.getShader(h3d.shader.pbr.PropsTexture);
 		var def = mainPass.getShader(h3d.shader.pbr.PropsValues);
 		if( specularTexture != null ) {
@@ -119,6 +119,7 @@ class PbrMaterial extends Material {
 				spec = new h3d.shader.pbr.PropsTexture();
 				mainPass.addShader(spec);
 			}
+			spec.emissive = emit;
 			spec.texture = specularTexture;
 			if( def != null )
 				mainPass.removeShader(def);
@@ -129,8 +130,10 @@ class PbrMaterial extends Material {
 				def = new h3d.shader.pbr.PropsValues();
 				mainPass.addShader(def);
 			}
+			def.emissive = emit;
 		}
 
+
 	}
 
 	override function clone( ?m : BaseMaterial ) : BaseMaterial {
@@ -142,6 +145,7 @@ class PbrMaterial extends Material {
 	#if js
 	override function editProps() {
 		var props : PbrProps = props;
+		if( props.emissive == 0 ) Reflect.deleteField(props,"emissive");
 		return new js.jquery.JQuery('
 			<dl>
 				<dt>Mode</dt>
@@ -161,6 +165,7 @@ class PbrMaterial extends Material {
 						<option value="AlphaAdd">AlphaAdd</option>
 					</select>
 				</dd>
+				<dt>Emissive</dt><dd><input type="range" min="0" max="10" field="emissive"/></dd>
 				<dt>Shadows</dt><dd><input type="checkbox" field="shadows"/></dd>
 				<dt>Culled</dt><dd><input type="checkbox" field="culling"/></dd>
 				<dt>AlphaKill</dt><dd><input type="checkbox" field="alphaKill"/></dd>

+ 83 - 75
h3d/pass/Blur.hx

@@ -4,76 +4,64 @@ package h3d.pass;
 class Blur extends ScreenFx<h3d.shader.Blur> {
 
 	/**
-		Gives the blur quality : 0 for disable, 1 for 3x3, 2 for 5x5, etc.
+		How far in pixels the blur will go.
 	**/
-	@range(1, 4, 1) @inspect
-	public var quality(default, set) : Int;
+	public var radius(default,set) : Float;
 
 	/**
-		The amount of blur (gaussian blur value).
+		How much the blur increases or decreases the color amount (default = 1)
 	**/
-	@range(0, 2) @inspect
-	public var sigma(default, set) : Float;
+	public var gain(default,set) : Float;
 
 	/**
-		The number of blur passes we perform (default = 1)
+		Set linear blur instead of gaussian (default = 0).
 	**/
-	@range(0, 5, 1) @inspect
-	public var passes : Int;
-
+	public var linear(default, set) : Float;
 
 	/**
-		How much the blur increases or decreases the color amount (default = 1)
+		Adjust how much quality/speed tradeoff we want (default = 1)
 	**/
-	@range(0, 5, 1) @inspect
-	public var gain(default,set) : Float;
-
-	public var depthBlur(default,set) : {
-		depths : h3d.mat.Texture,
-		normals : h3d.mat.Texture,
-		camera : h3d.Camera,
-	};
+	public var quality(default,set) : Float;
 
 	var values : Array<Float>;
+	var offsets : Array<Float>;
 
-	public function new(quality = 1, passes = 1, sigma = 1., gain = 1.) {
+	public function new( radius = 1., gain = 1., linear = 0., quality = 1. ) {
 		super(new h3d.shader.Blur());
+		this.radius = radius;
 		this.quality = quality;
-		this.passes = passes;
-		this.sigma = sigma;
 		this.gain = gain;
 	}
 
-	function set_quality(q) {
+	function set_radius(r) {
+		if( radius == r )
+			return r;
 		values = null;
-		return quality = q;
+		return radius = r;
 	}
 
-	function set_sigma(s) {
+	function set_quality(q) {
+		if( quality == q )
+			return q;
 		values = null;
-		return sigma = s;
+		return quality = q;
 	}
 
 	function set_gain(s) {
+		if( gain == s )
+			return s;
 		values = null;
 		return gain = s;
 	}
 
-	function set_depthBlur(d) {
-		depthBlur = d;
-		if( d == null ) {
-			shader.isDepthDependant = false;
-			shader.depthTexture = null;
-			shader.normalTexture = null;
-		} else {
-			shader.isDepthDependant = true;
-			shader.depthTexture = d.depths;
-			shader.normalTexture = d.normals;
-		}
-		return d;
+	function set_linear(b) {
+		if( linear == b )
+			return b;
+		values = null;
+		return linear = b;
 	}
 
-	function gauss( x:Int, s:Float ) : Float {
+	function gauss( x:Float, s:Float ) : Float {
 		if( s <= 0 ) return x == 0 ? 1 : 0;
 		var sq = s * s;
 		var p = Math.pow(2.718281828459, -(x * x) / (2 * sq));
@@ -82,61 +70,81 @@ class Blur extends ScreenFx<h3d.shader.Blur> {
 
 	function calcValues() {
 		values = [];
+		offsets = [];
+
 		var tot = 0.;
-		for( i in 0...quality + 1 ) {
-			var g = gauss(i, sigma);
+		var qadj = hxd.Math.clamp(quality) * 0.7 + 0.3;
+		var width = radius > 0 ? Math.ceil(hxd.Math.max(radius - 1, 1) * qadj / 2) : 0;
+		var sigma = Math.sqrt(radius) * 2;
+		for( i in 0...width + 1 ) {
+			var i1 = i * 2;
+			var i2 = i == 0 ? 0 : i * 2 - 1;
+			var g1 = gauss(i1, sigma);
+			var g2 = gauss(i2,sigma);
+			var g = g1 + g2;
 			values[i] = g;
+			offsets[i] = i == 0 ? 0 : (g1 * i1  + g2 * i2) / (g * i * Math.sqrt(qadj));
 			tot += g;
 			if( i > 0 ) tot += g;
 		}
-		if( passes > 0 )
-			tot /= Math.pow(gain,1/passes);
-		for( i in 0...quality + 1 )
+
+		// eliminate too low contributing values
+		var minVal = values[0] * (0.01 / qadj);
+		while( values.length > 2 ) {
+			var last = values[values.length-1];
+			if( last > minVal ) break;
+			tot -= last * 2;
+			values.pop();
+		}
+
+		tot /= gain;
+		for( i in 0...values.length )
 			values[i] /= tot;
+
+		if( linear > 0 ) {
+			var m = gain / (values.length * 2 - 1);
+			for( i in 0...values.length ) {
+				values[i] = hxd.Math.lerp(values[i], m, linear);
+				offsets[i] = hxd.Math.lerp(offsets[i], i == 0 ? 0 : (i * 2 - 0.5) / (i * qadj), linear);
+			}
+		}
 	}
 
-	public function apply( src : h3d.mat.Texture, ?tmp : h3d.mat.Texture, ?output : h3d.mat.Texture, ?isDepth = false ) {
+	public function getKernelSize() {
+		if( values == null ) calcValues();
+		return radius <= 0 ? 0 : values.length * 2 - 1;
+	}
 
-		if( (quality <= 0 || passes <= 0 || sigma <= 0) && shader.fixedColor == null ) return;
+	public function apply( ctx : h3d.impl.RenderContext, src : h3d.mat.Texture, ?output : h3d.mat.Texture ) {
 
-		if( output == null ) output = src;
-		else h3d.pass.Copy.run(src, output);
-
-		var alloc = tmp == null;
-		if( alloc )
-			tmp = new h3d.mat.Texture(src.width, src.height, [Target], src.format);
+		if( radius <= 0 && shader.fixedColor == null ) {
+			if( output != null ) Copy.run(src, output);
+			return;
+		}
 
+		if( output == null ) output = src;
 		if( values == null ) calcValues();
 
-		shader.Quality = quality + 1;
+		var tmp = ctx.textures.allocTarget(src.name+"BlurTmp",src.width,src.height,false,src.format);
+
+		shader.Quality = values.length;
 		shader.values = values;
-		shader.isDepth = isDepth;
+		shader.offsets = offsets;
 
-		if( depthBlur != null )
-			shader.cameraInverseViewProj = depthBlur.camera.getInverseViewProj();
+		shader.texture = src;
+		shader.pixel.set(1 / src.width, 0);
+		engine.pushTarget(tmp);
+		render();
+		engine.popTarget();
 
+		shader.texture = tmp;
+		shader.pixel.set(0, 1 / src.height);
 		var outDepth = output.depthBuffer;
-		var tmpDepth = tmp.depthBuffer;
 		output.depthBuffer = null;
-		tmp.depthBuffer = null;
-		for( i in 0...passes ) {
-			shader.texture = output;
-			shader.pixel.set(1 / src.width, 0);
-			engine.pushTarget(tmp);
-			render();
-			engine.popTarget();
-
-			shader.texture = tmp;
-			shader.pixel.set(0, 1 / tmp.height);
-			engine.pushTarget(output);
-			render();
-			engine.popTarget();
-		}
+		engine.pushTarget(output);
+		render();
+		engine.popTarget();
 		output.depthBuffer = outDepth;
-		tmp.depthBuffer = tmpDepth;
-
-		if( alloc )
-			tmp.dispose();
 	}
 
 }

+ 4 - 0
h3d/pass/ScreenFx.hx

@@ -21,6 +21,10 @@ class ScreenFx<T:h3d.shader.ScreenShader> {
 		engine = h3d.Engine.getCurrent();
 	}
 
+	function copy( src, dst ) {
+		h3d.pass.Copy.run(src,dst);
+	}
+
 	public function setGlobals( ctx :  h3d.scene.RenderContext ) {
 		for( g in @:privateAccess ctx.sharedGlobals )
 			manager.globals.fastSet(g.gid, g.value);

+ 6 - 4
h3d/pass/ShadowMap.hx

@@ -15,7 +15,7 @@ class ShadowMap extends Default {
 	@ignore public var border : Border;
 	public var size(default,set) : Int;
 	public var color : h3d.Vector;
-	public var power = 10.0;
+	public var power = 30.0;
 	public var bias = 0.01;
 	public var blur : Blur;
 
@@ -33,7 +33,9 @@ class ShadowMap extends Default {
 		shadowBiasId = hxsl.Globals.allocID("shadow.bias");
 		shader = dshader = new h3d.shader.DirShadow();
 		color = new h3d.Vector();
-		blur = new Blur(2, 3);
+		blur = new Blur(5);
+		blur.quality = 0.5;
+		blur.shader.isDepth = true;
 		border = new Border(size, size);
 		customDepth = h3d.Engine.getCurrent().driver.hasFeature(AllocDepthBuffer);
 		if( !customDepth ) depth = h3d.mat.DepthBuffer.getDefault();
@@ -153,8 +155,8 @@ class ShadowMap extends Default {
 		if( border != null ) border.render();
 		ctx.engine.popTarget();
 
-		if( blur.quality > 0 && blur.passes > 0 )
-			blur.apply(texture, ctx.textures.allocTarget("tmpBlur", size, size, false), true);
+		if( blur.radius > 0 )
+			blur.apply(ctx, texture);
 
 		dshader.shadowMap = texture;
 		dshader.shadowBias = bias;

+ 1 - 2
h3d/scene/DefaultRenderer.hx

@@ -6,7 +6,6 @@ class DepthPass extends h3d.pass.Default {
 
 	var depthMapId : Int;
 	public var enableSky : Bool = false;
-	public var reduceSize : Int = 0;
 
 	public function new() {
 		super("depth");
@@ -18,7 +17,7 @@ class DepthPass extends h3d.pass.Default {
 	}
 
 	override function draw( passes ) {
-		var texture = ctx.textures.allocTarget("depthMap", ctx.engine.width >> reduceSize, ctx.engine.height >> reduceSize, true);
+		var texture = ctx.textures.allocTarget("depthMap", ctx.engine.width, ctx.engine.height, true);
 		ctx.engine.pushTarget(texture);
 		ctx.engine.clear(enableSky ? 0 : 0xFF0000, 1);
 		passes = super.draw(passes);

+ 2 - 2
h3d/scene/Renderer.hx

@@ -89,8 +89,8 @@ class Renderer extends hxd.impl.AnyProps {
 		ctx.engine.clear(color, depth, stencil);
 	}
 
-	inline function allocTarget( name : String, size = 0, depth = true, ?format ) {
-		return ctx.textures.allocTarget(name, ctx.engine.width >> size, ctx.engine.height >> size, depth, format);
+	inline function allocTarget( name : String, depth = true, size = 1., ?format ) {
+		return ctx.textures.allocTarget(name, Math.round(ctx.engine.width * size), Math.round(ctx.engine.height * size), depth, format);
 	}
 
 	function copy( from, to, ?blend ) {

+ 184 - 66
h3d/scene/pbr/Renderer.hx

@@ -31,22 +31,41 @@ package h3d.scene.pbr;
 }
 
 typedef SaoProps = {
-	var size : Int;
-	var blur : Int;
+	var enable : Bool;
+	var size : Float;
+	var blur : Float;
 	var samples : Int;
 	var radius : Float;
 	var intensity : Float;
 	var bias : Float;
 }
 
-typedef PbrRenderProps = {
+typedef BloomProps = {
+	var enable : Bool;
+	var size : Float;
+	var threshold : Float;
+	var intensity : Float;
+	var blur : Float;
+}
+
+typedef ShadowProps = {
+	var enable : Bool;
+	var power : Float;
+	var blur : Float;
+	var bias : Float;
+	var quality : Float;
+}
+
+typedef RenderProps = {
 	var mode : DisplayMode;
 	var env : String;
 	var envPower : Float;
 	var exposure : Float;
 	var sky : SkyMode;
 	var tone : TonemapMap;
+	var bloom : BloomProps;
 	var sao : SaoProps;
+	var shadow : ShadowProps;
 }
 
 class Renderer extends h3d.scene.Renderer {
@@ -65,28 +84,32 @@ class Renderer extends h3d.scene.Renderer {
 	var sao = new h3d.pass.ScalableAO();
 	var saoBlur = new h3d.pass.Blur();
 	var saoCopy = new h3d.pass.Copy();
+	var bloomPass = new h3d.pass.ScreenFx(new h3d.shader.pbr.Bloom());
+	var bloomBlur = new h3d.pass.Blur();
+	var hasDebugEvent = false;
 
 	public var skyMode : SkyMode = Hide;
 	public var toneMode : TonemapMap = Reinhard;
 	public var displayMode : DisplayMode = Pbr;
 	public var env : Environment;
 	public var exposure(get,set) : Float;
+	public var debugMode = 0;
 
 	var output = new h3d.pass.Output("mrt",[
 		Value("output.color"),
 		Vec4([Value("output.normal",3),Value("output.depth",1)]),
 		Vec4([Value("output.metalness"), Value("output.roughness"), Value("output.occlusion"), Const(0)]),
+		Vec4([Value("output.emissive"),Const(0),Const(0),Const(0)])
 	]);
 
 	public function new(env) {
 		super();
 		this.env = env;
-		shadows.bias = 0.0;
-		shadows.power = 1000;
-		shadows.blur.passes = 1;
 		defaultPass = new h3d.pass.Default("default");
 		pbrOut.addShader(pbrIndirect);
 		pbrOut.addShader(pbrProps);
+		bloomPass.addShader(pbrProps);
+		slides.addShader(pbrProps);
 		allPasses.push(output);
 		allPasses.push(defaultPass);
 		allPasses.push(shadows);
@@ -131,53 +154,65 @@ class Renderer extends h3d.scene.Renderer {
 	}
 
 	override function render() {
-		var props : PbrRenderProps = props;
-
-		shadows.draw(get("shadow"));
+		var props : RenderProps = props;
+
+		if( props.shadow.enable ) {
+			var sh = props.shadow;
+			shadows.power = sh.power;
+			shadows.blur.radius = sh.blur;
+			shadows.blur.quality = sh.quality;
+			shadows.bias = sh.bias * 0.1;
+			shadows.draw(get("shadow"));
+		} else
+			get("shadow");
+		pbrDirect.enableShadow = props.shadow.enable;
 
 		var albedo = allocTarget("albedo");
-		var normal = allocTarget("normalDepth",0,false,RGBA32F);
-		var pbr = allocTarget("pbr",0,false);
-		setTargets([albedo,normal,pbr]);
+		var normal = allocTarget("normalDepth",false,1.,RGBA32F);
+		var pbr = allocTarget("pbr",false,1.);
+		var emit = allocTarget("emit",false,1.,RGBA16F);
+		setTargets([albedo,normal,pbr,emit]);
 		clear(0, 1, 0);
 		mainDraw();
 
 		setTarget(albedo);
 		draw("albedo");
 
-		if( props.sao != null ) {
+		if( displayMode == Env )
+			clear(0xFF404040);
+
+		if( displayMode == MatCap ) {
+			clear(0xFF808080);
+			setTarget(pbr);
+			clear(0x00FF80FF);
+		}
+
+		if( props.sao.enable ) {
 			var sp = props.sao;
-			var saoTex = allocTarget("sao",sp.size,false);
+			var saoTex = allocTarget("sao",false,sp.size);
 			setTarget(saoTex);
 			sao.shader.depthTextureChannel = A;
 			sao.shader.normalTextureChannel = R;
-			sao.shader.numSamples = 1 << sp.samples;
+			sao.shader.numSamples = sp.samples;
 			sao.shader.sampleRadius	= sp.radius;
 			sao.shader.intensity = sp.intensity;
 			sao.shader.bias = sp.bias * sp.bias;
 			sao.apply(normal,normal,ctx.camera);
-			saoBlur.passes = sp.blur;
-			saoBlur.apply(saoTex);
+			saoBlur.radius = sp.blur;
+			saoBlur.quality = 0.5;
+			saoBlur.apply(ctx, saoTex);
 			saoCopy.pass.setColorMask(false,false,true,false);
 			saoCopy.apply(saoTex, pbr, Multiply);
 		}
 
-		if( displayMode == Env )
-			clear(0xFF404040);
-
-		if( displayMode == MatCap ) {
-			clear(0xFF808080);
-			setTarget(pbr);
-			clear(0x00FF80FF);
-		}
-
-		var output = allocTarget("hdrOutput", 0, true, RGBA16F);
-		setTarget(output);
+		var hdr = allocTarget("hdrOutput", false, 1, RGBA16F);
+		setTarget(hdr);
 		if( ctx.engine.backgroundColor != null )
 			clear(ctx.engine.backgroundColor);
 		pbrProps.albedoTex = albedo;
 		pbrProps.normalTex = normal;
 		pbrProps.pbrTex = pbr;
+		pbrProps.emitTex = emit;
 		pbrProps.cameraInverseViewProj = ctx.camera.getInverseViewProj();
 
 		pbrDirect.cameraPosition.load(ctx.camera.pos);
@@ -196,9 +231,9 @@ class Renderer extends h3d.scene.Renderer {
 		} else {
 			var pdir = Std.instance(ls.shadowLight, h3d.scene.pbr.DirLight);
 			if( pbrOut.getShader(h3d.shader.pbr.Light.DirLight) == null ) {
+				pbrOut.addShader(shadows.shader);
 				pbrOut.addShader(pbrDirect);
 				pbrOut.addShader(pbrSun);
-				pbrOut.addShader(shadows.shader);
 			}
 			pbrSun.lightColor.load(ls.shadowLight.color);
 			if( pdir != null ) pbrSun.lightColor.scale3(pdir.power * pdir.power);
@@ -230,14 +265,29 @@ class Renderer extends h3d.scene.Renderer {
 		draw("lights");
 		pbrProps.isScreen = true;
 
-		var ldr = allocTarget("ldrOutput",0,true);
+		var bloom : h3d.mat.Texture = null;
+		if( props.bloom.enable ) {
+			var pb = props.bloom;
+			bloom = allocTarget("bloom", false, pb.size, RGBA16F);
+			setTarget(bloom);
+			bloomPass.shader.hdr = hdr;
+			bloomPass.shader.threshold = pb.threshold;
+			bloomPass.shader.intensity = pb.intensity;
+			bloomPass.render();
+			bloomBlur.radius = pb.blur;
+			bloomBlur.apply(ctx, bloom);
+		}
+
+		var ldr = allocTarget("ldrOutput");
 		setTarget(ldr);
+		tonemap.shader.bloom = bloom;
+		tonemap.shader.hasBloom = bloom != null;
 		tonemap.shader.mode = switch( toneMode ) {
 		case Linear: 0;
 		case Reinhard: 1;
 		default: 0;
 		};
-		tonemap.shader.hdrTexture = output;
+		tonemap.shader.hdrTexture = hdr;
 		tonemap.render();
 
 		postDraw();
@@ -251,26 +301,74 @@ class Renderer extends h3d.scene.Renderer {
 
 		case Debug:
 
-			slides.shader.shadowMap = ctx.textures.getNamed("shadowMap");
-			slides.shader.albedo = albedo;
-			slides.shader.normal = normal;
-			slides.shader.pbr = pbr;
+			var shadowMap = ctx.textures.getNamed("shadowMap");
+			slides.shader.shadowMap = shadowMap;
 			slides.render();
 
+			if( !hasDebugEvent ) {
+				hasDebugEvent = true;
+				hxd.Stage.getInstance().addEventTarget(onEvent);
+			}
+
+		}
+
+		if( hasDebugEvent && displayMode != Debug ) {
+			hasDebugEvent = false;
+			hxd.Stage.getInstance().removeEventTarget(onEvent);
+		}
+	}
+
+	function onEvent(e:hxd.Event) {
+		if( e.kind == EPush && e.button == 2 ) {
+			var st = hxd.Stage.getInstance();
+			var x = Std.int((e.relX / st.width) * 4);
+			var y = Std.int((e.relY / st.height) * 4);
+			if( slides.shader.mode != Full ) {
+				slides.shader.mode = Full;
+			} else {
+				var a : Array<h3d.shader.pbr.Slides.DebugMode>;
+				if( y == 3 )
+					a = [Emmissive,Depth,AO,Shadow];
+				else
+					a = [Albedo,Normal,Roughness,Metalness];
+				slides.shader.mode = a[x];
+			}
 		}
 	}
 
 	// ---- PROPS
 
 	override function getDefaultProps( ?kind : String ):Any {
-		var props : PbrRenderProps = {
+		var props : RenderProps = {
 			mode : Pbr,
 			env : null,
 			envPower : 1.,
 			exposure : 0.,
 			sky : Hide,
 			tone : Reinhard,
-			sao : null,
+			shadow : {
+				enable : true,
+				power : 40,
+				blur : 9,
+				bias : 0.1,
+				quality : 0.3,
+			},
+			sao : {
+				enable : false,
+				size : 1,
+				blur : 5,
+				samples : 30,
+				radius : 1,
+				intensity : 1,
+				bias : 0.1,
+			},
+			bloom : {
+				enable : false,
+				size : 0.5,
+				blur : 3,
+				intensity : 1.,
+				threshold : 0.5,
+			},
 		};
 		return props;
 	}
@@ -278,7 +376,7 @@ class Renderer extends h3d.scene.Renderer {
 	override function refreshProps() {
 		if( env == null )
 			return;
-		var props : PbrRenderProps = props;
+		var props : RenderProps = props;
 		if( props.env != null && props.env != env.source.name ) {
 			var t = hxd.res.Loader.currentInstance.load(props.env).toTexture();
 			var prev = env;
@@ -296,32 +394,39 @@ class Renderer extends h3d.scene.Renderer {
 
 	#if js
 	override function editProps() {
-		var props : PbrRenderProps = props;
-		if( props.sao == cast true ) {
-			props.sao = {
-				size : 0,
-				blur : 1,
-				samples : 5,
-				radius : 1,
-				intensity : 1,
-				bias : 0.1,
-			};
-		} else if( props.sao == cast false ) {
-			Reflect.deleteField(props,"sao");
-		}
+		var props : RenderProps = props;
 
-		var saoProps = props.sao == null ? '' : '
+		var shadowProps = '
+			<dl>
+			<dt>Power</dt><dd><input type="range" min="0" max="100" step="0.1" field="shadow.power"/></dd>
+			<dt>Blur</dt><dd><input type="range" min="0" max="20" field="shadow.blur"/></dd>
+			<dt>Quality</dt><dd><input type="range" field="shadow.quality"/></dd>
+			<dt>Bias</dt><dd><input type="range" min="0" max="1" field="shadow.bias"/></dd>
+			</dt>
+		';
+
+		var saoProps = '
 			<dl>
 			<dt>Intensity</dt><dd><input type="range" min="0" max="10" field="sao.intensity"/></dd>
 			<dt>Radius</dt><dd><input type="range" min="0" max="10" field="sao.radius"/></dd>
 			<dt>Bias</dt><dd><input type="range" min="0" max="0.5" field="sao.bias"/></dd>
-			<dt>Size</dt><dd><input type="range" min="0" max="3" field="sao.size" step="1"/></dd>
-			<dt>Blur</dt><dd><input type="range" min="0" max="5" field="sao.blur" step="1"/></dd>
-			<dt>Samples</dt><dd><input type="range" min="3" max="10" field="sao.samples" step="1"/></dd>
+			<dt>Size</dt><dd><input type="range" min="0" max="1" field="sao.size"/></dd>
+			<dt>Blur</dt><dd><input type="range" min="0" max="20" field="sao.blur"/></dd>
+			<dt>Samples</dt><dd><input type="range" min="3" max="256" field="sao.samples" step="1"/></dd>
+			</dl>
+		';
+
+		var bloomProps = '
+			<dl>
+			<dt>Intensity</dt><dd><input type="range" min="0" max="2" field="bloom.intensity"/></dd>
+			<dt>Threshold</dt><dd><input type="range" min="0" max="1" field="bloom.threshold"/></dd>
+			<dt>Size</dt><dd><input type="range" min="0" max="1" field="bloom.size"/></dd>
+			<dt>Blur</dt><dd><input type="range" min="0" max="20" field="bloom.blur"/></dd>
 			</dl>
 		';
 
 		return new js.jquery.JQuery('
+			<div class="group" name="Renderer">
 			<dl>
 				<dt>Tone</dt>
 				<dd>
@@ -347,20 +452,33 @@ class Renderer extends h3d.scene.Renderer {
 						<option value="Env">Show</option>
 						<option value="Irrad">Show Irrad</option>
 					</select>
+					<br/>
+					<input type="range" min="0" max="2" field="envPower"/>
 				</dd>
-				<dt>&nbsp;</dt><dd><input type="range" min="0" max="2" field="envPower"/></dd>
 				<dt>Exposure</dt><dd><input type="range" min="-3" max="3" field="exposure"></dd>
 			</dl>
-
-			<dl>
-				<dt>SAO</dt>
-				<dd>
-					<input type="checkbox" field="sao"/>
-					$saoProps
-				</dd>
-			</dl>
-
-
+			</div>
+
+			<div class="group">
+				<div class="title">
+					<input type="checkbox" field="shadow.enable"/> Shadows
+				</div>
+				$shadowProps
+			</div>
+
+			<div class="group">
+				<div class="title">
+					<input type="checkbox" field="bloom.enable"/> Bloom
+				</div>
+				$bloomProps
+			</div>
+
+			<div class="group">
+				<div class="title">
+					<input type="checkbox" field="sao.enable"/> SAO
+				</div>
+				$saoProps
+			</div>
 		');
 	}
 	#end

+ 4 - 3
h3d/shader/Blur.hx

@@ -11,6 +11,7 @@ class Blur extends ScreenShader {
 		@param @const var Quality : Int;
 		@param @const var isDepth : Bool;
 		@param var values : Array<Float,Quality>;
+		@param var offsets : Array<Float,Quality>;
 		@param var pixel : Vec2;
 
 		@const var hasFixedColor : Bool;
@@ -28,7 +29,7 @@ class Blur extends ScreenShader {
 				var color = vec4(0, 0, 0, 0);
 				var ncur = unpackNormal(normalTexture.get(input.uv));
 				@unroll for( i in -Quality + 1...Quality ) {
-					var uv = input.uv + pixel * float(i);
+					var uv = input.uv + pixel * offsets[i < 0 ? -i : i];
 					var c = texture.get(uv);
 					var p = getPosition(uv);
 					var d = (p - pcur).dot(p - pcur);
@@ -43,12 +44,12 @@ class Blur extends ScreenShader {
 			else if( isDepth ) {
 				var val = 0.;
 				@unroll for( i in -Quality + 1...Quality )
-					val += unpack(texture.get(input.uv + pixel * float(i))) * values[i < 0 ? -i : i];
+					val += unpack(texture.get(input.uv + pixel * offsets[i < 0 ? -i : i] * i)) * values[i < 0 ? -i : i];
 				output.color = pack(val.min(0.9999999));
 			} else {
 				var color = vec4(0, 0, 0, 0);
 				@unroll for( i in -Quality + 1...Quality )
-					color += texture.get(input.uv + pixel * float(i)) * values[i < 0 ? -i : i];
+					color += texture.get(input.uv + pixel * offsets[i < 0 ? -i : i] * i) * values[i < 0 ? -i : i];
 				output.color = color;
 			}
 			if( hasFixedColor ) {

+ 1 - 1
h3d/shader/ScreenShader.hx

@@ -20,10 +20,10 @@ class ScreenShader extends hxsl.Shader {
 
 		function __init__() {
 			output.color = pixelColor;
+			calculatedUV = input.uv;
 		}
 
 		function vertex() {
-			calculatedUV = input.uv;
 			output.position = vec4(input.position.x, input.position.y * flipY, 0, 1);
 		}
 	};

+ 23 - 0
h3d/shader/pbr/Bloom.hx

@@ -0,0 +1,23 @@
+package h3d.shader.pbr;
+
+class Bloom extends ScreenShader {
+
+	static var SRC = {
+
+		var albedo : Vec3;
+		var emissive : Float;
+
+		@param var hdr : Sampler2D;
+		@param var threshold : Float;
+		@param var intensity : Float;
+
+		function fragment() {
+			pixelColor = hdr.get(calculatedUV);
+			var lum = pixelColor.rgb.dot(vec3(0.2126, 0.7152, 0.0722));
+			if( lum < threshold ) pixelColor.rgb = vec3(0.) else pixelColor.rgb *= intensity * (lum - threshold) / lum;
+			pixelColor.rgb += albedo * emissive;
+		}
+
+	};
+
+}

+ 4 - 0
h3d/shader/pbr/Lighting.hx

@@ -48,8 +48,12 @@ class Direct extends PropsDefinition {
 		var pbrLightDirection : Vec3;
 		var pbrLightColor : Vec3;
 		@const var doDiscard : Bool = true;
+		@const var enableShadow : Bool = true;
 
 		function fragment() {
+
+			if( !enableShadow ) shadow = 1.;
+
 			var NdL = normal.dot(pbrLightDirection).max(0.);
 			if( pbrLightColor.dot(pbrLightColor) > 0.0001 && NdL > 0 ) {
 

+ 1 - 0
h3d/shader/pbr/PropsDefinition.hx

@@ -9,6 +9,7 @@ class PropsDefinition extends hxsl.Shader {
 		var metalness : Float;
 		var roughness : Float;
 		var occlusion : Float;
+		var emissive : Float;
 		var pbrSpecularColor : Vec3;
 		var transformedPosition : Vec3;
 

+ 4 - 0
h3d/shader/pbr/PropsImport.hx

@@ -6,6 +6,7 @@ class PropsImport extends hxsl.Shader {
 		@param var albedoTex : Sampler2D;
 		@param var normalTex : Sampler2D;
 		@param var pbrTex : Sampler2D;
+		@param var emitTex : Sampler2D;
 		@const var isScreen : Bool = true;
 
 		@param var cameraInverseViewProj : Mat4;
@@ -16,6 +17,7 @@ class PropsImport extends hxsl.Shader {
 		var metalness : Float;
 		var roughness : Float;
 		var occlusion : Float;
+		var emissive : Float;
 		var calculatedUV : Vec2;
 		var transformedPosition : Vec3;
 		var pbrSpecularColor : Vec3;
@@ -34,6 +36,8 @@ class PropsImport extends hxsl.Shader {
 			roughness = pbr.g;
 			occlusion = pbr.b;
 
+			emissive = emitTex.get(uv).r;
+
 			pbrSpecularColor = mix(vec3(0.04),albedo,metalness);
 
 			// this is the original object transformed position, not our current drawing object one

+ 3 - 0
h3d/shader/pbr/PropsTexture.hx

@@ -3,10 +3,12 @@ package h3d.shader.pbr;
 class PropsTexture extends hxsl.Shader {
 	static var SRC = {
 		@param var texture : Sampler2D;
+		@param var emissive : Float;
 		var output : {
 			metalness : Float,
 			roughness : Float,
 			occlusion : Float,
+			emissive : Float,
 		};
 		var calculatedUV : Vec2;
 		function fragment() {
@@ -14,6 +16,7 @@ class PropsTexture extends hxsl.Shader {
 			output.metalness = v.r;
 			output.roughness = 1 - v.g * v.g;
 			output.occlusion = v.b;
+			output.emissive = emissive * v.a;
 		}
 	}
 	public function new(?t) {

+ 5 - 1
h3d/shader/pbr/PropsValues.hx

@@ -8,25 +8,29 @@ class PropsValues extends hxsl.Shader {
 			metalness : Float,
 			roughness : Float,
 			occlusion : Float,
+			emissive : Float,
 		};
 
 		@param var metalness : Float;
 		@param var roughness : Float;
 		@param var occlusion : Float;
+		@param var emissive : Float;
 
 		function fragment() {
 			output.metalness = metalness;
 			output.roughness = roughness;
 			output.occlusion = occlusion;
+			output.emissive = emissive;
 		}
 
 	};
 
-	public function new(metalness=0.,roughness=1.,occlusion=1.) {
+	public function new(metalness=0.,roughness=1.,occlusion=1.,emissive=0.) {
 		super();
 		this.metalness = metalness;
 		this.roughness = roughness;
 		this.occlusion = occlusion;
+		this.emissive = emissive;
 	}
 
 }

+ 59 - 24
h3d/shader/pbr/Slides.hx

@@ -1,36 +1,71 @@
 package h3d.shader.pbr;
 
+@:enum abstract DebugMode(Int) {
+	var Full = 0;
+	var Albedo = 1;
+	var Normal = 2;
+	var Roughness = 3;
+	var Metalness = 4;
+	var Emmissive = 5;
+	var Depth = 6;
+	var AO = 7;
+	var Shadow = 8;
+}
+
 class Slides extends ScreenShader {
+
 	static var SRC = {
-		@param var albedo : Sampler2D;
-		@param var normal : Sampler2D;
-		@param var pbr : Sampler2D;
+
+		var albedo : Vec3;
+		var depth : Float;
+		var normal : Vec3;
+		var metalness : Float;
+		var roughness : Float;
+		var occlusion : Float;
+		var emissive : Float;
+
 		@param var shadowMap : Channel;
-		function fragment() {
-			var uv = input.uv;
-			var x = input.position.x;
-			var y = input.position.y;
-			var normDepth = normal.get(uv);
-			var normal = normDepth.xyz;
-			var depth = normDepth.w;
-			if( x < -0.5 ) {
-				output.color = albedo.get(uv);
-			} else if( x < 0 ) {
-				if( y < -0.5 )
-					output.color = vec4( depth.xxx.fract(), 1. );
-				else
-					output.color = packNormal( normal );
-			} else if( x < 0.5 ) {
-				if( y < -0.5 )
-					output.color = vec4( pbr.get(uv).zzz, 1. );
+		@const var smode : Int;
+
+		function getColor(x:Float,y:Float) : Vec3 {
+			var color : Vec3;
+			if( y < 3 ) {
+				if( x < 1 )
+					color = albedo.sqrt();
+				else if( x < 2 )
+					color = packNormal(normal).rgb;
+				else if( x < 3 )
+					color = roughness.xxx;
 				else
-					output.color = vec4( pbr.get(uv).xxx, 1. );
+					color = metalness.xxx;
 			} else {
-				if( y < -0.5 )
-					output.color = vec4( shadowMap.get((uv - 0.75) * 4).xxx, 1.);
+				if( x < 1 )
+					color = emissive.xxx;
+				else if( x < 2 )
+					color = depth.xxx;
+				else if( x < 3 )
+					color = occlusion.xxx;
 				else
-					output.color = vec4( pbr.get(uv).yyy, 1. );
+					color = shadowMap.get(vec2(x,y) - 3).xxx;
 			}
+			return color;
+		}
+
+		function fragment() {
+			var color : Vec3;
+			var x = input.uv.x * 4;
+			var y = input.uv.y * 4;
+			if( smode == 0 )
+				color = getColor(x,y);
+			else
+				color = getColor( (smode - 1)%4 + input.uv.x, int((smode - 1) / 4) * 3 + input.uv.y );
+			pixelColor = vec4(color, 1.);
 		}
 	};
+
+	public var mode(get,set) : DebugMode;
+
+	function get_mode() : DebugMode { return cast smode; }
+	function set_mode(m) { smode = cast m; return m; }
+
 }

+ 5 - 1
h3d/shader/pbr/ToneMapping.hx

@@ -8,11 +8,15 @@ class ToneMapping extends ScreenShader {
 		@param var hdrTexture : Sampler2D;
 		@param var exposureExp : Float;
 		@const var isSRBG : Bool;
-
 		@const var mode : Int;
 
+		@const var hasBloom : Bool;
+		@param var bloom : Sampler2D;
+
 		function fragment() {
 			var color = hdrTexture.get(calculatedUV);
+			if( hasBloom )
+				color += bloom.get(calculatedUV);
 
 			color.rgb *= exposureExp;
 

+ 79 - 0
samples/Blur.hx

@@ -0,0 +1,79 @@
+class Blur extends SampleApp {
+
+	var bmp : h2d.Bitmap;
+	var blur : h2d.filter.Blur;
+	var gfx : h2d.Graphics;
+	var text : h2d.Text;
+
+	override function init() {
+		super.init();
+
+		bmp = new h2d.Bitmap(hxd.Res.hxlogo.toTile(), s2d);
+		bmp.x = bmp.y = 100;
+
+		blur = new h2d.filter.Blur();
+		bmp.filter = blur;
+
+		gfx = new h2d.Graphics(s2d);
+		gfx.x = 500;
+		gfx.y = 500;
+		gfx.scale(16);
+
+		text = new h2d.Text(hxd.res.DefaultFont.get(), s2d);
+		text.y = 720;
+		text.x = 5;
+
+		addSlider("Radius", function() return blur.radius, function(v) { blur.radius = v; sync(); }, 0, 40).width = 200;
+		addSlider("Linear", function() return blur.linear, function(b) { blur.linear = b; sync(); });
+		addSlider("Gain", function() return blur.gain, function(b) { blur.gain = b; sync(); }, 0, 2);
+		addSlider("Quality", function() return blur.quality, function(v) { blur.quality = v; sync(); }, 0, 1);
+
+		sync();
+	}
+
+	function sync() @:privateAccess {
+
+		gfx.clear();
+		blur.pass.calcValues();
+
+		gfx.beginFill(0xFF0000,0.2);
+		gfx.drawRect(-blur.radius,0,blur.radius*2,10);
+		gfx.endFill();
+
+		gfx.lineStyle(0.1,0xFF0000, 0.5);
+		for( i in 0...Math.ceil(blur.radius) ) {
+			gfx.moveTo(i, 0);
+			gfx.lineTo(i, 10);
+			gfx.moveTo(-i, 0);
+			gfx.lineTo(-i, 10);
+		}
+
+		gfx.lineStyle(0.1,0xFFFFFF);
+		var q = blur.pass.values.length - 1;
+		for( i in -q...q+1 ) {
+			var x = blur.pass.offsets[i < 0 ? -i : i] * i;
+			var y = (1 - blur.pass.values[i < 0 ? -i : i]) * 10;
+			if( i == -q )
+				gfx.moveTo(x,y);
+			else
+				gfx.lineTo(x,y);
+		}
+		gfx.lineStyle();
+
+		gfx.beginFill(0x00FF00);
+		for( i in -q...q+1 ) {
+			var x = blur.pass.offsets[i < 0 ? -i : i] * i;
+			var y = (1 - blur.pass.values[i < 0 ? -i : i]) * 10;
+			gfx.drawCircle(x,y,0.3,8);
+		}
+
+		var k = blur.pass.values.length * 2 - 1;
+		text.text = k+"x"+k+"\n"+[for( v in blur.pass.values ) hxd.Math.fmt(v)].toString()+"\n"+[for( i in 0...q+1 ) hxd.Math.fmt(blur.pass.offsets[i]*i)].toString();
+	}
+
+	static function main() {
+		hxd.Res.initEmbed();
+		new Blur();
+	}
+
+}

+ 11 - 11
samples/Filters.hx

@@ -10,6 +10,12 @@ class Filters extends hxd.App {
 	override function init() {
 		engine.backgroundColor = 0x002000;
 
+		mask = new h2d.Graphics(s2d);
+		mask.beginFill(0xFF0000, 0.5);
+		mask.drawCircle(0, 0, 60);
+		mask.x = s2d.width*0.5-20;
+		mask.y = s2d.height*0.5-50;
+
 		spr = new h2d.Sprite(s2d);
 		spr.x = s2d.width * 0.5;
 		spr.y = s2d.height * 0.5;
@@ -17,12 +23,6 @@ class Filters extends hxd.App {
 		bmp = new h2d.Bitmap(hxd.Res.hxlogo.toTile(), spr);
 		bmp.colorKey = 0xFFFFFF;
 
-		mask = new h2d.Graphics(spr);
-		mask.beginFill(0xFF0000, 0.5);
-		mask.drawCircle(0, 0, 60);
-		mask.x = -20;
-		mask.y = -50;
-
 		disp = hxd.Res.normalmap.toTile();
 		setFilters(6);
 
@@ -57,11 +57,11 @@ class Filters extends hxd.App {
 		case 0:
 			spr.filter = null;
 		case 1:
-			spr.filter = new h2d.filter.Blur(2, 1, 100);
+			spr.filter = new h2d.filter.Blur(5);
 		case 2:
-			spr.filter = new h2d.filter.Glow(0xFFFFFF, 100, 2);
+			spr.filter = new h2d.filter.Glow(0xFFFFFF, 100, 5);
 		case 3:
-			spr.filter = new h2d.filter.DropShadow(8,Math.PI/4,0,1,2,2);
+			spr.filter = new h2d.filter.DropShadow(8,Math.PI/4);
 		case 4:
 			spr.filter = new h2d.filter.Displacement(disp,4,4);
 		case 5:
@@ -69,9 +69,9 @@ class Filters extends hxd.App {
 			g.knockout = true;
 			spr.filter = g;
 		case 6:
-			var g = new h2d.filter.Glow(0xFFA500, 50, 2, 2);
+			var g = new h2d.filter.Glow(0xFFA500, 50, 2);
 			g.knockout = true;
-			spr.filter = new h2d.filter.Group([g, new h2d.filter.Displacement(disp, 3, 3), new h2d.filter.Blur(3, 2, 0.8), new h2d.filter.DropShadow(8, Math.PI / 4, 0, 1, 3, 3, 0.5)]);
+			spr.filter = new h2d.filter.Group([g, new h2d.filter.Displacement(disp, 3, 3), new h2d.filter.Blur(3), new h2d.filter.DropShadow(8, Math.PI / 4)]);
 		case 7:
 			var m = new h3d.Matrix();
 			m.identity();

+ 6 - 6
samples/Sao.hx

@@ -16,7 +16,7 @@ class CustomRenderer extends h3d.scene.DefaultRenderer {
 	public function new() {
 		super();
 		sao = new h3d.pass.ScalableAO();
-		saoBlur = new h3d.pass.Blur(3, 3, 2);
+		saoBlur = new h3d.pass.Blur();
 		sao.shader.sampleRadius	= 0.2;
 		sao.shader.numSamples = 30;
 		hasMRT = h3d.Engine.getCurrent().driver.hasFeature(MultipleRenderTargets);
@@ -41,11 +41,11 @@ class CustomRenderer extends h3d.scene.DefaultRenderer {
 		resetTarget();
 		if(mode != 1) {
 			bench.measure("sao");
-			var saoTarget = allocTarget("sao",0,false);
+			var saoTarget = allocTarget("sao");
 			setTarget(saoTarget);
 			sao.apply(depth, normal, ctx.camera);
 			bench.measure("saoBlur");
-			saoBlur.apply(saoTarget, allocTarget("saoBlurTmp", 0, false));
+			saoBlur.apply(ctx, saoTarget);
 			bench.measure("saoBlend");
 			copy(color, null);
 			copy(saoTarget, null, mode == 0 ? Multiply : null);
@@ -111,7 +111,9 @@ class Sao extends SampleApp {
 		addSlider("Bias", function() return c.sao.shader.bias, function(v) c.sao.shader.bias = v, 0, 0.3);
 		addSlider("Intensity", function() return c.sao.shader.intensity, function(v) c.sao.shader.intensity = v, 0, 10);
 		addSlider("Radius", function() return c.sao.shader.sampleRadius, function(v) c.sao.shader.sampleRadius = v);
-		addSlider("Blur", function() return c.saoBlur.sigma, function(v) c.saoBlur.sigma = v, 0, 3);
+		addSlider("Blur", function() return c.saoBlur.radius, function(v) c.saoBlur.radius = v, 0, 10);
+		addSlider("BlurQuality", function() return c.saoBlur.quality, function(v) c.saoBlur.quality = v);
+		addSlider("BlurLineary", function() return c.saoBlur.linear, function(v) c.saoBlur.linear = v);
 
 		onResize();
 	}
@@ -139,8 +141,6 @@ class Sao extends SampleApp {
 			r.mode = 1;
 		if(K.isPressed(K.NUMBER_3))
 			r.mode = 2;
-		if(K.isPressed("B".code))
-			r.saoBlur.passes = r.saoBlur.passes == 0 ? 3 : 0;
 		#if hl
 		if( K.isPressed("V".code) )
 			@:privateAccess hxd.Stage.getInstance().window.vsync = !hxd.Stage.getInstance().window.vsync;

+ 6 - 2
samples/Shadows.hx

@@ -1,6 +1,6 @@
 import hxd.Math;
 
-class Shadows extends hxd.App {
+class Shadows extends SampleApp {
 
 	var time : Float = 0.;
 	var spheres : Array<h3d.scene.Object>;
@@ -8,6 +8,7 @@ class Shadows extends hxd.App {
 	var shadow : h3d.pass.ShadowMap;
 
 	override function init() {
+		super.init();
 
 		var floor = new h3d.prim.Cube(10, 10, 0.1);
 		floor.addNormals();
@@ -37,7 +38,10 @@ class Shadows extends hxd.App {
 		dir.enableSpecular = true;
 
 		shadow = s3d.renderer.getPass(h3d.pass.ShadowMap);
-		shadow.blur.passes = 3;
+		addSlider("Power", function() return shadow.power, function(p) shadow.power = p, 0, 100);
+		addSlider("Radius", function() return shadow.blur.radius, function(r) shadow.blur.radius = r, 0, 20);
+		addSlider("Quality", function() return shadow.blur.quality, function(r) shadow.blur.quality = r);
+		addSlider("Bias", function() return shadow.bias, function(r) shadow.bias = r, 0, 0.1);
 
 		s3d.camera.pos.set(12, 12, 6);
 		new h3d.scene.CameraController(s3d).loadFromCamera();