2
0
Эх сурвалжийг харах

- Add volumetric lightmaps

ShiroSmith 7 жил өмнө
parent
commit
209f5ac6dc

+ 3 - 3
h3d/Camera.hx

@@ -116,7 +116,7 @@ class Camera {
 	/**
 		Setup camera for cubemap rendering on the given face.
 	**/
-	public function setCubeMap( face : Int ) {
+	public function setCubeMap( face : Int, position : h3d.Vector ) {
 		var dx = 0, dy = 0, dz = 0;
 		switch( face ) {
 		case 0: dx = 1; up.set(0,1,0);
@@ -126,8 +126,8 @@ class Camera {
 		case 4: dz = 1; up.set(0,1,0);
 		case 5: dz = -1; up.set(0,1,0);
 		}
-		pos.set(0,0,0);
-		target.set(dx,dy,dz);
+		pos.set(position.x,position.y,position.z);
+		target.set(pos.x + dx,pos.y + dy,pos.z + dz);
 		setFovX(90,1);
 	}
 

+ 26 - 0
h3d/col/IPoint.hx

@@ -0,0 +1,26 @@
+package h3d.col;
+using hxd.Math;
+
+class IPoint {
+
+	public var x : Int;
+	public var y : Int;
+	public var z : Int;
+
+	public inline function new(x=0,y=0,z=0) {
+		this.x = x;
+		this.y = y;
+		this.z = z;
+	}
+
+	public function toString() {
+		return 'IPoint{$x,$y,$z}';
+	}
+
+	public inline function set(x, y, z) {
+		this.x = x;
+		this.y = y;
+		this.z = z;
+	}
+
+}

+ 9 - 2
h3d/scene/Renderer.hx

@@ -10,7 +10,12 @@ class PassObjects {
 	}
 }
 
-private typedef SMap<T> = #if flash haxe.ds.UnsafeStringMap<T> #else Map<String,T> #end
+private typedef SMap<T> = #if flash haxe.ds.UnsafeStringMap<T> #else Map<String,T> #end;
+
+enum RenderMode{
+	Default;
+	LightProbe;
+}
 
 class Renderer extends hxd.impl.AnyProps {
 
@@ -19,7 +24,9 @@ class Renderer extends hxd.impl.AnyProps {
 	var allPasses : Array<h3d.pass.Base>;
 	var ctx : RenderContext;
 	var hasSetTarget = false;
+
 	public var effects : Array<hxd.prefab.rfx.RendererFX> = [];
+	public var renderMode : RenderMode = Default;
 
 	public function new() {
 		allPasses = [];
@@ -95,7 +102,7 @@ class Renderer extends hxd.impl.AnyProps {
 	}
 
 	function copy( from, to, ?blend ) {
-		h3d.pass.Copy.run(from, to, blend);
+		h3d.pass.Copy.run(from, to == null ? ctx.engine.getCurrentTarget() : to, blend);
 	}
 
 	function setTarget( tex ) {

+ 11 - 0
h3d/scene/pbr/LightProbe.hx

@@ -0,0 +1,11 @@
+package h3d.scene.pbr;
+
+class LightProbe {
+
+	public var position = new h3d.Vector(0,0,0);
+	public var sh : h3d.scene.pbr.SphericalHarmonic;
+
+	public function new() {
+	}
+
+}

+ 297 - 0
h3d/scene/pbr/LightProbeBaker.hx

@@ -0,0 +1,297 @@
+package h3d.scene.pbr;
+
+import h3d.scene.pbr.Renderer;
+
+class LightProbeBaker{
+
+	public var useGPU = false;
+	public var environment : h3d.scene.pbr.Environment;
+
+	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)])
+	]);
+
+	var envMap : h3d.mat.Texture;
+	var customCamera = new h3d.Camera();
+	var cubeDir = [ h3d.Matrix.L([0,0,-1,0, 0,1,0,0, -1,-1,1,0]),
+					h3d.Matrix.L([0,0,1,0, 0,1,0,0, 1,-1,-1,0]),
+	 				h3d.Matrix.L([-1,0,0,0, 0,0,1,0, 1,-1,-1,0]),
+	 				h3d.Matrix.L([-1,0,0,0, 0,0,-1,0, 1,1,1,0]),
+				 	h3d.Matrix.L([-1,0,0,0, 0,1,0,0, 1,-1,1,0]),
+				 	h3d.Matrix.L([1,0,0,0, 0,1,0,0, -1,-1,-1,0]) ];
+
+	function getSwiz(name,comp) : hxsl.Output { return Swiz(Value(name,3),[comp]); }
+
+	var computeSH : h3d.pass.ScreenFx<h3d.shader.pbr.ComputeSH>;
+	var output0 = new h3d.mat.Texture(1, 1, [Target], RGBA32F);
+	var output1 = new h3d.mat.Texture(1, 1, [Target], RGBA32F);
+	var output2 = new h3d.mat.Texture(1, 1, [Target], RGBA32F);
+	var output3 = new h3d.mat.Texture(1, 1, [Target], RGBA32F);
+	var output4 = new h3d.mat.Texture(1, 1, [Target], RGBA32F);
+	var output5 = new h3d.mat.Texture(1, 1, [Target], RGBA32F);
+	var output6 = new h3d.mat.Texture(1, 1, [Target], RGBA32F);
+	var output7 = new h3d.mat.Texture(1, 1, [Target], RGBA32F);
+	var textureArray: Array<h3d.mat.Texture>;
+
+	public function new(){
+	}
+
+	public function bakePartial(renderer : h3d.scene.Renderer, s3d : Scene, volumetricLightMap : VolumetricLightmap, resolution : Int, time :Float) {
+
+		var timer = haxe.Timer.stamp();
+		var timeElapsed = 0.0;
+
+		var index = volumetricLightMap.lastBakedProbeIndex + 1;
+		if(index > volumetricLightMap.lightProbes.length - 1) return time;
+
+		setupEnvMap(resolution);
+		setupShaderOutput(volumetricLightMap.shOrder);
+
+		// Save Scene Config
+		var oldRenderer = s3d.renderer;
+		var oldCamera = s3d.camera;
+		var oldRenderMode = renderer.renderMode;
+		s3d.renderer = renderer;
+		s3d.renderer.renderMode = LightProbe;
+		s3d.camera = customCamera;
+		var engine = h3d.Engine.getCurrent();
+
+		while(timeElapsed < time){
+
+			// Bake a Probe
+			for( f in 0...6 ) {
+				engine.begin();
+				s3d.camera.setCubeMap(f, volumetricLightMap.lightProbes[index].position);
+				s3d.camera.update();
+				engine.pushTarget(envMap, f);
+				engine.clear(0,1,0);
+				s3d.render(engine);
+				engine.popTarget();
+			}
+			volumetricLightMap.lightProbes[index].sh = useGPU ? convertEnvIntoSH_GPU(renderer, envMap, volumetricLightMap.shOrder) : convertEnvIntoSH_CPU(envMap, volumetricLightMap.shOrder);
+			volumetricLightMap.lastBakedProbeIndex = index;
+
+			index = volumetricLightMap.lastBakedProbeIndex + 1;
+			if(index > volumetricLightMap.lightProbes.length - 1) break;
+
+			timeElapsed = haxe.Timer.stamp() - timer;
+		}
+
+		// Restore Scene Config
+		s3d.camera = oldCamera;
+		s3d.renderer = oldRenderer;
+		s3d.renderer.renderMode = oldRenderMode;
+
+		return time - timeElapsed;
+	}
+
+
+	public function bake(renderer : h3d.scene.Renderer, s3d : Scene, volumetricLightMap : VolumetricLightmap, resolution : Int) {
+
+		setupEnvMap(resolution);
+		setupShaderOutput(volumetricLightMap.shOrder);
+
+		// Save Scene Config
+		var oldRenderer = s3d.renderer;
+		var oldCamera = s3d.camera;
+		var oldRenderMode = renderer.renderMode;
+		s3d.renderer = renderer;
+		s3d.renderer.renderMode = LightProbe;
+		var camera = new h3d.Camera();
+		s3d.camera = camera;
+		var engine = h3d.Engine.getCurrent();
+
+		for( i in 0 ... volumetricLightMap.lightProbes.length){
+			// Render the 6 faces
+			for( f in 0...6 ) {
+				engine.begin();
+				camera.setCubeMap(f, volumetricLightMap.lightProbes[i].position);
+				camera.update();
+				engine.pushTarget(envMap, f);
+				engine.clear(0,1,0);
+				s3d.render(engine);
+				engine.popTarget();
+			}
+			volumetricLightMap.lightProbes[i].sh = useGPU ? convertEnvIntoSH_GPU(renderer, envMap, volumetricLightMap.shOrder) : convertEnvIntoSH_CPU(envMap, volumetricLightMap.shOrder);
+			volumetricLightMap.lastBakedProbeIndex = i;
+		}
+
+		// Restore Scene Config
+		s3d.camera = oldCamera;
+		s3d.renderer = oldRenderer;
+		s3d.renderer.renderMode = oldRenderMode;
+	}
+
+	function setupEnvMap(resolution : Int){
+		if(envMap == null || resolution != envMap.width)
+			envMap = new h3d.mat.Texture(resolution, resolution, [Cube, Target], RGBA32F);
+	}
+
+	function setupShaderOutput(order : Int){
+
+		if(order > 3 || order <= 0){ throw "Not Supported"; return; }
+
+		switch(order){
+			case 1:
+				textureArray = [output0];
+				computeSH = new h3d.pass.ScreenFx(new h3d.shader.pbr.ComputeSH(),[
+								Vec4([Value("out.coefL00", 3), Const(0)])]);
+			case 2:
+				textureArray = [output0, output1, output2, output3];
+				computeSH = new h3d.pass.ScreenFx(new h3d.shader.pbr.ComputeSH(),[
+							Vec4([Value("out.coefL00", 3), Const(0)]),
+							Vec4([Value("out.coefL1n1", 3), Const(0)]),
+							Vec4([Value("out.coefL10", 3), Const(0)]),
+							Vec4([Value("out.coefL11", 3), Const(0)])]);
+
+			case 3:
+				textureArray = [output0, output1, output2, output3, output4, output5, output6, output7];
+				computeSH = new h3d.pass.ScreenFx(new h3d.shader.pbr.ComputeSH(),[
+							Vec4([Value("out.coefL00", 3), getSwiz("out.coefL22",X) ]),
+							Vec4([Value("out.coefL1n1", 3), getSwiz("out.coefL22",Y) ]),
+							Vec4([Value("out.coefL10", 3), getSwiz("out.coefL22",Z) ]),
+							Vec4([Value("out.coefL11", 3), Const(0)]),
+							Vec4([Value("out.coefL2n2", 3), Const(0)]),
+							Vec4([Value("out.coefL2n1", 3), Const(0)]),
+							Vec4([Value("out.coefL20", 3), Const(0)]),
+							Vec4([Value("out.coefL21", 3), Const(0)])]);
+		}
+	}
+
+	function convertEnvIntoSH_GPU(renderer : h3d.scene.Renderer, env : h3d.mat.Texture, order : Int) : SphericalHarmonic {
+		var t0 = haxe.Timer.stamp();
+
+		@:privateAccess renderer.ctx.engine = h3d.Engine.getCurrent();
+		@:privateAccess renderer.setTargets(textureArray);
+		computeSH.shader.ORDER = order;
+		computeSH.shader.width = env.width;
+		computeSH.shader.environment = env;
+		computeSH.shader.cubeDir = cubeDir;
+		computeSH.render();
+		@:privateAccess renderer.resetTarget();
+		@:privateAccess renderer.ctx.engine.flushTarget();
+
+		var sphericalHarmonic = new SphericalHarmonic(order);
+		var coefCount = order * order;
+		var maxCoef : Int = Std.int(Math.min(8, coefCount));
+
+		for(i in 0...maxCoef){
+			var pixel : hxd.Pixels.PixelsFloat = textureArray[i].capturePixels();
+			var coefs : h3d.Vector = pixel.getPixelF(0, 0);
+			sphericalHarmonic.coefR[i] = coefs.r;
+			sphericalHarmonic.coefG[i] = coefs.g;
+			sphericalHarmonic.coefB[i] = coefs.b;
+			// Last coefs is inside the alpha channel
+			if( order == 3 ){
+				if( i == 0 ){ sphericalHarmonic.coefR[8] = coefs.a; }
+				if( i == 1 ){ sphericalHarmonic.coefG[8] = coefs.a; }
+				if( i == 2 ){ sphericalHarmonic.coefB[8] = coefs.a; }
+			}
+		}
+		//trace("SH compute time GPU", haxe.Timer.stamp() - t0);
+		return sphericalHarmonic;
+	}
+
+	function convertEnvIntoSH_CPU(env : h3d.mat.Texture, order : Int) : SphericalHarmonic {
+		var coefCount = order * order;
+		var sphericalHarmonic = new SphericalHarmonic(order);
+		var face : hxd.Pixels.PixelsFloat;
+		var weightSum = 0.0;
+		var invWidth = 1.0 / env.width;
+		var shData : Array<Float> = [for (value in 0...coefCount) 0];
+
+		for(f in 0...6){
+			face = env.capturePixels(f, 0);
+			for (u in 0...face.width) {
+				var fU : Float = (u / face.width ) * 2 - 1;// Texture coordinate U in range [-1 to 1]
+				fU *= fU;
+				var uCoord = 2.0 * u * invWidth + invWidth;
+				for (v in 0...face.width) {
+        			var fV : Float = (v / face.height ) * 2 - 1;// Texture coordinate V in range [-1 to 1]
+					fV *= fV;
+					var vCoord = 2.0 * v * invWidth + invWidth;
+					var dir = getDir(uCoord, vCoord, f);// Get direction from center of cube texture to current texel
+           			var diffSolid = 4.0 / ((1.0 + fU + fV) * Math.sqrt(1.0 + fU + fV));	// Scale factor depending on distance from center of the face
+					weightSum += diffSolid;
+					var color = face.getPixelF(u,v);// Get color from the current face
+					evalSH(order, dir, shData);// Calculate coefficients of spherical harmonics for current direction
+					for(i in 0...coefCount){
+						sphericalHarmonic.coefR[i] += shData[i] * color.r * diffSolid;
+						sphericalHarmonic.coefG[i] += shData[i] * color.g * diffSolid;
+						sphericalHarmonic.coefB[i] += shData[i] * color.b * diffSolid;
+					}
+				}
+			}
+		}
+		// Final scale for coefficients
+		var normProj = (4.0 * Math.PI) / weightSum;
+		for(i in 0...coefCount){
+			sphericalHarmonic.coefR[i] *= normProj;
+			sphericalHarmonic.coefG[i] *= normProj;
+			sphericalHarmonic.coefB[i] *= normProj;
+		}
+		return sphericalHarmonic;
+	}
+
+	inline function evalSH(order:Int, dir:h3d.Vector, shData : Array<Float>) {
+		for (l in 0...order) {
+       		for (m in -l...l+1) {
+				shData[getIndex(l, m)] = evalCoef(l, m, dir);
+			}
+		}
+	}
+
+	inline function getIndex(l : Int, m :Int) : Int {
+		return l * (l + 1) + m;
+	}
+
+	inline function getDir(u: Float, v:Float, face:Int) : h3d.Vector {
+		var dir = new h3d.Vector();
+		switch(face) {
+			case 0: dir.x = -1.0; dir.y = -1.0 + v; dir.z = 1.0 - u;
+			case 1: dir.x = 1.0; dir.y = -1.0 + v; dir.z = -1.0 + u;
+			case 2: dir.x = 1.0 - u; dir.y = -1.0; dir.z = -1.0 + v;
+			case 3: dir.x = 1.0 - u; dir.y = 1.0; dir.z = 1.0 - v;
+			case 4: dir.x = 1.0 - u;  dir.y = -1.0 + v; dir.z = 1.0;
+			case 5: dir.x = -1.0 + u; dir.y = -1.0 + v;  dir.z = -1.0;
+			default:
+		}
+		dir.normalizeFast();
+		return dir;
+	}
+
+	inline function evalCoef(l : Int, m : Int, dir: h3d.Vector) : Float{
+		// Coef from Stupid Spherical Harmonics (SH) Peter-Pike Sloan Microsoft Corporation
+		return switch [l,m] {
+			case[0,0]:	0.282095; // 0.5 * sqrt(1/pi)
+			case[1,-1]:	-0.488603 * dir.y;  // -sqrt(3/(4pi)) * y
+			case[1,0]:	0.488603 * dir.z; // sqrt(3/(4pi)) * z
+			case[1,1]:	-0.488603 * dir.x; // -sqrt(3/(4pi)) * x
+			case[2,-2]:	1.092548 * dir.y * dir.x;// 0.5 * sqrt(15/pi) * y * x
+			case[2,-1]:	-1.092548 * dir.y * dir.z; // -0.5 * sqrt(15/pi) * y * z
+			case[2,0]:	0.315392 * (-dir.x * dir.x - dir.y * dir.y + 2.0 * dir.z * dir.z);// 0.25 * sqrt(5/pi) * (-x^2-y^2+2z^2)
+			case[2,1]:  -1.092548 * dir.x * dir.z; // -0.5 * sqrt(15/pi) * x * z
+			case[2,2]:	0.546274 * (dir.x * dir.x - dir.y * dir.y); // 0.25 * sqrt(15/pi) * (x^2 - y^2)
+			case[3,-3]:	-0.590044 * dir.y * (3.0 * dir.x * dir.x - dir.y * dir.y);// -0.25 * sqrt(35/(2pi)) * y * (3x^2 - y^2)
+			case[3,-2]: 2.890611 * dir.x * dir.y * dir.z; // 0.5 * sqrt(105/pi) * x * y * z
+			case[3,-1]: -0.457046 * dir.y * (4.0 * dir.z * dir.z - dir.x * dir.x - dir.y * dir.y); // -0.25 * sqrt(21/(2pi)) * y * (4z^2-x^2-y^2)
+			case[3,0]:  0.373176 * dir.z * (2.0 * dir.z * dir.z - 3.0 * dir.x * dir.x - 3.0 * dir.y * dir.y);  // 0.25 * sqrt(7/pi) * z * (2z^2 - 3x^2 - 3y^2)
+			case[3,1]:	-0.457046 * dir.x * (4.0 * dir.z * dir.z - dir.x * dir.x - dir.y * dir.y); // -0.25 * sqrt(21/(2pi)) * x * (4z^2-x^2-y^2)
+			case[3,2]:  1.445306 * dir.z * (dir.x * dir.x - dir.y * dir.y); // 0.25 * sqrt(105/pi) * z * (x^2 - y^2)
+			case[3,3]:	-0.590044 * dir.x * (dir.x * dir.x - 3.0 * dir.y * dir.y); // -0.25 * sqrt(35/(2pi)) * x * (x^2-3y^2)
+			case[4,-4]: 2.503343 * dir.x * dir.y * (dir.x * dir.x - dir.y * dir.y);// 0.75 * sqrt(35/pi) * x * y * (x^2-y^2)
+			case[4,-3]: -1.770131 * dir.y * dir.z * (3.0 * dir.x * dir.x - dir.y * dir.y); // -0.75 * sqrt(35/(2pi)) * y * z * (3x^2-y^2)
+			case[4,-2]: 0.946175 * dir.x * dir.y * (7.0 * dir.z * dir.z - 1.0);// 0.75 * sqrt(5/pi) * x * y * (7z^2-1)
+			case[4,-1]: -0.669047 * dir.y * dir.z * (7.0 * dir.z * dir.z - 3.0);// -0.75 * sqrt(5/(2pi)) * y * z * (7z^2-3)
+			case[4,0]:  0.105786 * (35.0 * dir.z * dir.z * dir.z * dir.z - 30.0 * dir.z * dir.z + 3.0);// 3/16 * sqrt(1/pi) * (35z^4-30z^2+3)
+			case[4,1]:  -0.669047 * dir.x * dir.z * (7.0 * dir.z * dir.z - 3.0);// -0.75 * sqrt(5/(2pi)) * x * z * (7z^2-3)
+			case[4,2]:  0.473087 * (dir.x * dir.x - dir.y * dir.y) * (7.0 * dir.z * dir.z - 1.0);// 3/8 * sqrt(5/pi) * (x^2 - y^2) * (7z^2 - 1)
+			case[4,3]:  -1.770131 * dir.x * dir.z * (dir.x * dir.x - 3.0 * dir.y * dir.y);// -0.75 * sqrt(35/(2pi)) * x * z * (x^2 - 3y^2)
+			case[4,4]:  0.625836 * (dir.x * dir.x * (dir.x * dir.x - 3.0 * dir.y * dir.y) - dir.y * dir.y * (3.0 * dir.x * dir.x - dir.y * dir.y)); // 3/16*sqrt(35/pi) * (x^2 * (x^2 - 3y^2) - y^2 * (3x^2 - y^2))
+			default: 0;
+		}
+	}
+}

+ 1 - 1
h3d/scene/pbr/LightSystem.hx

@@ -13,7 +13,7 @@ class LightSystem extends h3d.scene.LightSystem {
 		return shaders;
 	}
 
-	public function drawLights( r : Renderer, lightPass : h3d.pass.ScreenFx<Dynamic> ) {
+	public function drawLights( r : h3d.scene.Renderer, lightPass : h3d.pass.ScreenFx<Dynamic> ) {
 		var light = @:privateAccess ctx.lights;
 		var currentTarget = ctx.engine.getCurrentTarget();
 		var width = currentTarget == null ? ctx.engine.width : currentTarget.width;

+ 39 - 12
h3d/scene/pbr/Renderer.hx

@@ -50,7 +50,6 @@ typedef RenderProps = {
 }
 
 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.ScreenShader());
 	var tonemap = new h3d.pass.ScreenFx(new h3d.shader.pbr.ToneMapping());
@@ -175,22 +174,28 @@ class Renderer extends h3d.scene.Renderer {
 		setTarget(albedo);
 		draw("albedo");
 
-		if( displayMode == Env )
-			clear(0xFF404040);
+		if(renderMode == Default){
+			if( displayMode == Env )
+				clear(0xFF404040);
 
-		if( displayMode == MatCap ) {
-			clear(0xFF808080);
-			setTarget(pbr);
-			clear(0x00FF80FF);
-		}
+			if( displayMode == MatCap ) {
+				clear(0xFF808080);
+				setTarget(pbr);
+				clear(0x00FF80FF);
+			}
 
+		}
 		apply(BeforeHdr);
 
+
 		var hdr = allocTarget("hdrOutput", false, 1, RGBA16F);
 		ctx.setGlobal("hdr", hdr);
 		setTarget(hdr);
-		if( ctx.engine.backgroundColor != null )
+		if( renderMode == LightProbe )
+			clear(0);
+		else if( ctx.engine.backgroundColor != null )
 			clear(ctx.engine.backgroundColor);
+
 		pbrProps.albedoTex = albedo;
 		pbrProps.normalTex = normal;
 		pbrProps.pbrTex = pbr;
@@ -227,9 +232,18 @@ class Renderer extends h3d.scene.Renderer {
 
 		pbrOut.setGlobals(ctx);
 		pbrDirect.doDiscard = false;
-		pbrIndirect.showSky = skyMode != Hide;
-		pbrIndirect.skyMap = skyMode == Irrad ? env.diffuse : env.env;
 		pbrIndirect.cameraInvViewProj.load(ctx.camera.getInverseViewProj());
+		switch( renderMode ) {
+		case Default:
+			pbrIndirect.drawIndirect = true;
+			pbrIndirect.showSky = skyMode != Hide;
+			pbrIndirect.skyMap = skyMode == Irrad ? env.diffuse : env.env;
+		case LightProbe:
+			pbrIndirect.drawIndirect = false;
+			pbrIndirect.showSky = true;
+			pbrIndirect.skyMap = env.env;
+		}
+
 		pbrOut.render();
 		pbrDirect.doDiscard = true;
 
@@ -246,6 +260,19 @@ class Renderer extends h3d.scene.Renderer {
 
 		pbrProps.isScreen = false;
 		draw("lights");
+
+		if( renderMode == LightProbe ) {
+			pbrProps.isScreen = true;
+			resetTarget();
+			copy(hdr, null);
+			// no warnings
+			for( p in passObjects ) p.rendered = true;
+			return;
+		}
+
+		ctx.extraShaders = new hxsl.ShaderList(pbrProps, null);
+		draw("volumetricLightmap");
+		ctx.extraShaders = null;
 		pbrProps.isScreen = true;
 
 		apply(AfterHdr);
@@ -326,7 +353,7 @@ class Renderer extends h3d.scene.Renderer {
 				blur : 9,
 				bias : 0.1,
 				quality : 0.3,
-			}
+			},
 		};
 		return props;
 	}

+ 15 - 0
h3d/scene/pbr/SphericalHarmonic.hx

@@ -0,0 +1,15 @@
+package h3d.scene.pbr;
+
+class SphericalHarmonic {
+
+	public var coefR : Array<Float> = [];
+	public var coefG : Array<Float> = [];
+	public var coefB : Array<Float> = [];
+
+	public function new(order:Int) {
+		var coefCount = order * order;
+		coefR = [for (value in 0...coefCount) 0];
+		coefG = [for (value in 0...coefCount) 0];
+		coefB = [for (value in 0...coefCount) 0];
+	}
+}

+ 169 - 0
h3d/scene/pbr/VolumetricLightmap.hx

@@ -0,0 +1,169 @@
+package h3d.scene.pbr;
+
+class VolumetricLightmap extends h3d.scene.Mesh {
+
+	public var lightProbes:Array<LightProbe> = [];
+	public var lightProbeBuffer : h3d.Buffer;
+	public var lightProbeTexture : h3d.mat.Texture;
+	public var shOrder : Int = 1;
+	public var voxelSize (default, set) : h3d.Vector;
+	public var probeCount : h3d.col.IPoint;
+	public var useAlignedProb : Bool = false;
+	public var strength : Float = 1.;
+
+	public var lastBakedProbeIndex = -1;
+
+	var shader : h3d.shader.pbr.VolumetricLightmap;
+
+	public function new(?parent) {
+		super(new h3d.prim.Cube(1,1,1,false), null, parent);
+		shader = new h3d.shader.pbr.VolumetricLightmap();
+		material.mainPass.removeShader(material.mainPass.getShader(h3d.shader.pbr.PropsValues));
+		material.mainPass.addShader(shader);
+		material.mainPass.setPassName("volumetricLightmap");
+		material.mainPass.blend(One, One);
+		material.mainPass.culling = Front;
+		material.mainPass.depth(false, Greater);
+		material.mainPass.enableLights = false;
+		material.castShadows = false;
+		material.shadows = false;
+		material.mainPass.stencil = new h3d.mat.Stencil();
+		material.mainPass.stencil.setFunc(Equal, 0, 0xFF, 0xFF);
+		material.mainPass.stencil.setOp(Keep, Keep, Increment);
+		probeCount = new h3d.col.IPoint();
+		voxelSize = new h3d.Vector(1,1,1);
+	}
+
+	function set_voxelSize(newSize) :h3d.Vector {
+		voxelSize = newSize;
+		updateProbeCount();
+		return voxelSize;
+	}
+
+	public function updateProbeCount(){
+		probeCount.set(Std.int(Math.max(1,Math.floor(scaleX/voxelSize.x)) + 1),
+						Std.int(Math.max(1,Math.floor(scaleY/voxelSize.y)) + 1),
+						Std.int(Math.max(1,Math.floor(scaleZ/voxelSize.z)) + 1));
+	}
+
+	override function sync(ctx:RenderContext) {
+		shader.ORDER = shOrder;
+		shader.SIZE = lightProbes.length * shader.ORDER * shader.ORDER;
+		shader.lightmapInvPos.load(getInvPos());
+		shader.lightmapSize.load(new h3d.Vector(probeCount.x, probeCount.y, probeCount.z));
+		shader.voxelSize.load(new h3d.Vector(scaleX/(probeCount.x - 1), scaleY/(probeCount.y - 1), scaleZ/(probeCount.z - 1)));
+		shader.lightProbeTexture = lightProbeTexture;
+		shader.cameraInverseViewProj.load(ctx.camera.getInverseViewProj());
+		shader.cameraPos.load(ctx.camera.pos);
+		shader.strength = strength;
+	}
+
+	public function generateProbes() {
+		var totalProbeCount : Int = probeCount.x * probeCount.y * probeCount.z ;
+		lightProbes.resize(totalProbeCount);
+		lastBakedProbeIndex = -1;
+
+		for(i in 0 ... probeCount.x){
+			for(j in 0 ... probeCount.y){
+				for(k in 0 ... probeCount.z){
+					var index : Int = i + j * probeCount.x + k * probeCount.x * probeCount.y;
+					var probePos : h3d.Vector = new h3d.Vector( i/(probeCount.x - 1),  j/(probeCount.y - 1), k/(probeCount.z - 1));
+					localToGlobal(probePos);
+
+					if(useAlignedProb){
+						var overlappedLightmaps : Array<VolumetricLightmap> = [];
+						// Check if a probe is inside an other lightmap
+						/*for(i in 0 ... volumetricLightmaps.length){
+							if(volumetricLightmaps[i] != this && volumetricLightmaps[i].isInsideVolume(probePos)){
+								overlappedLightmaps.push(volumetricLightmaps[i]);
+							}
+						}*/
+						if(overlappedLightmaps.length > 0){
+							var alignment = getWorldAlignment(overlappedLightmaps);
+							probePos.set(probePos.x - probePos.x % alignment.x, probePos.y - probePos.y % alignment.y, probePos.z - probePos.z % alignment.z);
+						}
+					}
+
+					lightProbes[index] = new h3d.scene.pbr.LightProbe();
+					lightProbes[index].position.set(probePos.x, probePos.y, probePos.z);
+				}
+			}
+		}
+	}
+
+	public function getWorldAlignment(lightmaps : Array<VolumetricLightmap>) : h3d.Vector {
+		var result =  new h3d.Vector(scaleX/(probeCount.x - 1), scaleY/(probeCount.y - 1), scaleZ/(probeCount.z - 1));
+		for(i in 0...lightmaps.length){
+			result.x = Math.max(result.x, lightmaps[i].scaleX / (lightmaps[i].probeCount.x - 1.0));
+			result.y = Math.max(result.y, lightmaps[i].scaleY / (lightmaps[i].probeCount.y - 1.0));
+			result.z = Math.max(result.z, lightmaps[i].scaleZ / (lightmaps[i].probeCount.z - 1.0));
+		}
+		return result;
+	}
+
+	public function isInsideVolume(worldPos: h3d.Vector) : Bool {
+		var localPos = worldPos.clone();
+		globalToLocal(localPos);
+		return (localPos.x >= 0 && localPos.y >= 0 && localPos.z >= 0 && localPos.x <= 1 && localPos.y <= 1 && localPos.z <= 1);
+	}
+
+	// Pack data inside an Uniform Buffer
+	public function packData(){
+		var coefCount : Int = shOrder * shOrder;
+		var size = lightProbes.length;
+		lightProbeBuffer = new h3d.Buffer(size, 4 * coefCount, [UniformBuffer, Dynamic]);
+		var buffer = new hxd.FloatBuffer();
+		var probeIndex : Int = 0;
+		var dataIndex : Int = 0;
+
+		buffer.resize(size * 4);
+
+		while(probeIndex < lightProbes.length) {
+			var index = probeIndex * coefCount * 4;
+			for(i in 0... coefCount) {
+				buffer[index + i * 4 + 0] = lightProbes[probeIndex].sh.coefR[i];
+				buffer[index + i * 4 + 1] = lightProbes[probeIndex].sh.coefG[i];
+				buffer[index + i * 4 + 2] = lightProbes[probeIndex].sh.coefB[i];
+				buffer[index + i * 4 + 3] = 0;
+			}
+			++probeIndex;
+		}
+
+		lightProbeBuffer.uploadVector(buffer, 0, size, 0);
+		shader.lightProbeBuffer = lightProbeBuffer;
+	}
+
+	// Pack data inside a 2D texture
+	public function packDataInsideTexture(){
+		var coefCount : Int = shOrder * shOrder;
+		var sizeX = probeCount.x * coefCount;
+		var sizeY = probeCount.y * probeCount.z;
+
+		if(lightProbeTexture == null || lightProbeTexture.width != sizeX || lightProbeTexture.height != sizeY){
+			lightProbeTexture = new h3d.mat.Texture(sizeX, sizeY, [Dynamic], RGBA32F);
+			lightProbeTexture.filter = Nearest;
+		}
+		var pixels : hxd.Pixels.PixelsFloat = hxd.Pixels.alloc(sizeX, sizeY, RGBA32F);
+
+		for(k in 0 ... probeCount.z){
+			for(j in 0 ... probeCount.y){
+				for(i in 0 ... probeCount.x){
+					var probeIndex : Int = i + j * probeCount.x + k * probeCount.x * probeCount.y;
+					if(probeIndex > lastBakedProbeIndex) {
+						lightProbeTexture.uploadPixels(pixels,0,0);
+						return;
+					}
+					for(coef in 0... coefCount){
+						var u = i + probeCount.x * coef;
+						var v = j + k * probeCount.y;
+						var sh = lightProbes[probeIndex].sh;
+						var	color = new h3d.Vector(lightProbes[probeIndex].sh.coefR[coef], lightProbes[probeIndex].sh.coefG[coef], lightProbes[probeIndex].sh.coefB[coef], 0);
+						pixels.setPixelF(u, v, color);
+					}
+				}
+			}
+		}
+
+		lightProbeTexture.uploadPixels(pixels,0,0);
+	}
+}

+ 167 - 0
h3d/shader/pbr/ComputeSH.hx

@@ -0,0 +1,167 @@
+package h3d.shader.pbr;
+
+class ComputeSH extends h3d.shader.ScreenShader {
+	static var SRC = {
+
+		@param var environment : SamplerCube;
+		@param var width : Int;
+		@param var cubeDir : Array<Mat3, 6>;
+		@const var ORDER : Int;
+
+		@param var PI = 3.1416;
+
+		var shCoefL00 : Float;
+		var shCoefL1n1: Float;
+		var shCoefL10 : Float;
+		var shCoefL11 : Float;
+		var shCoefL2n2 : Float;
+		var shCoefL2n1 : Float;
+		var shCoefL20 : Float;
+		var shCoefL21 : Float;
+		var shCoefL22 : Float;
+
+		var coefL00Final : Vec3;
+		var coefL1n1Final: Vec3;
+		var coefL10Final : Vec3;
+		var coefL11Final : Vec3;
+		var coefL2n2Final : Vec3;
+		var coefL2n1Final : Vec3;
+		var coefL20Final : Vec3;
+		var coefL21Final : Vec3;
+		var coefL22Final : Vec3;
+
+		function evalSH(dir:Vec3) {
+
+			if (ORDER >= 1){
+				shCoefL00 = evalCoefL00(dir);
+			}
+			if (ORDER >= 2){
+				shCoefL1n1 = evalCoefL1n1(dir);
+				shCoefL10 = evalCoefL10(dir);
+				shCoefL11 = evalCoefL11(dir);
+			}
+			if (ORDER >= 3){
+				shCoefL2n2 = evalCoefL2n2(dir);
+				shCoefL2n1 = evalCoefL2n1(dir);
+				shCoefL20 = evalCoefL20(dir);
+				shCoefL21 = evalCoefL21(dir);
+				shCoefL22 = evalCoefL22(dir);
+			}
+		}
+
+		function getDir(u: Float, v:Float, face:Int) : Vec3 {
+			var dir = vec3(u, v, 1) * cubeDir[face];
+			dir.normalize();
+			return dir;
+		}
+
+		function evalCoefL00(dir: Vec3) : Float { return 0.282095; }
+		function evalCoefL1n1(dir: Vec3) : Float { return -0.488603 * dir.y; }
+		function evalCoefL10(dir: Vec3) : Float { return 0.488603 * dir.z; }
+		function evalCoefL11(dir: Vec3) : Float { return -0.488603 * dir.x; }
+		function evalCoefL2n2(dir: Vec3) : Float { return 1.092548 * dir.y * dir.x; }
+		function evalCoefL2n1(dir: Vec3) : Float { return -1.092548 * dir.y * dir.z; }
+		function evalCoefL20(dir: Vec3) : Float { return 0.315392 * (-dir.x * dir.x - dir.y * dir.y + 2.0 * dir.z * dir.z); }
+		function evalCoefL21(dir: Vec3) : Float { return -1.092548 * dir.x * dir.z; }
+		function evalCoefL22(dir: Vec3) : Float { return 0.546274 * (dir.x * dir.x - dir.y * dir.y); }
+
+		var out : {
+			position : Vec4,
+			coefL00 : Vec3,
+			coefL1n1: Vec3,
+			coefL10 : Vec3,
+			coefL11 : Vec3,
+			coefL2n2 : Vec3,
+			coefL2n1 : Vec3,
+			coefL20 : Vec3,
+			coefL21 : Vec3,
+			coefL22 : Vec3,
+		};
+
+		function fragment() {
+
+			if (ORDER >= 1){
+				coefL00Final = vec3(0);
+			}
+			if (ORDER >= 2){
+				coefL1n1Final = vec3(0);
+				coefL10Final = vec3(0);
+				coefL11Final = vec3(0);
+			}
+			if (ORDER >= 3){
+				coefL2n2Final = vec3(0);
+				coefL2n1Final = vec3(0);
+				coefL20Final = vec3(0);
+				coefL21Final = vec3(0);
+				coefL22Final = vec3(0);
+			}
+
+			var weightSum = 0.0;
+			var fWidth : Float = width;
+			var invWidth : Float = 1.0 / fWidth;
+
+			for(f in 0...6) {
+				for (u in 0...width) {
+					var fU : Float = (u / fWidth - 0.5) * 2.0;// Texture coordinate U in range [-1 to 1]
+					fU *= fU;
+					var uCoord = 2.0 * u * invWidth + invWidth;
+					for (v in 0...width) {
+						var fV : Float = (v / fWidth - 0.5) * 2.0;// Texture coordinate V in range [-1 to 1]
+						fV *= fV;
+						var vCoord = 2.0 * v * invWidth + invWidth;
+
+						var dir = getDir(uCoord, vCoord, f);// Get direction from center of cube texture to current texel
+
+						var diffSolid = 4.0 / ((1.0 + fU + fV) * sqrt(1.0 + fU + fV));	// Scale factor depending on distance from center of the face
+						weightSum += diffSolid;
+
+						var flippedDir = vec3(dir.x, dir.y, -dir.z);
+						var color : Vec3 = environment.get(-flippedDir).rgb;// Get color from texture
+
+						evalSH(dir);// Calculate coefficients of spherical harmonics for current direction
+
+						if (ORDER >= 1){
+							coefL00Final += shCoefL00 * color * diffSolid;
+						}
+						if (ORDER >= 2){
+							coefL1n1Final += shCoefL1n1 * color * diffSolid;
+							coefL10Final += shCoefL10 * color * diffSolid;
+							coefL11Final += shCoefL11 * color * diffSolid;
+						}
+						if (ORDER >= 3){
+							coefL2n2Final += shCoefL2n2 * color * diffSolid;
+							coefL2n1Final += shCoefL2n1 * color * diffSolid;
+							coefL20Final += shCoefL20 * color * diffSolid;
+							coefL21Final += shCoefL21 * color * diffSolid;
+							coefL22Final += shCoefL22 * color * diffSolid;
+						}
+					}
+				}
+			}
+
+			// Final scale for coefficients
+			var normProj = (4.0 * PI) / weightSum;
+
+			if (ORDER >= 1){
+				out.coefL00 = coefL00Final * normProj;
+			}
+
+			if (ORDER >= 2){
+				out.coefL1n1 = coefL1n1Final * normProj;
+				out.coefL10 = coefL10Final * normProj;
+				out.coefL11 = coefL11Final * normProj;
+			}
+			else{ out.coefL1n1 = vec3(0); out.coefL10 = vec3(0); out.coefL11 = vec3(0); }
+
+			if (ORDER >= 3){
+				out.coefL2n2 = coefL2n2Final * normProj;
+				out.coefL2n1 = coefL2n1Final * normProj;
+				out.coefL20 = coefL20Final * normProj;
+				out.coefL21 = coefL21Final * normProj;
+				out.coefL22 = coefL22Final * normProj;
+			}
+			else{ out.coefL2n2 = vec3(0); out.coefL2n1 = vec3(0); out.coefL20 = vec3(0); out.coefL21 = vec3(0); out.coefL22 = vec3(0); }
+		}
+	}
+
+}

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

@@ -11,6 +11,7 @@ class Indirect extends PropsDefinition {
 		@param var irrPower : Float;
 
 		@const var showSky : Bool;
+		@const var drawIndirect : Bool;
 		@param var skyMap : SamplerCube;
 		@param var cameraInvViewProj : Mat4;
 		@param var emissivePower : Float;
@@ -35,6 +36,7 @@ class Indirect extends PropsDefinition {
 				var specular = envSpec * (F * envBRDF.x + envBRDF.y);
 
 				var indirect = (diffuse * (1 - metalness) * (1 - F) + specular) * irrPower;
+				if( !drawIndirect ) indirect = vec3(0.);
 				pixelColor.rgb += indirect * occlusion + albedo * emissive * emissivePower;
 			}
 		}

+ 62 - 0
h3d/shader/pbr/SHDisplay.hx

@@ -0,0 +1,62 @@
+package h3d.shader.pbr;
+
+class SHDisplay extends hxsl.Shader {
+	static var SRC = {
+
+		function computeIrradiance(norm : Vec3) : Vec3 {
+			// An Efficient Representation for Irradiance Environment Maps
+			// Ravi Ramamoorthi Pat Hanrahan
+
+			var c1 = 0.429043;
+			var c2 = 0.511664;
+			var c3 = 0.743125;
+			var c4 = 0.886227;
+			var c5 = 0.247708;
+
+			var irradiance = vec3(0,0,0);
+
+			if (order >= 1){
+				var L00 = vec3(shCoefsRed[0], shCoefsGreen[0], shCoefsBlue[0]);
+				irradiance += c4 * L00;
+			}
+
+			if (order >= 2){
+				var L1n1 = vec3(shCoefsRed[1], shCoefsGreen[1], shCoefsBlue[1]);
+				var L10 = vec3(shCoefsRed[2], shCoefsGreen[2], shCoefsBlue[2]);
+				var L11 = vec3(shCoefsRed[3], shCoefsGreen[3], shCoefsBlue[3]);
+				irradiance += 2 * c2 * (L11 * norm.x + L1n1 * norm.y + L10 * norm.z);
+			}
+
+			if(order >= 3){
+				var L2n2 = vec3(shCoefsRed[4], shCoefsGreen[4], shCoefsBlue[4]);
+				var L2n1 = vec3(shCoefsRed[5], shCoefsGreen[5], shCoefsBlue[5]);
+				var L20 = vec3(shCoefsRed[6], shCoefsGreen[6], shCoefsBlue[6]);
+				var L21 = vec3(shCoefsRed[7], shCoefsGreen[7], shCoefsBlue[7]);
+				var L22 = vec3(shCoefsRed[8], shCoefsGreen[8], shCoefsBlue[8]);
+				irradiance += c1 * L22 * (norm.x*norm.x - norm.y*norm.y) + c3 * L20  * (norm.z * norm.z) - c5 * L20 +
+				2 * c1 * (L2n2 * (norm.x*norm.y) + L21 * (norm.x*norm.z) + L2n1 * (norm.y*norm.z));
+			}
+
+			return irradiance;
+		}
+
+		@const var order : Int;
+		@const var SIZE : Int; // Equal to order*order
+
+		@param var shCoefsRed : Array<Float,SIZE>;
+		@param var shCoefsGreen : Array<Float,SIZE>;
+		@param var shCoefsBlue : Array<Float,SIZE>;
+
+		var transformedNormal : Vec3;
+
+		var output : {
+			color : Vec4
+		};
+
+		function fragment() {
+			var normal = vec4(transformedNormal.x, transformedNormal.y, transformedNormal.z, 1.0);
+			output.color = vec4(computeIrradiance(normal.xyz) / 3.14, 1.0);
+		}
+	}
+
+}

+ 162 - 0
h3d/shader/pbr/VolumetricLightmap.hx

@@ -0,0 +1,162 @@
+package  h3d.shader.pbr;
+
+class VolumetricLightmap extends hxsl.Shader {
+
+	static var SRC =
+	{
+		function getCoef(shCoords : Vec3, band : Int) : Vec3 {
+			var u = (shCoords.x + band * lightmapSize.x ) * texelSize.x + texelSize.x / 2.0;
+			var v = (shCoords.y + shCoords.z * lightmapSize.y)  * texelSize.y  + texelSize.y / 2.0;
+			return lightProbeTexture.get(vec2(u, v)).rgb;
+		}
+
+		function getCoefFinal(band : Int) : Vec3 {
+			var p00 = mix(getCoef(p000, band), getCoef(p100, band), dist.x);
+			var p01 = mix(getCoef(p001, band), getCoef(p101, band), dist.x);
+			var p10 = mix(getCoef(p010, band), getCoef(p110, band), dist.x);
+			var p11 = mix(getCoef(p011, band), getCoef(p111, band), dist.x);
+			var p0 = mix(p00, p10 , dist.y);
+			var p1 = mix(p01, p11 , dist.y);
+			var p = mix(p0, p1 , dist.z);
+			return p;
+		}
+
+		function computeIrradiance(norm : Vec3) : Vec3 {
+		// An Efficient Representation for Irradiance Environment Maps
+		// Ravi Ramamoorthi Pat Hanrahan
+
+			var c1 = 0.429043;
+			var c2 = 0.511664;
+			var c3 = 0.743125;
+			var c4 = 0.886227;
+			var c5 = 0.247708;
+
+			var irradiance = vec3(0,0,0);
+
+			if (ORDER >= 1){
+				var L00 = getCoefFinal(0);
+				irradiance += c4 * L00;
+			}
+			if (ORDER >= 2){
+				var L1n1 = getCoefFinal(1);
+				var L10 = getCoefFinal(2);
+				var L11 = getCoefFinal(3);
+				irradiance += 2 * c2 * (L11 * norm.x + L1n1 * norm.y + L10 * norm.z);
+			}
+			if(ORDER >= 3){
+				var L2n2 = getCoefFinal(4);
+				var L2n1 = getCoefFinal(5);
+				var L20 = getCoefFinal(6);
+				var L21 = getCoefFinal(7);
+				var L22 = getCoefFinal(8);
+				irradiance += c1 * L22 * (norm.x*norm.x - norm.y*norm.y) + c3 * L20  * (norm.z * norm.z) - c5 * L20 +
+				2 * c1 * (L2n2 * (norm.x*norm.y) + L21 * (norm.x*norm.z) + L2n1 * (norm.y*norm.z));
+			}
+			return irradiance / 3.14;
+		}
+
+		function getLightProbeIndex( coords : Vec3 ) : Int {
+			return int(floor(coords.x + lightmapSize.x * coords.y + lightmapSize.y * lightmapSize.x * coords.z));
+		}
+
+		function getVoxelCoords(pos : Vec3 ) : Vec3 {
+			posLightmapSpace = (vec4(pos, 1) * lightmapInvPos).xyz;
+			return floor(posLightmapSpace * (lightmapSize - vec3(1,1,1)));
+		}
+
+		function getLightProbePosition( coords : Vec3 ) : Vec3 {
+			return coords * voxelSize;
+		}
+
+		function getPixelPosition() : Vec3 {
+			var uv2 = (screenUV - 0.5) * vec2(2, -2);
+			var temp = vec4(uv2, depth, 1) * cameraInverseViewProj;
+			var originWS = temp.xyz / temp.w;
+			return originWS;
+		}
+
+		function getClampedCoords(coords : Vec3) : Vec3{
+			return min(lightmapSize - vec3(1,1,1), max( vec3(0,0,0), coords));
+		}
+
+		@const var ORDER : Int;
+		@const(327676) var SIZE : Int;
+
+		@param var lightProbeBuffer : Buffer<Vec4,SIZE>;
+		@param var lightProbeTexture : Sampler2D;
+		@param var lightmapInvPos : Mat4;
+		@param var lightmapSize : Vec3;
+		@param var voxelSize : Vec3;
+		@param var strength : Float;
+
+		@param var cameraInverseViewProj : Mat4;
+		@param var cameraPos : Vec3;
+
+		var transformedPosition : Vec3;
+		var screenUV : Vec2;
+
+		var depth : Float;
+		var normal : Vec3;
+		var position : Vec3;
+		var posLightmapSpace : Vec3;
+		var voxelCoords : Vec3;
+		var albedo : Vec3;
+		var occlusion : Float;
+		var roughness : Float;
+		var metalness : Float;
+
+		var probeCount : Float;
+		var coefCount : Float;
+		var texelSize : Vec2;
+		var dist : Vec3;
+
+		var p000 : Vec3;
+		var p100 : Vec3;
+		var p010 : Vec3;
+		var p001 : Vec3;
+		var p110 : Vec3;
+		var p101 : Vec3;
+		var p011 : Vec3;
+		var p111 : Vec3;
+
+		var output : {
+			color : Vec4
+		};
+
+
+		function fragment(){
+
+			position = getPixelPosition();
+			voxelCoords = getVoxelCoords(position);
+			probeCount = lightmapSize.x * lightmapSize.y * lightmapSize.z;
+			coefCount = float(ORDER * ORDER);
+
+			texelSize = vec2(1/(lightmapSize.x * coefCount), 1/(lightmapSize.y * lightmapSize.z));
+
+			if(posLightmapSpace.x < 0 || posLightmapSpace.y < 0 || posLightmapSpace.z < 0 ||
+			posLightmapSpace.x > 1 || posLightmapSpace.y > 1 || posLightmapSpace.z > 1)
+				discard;
+
+			p000 = getClampedCoords(voxelCoords + vec3(0,0,0));
+			p100 = getClampedCoords(voxelCoords + vec3(1,0,0));
+			p010 = getClampedCoords(voxelCoords + vec3(0,1,0));
+			p001 = getClampedCoords(voxelCoords + vec3(0,0,1));
+			p110 = getClampedCoords(voxelCoords + vec3(1,1,0));
+			p101 = getClampedCoords(voxelCoords + vec3(1,0,1));
+			p011 = getClampedCoords(voxelCoords + vec3(0,1,1));
+			p111 = getClampedCoords(voxelCoords + vec3(1,1,1));
+
+			var voxelPos = (voxelCoords) / (lightmapSize - vec3(1,1,1));
+			dist = (posLightmapSpace - voxelPos) * (lightmapSize - vec3(1,1,1));
+
+			var irradiance : Vec3 = computeIrradiance(normal) * min(vec3(1),albedo) * occlusion;
+
+			var NdV = dot(normal, normalize(cameraPos - position));
+			var F0 = mix(vec3(0.04), albedo, metalness);
+			var F = F0 + (max(vec3(1 - roughness), F0) - F0) * exp2( ( -5.55473 * NdV - 6.98316) * NdV );
+			var indirect = (irradiance * (1 - metalness) * (1 - F) ) * strength;
+
+			output.color = vec4(indirect, 1.0);
+		}
+	}
+}