Răsfoiți Sursa

irradiance diffuse calculus

ncannasse 8 ani în urmă
părinte
comite
7e48d5114e
1 a modificat fișierele cu 212 adăugiri și 31 ștergeri
  1. 212 31
      samples/Pbr.hx

+ 212 - 31
samples/Pbr.hx

@@ -15,7 +15,9 @@ class PbrShader extends hxsl.Shader {
 
 
 		@param var roughness : Float;
 		@param var roughness : Float;
 
 
-		@param var cubeMap : SamplerCube;
+		@param var envMap : SamplerCube;
+		@param var irrDiffuse : SamplerCube;
+		@param var irrSpecular : SamplerCube;
 
 
 		var l : Vec3;
 		var l : Vec3;
 		var n : Vec3;
 		var n : Vec3;
@@ -105,13 +107,12 @@ class PbrShader extends hxsl.Shader {
 
 
 				// ------------- INDIRECT LIGHT -------------------------
 				// ------------- INDIRECT LIGHT -------------------------
 
 
-				var diffuse = vec3(0, 0, 0); // TODO - generate Irradiance EnvMap from CubeMap
+				var diffuse = irrDiffuse.get(normal).rgb.pow(vec3(2.2)) * diffuseColor;
 				var specular = vec3(0, 0, 0); // TODO - generate PMREM from CubeMap
 				var specular = vec3(0, 0, 0); // TODO - generate PMREM from CubeMap
 				var indirect = diffuse + specular;
 				var indirect = diffuse + specular;
 
 
 				color = direct + indirect;
 				color = direct + indirect;
 
 
-
 				// reinhard tonemapping
 				// reinhard tonemapping
 				color *= exp(exposure);
 				color *= exp(exposure);
 				color = color / (color + vec3(1.));
 				color = color / (color + vec3(1.));
@@ -148,6 +149,14 @@ class Irradiance extends h3d.shader.ScreenShader {
 		@const var samplesBits : Int;
 		@const var samplesBits : Int;
 		@param var envMap : SamplerCube;
 		@param var envMap : SamplerCube;
 
 
+		@const var isSpecular : Bool;
+		@param var roughness : Float;
+
+		#if (js || flash)
+		@const var SamplesCount : Int;
+		@param var hammerTbl : Array<Vec4,SamplesCount>;
+		#end
+
 		function _reversebits( i : Int ) : Int {
 		function _reversebits( i : Int ) : Int {
 			var r = (i << 16) | (i >>> 16);
 			var r = (i << 16) | (i >>> 16);
 			r = ((r & 0x00ff00ff) << 8) | ((r & 0xff00ff00) >>> 8);
 			r = ((r & 0x00ff00ff) << 8) | ((r & 0xff00ff00) >>> 8);
@@ -158,8 +167,12 @@ class Irradiance extends h3d.shader.ScreenShader {
 		}
 		}
 
 
 		function hammersley( i : Int, max : Int ) : Vec2 {
 		function hammersley( i : Int, max : Int ) : Vec2 {
+			#if (js || flash)
+			return hammerTbl[i].xy;
+			#else
 			var ri = _reversebits(i) * 2.3283064365386963e-10;
 			var ri = _reversebits(i) * 2.3283064365386963e-10;
 			return vec2(float(i) / float(max), ri);
 			return vec2(float(i) / float(max), ri);
+			#end
 		}
 		}
 
 
 		function cosineWeightedSampling( p : Vec2 ) : Vec3 {
 		function cosineWeightedSampling( p : Vec2 ) : Vec3 {
@@ -172,8 +185,12 @@ class Irradiance extends h3d.shader.ScreenShader {
 			var d = input.uv * 2. - 1.;
 			var d = input.uv * 2. - 1.;
 			var n : Vec3;
 			var n : Vec3;
 			switch( face ) {
 			switch( face ) {
-			case 0: n = vec3(1, -d.y, -d.x);
-			default: n = vec3( -d.x, -d.y, -1);
+			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();
 			return n.normalize();
 		}
 		}
@@ -183,18 +200,33 @@ class Irradiance extends h3d.shader.ScreenShader {
 			var n = getNormal();
 			var n = getNormal();
 			var up = abs(n.z) < 0.999 ? vec3(0, 0, 1) : vec3(1, 0, 0);
 			var up = abs(n.z) < 0.999 ? vec3(0, 0, 1) : vec3(1, 0, 0);
 			var tanX = normalize(cross(up, n));
 			var tanX = normalize(cross(up, n));
-			var tanY = cross(n, tanX);
+			var tanY = normalize(cross(n, tanX));
 			var totalWeight = 1e-10;
 			var totalWeight = 1e-10;
 			var numSamples = 1 << samplesBits;
 			var numSamples = 1 << samplesBits;
-			for( i in 0...numSamples ) {
+			for( i in 0...1 << samplesBits ) {
 				var p = hammersley(i, numSamples);
 				var p = hammersley(i, numSamples);
-				var ltan = cosineWeightedSampling(p);
-				var l = normalize(tanX * ltan.x + tanY * ltan.y + n * ltan.z);
+				var l : Vec3;
+				if( isSpecular ) {
+					// importance sampling GGX BRDF
+					var a = roughness * roughness;
+					var phi = 2 * PI * p.x;
+					var cosT = sqrt((1 - p.y) / (1 + (a * a - 1) * p.y));
+					var sinT = sqrt(1 - cosT * cosT);
+					var ltan = vec3(sinT * cos(phi), sinT * sin(phi), cosT);
+					var h = tanX * ltan.x + tanY * ltan.y + n * ltan.z;
+					var v = n; // approx
+					l = -reflect(v, h);
+				} else {
+					var ltan = cosineWeightedSampling(p);
+					l = tanX * ltan.x + tanY * ltan.y + n * ltan.z;
+				}
 				var amount = n.dot(l);
 				var amount = n.dot(l);
-				color += envMap.get(l).rgb * amount;
-				totalWeight += amount;
+				if( amount > 0 ) {
+					color += envMap.get(l).rgb.pow(vec3(2.2)) * amount;
+					totalWeight += amount;
+				}
 			}
 			}
-			output.color = vec4(color / totalWeight, 1.);
+			output.color = vec4((color / totalWeight).pow(vec3(1/2.2)), 1.);
 		}
 		}
 
 
 	}
 	}
@@ -214,8 +246,20 @@ class Pbr extends hxd.App {
 	var color : h2d.Bitmap;
 	var color : h2d.Bitmap;
 	var sphere : h3d.scene.Mesh;
 	var sphere : h3d.scene.Mesh;
 
 
+	var sampleBits : Int;
+
+	var envMap : h3d.mat.Texture;
+	var irradDiffuse : h3d.mat.Texture;
+	var irradSpecular : h3d.mat.Texture;
+
 	override function init() {
 	override function init() {
 
 
+		#if flash
+		engine.debug = true;
+		#end
+
+		//displaySampling(256, new h3d.Vector(1, 0., 0.), 0.5);
+
 		font = hxd.res.DefaultFont.get();
 		font = hxd.res.DefaultFont.get();
 
 
 		var sp = new h3d.prim.Sphere(1, 128, 128);
 		var sp = new h3d.prim.Sphere(1, 128, 128);
@@ -245,17 +289,14 @@ class Pbr extends hxd.App {
 		g.lineTo(0, 0, 2);
 		g.lineTo(0, 0, 2);
 		g.lineStyle();
 		g.lineStyle();
 
 
-		engine.debug = true;
-
-
-		var cube = new h3d.mat.Texture(512, 512, [Cubic]);
+		envMap = new h3d.mat.Texture(512, 512, [Cubic]);
 		inline function set(face:Int, res:hxd.res.Image) {
 		inline function set(face:Int, res:hxd.res.Image) {
 			#if flash
 			#if flash
 			// all mipmap levels required
 			// all mipmap levels required
 			var bmp = res.toBitmap();
 			var bmp = res.toBitmap();
 			var level = 0;
 			var level = 0;
 			while( true ) {
 			while( true ) {
-				cube.uploadBitmap(bmp, level++, face);
+				envMap.uploadBitmap(bmp, level++, face);
 				if( bmp.width == 1 ) break;
 				if( bmp.width == 1 ) break;
 				var sub = new hxd.BitmapData(bmp.width >> 1, bmp.height >> 1);
 				var sub = new hxd.BitmapData(bmp.width >> 1, bmp.height >> 1);
 				sub.drawScaled(0, 0, sub.width, sub.height, bmp, 0, 0, bmp.width, bmp.height);
 				sub.drawScaled(0, 0, sub.width, sub.height, bmp, 0, 0, bmp.width, bmp.height);
@@ -265,7 +306,7 @@ class Pbr extends hxd.App {
 			bmp.dispose();
 			bmp.dispose();
 			#else
 			#else
 			var pix = res.getPixels();
 			var pix = res.getPixels();
-			cube.uploadPixels(pix, 0, face);
+			envMap.uploadPixels(pix, 0, face);
 			#end
 			#end
 		}
 		}
 		set(0, hxd.Res.front);
 		set(0, hxd.Res.front);
@@ -275,21 +316,24 @@ class Pbr extends hxd.App {
 		set(4, hxd.Res.top);
 		set(4, hxd.Res.top);
 		set(5, hxd.Res.bottom);
 		set(5, hxd.Res.bottom);
 
 
-		shader.cubeMap = cube;
 
 
+		var size = 64;
+		#if (js || flash)
+		sampleBits = 5;
+		size = 16;
+		#else
+		sampleBits = 10;
+		#end
 
 
-		var irradDiffuse = new h3d.mat.Texture(128, 128, [Cubic]);
-		var screen = new h3d.pass.ScreenFx(new Irradiance());
-		screen.shader.samplesBits = 8;
-		screen.shader.envMap = cube;
-		for( i in 0...6 ) {
-			screen.shader.face = i;
-			engine.driver.setRenderTarget(irradDiffuse, i);
-			screen.render();
-		}
-		engine.driver.setRenderTarget(null);
+		irradDiffuse = new h3d.mat.Texture(size, size, [Cubic]);
+		irradSpecular = new h3d.mat.Texture(size, size, [Cubic, MipMapped]);
+		computeIrradiance();
 
 
-		bg.material.mainPass.addShader(new h3d.shader.CubeMap(cube));
+		shader.envMap = envMap;
+		shader.irrDiffuse = irradDiffuse;
+		shader.irrSpecular = irradSpecular;
+
+		var cubeShader = bg.material.mainPass.addShader(new h3d.shader.CubeMap(envMap));
 
 
 		shader.metalness = 0.2;
 		shader.metalness = 0.2;
 		shader.roughness = 0.3;
 		shader.roughness = 0.3;
@@ -308,11 +352,45 @@ class Pbr extends hxd.App {
 		addSlider("Hue", 0, Math.PI, function() return hue, function(v) hue = v);
 		addSlider("Hue", 0, Math.PI, function() return hue, function(v) hue = v);
 		addSlider("Saturation", 0, 1, function() return saturation, function(v) saturation = v);
 		addSlider("Saturation", 0, 1, function() return saturation, function(v) saturation = v);
 		addSlider("Brightness", 0, 1, function() return brightness, function(v) brightness = v);
 		addSlider("Brightness", 0, 1, function() return brightness, function(v) brightness = v);
-
+		addChoice("Env", ["Default", "IDiff", "ISpec"], function(i) cubeShader.texture = [envMap, irradDiffuse, irradSpecular][i]);
 
 
 		s2d.addEventListener(onEvent);
 		s2d.addEventListener(onEvent);
 	}
 	}
 
 
+	function computeIrradiance() {
+		var screen = new h3d.pass.ScreenFx(new Irradiance());
+		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
+
+		for( i in 0...6 ) {
+			screen.shader.face = i;
+			engine.driver.setRenderTarget(irradDiffuse, i);
+			screen.render();
+		}
+
+		screen.shader.isSpecular = true;
+
+		var mipLevels = 1;
+		while( irradSpecular.width > 1 << (mipLevels - 1) )
+			mipLevels++;
+
+		for( i in 0...6 ) {
+			screen.shader.face = i;
+			for( j in 0...mipLevels ) {
+				screen.shader.roughness = j / mipLevels;
+				engine.driver.setRenderTarget(irradSpecular, i, j);
+				screen.render();
+			}
+		}
+		engine.driver.setRenderTarget(null);
+	}
+
 	function onEvent(e:hxd.Event) {
 	function onEvent(e:hxd.Event) {
 		switch( e.kind ) {
 		switch( e.kind ) {
 		case EPush:
 		case EPush:
@@ -361,6 +439,31 @@ class Pbr extends hxd.App {
 		i.onOut(null);
 		i.onOut(null);
 	}
 	}
 
 
+	function addChoice( text, choices, callb, value = 0 ) {
+		var i = new h2d.Interactive(80, font.lineHeight, fui);
+		i.backgroundColor = 0xFF808080;
+		fui.getProperties(i).paddingLeft = 20;
+
+		var t = new h2d.Text(font, i);
+		t.maxWidth = i.width;
+		t.text = text+":"+choices[value];
+		t.textAlign = Center;
+
+		i.onClick = function(_) {
+			value++;
+			value %= choices.length;
+			callb(value);
+			t.text = text + ":" + choices[value];
+		};
+		i.onOver = function(_) {
+			t.textColor = 0xFFFFFF;
+		};
+		i.onOut = function(_) {
+			t.textColor = 0xEEEEEE;
+		};
+		i.onOut(null);
+	}
+
 	function addSlider( text, min : Float, max : Float, get : Void -> Float, set : Float -> Void ) {
 	function addSlider( text, min : Float, max : Float, get : Void -> Float, set : Float -> Void ) {
 		var f = new h2d.Flow(fui);
 		var f = new h2d.Flow(fui);
 
 
@@ -407,6 +510,84 @@ class Pbr extends hxd.App {
 
 
 	}
 	}
 
 
+
+	// ----- DEBUG
+
+
+	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 ) : h3d.Vector {
+		var ri = _reversebits(i) * 2.3283064365386963e-10;
+		return new h3d.Vector(i / max, ri);
+	}
+
+	function cosineWeightedSampling( p : h3d.Vector ) : h3d.Vector {
+		var sq = Math.sqrt(1 - p.x);
+		var alpha = 2 * Math.PI * p.y;
+		return new h3d.Vector( sq * Math.cos(alpha),  sq * Math.sin(alpha), Math.sqrt(p.x));
+	}
+
+	function displaySampling( max : Int, n : h3d.Vector, ?roughness : Float ) {
+		n.normalize();
+		var up = n.z < 0.999 ? new h3d.Vector(0, 0, 1) : new h3d.Vector(1, 0, 0);
+		var tanX = up.cross(n);
+		tanX.normalize();
+		var tanY = n.cross(tanX);
+		tanY.normalize();
+
+		var sp = new h3d.prim.Sphere(0.01, 4, 4);
+		for( i in 0...max ) {
+			var h = hammersley(i, max);
+
+			var l = new h3d.Vector();
+			if( roughness != null ) {
+
+				var a = roughness * roughness;
+				var phi = 2 * Math.PI * h.x;
+				var cosT = Math.sqrt((1 - h.y) / (1 + (a * a - 1) * h.y));
+				var sinT = Math.sqrt(1 - cosT * cosT);
+				var ltan = new h3d.Vector(sinT * Math.cos(phi), sinT * Math.sin(phi), cosT);
+
+				var hx = tanX.x * ltan.x + tanY.x * ltan.y + n.x * ltan.z;
+				var hy = tanX.y * ltan.x + tanY.y * ltan.y + n.y * ltan.z;
+				var hz = tanX.z * ltan.x + tanY.z * ltan.y + n.z * ltan.z;
+
+				var v = n; // approx
+
+				var hDotV = v.dot3(new h3d.Vector(hx, hy, hz));
+				// -reflect(v,h)
+				l.x = v.x - 2 * hDotV * h.x;
+				l.y = v.y - 2 * hDotV * h.y;
+				l.z = v.z - 2 * hDotV * h.z;
+
+			} else {
+
+				var c = cosineWeightedSampling(h);
+				l.x = tanX.x * c.x + tanY.x * c.y + n.x * c.z;
+				l.y = tanX.y * c.x + tanY.y * c.y + n.y * c.z;
+				l.z = tanX.z * c.x + tanY.z * c.y + n.z * c.z;
+
+			}
+
+			l.normalize();
+
+			var s = new h3d.scene.Mesh(sp, s3d);
+			s.x = l.x;
+			s.y = l.y;
+			s.z = l.z;
+		}
+	}
+
+	// ---------------------
+
+
 	static function main() {
 	static function main() {
 		hxd.Res.initEmbed();
 		hxd.Res.initEmbed();
 		#if hl
 		#if hl