Browse Source

pbr sample hl working

ncannasse 8 years ago
parent
commit
83900b123a

+ 282 - 113
samples/Pbr.hx

@@ -11,121 +11,144 @@ class PbrShader extends hxsl.Shader {
 		@param var lightColor : Vec3;
 
 		@const var specularMode : Bool;
-		@param var metalness : Float;
+		var metalness : Float;
+		var roughness : Float;
 
-		@param var roughness : Float;
+		@param var defMetalness : Float;
+		@param var defRoughness : Float;
 
 		@param var envMap : SamplerCube;
 		@param var irrDiffuse : SamplerCube;
 		@param var irrSpecular : SamplerCube;
+		@param var irrSpecularLevels : Float;
+		@param var irrLut : Sampler2D;
+
+		@const var directLighting : Bool;
+		@const var indirectLighting : Bool;
+		@const var specularLighting : Bool;
 
 		var l : Vec3;
 		var n : Vec3;
 		var v : Vec3;
 		var h : Vec3;
 
-		function calcG(v:Vec3) : Float {
-
-			// UE4 uses   k = (R + 1)² / 8  "disney hotness" for analytic light sources
-
-			var k = (roughness * roughness) / 2;
-			return n.dot(v) / (n.dot(v) * (1 - k) + k);
+		function __init__fragment() {
+			metalness = defMetalness;
+			roughness = defRoughness;
 		}
 
-		function fragment() {
+		function calcG(v:Vec3) : Float {
+			var k = (roughness + 1).pow(2) / 8;// (roughness * roughness) / 2;
+			var NdV = n.dot(v);
+			return NdV / (NdV * (1 - k) + k);
+		}
 
-			var color : Vec3;
+		function calcLight( lightPos : Vec3 ) : Vec3 {
 
+			var color = vec3(0.);
 			var diffuseColor = pixelColor.rgb;
-			var lightDir = (lightPos - transformedPosition).normalize();
+			var specularColor = vec3(metalness);
 			var normal = transformedNormal.normalize();
 			var view = (camera.position - transformedPosition).normalize();
-
-
-			var lambert = lightDir.dot(normal).max(0.);
+			var lightDir = (lightPos - transformedPosition).normalize();
 
 			if( specularMode ) {
 
+				var diffuse = lightDir.dot(normal).max(0.);
+
 				var specularPower = metalness * 40.;
 				var r = reflect(-lightDir, normal).normalize();
-				var specValue = r.dot(view).max(0.).pow(specularPower);
-				color = diffuseColor * (lambert + specValue) * lightColor;
+				var specular = r.dot(view).max(0.).pow(specularPower);
+				color = diffuseColor * (diffuse + specular) * lightColor;
 
 			} else {
 
+				//if( metalness > 0.5 )
+				//	specularColor = diffuseColor;
+
 				l = lightDir;
 				n = normal;
-				v = (camera.position - transformedPosition).normalize();
+				v = view;
 				h = (l + v).normalize();
 
 
 
 				// diffuse BRDF
 				var direct : Vec3;
-				var F = 0.;
 
-				#if !flash
-				if( n.dot(l) < 0 ) {
-					direct = vec3(0.);
-				} else
-				#end
-				{
 
-					// ------------- DIRECT LIGHT -------------------------
+				// ------------- DIRECT LIGHT -------------------------
 
-					var F0 = metalness;
-					var diffuse = diffuseColor * n.dot(l) / PI;
+				var F0 = metalness;
+				var diffuse = diffuseColor * n.dot(l).saturate() / 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)
+				// 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);
+				// 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 * ( n.dot(h).pow(2) * (alpha.pow(2) - 1.) + 1).pow(2));
+				// D = normal distribution fonction
+				// GGX (Trowbridge-Reitz) "Disney"
+				var D = alpha.pow(2) / (PI * ( n.dot(h).pow(2) * (alpha.pow(2) - 1.) + 1).pow(2));
 
-					// F = fresnel term
+				// 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 * v.dot(h) - 6.98316) * v.dot(h) );
 
-					// Schlick approx
-					// var F = F0 + (1 - F0) * pow(1 - v.dot(h), 5.);
-					// pow 5 optimized with Spherical Gaussian
-					F = F0 + (1 - F0) * (2.).pow( ( -5.55473 * v.dot(h) - 6.98316) * v.dot(h) );
+				// G = geometric attenuation
+				// Schlick (modified for UE4 with k=alpha/2)
+				var G = calcG(l) * calcG(v);
 
-					// G = geometric attenuation
-					// Schlick (modified for UE4 with k=alpha/2)
-					var G = calcG(l) * calcG(v);
+				var specular = (D * F * G / (4 * n.dot(l) * n.dot(v))).max(0.);
 
-					var specular = D * F * G / (4 * n.dot(l) * n.dot(v));
+				// custom falloff to prevent infinite when we have n.h ~= 0
+				if( n.dot(l) < 0 ) specular *= (1 + n.dot(l) * 6).saturate();
 
-					direct = (diffuse + specular) * lightColor;
+				if( !specularLighting )
+					specular = 0.;
 
-
-				}
+				direct = (diffuse + specular) * lightColor;
 
 
 				// ------------- INDIRECT LIGHT -------------------------
 
 				var diffuse = irrDiffuse.get(normal).rgb.pow(vec3(2.2)) * diffuseColor;
-				var specular = vec3(0, 0, 0); // TODO - generate PMREM from CubeMap
+
+				var envSpec = textureCubeLod(irrSpecular, reflect(-v,n), roughness * irrSpecularLevels).rgb.pow(vec3(2.2));
+
+				var envBRDF = irrLut.get(vec2(roughness, n.dot(v)));
+				var specular = envSpec * (specularColor * envBRDF.x + envBRDF.y);
+
+				if( !specularLighting )
+					specular = vec3(0.);
+
 				var indirect = diffuse + specular;
 
-				color = direct + indirect;
+				if( directLighting )
+					color += direct;
+				if( indirectLighting )
+					color += indirect;
+			}
+
+			return color;
+		}
+
 
+		function fragment() {
+			var color = calcLight(lightPos);
+
+			if( !specularMode ) {
 				// reinhard tonemapping
 				color *= exp(exposure);
 				color = color / (color + vec3(1.));
 
 				// gamma correct
 				color = color.pow(vec3(1 / 2.2));
-
 			}
 
-			#if flash
-			color *= float(n.dot(l) > 0.);
-			#end
-
 			pixelColor.rgb = color;
 		}
 
@@ -135,23 +158,41 @@ class PbrShader extends hxsl.Shader {
 		super();
 		exposure = 0;
 		specularMode = false;
-		lightPos.set(5, 15, 20);
-		lightColor.set(1, 1, 1);
+		directLighting = true;
+		indirectLighting = true;
+		specularLighting = true;
+		lightPos.set(10, 30, 40);
+		lightColor.set(0.1, 0.1, 0.1);
 	}
 
 }
 
-class Irradiance extends h3d.shader.ScreenShader {
+class AdjustShader extends hxsl.Shader {
 
 	static var SRC = {
+		@param var roughnessValue : Float;
+		@param var metalnessValue : Float;
+		var roughness : Float;
+		var metalness : Float;
+		function fragment() {
+			roughness = roughnessValue;
+			metalness = metalnessValue;
+		}
+	}
 
-		@const var face : Int;
-		@const var samplesBits : Int;
-		@param var envMap : SamplerCube;
+	public function new(rough, metal) {
+		super();
+		roughnessValue = rough;
+		metalnessValue = metal;
+	}
 
-		@const var isSpecular : Bool;
-		@param var roughness : Float;
+}
+
+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>;
@@ -175,14 +216,56 @@ class Irradiance extends h3d.shader.ScreenShader {
 			#end
 		}
 
-		function cosineWeightedSampling( p : Vec2 ) : Vec3 {
+		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 Irradiance 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;
-			return vec3( sq * cos(alpha),  sq * sin(alpha), sqrt(p.x));
+			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);
+			}
 			var n : Vec3;
 			switch( face ) {
 			case 0: n = vec3(1, d.y, -d.x);
@@ -198,29 +281,19 @@ class Irradiance extends h3d.shader.ScreenShader {
 		function fragment() {
 			var color = vec3(0.);
 			var n = getNormal();
-			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));
 			var totalWeight = 1e-10;
 			var numSamples = 1 << samplesBits;
 			for( i in 0...1 << samplesBits ) {
 				var p = hammersley(i, numSamples);
 				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 h = importanceSampleGGX(roughness, p, n);
 					var v = n; // approx
-					l = -reflect(v, h);
+					l = reflect(-v, h).normalize();
 				} else {
-					var ltan = cosineWeightedSampling(p);
-					l = tanX * ltan.x + tanY * ltan.y + n * ltan.z;
+					l = cosineWeightedSampling(p, n);
 				}
-				var amount = n.dot(l);
+				var amount = n.dot(l).saturate();
 				if( amount > 0 ) {
 					color += envMap.get(l).rgb.pow(vec3(2.2)) * amount;
 					totalWeight += amount;
@@ -233,11 +306,56 @@ class Irradiance extends h3d.shader.ScreenShader {
 
 }
 
+class IrradianceLut 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.;
+			for( i in 0...numSamples ) {
+				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 Pbr extends hxd.App {
 
 	var fui : h2d.Flow;
-	var cameraRot = Math.PI / 4;
+	var cameraRot = -Math.PI / 4;
 	var cameraDist = 5.5;
 	var font : h2d.Font;
 	var hue : Float;
@@ -245,9 +363,12 @@ class Pbr extends hxd.App {
 	var brightness : Float;
 	var color : h2d.Bitmap;
 	var sphere : h3d.scene.Mesh;
+	var grid : h3d.scene.Object;
 
 	var sampleBits : Int;
 
+	var shader : PbrShader;
+
 	var envMap : h3d.mat.Texture;
 	var irradDiffuse : h3d.mat.Texture;
 	var irradSpecular : h3d.mat.Texture;
@@ -258,15 +379,16 @@ class Pbr extends hxd.App {
 		engine.debug = true;
 		#end
 
-		//displaySampling(256, new h3d.Vector(1, 0., 0.), 0.5);
+		//displaySampling(256, new h3d.Vector(0.5, 0.5, 0.5), 0.3);
 
 		font = hxd.res.DefaultFont.get();
 
+
+		shader = new PbrShader();
+
 		var sp = new h3d.prim.Sphere(1, 128, 128);
 		sp.addNormals();
 		sp.addUVs();
-		sphere = new h3d.scene.Mesh(sp, s3d);
-		var shader = sphere.material.mainPass.addShader(new PbrShader());
 
 		var bg = new h3d.scene.Mesh(sp, s3d);
 		bg.scale(10);
@@ -277,18 +399,6 @@ class Pbr extends hxd.App {
 		fui.verticalSpacing = 5;
 		fui.isVertical = true;
 
-		var g = new h3d.scene.Graphics(s3d);
-		g.lineStyle(1, 0xFF0000);
-		g.moveTo(0, 0, 0);
-		g.lineTo(2, 0, 0);
-		g.lineStyle(1, 0x00FF00);
-		g.moveTo(0, 0, 0);
-		g.lineTo(0, 2, 0);
-		g.lineStyle(1, 0x0000FF);
-		g.moveTo(0, 0, 0);
-		g.lineTo(0, 0, 2);
-		g.lineStyle();
-
 		envMap = new h3d.mat.Texture(512, 512, [Cubic]);
 		inline function set(face:Int, res:hxd.res.Image) {
 			#if flash
@@ -317,16 +427,22 @@ class Pbr extends hxd.App {
 		set(5, hxd.Res.bottom);
 
 
-		var size = 64;
+		var size, ssize;
 		#if (js || flash)
 		sampleBits = 5;
 		size = 16;
+		ssize = 16;
 		#else
+		size = 64;
+		ssize = 256;
 		sampleBits = 10;
 		#end
 
+		computeIrradLut();
+
 		irradDiffuse = new h3d.mat.Texture(size, size, [Cubic]);
-		irradSpecular = new h3d.mat.Texture(size, size, [Cubic, MipMapped]);
+		irradSpecular = new h3d.mat.Texture(ssize, ssize, [Cubic, MipMapped]);
+		irradSpecular.mipMap = Linear;
 		computeIrradiance();
 
 		shader.envMap = envMap;
@@ -335,29 +451,70 @@ class Pbr extends hxd.App {
 
 		var cubeShader = bg.material.mainPass.addShader(new h3d.shader.CubeMap(envMap));
 
-		shader.metalness = 0.2;
-		shader.roughness = 0.3;
+		shader.defMetalness = 0.2;
+		shader.defRoughness = 0.5;
+		shader.exposure = 0.;
 		hue = 0.2;
 		saturation = 0.2;
-		brightness = 0.2;
+		brightness = -1;
+
+
+		function addSphere(x,y) {
+			var sphere = new h3d.scene.Mesh(sp, s3d);
+			sphere.x = x;
+			sphere.y = y;
+			sphere.material.mainPass.addShader(shader);
+			return sphere;
+		}
+
+		sphere = addSphere(0, 0);
+
+		grid = new h3d.scene.Object(s3d);
+		var max = 5;
+		for( x in 0...max )
+			for( y in 0...max ) {
+				var s = addSphere(x - max * 0.5, y - max * 0.5);
+				grid.addChild(s);
+				s.scale(0.4);
+				s.material.mainPass.addShader(new AdjustShader( Math.pow(x / (max - 1), 1), Math.pow(y / (max - 1), 2)));
+			}
+		grid.visible = false;
 
 		addCheck("PBR", function() return !shader.specularMode, function(b) shader.specularMode = !b);
 		addSlider("Exposure", -3, 3, function() return shader.exposure, function(v) shader.exposure = v);
-		addSlider("Metalness", 0, 1, function() return shader.metalness, function(v) shader.metalness = v);
-		addSlider("Roughness", 0, 1, function() return shader.roughness, function(v) shader.roughness = v);
+		addSlider("Metalness", 0.02, 0.99, function() return shader.defMetalness, function(v) shader.defMetalness = v);
+		addSlider("Roughness", 0, 1, function() return shader.defRoughness, function(v) shader.defRoughness = v);
+
+		addCheck("Direct", function() return shader.directLighting, function(b) shader.directLighting = b);
+		addCheck("Indirect", function() return shader.indirectLighting, function(b) shader.indirectLighting = b);
+		addCheck("Specular", function() return shader.specularLighting, function(b) shader.specularLighting = b);
+		addCheck("Grid", function() return grid.visible, function(b) {
+			grid.visible = !grid.visible;
+			sphere.visible = !sphere.visible;
+		});
 
 		color = new h2d.Bitmap(h2d.Tile.fromColor(0xFFFFFF, 30, 30), fui);
 		fui.getProperties(color).paddingLeft = 50;
 
-		addSlider("Hue", 0, Math.PI, function() return hue, function(v) hue = v);
+		addSlider("Hue", 0, Math.PI*2, function() return hue, function(v) hue = 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", -1, 1, function() return brightness, function(v) brightness = v);
 		addChoice("Env", ["Default", "IDiff", "ISpec"], function(i) cubeShader.texture = [envMap, irradDiffuse, irradSpecular][i]);
 
 		s2d.addEventListener(onEvent);
 	}
 
+	function computeIrradLut() {
+		var irradLut = new h3d.mat.Texture(128, 128, #if js RGBA32F #else RGBA16F #end);
+		var screen = new h3d.pass.ScreenFx(new IrradianceLut());
+		engine.driver.setRenderTarget(irradLut);
+		screen.render();
+		engine.driver.setRenderTarget(null);
+		shader.irrLut = irradLut;
+	}
+
 	function computeIrradiance() {
+
 		var screen = new h3d.pass.ScreenFx(new Irradiance());
 		screen.shader.samplesBits = sampleBits;
 		screen.shader.envMap = envMap;
@@ -380,10 +537,15 @@ class Pbr extends hxd.App {
 		while( irradSpecular.width > 1 << (mipLevels - 1) )
 			mipLevels++;
 
+		shader.irrSpecularLevels = mipLevels - 4;
+
 		for( i in 0...6 ) {
 			screen.shader.face = i;
 			for( j in 0...mipLevels ) {
-				screen.shader.roughness = j / mipLevels;
+				var size = irradSpecular.width >> j;
+				screen.shader.cubeSize = size;
+				screen.shader.cubeScaleFactor = size == 1 ? 0 : (size * size) / Math.pow(size - 1, 3);
+				screen.shader.roughness = j / shader.irrSpecularLevels;
 				engine.driver.setRenderTarget(irradSpecular, i, j);
 				screen.render();
 			}
@@ -495,18 +657,22 @@ class Pbr extends hxd.App {
 	}
 
 	override function update(dt:Float) {
+
 		s3d.camera.pos.set(Math.cos(cameraRot) * cameraDist, Math.sin(cameraRot) * cameraDist, cameraDist * 2 / 3);
 
 		var color = new h3d.Vector(1, 0, 0);
 		var m = new h3d.Matrix();
 		m.identity();
-		m.colorHue(hue);
 		m.colorSaturation(saturation);
+		m.colorHue(hue);
 		m.colorBrightness(brightness);
 		color.transform3x4(m);
+		color.setColor(color.toColor()); // saturate
 
 		this.color.color.load(color);
 		this.sphere.material.color.load(color);
+		for( s in grid )
+			s.toMesh().material.color.load(color);
 
 	}
 
@@ -551,21 +717,24 @@ class Pbr extends hxd.App {
 
 				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 cosT = Math.min(Math.sqrt((1 - h.y) / (1 + (a * a - 1) * h.y)), 1.);
 				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 h = new h3d.Vector(
+					tanX.x * ltan.x + tanY.x * ltan.y + n.x * ltan.z,
+					tanX.y * ltan.x + tanY.y * ltan.y + n.y * ltan.z,
+					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;
+				var hDotV = v.dot3(h);
+				// reflect(-v,h)
+
+				l.x = 2 * hDotV * h.x - v.x;
+				l.y = 2 * hDotV * h.y - v.y;
+				l.z = 2 * hDotV * h.z - v.z;
 
 			} else {
 

BIN
samples/pbr_res/back.jpg


BIN
samples/pbr_res/bottom.jpg


BIN
samples/pbr_res/front.jpg


BIN
samples/pbr_res/left.jpg


BIN
samples/pbr_res/right.jpg


BIN
samples/pbr_res/top.jpg