Browse Source

added pbr renderer and light system

ncannasse 7 years ago
parent
commit
06ef2cc519

+ 272 - 0
h3d/scene/pbr/Irradiance.hx

@@ -0,0 +1,272 @@
+package h3d.scene.pbr;
+
+class IrradBase extends h3d.shader.ScreenShader {
+
+	static var SRC = {
+
+		@const var samplesBits : Int;
+		#if (js || flash)
+		@const var SamplesCount : Int;
+		@param var hammerTbl : Array<Vec4,SamplesCount>;
+		#end
+
+		function _reversebits( i : Int ) : Int {
+			var r = (i << 16) | (i >>> 16);
+			r = ((r & 0x00ff00ff) << 8) | ((r & 0xff00ff00) >>> 8);
+			r = ((r & 0x0f0f0f0f) << 4) | ((r & 0xf0f0f0f0) >>> 4);
+			r = ((r & 0x33333333) << 2) | ((r & 0xcccccccc) >>> 2);
+			r = ((r & 0x55555555) << 1) | ((r & 0xaaaaaaaa) >>> 1);
+			return r;
+		}
+
+		function hammersley( i : Int, max : Int ) : Vec2 {
+			#if (js || flash)
+			return hammerTbl[i].xy;
+			#else
+			var ri = _reversebits(i) * 2.3283064365386963e-10;
+			return vec2(float(i) / float(max), ri);
+			#end
+		}
+
+		function importanceSampleGGX( roughness : Float, p : Vec2, n : Vec3 ) : Vec3 {
+			var a = roughness * roughness;
+			var phi = 2 * PI * p.x;
+			var cosT = sqrt((1 - p.y) / (1 + (a * a - 1) * p.y)).min(1.);
+			var sinT = sqrt(1 - cosT * cosT);
+			var ltan = vec3(sinT * cos(phi), sinT * sin(phi), cosT);
+
+			var up = abs(n.z) < 0.999 ? vec3(0, 0, 1) : vec3(1, 0, 0);
+			var tanX = normalize(cross(up, n));
+			var tanY = normalize(cross(n, tanX));
+			return tanX * ltan.x + tanY * ltan.y + n * ltan.z;
+		}
+
+
+	};
+
+}
+
+
+class IrradShader extends IrradBase {
+
+	static var SRC = {
+
+		@const var face : Int;
+		@param var envMap : SamplerCube;
+
+		@const var isSpecular : Bool;
+		@param var roughness : Float;
+
+		@param var cubeSize : Float;
+		@param var cubeScaleFactor : Float;
+
+		function cosineWeightedSampling( p : Vec2, n : Vec3 ) : Vec3 {
+			var sq = sqrt(1 - p.x);
+			var alpha = 2 * PI * p.y;
+			var ltan = vec3( sq * cos(alpha),  sq * sin(alpha), sqrt(p.x));
+
+			var up = abs(n.z) < 0.999 ? vec3(0, 0, 1) : vec3(1, 0, 0);
+			var tanX = normalize(cross(up, n));
+			var tanY = normalize(cross(n, tanX));
+			return tanX * ltan.x + tanY * ltan.y + n * ltan.z;
+		}
+
+		function getNormal() : Vec3 {
+			var d = input.uv * 2. - 1.;
+			if( isSpecular ) {
+				// WRAP edge fixup
+				// https://seblagarde.wordpress.com/2012/06/10/amd-cubemapgen-for-physically-based-rendering/
+				d += cubeScaleFactor * (d * d * d);
+			}
+			#if hldx
+			d.y *= -1;
+			#end
+			var n : Vec3;
+			switch( face ) {
+			case 0: n = vec3(1, d.y, -d.x);
+			case 1: n = vec3(-1, d.y, d.x);
+			case 2: n = vec3(d.x, 1, -d.y);
+			case 3: n = vec3(d.x, -1, d.y);
+			case 4: n = vec3(d.x, d.y, 1);
+			default: n = vec3(-d.x, d.y,-1);
+			}
+			return n.normalize();
+		}
+
+		function fragment() {
+			var color = vec3(0.);
+			var n = getNormal();
+			var totalWeight = 1e-10;
+			var numSamples = 1 << samplesBits;
+			#if (js || flash) @:unroll #end
+			for( i in 0...1 << samplesBits ) {
+				var p = hammersley(i, numSamples);
+				var l : Vec3;
+				if( isSpecular ) {
+					var h = importanceSampleGGX(roughness, p, n);
+					var v = n; // approx
+					l = reflect(-v, h).normalize();
+				} else {
+					l = cosineWeightedSampling(p, n);
+				}
+				var amount = n.dot(l).saturate();
+				if( amount > 0 ) {
+					color += envMap.get(l).rgb.pow(vec3(2.2)) * amount;
+					totalWeight += amount;
+				}
+			}
+			output.color = vec4((color / totalWeight).pow(vec3(1/2.2)), 1.);
+		}
+
+	}
+
+}
+
+class IrradLut extends IrradBase {
+
+	static var SRC = {
+
+		function GGX( NdotV : Float, roughness : Float ) : Float {
+			var k = (roughness * roughness) * 0.5; // only in IBL, use (r + 1)² / 8 for lighting
+			return NdotV / (NdotV * (1.0 - k) + k);
+		}
+
+		function G_Smith(roughness : Float, nDotV : Float, nDotL : Float) : Float {
+			return GGX(nDotL, roughness) * GGX(nDotV, roughness);
+		}
+
+		function fragment() {
+			var roughness = input.uv.x;
+			var NoV = input.uv.y;
+			var v = vec3( sqrt( 1.0 - NoV * NoV ), 0., NoV );
+			var n = vec3(0, 0, 1.);
+			var numSamples = 1 << samplesBits;
+			var a = 0., b = 0.;
+			#if (js || flash) @:unroll #end
+			for( i in 0...1 << samplesBits ) {
+				var xi = hammersley(i, numSamples);
+				var h = importanceSampleGGX( roughness, xi, n );
+				var l = reflect(-v, h);
+				var NoL = saturate( dot(n, l) );
+				var NoH = saturate( dot(n, h) );
+				var VoH = saturate( dot(v, h) );
+				if( NoL > 0 ) {
+					var g = G_Smith( roughness, NoV, NoL );
+					var gvis = g * VoH / (NoH * NoV);
+					var fresnel = pow( 1 - VoH, 5. );
+					a += (1 - fresnel) * gvis;
+					b += fresnel * gvis;
+				}
+			}
+			output.color = vec4(a / numSamples, b / numSamples, 0, 1);
+		}
+	}
+
+	public function new() {
+		super();
+		samplesBits = 10;
+	}
+
+}
+
+class Irradiance  {
+
+	public var sampleBits : Int;
+	public var diffSize : Int;
+	public var specSize : Int;
+	public var specLevels : Int;
+
+	public var ignoredSpecLevels : Int = 5;
+
+	public var envMap : h3d.mat.Texture;
+	public var lut : h3d.mat.Texture;
+	public var diffuse : h3d.mat.Texture;
+	public var specular : h3d.mat.Texture;
+
+	public function new(envMap) {
+		this.envMap = envMap;
+		#if (js || flash)
+		sampleBits = 5;
+		diffSize = 16;
+		specSize = 16;
+		#else
+		diffSize = 64;
+		specSize = 256;
+		sampleBits = 10;
+		#end
+	}
+
+
+	public function compute() {
+
+		lut = new h3d.mat.Texture(128, 128, [Target], #if js RGBA32F #else RGBA16F #end);
+		lut.setName("irradLut");
+		diffuse = new h3d.mat.Texture(diffSize, diffSize, [Cube, Target]);
+		diffuse.setName("irradDiffuse");
+		specular = new h3d.mat.Texture(specSize, specSize, [Cube, Target, MipMapped, ManualMipMapGen]);
+		specular.setName("irradSpecular");
+		specular.mipMap = Linear;
+
+		computeIrradLut();
+		computeIrradiance();
+	}
+
+	function computeIrradLut() {
+		var screen = new h3d.pass.ScreenFx(new IrradLut());
+		screen.shader.samplesBits = sampleBits;
+		#if (js || flash)
+		var nsamples = 1 << sampleBits;
+		screen.shader.SamplesCount = nsamples;
+		screen.shader.hammerTbl = [for( i in 0...nsamples ) hammersley(i, nsamples)];
+		#end
+
+		var engine = h3d.Engine.getCurrent();
+		engine.driver.setRenderTarget(lut);
+		screen.render();
+		engine.driver.setRenderTarget(null);
+		screen.dispose();
+	}
+
+	function computeIrradiance() {
+
+		var screen = new h3d.pass.ScreenFx(new IrradShader());
+		screen.shader.samplesBits = sampleBits;
+		screen.shader.envMap = envMap;
+
+		#if (js || flash)
+		var nsamples = 1 << sampleBits;
+		screen.shader.SamplesCount = nsamples;
+		screen.shader.hammerTbl = [for( i in 0...nsamples ) hammersley(i, nsamples)];
+		#end
+
+		var engine = h3d.Engine.getCurrent();
+
+		for( i in 0...6 ) {
+			screen.shader.face = i;
+			engine.driver.setRenderTarget(diffuse, i);
+			screen.render();
+		}
+
+		screen.shader.isSpecular = true;
+
+		var mipLevels = 1;
+		while( specular.width > 1 << (mipLevels - 1) )
+			mipLevels++;
+
+		specLevels = mipLevels - ignoredSpecLevels;
+
+		for( i in 0...6 ) {
+			screen.shader.face = i;
+			for( j in 0...mipLevels ) {
+				var size = specular.width >> j;
+				screen.shader.cubeSize = size;
+				screen.shader.cubeScaleFactor = size == 1 ? 0 : (size * size) / Math.pow(size - 1, 3);
+				screen.shader.roughness = j / specLevels;
+				engine.driver.setRenderTarget(specular, i, j);
+				screen.render();
+			}
+		}
+		engine.driver.setRenderTarget(null);
+	}
+
+}

+ 26 - 0
h3d/scene/pbr/Light.hx

@@ -0,0 +1,26 @@
+package h3d.scene.pbr;
+
+class Light extends h3d.scene.Light {
+
+	var primitive : h3d.prim.Primitive;
+	public var isSun(get,set) : Bool;
+
+	function get_isSun() {
+		return false;
+	}
+
+	function set_isSun(b:Bool) {
+		if( b ) throw "Not supported on this light";
+		return b;
+	}
+
+	override function get_enableSpecular() {
+		return true;
+	}
+
+	override function set_enableSpecular(b) {
+		if( !b ) throw "Not implemented for this light";
+		return true;
+	}
+
+}

+ 41 - 0
h3d/scene/pbr/LightSystem.hx

@@ -0,0 +1,41 @@
+package h3d.scene.pbr;
+
+@:access(h3d.scene.pbr.Light)
+class LightSystem extends h3d.scene.LightSystem {
+
+	var lightPass : h3d.pass.ScreenFx<h3d.shader.pbr.PropsImport>;
+	var initDone = false;
+
+	override function computeLight( obj : h3d.scene.Object, shaders : hxsl.ShaderList ) : hxsl.ShaderList {
+		var light = Std.instance(obj, h3d.scene.pbr.Light);
+		if( light != null )
+			return ctx.allocShaderList(light.shader, shaders);
+		return shaders;
+	}
+
+	public function drawLights( r : Renderer ) {
+
+		if( lightPass == null ) {
+			initDone = true;
+			lightPass = new h3d.pass.ScreenFx(r.pbrProps);
+			lightPass.addShader(r.pbrDirect);
+			@:privateAccess lightPass.pass.setBlendMode(Add);
+		}
+
+		var light = @:privateAccess ctx.lights;
+		var currentTarget = ctx.engine.getCurrentTarget();
+		var width = currentTarget == null ? ctx.engine.width : currentTarget.width;
+		var height = currentTarget == null ? ctx.engine.height : currentTarget.height;
+		while( light != null ) {
+			if( light != shadowLight ) {
+				var light = Std.instance(light, h3d.scene.pbr.Light);
+				if( light != null /*&& light.primitive == null*/ ) {
+					lightPass.addShader(light.shader);
+					lightPass.render();
+					lightPass.removeShader(light.shader);
+				}
+			}
+			light = light.next;
+		}
+	}
+}

+ 65 - 0
h3d/scene/pbr/PointLight.hx

@@ -0,0 +1,65 @@
+package h3d.scene.pbr;
+
+class PointLight extends Light {
+
+	var _color : h3d.Vector;
+	var pbr : h3d.shader.pbr.Light.PointLight;
+	@:s public var power : Float = 1.;
+	public var size : Float;
+	/**
+		Alias for uniform scale.
+	**/
+	public var range(get,set) : Float;
+
+	public function new(?parent) {
+		pbr = new h3d.shader.pbr.Light.PointLight();
+		super(pbr,parent);
+		range = 10;
+		_color = new h3d.Vector(1,1,1,1);
+		primitive = h3d.prim.Sphere.defaultUnitSphere();
+	}
+
+	override function get_color() {
+		return _color;
+	}
+
+	override function set_color(v:h3d.Vector) {
+		return _color = v;
+	}
+
+	function get_range() {
+		return cullingDistance;
+	}
+
+	function set_range(v:Float) {
+		setScale(v);
+		return cullingDistance = v;
+	}
+
+	override function get_isSun() {
+		return pbr.isSun;
+	}
+
+	override function set_isSun(b:Bool) {
+		return pbr.isSun = b;
+	}
+
+	override function draw(ctx) {
+		primitive.render(ctx.engine);
+	}
+
+	override function emit(ctx:RenderContext) {
+		if( ctx.pbrLightPass == null )
+			throw "Rendering a pbr light require a PBR compatible scene renderer";
+		ctx.emitPass(ctx.pbrLightPass, this);
+		pbr.lightColor.load(_color);
+		var range = hxd.Math.max(range, 1e-10);
+		var size = hxd.Math.min(size, range);
+		var power = power * 10; // base scale
+		pbr.lightColor.scale3(power * power);
+		pbr.lightPos.set(absPos.tx, absPos.ty, absPos.tz);
+		pbr.invLightRange4 = 1 / (range * range * range * range);
+		pbr.pointSize = size;
+	}
+
+}

+ 140 - 0
h3d/scene/pbr/Renderer.hx

@@ -0,0 +1,140 @@
+package h3d.scene.pbr;
+
+enum DisplayMode {
+	Pbr;
+	Slides;
+}
+
+class Renderer extends h3d.scene.Renderer {
+
+	var slides = new h3d.pass.ScreenFx(new h3d.shader.pbr.Slides());
+	var pbrOut = new h3d.pass.ScreenFx(new h3d.shader.pbr.Lighting.Indirect());
+	var pbrSun = new h3d.shader.pbr.Light.DirLight();
+	var pbrLightPass : h3d.mat.Pass;
+	var fxaa = new h3d.pass.FXAA();
+	var shadows = new h3d.pass.ShadowMap(2048);
+	var hasSun = false;
+
+	public var pbrDirect = new h3d.shader.pbr.Lighting.Direct();
+	public var pbrProps = new h3d.shader.pbr.PropsImport();
+	public var displayMode : DisplayMode = Pbr;
+	public var irrad : Irradiance;
+
+	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.gloss"), Value("output.occlusion"), Const(0)]),
+	]);
+
+	public function new(irrad) {
+		super();
+		this.irrad = irrad;
+		shadows.bias = 0.0;
+		shadows.power = 1000;
+		shadows.blur.passes = 1;
+		defaultPass = new h3d.pass.Default("default");
+		pbrDirect.doDiscard = false;
+		pbrOut.addShader(new h3d.shader.ScreenShader());
+		pbrOut.addShader(pbrProps);
+		pbrOut.addShader(new h3d.shader.pbr.Shadow());
+		allPasses.push(output);
+		allPasses.push(defaultPass);
+		allPasses.push(shadows);
+	}
+
+	function allocFTarget( name : String, size = 0, depth = true ) {
+		return ctx.textures.allocTarget(name, ctx.engine.width >> size, ctx.engine.height >> size, depth, RGBA32F);
+	}
+
+	override function debugCompileShader(pass:h3d.mat.Pass) {
+		output.setContext(this.ctx);
+		return output.compileShader(pass);
+	}
+
+	override function start() {
+		if( pbrLightPass == null ) {
+			pbrLightPass = new h3d.mat.Pass("lights");
+			pbrLightPass.addShader(new h3d.shader.BaseMesh());
+			pbrLightPass.addShader(pbrDirect);
+			pbrLightPass.addShader(pbrProps);
+			pbrLightPass.blend(One, One);
+			pbrLightPass.depthWrite = false;
+			pbrLightPass.enableLights = true;
+		}
+		ctx.pbrLightPass = pbrLightPass;
+	}
+
+	override function render() {
+
+		shadows.draw(get("shadow"));
+
+		var albedo = allocTarget("albedo");
+		var normal = allocFTarget("normal",0,false);
+		var pbr = allocTarget("pbr",0,false);
+		setTargets([albedo,normal,pbr]);
+		clear(0, 1);
+		output.draw(getSort("default", true));
+
+		setTarget(albedo);
+		draw("color");
+
+		var output = allocTarget("hdrOutput", 0, true);
+		setTarget(output);
+		pbrProps.albedoTex = albedo;
+		pbrProps.normalTex = normal;
+		pbrProps.pbrTex = pbr;
+		pbrProps.cameraInverseViewProj = ctx.camera.getInverseViewProj();
+
+		pbrOut.shader.cameraPosition.load(ctx.camera.pos);
+		pbrOut.shader.irrLut = irrad.lut;
+		pbrOut.shader.irrDiffuse = irrad.diffuse;
+		pbrOut.shader.irrSpecular = irrad.specular;
+		pbrOut.shader.irrSpecularLevels = irrad.specLevels;
+
+		var ls = getLightSystem();
+		if( ls.shadowLight == null ) {
+			pbrOut.removeShader(pbrDirect);
+			pbrOut.removeShader(pbrSun);
+		} else {
+			if( pbrOut.getShader(h3d.shader.pbr.Light.DirLight) == null ) {
+				pbrOut.addShader(pbrDirect);
+				pbrOut.addShader(pbrSun);
+			}
+			pbrSun.lightColor.load(ls.shadowLight.color);
+			pbrSun.lightDir.load(@:privateAccess ls.shadowLight.getShadowDirection());
+			pbrSun.lightDir.scale3(-1);
+			pbrSun.lightDir.normalize();
+			pbrSun.isSun = true;
+		}
+
+		pbrOut.setGlobals(ctx);
+		pbrOut.render();
+
+		var ls = Std.instance(ls, LightSystem);
+		if( ls != null ) ls.drawLights(this);
+
+		pbrProps.isScreen = false;
+		draw("lights");
+		pbrProps.isScreen = true;
+
+		draw("late");
+		resetTarget();
+
+
+		switch( displayMode ) {
+
+		case Pbr:
+			fxaa.apply(output);
+
+		case Slides:
+
+			slides.shader.albedo = albedo;
+			slides.shader.normal = normal;
+			slides.shader.pbr = pbr;
+			slides.render();
+
+		}
+
+	}
+
+}

+ 67 - 0
h3d/shader/pbr/Light.hx

@@ -0,0 +1,67 @@
+package h3d.shader.pbr;
+
+class Light extends hxsl.Shader {
+	static var SRC = {
+		var pbrLightDirection : Vec3;
+		var pbrLightColor : Vec3;
+		var transformedPosition : Vec3;
+		var occlusion : Float;
+
+		/**
+			Tells that we need to keep occlusion / shadow map.
+		**/
+		@param var lightColor = vec3(0.5, 0.5, 0.5);
+		@const var isSun : Bool;
+
+	};
+}
+
+
+class PointLight extends Light {
+
+	static var SRC = {
+
+		@param var lightPos : Vec3;
+		@param var invLightRange4 : Float; // 1 / range^4
+		@param var pointSize : Float;
+
+		function fragment() {
+			var delta = lightPos - transformedPosition;
+			pbrLightDirection = delta.normalize();
+			/*
+				UE4 [Karis12] "Real Shading in Unreal Engine 4"
+				Modified with pointSize
+			*/
+			var dist = delta.dot(delta);
+			var falloff = saturate(1 - dist*dist * invLightRange4);
+			if( pointSize > 0 ) {
+				dist = (dist.sqrt() - pointSize).max(0.);
+				dist *= dist;
+			}
+			falloff *= falloff;
+			falloff *= 1 / (dist + 1);
+			pbrLightColor = lightColor * falloff;
+
+			if( !isSun ) occlusion = 1.;
+		}
+
+	};
+
+}
+
+
+class DirLight extends Light {
+
+	static var SRC = {
+
+		@param var lightDir : Vec3;
+
+		function fragment() {
+			pbrLightDirection = lightDir;
+			pbrLightColor = lightColor;
+			if( !isSun ) occlusion = 1.;
+		}
+
+	};
+
+}

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

@@ -0,0 +1,86 @@
+package h3d.shader.pbr;
+
+class Indirect extends PropsDefinition {
+
+	static var SRC = {
+
+		@param var irrLut : Sampler2D;
+		@param var irrDiffuse : SamplerCube;
+		@param var irrSpecular : SamplerCube;
+		@param var irrSpecularLevels : Float;
+
+		function fragment() {
+			var diffuse = irrDiffuse.get(normal).rgb.pow(vec3(2.2)) * albedo;
+			var envSpec = textureCubeLod(irrSpecular, reflect(-view,normal), roughness * irrSpecularLevels).rgb.pow(vec3(2.2));
+			var envBRDF = irrLut.get(vec2(roughness, NdV));
+			var specular = envSpec * (specularColor * envBRDF.x + envBRDF.y);
+			/*
+				// diffuse *= occlusion
+				Usually indirect diffuse is multiplied by occlusion, but since our occlusion mosly
+				comes from shadow map, we want to keep the diffuse term for colored shadows here.
+			*/
+			var indirect = diffuse + specular;
+			pixelColor.rgb += indirect;
+		}
+
+	};
+
+}
+
+class Direct extends PropsDefinition {
+
+	static var SRC = {
+
+		var pbrLightDirection : Vec3;
+		var pbrLightColor : Vec3;
+		@const var doDiscard : Bool = true;
+
+		function fragment() {
+			if( pbrLightColor.dot(pbrLightColor) > 0.0001 ) {
+				var NdL = normal.dot(pbrLightDirection).max(0.);
+
+				if( NdL <= 0 && doDiscard ) discard;
+
+				var half = (pbrLightDirection + view).normalize();
+				var NdH = normal.dot(half).max(0.);
+				var VdH = view.dot(half).max(0.);
+
+				// diffuse BRDF
+				var direct : Vec3 = vec3(0.);
+
+				// ------------- DIRECT LIGHT -------------------------
+
+				var F0 = metalness;
+				var diffuse = albedo * NdL / PI;
+
+				// General Cook-Torrance formula for microfacet BRDF
+				// 		f(l,v) = D(h).F(v,h).G(l,v,h) / 4(n.l)(n.v)
+
+				// formulas below from 2013 Siggraph "Real Shading in UE4"
+				var alpha = roughness.pow(2);
+
+				// D = normal distribution fonction
+				// GGX (Trowbridge-Reitz) "Disney"
+				var D = alpha.pow(2) / (PI * ( NdH.pow(2) * (alpha.pow(2) - 1.) + 1).pow(2));
+
+				// F = fresnel term
+				// Schlick approx
+				// pow 5 optimized with Spherical Gaussian
+				// var F = F0 + (1 - F0) * pow(1 - v.dot(h).min(1.), 5.);
+				var F = F0 + (1.01 - F0) * exp2( ( -5.55473 * VdH - 6.98316) * VdH );
+
+				// G = geometric attenuation
+				// Schlick (modified for UE4 with k=alpha/2)
+				var G = calcG(pbrLightDirection) * calcG(view);
+
+				var specular = if( NdL > 0.1 && NdV > 0.1 ) (D * F * G / (4 * NdL * NdV)).max(0.) else 0.;
+				direct += (diffuse + specular) * pbrLightColor;
+				direct *= occlusion;
+				pixelColor.rgb += direct;
+			} else if( doDiscard )
+				discard;
+		}
+	};
+
+}
+

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

@@ -0,0 +1,36 @@
+package h3d.shader.pbr;
+
+class PropsDefinition extends hxsl.Shader {
+
+	static var SRC = {
+		var albedo : Vec3;
+		var depth : Float;
+		var normal : Vec3;
+		var metalness : Float;
+		var roughness : Float;
+		var occlusion : Float;
+		var specularColor : Vec3;
+		var transformedPosition : Vec3;
+
+		var view : Vec3;
+		var NdV : Float;
+
+		@param var cameraPosition : Vec3;
+		var pixelColor : Vec4;
+
+		function __init__fragment() {
+			pixelColor = vec4(0.,0.,0.,1.);
+			{
+				view = (cameraPosition - transformedPosition).normalize();
+				NdV = normal.dot(view).max(0.);
+			}
+		}
+
+		function calcG(v:Vec3) : Float {
+			var k = (roughness + 1).pow(2) / 8;// (roughness * roughness) / 2;
+			return NdV / (NdV * (1 - k) + k);
+		}
+
+	}
+
+}

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

@@ -0,0 +1,47 @@
+package h3d.shader.pbr;
+
+class PropsImport extends hxsl.Shader {
+
+	static var SRC = {
+		@param var albedoTex : Sampler2D;
+		@param var normalTex : Sampler2D;
+		@param var pbrTex : Sampler2D;
+		@const var isScreen : Bool = true;
+
+		@param var cameraInverseViewProj : Mat4;
+
+		var albedo : Vec3;
+		var depth : Float;
+		var normal : Vec3;
+		var metalness : Float;
+		var roughness : Float;
+		var occlusion : Float;
+		var calculatedUV : Vec2;
+		var transformedPosition : Vec3;
+		var specularColor : Vec3;
+		var screenUV : Vec2; // BaseMesh
+
+		function fragment() {
+			var uv = isScreen ? calculatedUV : screenUV;
+			albedo = albedoTex.get(uv).rgb;
+			var normalDepth = normalTex.get(uv);
+			normal = normalDepth.xyz;
+			depth = normalDepth.w;
+			var pbr = pbrTex.get(uv);
+			metalness = pbr.r;
+			roughness = 1 - pbr.g;
+			occlusion = pbr.b;
+
+			specularColor = metalness < 0.3 ? vec3(metalness) : albedo;
+
+			// this is the original object transformed position, not our current drawing object one
+			var uv2 = (uv - 0.5) * vec2(2, -2);
+			var temp = vec4(uv2, depth, 1) * cameraInverseViewProj;
+			var originWS = temp.xyz / temp.w;
+			transformedPosition = originWS;
+		}
+
+	};
+
+}
+

+ 29 - 0
h3d/shader/pbr/Shadow.hx

@@ -0,0 +1,29 @@
+package h3d.shader.pbr;
+
+class Shadow extends hxsl.Shader {
+
+	static var SRC = {
+
+		@global var shadow : {
+			map : Channel,
+			proj : Mat3x4,
+			color : Vec3, // unused ?
+			power : Float,
+			bias : Float,
+		};
+		@param var shadowMap : Sampler2D;
+		var transformedPosition : Vec3;
+		var occlusion : Float;
+
+		function fragment() {
+			var shadowPos = transformedPosition * shadow.proj * vec3(0.5, -0.5, 1) + vec3(0.5, 0.5, 0);
+			var depth = shadow.map.get(shadowPos.xy);
+			var zMax = shadowPos.z.saturate();
+			var delta = (depth + shadow.bias).min(zMax) - zMax;
+			var shade = exp( shadow.power * delta ).saturate();
+			occlusion *= shade;
+		}
+
+	}
+
+}

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

@@ -0,0 +1,24 @@
+package h3d.shader.pbr;
+
+class Slides extends ScreenShader {
+	static var SRC = {
+		@param var albedo : Sampler2D;
+		@param var normal : Sampler2D;
+		@param var pbr : Sampler2D;
+		function fragment() {
+			var uv = input.uv;
+			var x = input.position.x;
+			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 )
+				output.color = packNormal(normal);
+			else if( x < 0.5 )
+				output.color = vec4( pbr.get(uv).xxx, 1. );
+			else
+				output.color = vec4( pbr.get(uv).yyy, 1. );
+		}
+	};
+}