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

Add PBR Forward
Minor changes

ShiroSmith 4 жил өмнө
parent
commit
adc1dd1a1f

+ 1 - 1
h3d/Buffer.hx

@@ -69,7 +69,7 @@ class Buffer {
 			h3d.Engine.getCurrent().mem.allocBuffer(this, stride);
 			h3d.Engine.getCurrent().mem.allocBuffer(this, stride);
 	}
 	}
 
 
-	public function isDisposed() {
+	public inline function isDisposed() {
 		return buffer == null || buffer.isDisposed();
 		return buffer == null || buffer.isDisposed();
 	}
 	}
 
 

+ 1 - 0
h3d/impl/RendererFX.hx

@@ -5,6 +5,7 @@ enum Step {
 	Decals;
 	Decals;
 	Shadows;
 	Shadows;
 	Lighting;
 	Lighting;
+	Forward;
 	BeforeTonemapping;
 	BeforeTonemapping;
 	AfterTonemapping;
 	AfterTonemapping;
 	Overlay;
 	Overlay;

+ 6 - 4
h3d/mat/PbrMaterial.hx

@@ -2,6 +2,7 @@ package h3d.mat;
 
 
 @:enum abstract PbrMode(String) {
 @:enum abstract PbrMode(String) {
 	var PBR = "PBR";
 	var PBR = "PBR";
+	var Forward = "Forward";
 	var Overlay = "Overlay";
 	var Overlay = "Overlay";
 	var Decal = "Decal";
 	var Decal = "Decal";
 	var BeforeTonemapping = "BeforeTonemapping";
 	var BeforeTonemapping = "BeforeTonemapping";
@@ -157,10 +158,7 @@ class PbrMaterial extends Material {
 		var props : PbrProps = getDefaultProps();
 		var props : PbrProps = getDefaultProps();
 		props.blend = switch( blendMode ) {
 		props.blend = switch( blendMode ) {
 			case None: None;
 			case None: None;
-			case Alpha:
-				// in PBR, Alpha blending is not correct - use alphaKill by default
-				props.alphaKill = true;
-				None;
+			case Alpha: Alpha;
 			case Add: Add;
 			case Add: Add;
 			case Multiply: Multiply;
 			case Multiply: Multiply;
 			case AlphaMultiply: AlphaMultiply;
 			case AlphaMultiply: AlphaMultiply;
@@ -181,6 +179,7 @@ class PbrMaterial extends Material {
 
 
 	function resetProps() {
 	function resetProps() {
 		var props : PbrProps = props;
 		var props : PbrProps = props;
+		mainPass.enableLights = true;
 		// Remove superfluous shader
 		// Remove superfluous shader
 		mainPass.removeShader(mainPass.getShader(h3d.shader.VolumeDecal));
 		mainPass.removeShader(mainPass.getShader(h3d.shader.VolumeDecal));
 		mainPass.removeShader(mainPass.getShader(h3d.shader.pbr.StrengthValues));
 		mainPass.removeShader(mainPass.getShader(h3d.shader.pbr.StrengthValues));
@@ -214,6 +213,8 @@ class PbrMaterial extends Material {
 		switch( props.mode ) {
 		switch( props.mode ) {
 		case PBR:
 		case PBR:
 			// pass name set below (in set_blendMode)
 			// pass name set below (in set_blendMode)
+		case Forward:
+			mainPass.setPassName("forward");
 		case BeforeTonemapping:
 		case BeforeTonemapping:
 			mainPass.setPassName("beforeTonemapping");
 			mainPass.setPassName("beforeTonemapping");
 			if( props.emissive > 0 ) {
 			if( props.emissive > 0 ) {
@@ -432,6 +433,7 @@ class PbrMaterial extends Material {
 				<dd>
 				<dd>
 					<select field="mode">
 					<select field="mode">
 						<option value="PBR">PBR</option>
 						<option value="PBR">PBR</option>
+						<option value="Forward">Forward PBR</option>
 						<option value="BeforeTonemapping">Before Tonemapping</option>
 						<option value="BeforeTonemapping">Before Tonemapping</option>
 						<option value="AfterTonemapping">After Tonemapping</option>
 						<option value="AfterTonemapping">After Tonemapping</option>
 						<option value="Overlay">Overlay</option>
 						<option value="Overlay">Overlay</option>

+ 182 - 0
h3d/scene/pbr/LightBuffer.hx

@@ -0,0 +1,182 @@
+package h3d.scene.pbr;
+
+class LightBuffer {
+
+	public var defaultForwardShader = new h3d.shader.pbr.DefaultFoward();
+
+	var MAX_DIR_LIGHT = 2;
+	var MAX_SPOT_LIGHT = 3;
+	var MAX_POINT_LIGHT = 3;
+
+	var lightInfos : hxd.FloatBuffer;
+	final POINT_LIGHT_INFO_SIZE = 3;
+	final SPOT_LIGHT_INFO_SIZE = 8;
+	final DIR_LIGHT_INFO_SIZE = 5;
+
+	public function new() {
+		createBuffers();
+	}
+
+	function createBuffers() {
+		var stride = 4;
+		var size = 0;
+		size += MAX_DIR_LIGHT * DIR_LIGHT_INFO_SIZE;
+		size += MAX_POINT_LIGHT * POINT_LIGHT_INFO_SIZE;
+		size += MAX_SPOT_LIGHT * SPOT_LIGHT_INFO_SIZE;
+		size = hxd.Math.imax(1, size); // Avoid empty buffer
+		lightInfos = new hxd.FloatBuffer(size * stride);
+		defaultForwardShader.lightInfos = new h3d.Buffer(size, stride, [UniformBuffer, Dynamic]);
+		defaultForwardShader.BUFFER_SIZE = size;
+		defaultForwardShader.dirLightStride = DIR_LIGHT_INFO_SIZE * MAX_DIR_LIGHT;
+		defaultForwardShader.pointLightStride = POINT_LIGHT_INFO_SIZE * MAX_POINT_LIGHT;
+	}
+
+	public function setBuffers( s : h3d.shader.pbr.DefaultFoward ) {
+		s.lightInfos = defaultForwardShader.lightInfos;
+		s.dirLightStride = defaultForwardShader.dirLightStride;
+		s.pointLightStride = defaultForwardShader.pointLightStride;
+		s.cameraPosition = defaultForwardShader.cameraPosition;
+		s.emissivePower = defaultForwardShader.emissivePower;
+		s.BUFFER_SIZE = defaultForwardShader.BUFFER_SIZE;
+
+		s.pointLightCount = defaultForwardShader.pointLightCount;
+		s.spotLightCount = defaultForwardShader.spotLightCount;
+		s.dirLightCount = defaultForwardShader.dirLightCount;
+
+		for( i in 0 ... s.pointLightCount )
+			s.pointShadowMaps[i] = defaultForwardShader.pointShadowMaps[i];
+		for( i in 0 ... s.spotLightCount )
+			s.spotShadowMaps[i] = defaultForwardShader.spotShadowMaps[i];
+		for( i in 0 ... s.dirLightCount )
+			s.dirShadowMaps[i] = defaultForwardShader.dirShadowMaps[i];
+
+		s.USE_INDIRECT = defaultForwardShader.USE_INDIRECT;
+		if( s.USE_INDIRECT > 0.0 ) {
+			s.irrRotation = defaultForwardShader.irrRotation;
+			s.irrPower = defaultForwardShader.irrPower;
+			s.irrLut = defaultForwardShader.irrLut;
+			s.irrDiffuse = defaultForwardShader.irrDiffuse;
+			s.irrSpecular = defaultForwardShader.irrSpecular;
+			s.irrSpecularLevels = defaultForwardShader.irrSpecularLevels;
+		}
+	}
+
+	inline function fillFloats( b : hxd.FloatBuffer, f1 : Float, f2 : Float, f3 : Float, f4 : Float, i : Int ) {
+		b[i+0] = f1;
+		b[i+1] = f2;
+		b[i+2] = f3;
+		b[i+3] = f4;
+	}
+
+	inline function fillVector( b : hxd.FloatBuffer, v : h3d.Vector, i : Int ) {
+		b[i+0] = v.r;
+		b[i+1] = v.g;
+		b[i+2] = v.b;
+		b[i+3] = v.a;
+	}
+
+	public function sync( ctx : h3d.scene.RenderContext ) {
+
+		var r = @:privateAccess ctx.scene.renderer;
+		var pbrRenderer = Std.downcast(r, Renderer);
+		var p : h3d.scene.pbr.Renderer.RenderProps = pbrRenderer.props;
+		var s = defaultForwardShader;
+
+		s.cameraPosition = ctx.camera.pos;
+		s.emissivePower = p.emissive * p.emissive;
+
+		s.pointLightCount = 0;
+		s.spotLightCount = 0;
+		s.dirLightCount = 0;
+
+		// Safe Reset
+		for( i in 0 ... lightInfos.length )
+			lightInfos[i] = 0;
+
+		var l = @:privateAccess ctx.lights;
+		while( l != null ) {
+
+			// Dir Light
+			var dl = Std.downcast(l, DirLight);
+			if( dl != null ) {
+				var li = s.dirLightCount;
+				var i = li * DIR_LIGHT_INFO_SIZE * 4;
+				var pbr = @:privateAccess dl.pbr;
+				fillVector(lightInfos, pbr.lightColor, i);
+				lightInfos[i+3] = (dl.shadows != null && dl.shadows.mode != None) ? 1.0 : -1.0;
+				fillVector(lightInfos, pbr.lightDir, i+4);
+				if( lightInfos[i+3] > 0 ) {
+					lightInfos[i+7] = dl.shadows.bias;
+					s.dirShadowMaps[li] = dl.shadows.getShadowTex();
+					var mat = dl.shadows.getShadowProj();
+					fillFloats(lightInfos, mat._11, mat._21, mat._31, mat._41, i+8);
+					fillFloats(lightInfos, mat._12, mat._22, mat._32, mat._42, i+12);
+					fillFloats(lightInfos, mat._13, mat._23, mat._33, mat._43, i+16);
+				}
+				s.dirLightCount++;
+			}
+
+			// Point Light
+			var pl = Std.downcast(l, PointLight);
+			if( pl != null ) {
+				var offset = MAX_DIR_LIGHT * DIR_LIGHT_INFO_SIZE * 4;
+				var li = s.pointLightCount;
+				var i = li * POINT_LIGHT_INFO_SIZE * 4 + offset;
+				var pbr = @:privateAccess pl.pbr;
+				fillVector(lightInfos, pbr.lightColor, i);
+				lightInfos[i+3] = pbr.pointSize;
+				fillVector(lightInfos, pbr.lightPos, i+4);
+				lightInfos[i+7] = pbr.invLightRange4;
+				lightInfos[i+8] = pl.range;
+				lightInfos[i+9] = (pl.shadows != null && pl.shadows.mode != None) ? 1.0 : -1.0;
+				if( lightInfos[i+9] > 0 ) {
+					lightInfos[i+10] = pl.shadows.bias;
+					s.pointShadowMaps[li] = pl.shadows.getShadowTex();
+				}
+				s.pointLightCount++;
+			}
+
+			// Spot Light
+			var sl = Std.downcast(l, SpotLight);
+			if( sl != null ) {
+				var offset = (MAX_DIR_LIGHT * DIR_LIGHT_INFO_SIZE + MAX_POINT_LIGHT * POINT_LIGHT_INFO_SIZE) * 4 ;
+				var li = s.spotLightCount;
+				var i = s.spotLightCount * SPOT_LIGHT_INFO_SIZE * 4 + offset;
+				var pbr = @:privateAccess sl.pbr;
+				fillVector(lightInfos, pbr.lightColor, i);
+				lightInfos[i+3] = pbr.range;
+				fillVector(lightInfos, pbr.lightPos, i+4);
+				lightInfos[i+7] = pbr.invLightRange4;
+				fillVector(lightInfos, pbr.spotDir, i+8);
+				lightInfos[i+12] = pbr.angle;
+				lightInfos[i+13] = pbr.fallOff;
+				lightInfos[i+14] = (sl.shadows != null && sl.shadows.mode != None) ? 1.0 : -1.0;
+				if( lightInfos[i+14] > 0 ) {
+					lightInfos[i+15] = sl.shadows.bias;
+					var mat = sl.shadows.getShadowProj();
+					fillFloats(lightInfos, mat._11, mat._21, mat._31, mat._41, i+16);
+					fillFloats(lightInfos, mat._12, mat._22, mat._32, mat._42, i+20);
+					fillFloats(lightInfos, mat._13, mat._23, mat._33, mat._43, i+24);
+					fillFloats(lightInfos, mat._14, mat._24, mat._34, mat._44, i+28);
+					s.spotShadowMaps[li] = sl.shadows.getShadowTex();
+				}
+				s.spotLightCount++;
+			}
+
+			l = l.next;
+		}
+
+		s.lightInfos.uploadVector(lightInfos, 0, s.lightInfos.vertices, 0);
+
+		s.USE_INDIRECT = pbrRenderer.env != null ? 1.0 : 0.0;
+		var pbrIndirect = @:privateAccess pbrRenderer.pbrIndirect;
+		if( s.USE_INDIRECT > 0.0 ) {
+			s.irrRotation = pbrIndirect.irrRotation;
+			s.irrPower = pbrIndirect.irrPower;
+			s.irrLut = pbrIndirect.irrLut;
+			s.irrDiffuse = pbrIndirect.irrDiffuse;
+			s.irrSpecular = pbrIndirect.irrSpecular;
+			s.irrSpecularLevels = pbrIndirect.irrSpecularLevels;
+		}
+	}
+}

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

@@ -3,6 +3,14 @@ package h3d.scene.pbr;
 @:access(h3d.scene.pbr.Light)
 @:access(h3d.scene.pbr.Light)
 class LightSystem extends h3d.scene.LightSystem {
 class LightSystem extends h3d.scene.LightSystem {
 
 
+	public var lightBuffer : h3d.scene.pbr.LightBuffer;
+	public var forwardMode = false;
+
+	public function new() {
+		super();
+		lightBuffer = new h3d.scene.pbr.LightBuffer();
+	}
+
 	override function computeLight( obj : h3d.scene.Object, shaders : hxsl.ShaderList ) : hxsl.ShaderList {
 	override function computeLight( obj : h3d.scene.Object, shaders : hxsl.ShaderList ) : hxsl.ShaderList {
 		var light = hxd.impl.Api.downcast(obj, h3d.scene.pbr.Light);
 		var light = hxd.impl.Api.downcast(obj, h3d.scene.pbr.Light);
 		if( light != null ) {
 		if( light != null ) {
@@ -10,9 +18,26 @@ class LightSystem extends h3d.scene.LightSystem {
 			if( light.shadows.shader != null && light.shadows.mode != None )
 			if( light.shadows.shader != null && light.shadows.mode != None )
 				shaders = ctx.allocShaderList(light.shadows.shader, shaders);
 				shaders = ctx.allocShaderList(light.shadows.shader, shaders);
 		}
 		}
+		else if( forwardMode ) {
+			var found = false;
+            for( s in shaders ) {
+                var forward = Std.downcast(s, h3d.shader.pbr.DefaultFoward);
+                if( forward != null ) {
+                    lightBuffer.setBuffers(forward);
+                    found = true;
+                    break;
+                }
+            }
+            if( !found )
+                shaders = ctx.allocShaderList(lightBuffer.defaultForwardShader, shaders);
+		}
 		return shaders;
 		return shaders;
 	}
 	}
 
 
+	override function initLights( ctx : h3d.scene.RenderContext ) @:privateAccess {
+		super.initLights(ctx);
+		lightBuffer.sync(ctx);
+	}
 
 
 	public function drawShadows( light : Light, passes : h3d.pass.PassList ) {
 	public function drawShadows( light : Light, passes : h3d.pass.PassList ) {
 		light.shadows.setContext(ctx);
 		light.shadows.setContext(ctx);

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

@@ -464,6 +464,14 @@ class Renderer extends h3d.scene.Renderer {
 		setTarget(textures.hdr);
 		setTarget(textures.hdr);
 		clear(0);
 		clear(0);
 		lighting();
 		lighting();
+
+		begin(Forward);
+		var ls = hxd.impl.Api.downcast(getLightSystem(), h3d.scene.pbr.LightSystem);
+		ls.forwardMode = true;
+		draw("forward");
+		ls.forwardMode = false;
+		end();
+
 		if( renderMode == LightProbe ) return;
 		if( renderMode == LightProbe ) return;
 
 
 		begin(BeforeTonemapping);
 		begin(BeforeTonemapping);

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

@@ -49,7 +49,7 @@ class SpotLight extends Light {
 		return angle = v;
 		return angle = v;
 	}
 	}
 
 
-	public static function spotLightPrim() {
+	public static function spotLightPrim() : h3d.prim.Polygon {
 		var engine = h3d.Engine.getCurrent();
 		var engine = h3d.Engine.getCurrent();
 		var p : h3d.prim.Polygon = @:privateAccess engine.resCache.get(SpotLight);
 		var p : h3d.prim.Polygon = @:privateAccess engine.resCache.get(SpotLight);
 		if( p != null )
 		if( p != null )

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

@@ -0,0 +1,67 @@
+package h3d.shader.pbr;
+
+class BDRF extends hxsl.Shader {
+
+	static var SRC = {
+
+		function getAnisotropicRoughness( roughness : Float, anisotropy : Float ) : Vec2 {
+			// [Burley12] Offers more pleasant and intuitive results, but is slightly more expensive
+			//var da = sqrt(1 - 0.9 * anisotropy);
+			//var at = anisotropy / da;
+			//var ab = anisotropy * da;
+
+			// [Kulla17] Allows creation of sharp highlights
+			var at = max(roughness * (1.0 + anisotropy), 0.001);
+			var ab = max(roughness * (1.0 - anisotropy), 0.001);
+
+			return vec2(at, ab);
+		}
+
+		// [Heitz14] Anisotropic visibility function
+		function smithGGXCorrelatedAnisotropic( at : Float, ab : Float, TdV : Float, BdV : Float, TdL : Float, BdL : Float, NdV: Float, NdL : Float ) : Float {
+			var lambdaV = NdL * length(vec3(at * TdV, ab * BdV, NdV));
+			var lambdaL = NdV * length(vec3(at * TdL, ab * BdL, NdL));
+			var v = 0.5 / (lambdaV + lambdaL);
+			return saturate(v);
+		}
+
+		// GGX Anisotropic
+		function normalDistributionGGXAnisotropic( at : Float, ab : Float, NdH : Float, TdH : Float, BdH : Float, roughness : Float, anisotropy : Float ) : Float {
+			var a2 = at * ab;
+			var v = vec3(ab * TdH, at * BdH, a2 * NdH);
+			var v2 = dot(v, v);
+			var w2 = a2 / v2;
+			return a2 * w2 * w2 * (1.0 / PI);
+		}
+
+		// Formulas below from 2013 Siggraph "Real Shading in UE4"
+
+		// GGX (Trowbridge-Reitz) "Disney"
+		function normalDistributionGGX( NdH : Float, roughness: Float ) : Float {
+			var alpha = roughness * roughness;
+			var alpha2 = alpha * alpha;
+			var denom = NdH * NdH * (alpha2 - 1.) + 1;
+			return alpha2 / (PI * denom * denom);
+		}
+
+		// G = geometric attenuation
+		// Schlick (modified for UE4 with k=alpha/2)
+		// k = (rough + 1)² / 8
+		function geometrySchlickGGX( NdV : Float, NdL : Float, roughness : Float ) : Float {
+			var k = (roughness + 1);
+			k *= k;
+			k *= 0.125;
+			//var G = (1 / (NdV * (1 - k) + k)) * (1 / (NdL * (1 - k) + k)) * NdL * NdV;
+			//var Att = 1 / (4 * NdL * NdV);
+			return (1 / (NdV * (1 - k) + k)) * (1 / (NdL * (1 - k) + k)) * 0.25;
+		}
+
+		// Schlick approx
+		// pow 5 optimized with Spherical Gaussian
+		// var F = F0 + (1 - F0) * pow(1 - v.dot(h), 5.);
+		function fresnelSchlick( VdH : Float, F0 : Vec3 ) : Vec3 {
+			return F0 + (1. - F0) * exp2( ( -5.55473 * VdH - 6.98316) * VdH );
+		}
+	}
+
+}

+ 215 - 0
h3d/shader/pbr/DefaultFoward.hx

@@ -0,0 +1,215 @@
+package h3d.shader.pbr;
+
+class DefaultFoward extends hxsl.Shader {
+
+	static var SRC = {
+
+		@:import h3d.shader.pbr.Light.LightEvaluation;
+		@:import h3d.shader.pbr.BDRF;
+
+		// Import pbr info
+		var output : {color : Vec4, metalness : Float, roughness : Float, occlusion : Float, emissive : Float };
+
+		@const(256) var BUFFER_SIZE : Int;
+		@param var lightInfos : Buffer<Vec4, BUFFER_SIZE>;
+
+		// Buffer Info
+		@param var dirLightCount : Int;
+		@param var pointLightCount : Int;
+		@param var spotLightCount : Int;
+		@const(8) var dirLightStride : Int;
+		@const(8) var pointLightStride : Int;
+
+		// ShadowMaps
+		@param var dirShadowMaps : Array<Sampler2D, 2>;
+		@param var pointShadowMaps : Array<SamplerCube, 3>;
+		@param var spotShadowMaps : Array<Sampler2D, 3>;
+
+		// Direct Lighting
+		@param var cameraPosition : Vec3;
+		@param var emissivePower : Float;
+		var view : Vec3;
+		var NdV : Float;
+		var albedo : Vec3;
+		var pbrSpecularColor : Vec3;
+		var metalness : Float;
+		var roughness : Float;
+		var occlusion : Float;
+		var emissive : Float;
+		var F0 : Vec3;
+
+		// Indirect Lighting
+		@param var USE_INDIRECT = 1.0;
+		@param var irrLut : Sampler2D;
+		@param var irrDiffuse : SamplerCube;
+		@param var irrSpecular : SamplerCube;
+		@param var irrSpecularLevels : Float;
+		@param var irrPower : Float;
+		@param var irrRotation : Vec2;
+
+		var transformedNormal : Vec3;
+		var transformedPosition : Vec3;
+		var pixelColor : Vec4;
+
+		function rotateNormal( n : Vec3 ) : Vec3 {
+			return vec3(n.x * irrRotation.x - n.y * irrRotation.y, n.x * irrRotation.y + n.y * irrRotation.x, n.z);
+		}
+
+		function indirectLighting() : Vec3 {
+			var F = F0 + (max(vec3(1 - roughness), F0) - F0) * exp2( ( -5.55473 * NdV - 6.98316) * NdV );
+			var rotatedNormal = rotateNormal(transformedNormal);
+			var diffuse = irrDiffuse.get(rotatedNormal).rgb * albedo;
+			var reflectVec = reflect(-view, transformedNormal);
+			var rotatedReflecVec = rotateNormal(reflectVec);
+			var envSpec = textureLod(irrSpecular, rotatedReflecVec, roughness * irrSpecularLevels).rgb;
+			var envBRDF = irrLut.get(vec2(roughness, NdV));
+			var specular = envSpec * (F * envBRDF.x + envBRDF.y);
+			var indirect = (diffuse * (1 - metalness) * (1 - F) + specular) * irrPower;
+			return indirect * occlusion;
+		}
+
+		function directLighting( lightColor : Vec3, lightDirection : Vec3) : Vec3 {
+			var result = vec3(0);
+			var NdL = clamp(transformedNormal.dot(lightDirection), 0.0, 1.0);
+			if( lightColor.dot(lightColor) > 0.0001 && NdL > 0 ) {
+				var half = (lightDirection + view).normalize();
+				var NdH = clamp(transformedNormal.dot(half), 0.0, 1.0);
+				var VdH = clamp(view.dot(half), 0.0, 1.0);
+				var diffuse = albedo / 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)
+				var D = normalDistributionGGX(NdH, roughness);// Normal distribution fonction
+				var F = fresnelSchlick(VdH, F0);// Fresnel term
+				var G = geometrySchlickGGX(NdV, NdL, roughness);// Geometric attenuation
+				var specular = (D * F * G).max(0.);
+
+				result = (diffuse * (1 - metalness) * (1 - F) + specular) * lightColor * NdL;
+			}
+			return result;
+		}
+
+		function __init__fragment() {
+			pbrSpecularColor = vec3(0.04);
+			albedo = pixelColor.rgb * pixelColor.rgb; // gamma correct
+		}
+
+		function init() {
+			view = (cameraPosition - transformedPosition).normalize();
+			NdV = transformedNormal.dot(view).max(0.);
+			metalness = output.metalness;
+			roughness = output.roughness;
+			occlusion = output.occlusion;
+			emissive = output.emissive;
+		}
+
+		function evaluateDirLight( index : Int ) : Vec3 {
+			var i = index * 3;
+			var lightColor = lightInfos[i].rgb;
+			var lightDir = lightInfos[i+1].xyz;
+			var hasShadowMap = lightInfos[i].a > 0;
+
+			// Shadow
+			var shadow = 1.0;
+			if( hasShadowMap ) {
+				var shadowBias = lightInfos[i+1].a;
+				var shadowProj = mat3x4(lightInfos[i+2], lightInfos[i+3], lightInfos[i+4]);
+				var shadowPos = transformedPosition * shadowProj;
+				var shadowUv = screenToUv(shadowPos.xy);
+				var depth = dirShadowMaps[index].get(shadowUv.xy).r;
+				shadow = (shadowPos.z - shadowBias > depth) ? 0.0 : 1.0;
+			}
+
+			return directLighting(lightColor, lightDir) * shadow;
+		}
+
+		function evaluatePointLight( index : Int ) : Vec3 {
+			var i = index * 3 + dirLightStride;
+			var lightColor = lightInfos[i].rgb;
+			var size = lightInfos[i].a;
+			var lightPos = lightInfos[i+1].rgb;
+			var invRange4 = lightInfos[i+1].a;
+			var range = lightInfos[i+2].r;
+			var hasShadowMap = lightInfos[i+2].g > 0;
+			var delta = lightPos - transformedPosition;
+
+			// Shadow
+			var shadow = 1.0;
+			if( hasShadowMap ) {
+				var shadowBias = lightInfos[i+2].b;
+				var posToLight = transformedPosition.xyz - lightPos;
+				var dir = normalize(posToLight.xyz);
+				var depth = pointShadowMaps[index].getLod(dir, 0).r * range;
+				var zMax = length(posToLight);
+				shadow = (zMax - shadowBias > depth) ? 0.0 : 1.0;
+			}
+
+			return directLighting(pointLightIntensity(delta, size, invRange4) * lightColor, delta.normalize()) * shadow;
+		}
+
+		function evaluateSpotLight( index : Int ) : Vec3 {
+			var i = index * 4 + dirLightStride + pointLightStride;
+			var lightColor = lightInfos[i].rgb;
+			var range = lightInfos[i].a;
+			var lightPos = lightInfos[i+1].xyz;
+			var invRange4 = lightInfos[i+1].a;
+			var lightDir = lightInfos[i+2].xyz;
+			var angle = lightInfos[i+3].r;
+			var fallOff = lightInfos[i+3].g;
+			var hasShadowMap = lightInfos[i+3].b > 0;
+			var delta = lightPos - transformedPosition;
+
+			// Shadow
+			var shadow = 1.0;
+			if( hasShadowMap ) {
+				var shadowBias = lightInfos[i+3].a;
+				var shadowProj = mat4(lightInfos[i+4], lightInfos[i+5], lightInfos[i+6], lightInfos[i+7]);
+				var shadowPos = vec4(transformedPosition, 1.0) * shadowProj;
+				shadowPos.xyz /= shadowPos.w;
+				var shadowUv = screenToUv(shadowPos.xy);
+				var depth = spotShadowMaps[index].get(shadowUv.xy).r;
+				shadow = (shadowPos.z - shadowBias > depth) ? 0.0 : 1.0;
+			}
+
+			var fallOffInfo = spotLightIntensity(delta, lightDir, range, invRange4, fallOff, angle);
+			var fallOff = fallOffInfo.x;
+			var fallOffInfoAngle = fallOffInfo.y;
+
+			return directLighting(fallOff * lightColor * fallOffInfoAngle, delta.normalize()) * shadow;
+		}
+
+		function evaluateLighting() : Vec3 {
+
+			var lightAccumulation = vec3(0);
+
+			F0 = mix(pbrSpecularColor, albedo, metalness);
+
+			// Dir Light
+			for( l in 0 ... dirLightCount )
+				lightAccumulation += evaluateDirLight(l);
+
+			// Point Light
+			for( l in 0 ... pointLightCount )
+				lightAccumulation += evaluatePointLight(l);
+
+			// Spot Light
+			for( l in 0 ... spotLightCount )
+				lightAccumulation += evaluateSpotLight(l);
+
+			// Indirect only support the main env from the scene at the moment
+			if( USE_INDIRECT > 0.0)
+				lightAccumulation += indirectLighting();
+
+			// Emissive Pass
+			lightAccumulation += emissive * emissivePower;
+
+			return lightAccumulation;
+		}
+
+		function fragment() {
+			init();
+			output.color = vec4(evaluateLighting(), pixelColor.a);
+		}
+
+	};
+}

+ 53 - 39
h3d/shader/pbr/Light.hx

@@ -1,6 +1,47 @@
 package h3d.shader.pbr;
 package h3d.shader.pbr;
 
 
-class Light extends hxsl.Shader {
+class LightEvaluation extends hxsl.Shader {
+
+	static var SRC = {
+
+		/*
+			UE4 [Karis12] "Real Shading in Unreal Engine 4"
+			Modified with pointSize
+		*/
+		function pointLightIntensity( delta : Vec3, size : Float, invRange4 : Float ) : Float {
+			var dist = delta.dot(delta);
+			var falloff = saturate(1 - dist*dist * invRange4);
+			if( size > 0 ) {
+				dist = (dist.sqrt() - size).max(0.);
+				dist *= dist;
+			}
+			falloff *= falloff;
+			falloff *= 1 / (dist + 1);
+			return falloff;
+		}
+
+		function spotLightIntensity( delta : Vec3, lightDir : Vec3, range : Float, invRange4 : Float, angleFallOff : Float, angle : Float ) : Vec2 {
+			var dist = delta.dot(delta);
+			var falloff = saturate(1 - dist*dist * invRange4);
+			if( range > 0 ) {
+				dist = (dist.sqrt() - range).max(0.);
+				dist *= dist;
+			}
+			falloff *= falloff;
+			falloff *= 1 / (dist + 1);
+
+			var theta = dot(delta.normalize(), -lightDir);
+			var epsilon = angleFallOff - angle;
+			var angleFalloff = clamp((theta - angle) / epsilon, 0.0, 1.0);
+
+			return vec2(falloff, angleFalloff);
+		}
+
+
+	};
+}
+
+class Light extends LightEvaluation {
 
 
 	static var SRC = {
 	static var SRC = {
 
 
@@ -31,26 +72,15 @@ class SpotLight extends Light {
 		@param var cookieTex : Sampler2D;
 		@param var cookieTex : Sampler2D;
 
 
 		function fragment() {
 		function fragment() {
-			pbrOcclusionFactor = occlusionFactor;
-			pbrLightDirection = normalize(lightPos - transformedPosition);
-
 			var delta = lightPos - transformedPosition;
 			var delta = lightPos - transformedPosition;
-			/*
-				UE4 [Karis12] "Real Shading in Unreal Engine 4"
-				Modified with pointSize
-			*/
-			var dist = delta.dot(delta);
-			var falloff = saturate(1 - dist*dist * invLightRange4);
-			if( range > 0 ) {
-				dist = (dist.sqrt() - range).max(0.);
-				dist *= dist;
-			}
-			falloff *= falloff;
-			falloff *= 1 / (dist + 1);
-
-			pbrLightColor = lightColor * falloff;
+			pbrLightDirection = delta.normalize();
+			var fallOffInfo =  spotLightIntensity(delta, spotDir, range, invLightRange4, fallOff, angle);
+			var fallOff = fallOffInfo.x;
+			var fallOffInfoAngle = fallOffInfo.y;
+			pbrLightColor = fallOff * lightColor;
+			pbrOcclusionFactor = occlusionFactor;
 
 
-			if(useCookie){
+			if( useCookie ) {
 				var posLightSpace = vec4(transformedPosition, 1.0) * lightProj;
 				var posLightSpace = vec4(transformedPosition, 1.0) * lightProj;
 				var posUV = screenToUv(posLightSpace.xy/posLightSpace.w);
 				var posUV = screenToUv(posLightSpace.xy/posLightSpace.w);
 				if(posUV.x > 1 || posUV.x < 0 || posUV.y > 1 || posUV.y < 0)
 				if(posUV.x > 1 || posUV.x < 0 || posUV.y > 1 || posUV.y < 0)
@@ -58,12 +88,8 @@ class SpotLight extends Light {
 				var cookie = cookieTex.get(posUV).rgba;
 				var cookie = cookieTex.get(posUV).rgba;
 				pbrLightColor *= cookie.rgb * cookie.a;
 				pbrLightColor *= cookie.rgb * cookie.a;
 			}
 			}
-			else{
-				var theta = dot(pbrLightDirection, -spotDir);
-				var epsilon = fallOff - angle;
-				var intensity = clamp((theta - angle) / epsilon, 0.0, 1.0);
-				pbrLightColor *= intensity;
-			}
+			else
+				pbrLightColor *= fallOffInfoAngle;
 		}
 		}
 	}
 	}
 }
 }
@@ -77,22 +103,10 @@ class PointLight extends Light {
 		@param var pointSize : Float;
 		@param var pointSize : Float;
 
 
 		function fragment() {
 		function fragment() {
-			pbrOcclusionFactor = occlusionFactor;
 			var delta = lightPos - transformedPosition;
 			var delta = lightPos - transformedPosition;
 			pbrLightDirection = delta.normalize();
 			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;
+			pbrLightColor = pointLightIntensity(delta, pointSize, invLightRange4) * lightColor;
+			pbrOcclusionFactor = occlusionFactor;
 		}
 		}
 	};
 	};
 }
 }

+ 9 - 28
h3d/shader/pbr/Lighting.hx

@@ -4,6 +4,8 @@ class Indirect extends PropsDefinition {
 
 
 	static var SRC = {
 	static var SRC = {
 
 
+		@:import h3d.shader.pbr.BDRF;
+
 		// Flags
 		// Flags
 		@const var drawIndirectDiffuse : Bool;
 		@const var drawIndirectDiffuse : Bool;
 		@const var drawIndirectSpecular : Bool;
 		@const var drawIndirectSpecular : Bool;
@@ -88,6 +90,8 @@ class Direct extends PropsDefinition {
 
 
 	static var SRC = {
 	static var SRC = {
 
 
+		@:import h3d.shader.pbr.BDRF;
+
 		var pbrLightDirection : Vec3;
 		var pbrLightDirection : Vec3;
 		var pbrLightColor : Vec3;
 		var pbrLightColor : Vec3;
 		var pbrOcclusionFactor : Float;
 		var pbrOcclusionFactor : Float;
@@ -108,34 +112,11 @@ class Direct extends PropsDefinition {
 				var diffuse = albedo / PI;
 				var diffuse = albedo / PI;
 
 
 				// General Cook-Torrance formula for microfacet BRDF
 				// 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 * roughness;
-
-				// D = normal distribution fonction
-				// GGX (Trowbridge-Reitz) "Disney"
-				var alpha2 = alpha * alpha;
-				var denom = NdH * NdH * (alpha2 - 1.) + 1;
-				var D = alpha2 / (PI * denom * denom);
-
-				// F = fresnel term
-				// Schlick approx
-				// pow 5 optimized with Spherical Gaussian
-				// var F = F0 + (1 - F0) * pow(1 - v.dot(h), 5.);
-				var F = F0 + (1. - F0) * exp2( ( -5.55473 * VdH - 6.98316) * VdH );
-
-				// G = geometric attenuation
-				// Schlick (modified for UE4 with k=alpha/2)
-				// k = (rough + 1)² / 8
-				var k = (roughness + 1);
-				k *= k;
-				k *= 0.125;
-
-				//var G = (1 / (NdV * (1 - k) + k)) * (1 / (NdL * (1 - k) + k)) * NdL * NdV;
-				//var Att = 1 / (4 * NdL * NdV);
-				var G_Att = (1 / (NdV * (1 - k) + k)) * (1 / (NdL * (1 - k) + k)) * 0.25;
-				var specular = (D * F * G_Att).max(0.);
+				// 	f(l,v) = D(h).F(v,h).G(l,v,h) / 4(n.l)(n.v)
+				var D = normalDistributionGGX(NdH, roughness);// Normal distribution fonction
+				var F = fresnelSchlick(VdH, F0);// Fresnel term
+				var G = geometrySchlickGGX(NdV, NdL, roughness);// Geometric attenuation
+				var specular = (D * F * G).max(0.);
 
 
 				var direct = (diffuse * (1 - metalness) * (1 - F) + specular) * pbrLightColor * NdL;
 				var direct = (diffuse * (1 - metalness) * (1 - F) + specular) * pbrLightColor * NdL;
 				pixelColor.rgb += direct * shadow * mix(1, occlusion, pbrOcclusionFactor);
 				pixelColor.rgb += direct * shadow * mix(1, occlusion, pbrOcclusionFactor);