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

New volumetric lighting rfx based on light buffer.

clementlandrin 1 жил өмнө
parent
commit
ff481ced06

+ 242 - 174
hrt/prefab/rfx/VolumetricLighting.hx

@@ -1,99 +1,147 @@
 package hrt.prefab.rfx;
 
-class VolumetricLightingShader extends hrt.shader.PbrShader {
+class VolumetricLightingShader extends h3d.shader.pbr.DefaultForward {
 
 	static var SRC = {
-		@param var bottom : Float;
-		@param var top : Float;
-		@param var fallOff : Float;
 
-		@param var shadowMap : Sampler2D;
-		@param var shadowProj : Mat3x4;
-		@param var shadowBias : Float;
-		@param var lightDir : Vec3;
-		@param var angleThreshold : Float;
+		@global var global : {
+			var time : Float;
+		}
+
+		@global var camera : {
+			var position : Vec3;
+			var inverseViewProj : Mat4;
+		}
 
-		@param var brightOpacity : Float;
-		@param var darkOpacity : Float;
-		@param var darkColor : Vec3;
-		@param var brightColor : Vec3;
-		@param var lightColor : Vec3;
-		@param var gamma : Float;
+		@param var invViewProj : Mat4;
 
-		@param var maxDist : Float;
-		@param var decayPower : Float;
+		@param var noiseTurmoil : Float;
+		@param var noiseScale : Float;
+		@param var noiseLacunarity : Float;
+		@param var noisePersistence : Float;
+		@param var noiseSharpness : Float;
+		@param var noiseOctave : Int;
+		@param var noiseTex : Sampler2D;
 
-		@param var cameraInverseViewProj : Mat4;
-		@param var cameraPosition : Vec3;
+		@param var depthMap : Sampler2D;
 
-		@const @param var steps : Int;
+		@param var steps : Int;
+		@param var color : Vec3;
+		@param var startDistance : Float;
+		@param var endDistance : Float;
+		@param var distanceOpacity : Float;
 
 		@param var ditheringNoise : Sampler2D;
-		@param var ditheringIntensity : Float;
-		@param var targetSize : Vec2;
 		@param var ditheringSize : Vec2;
-		@const var USE_DITHERING : Bool;
-
-		// function computeScattering(rayDir : Vec3) : Float {
-		// 	var G_SCATTERING = 1.0;
-		// 	var d = dot(rayDir, lightDir);
-		// 	var res = 1.0 - d * d;
-		// 	res = res / 4.0 * PI * pow(1.0 + G_SCATTERING * G_SCATTERING - 2.0 * G_SCATTERING * d, 1.5);
-		// 	return res;
-		// }
-
-		function getFogDensity(z : Float) : Float {
-			return smoothstep(0.0, 1.0, (top - z) / (top - fallOff));
-		}
+		@param var targetSize : Vec2;
+		@param var ditheringIntensity : Float;
 
-		function fragment() {
-			var origin = getPosition();
-			var amount = 0.;
 
-			var camDir = normalize(origin - cameraPosition);
+		@param var fogDensity : Float;
+		@param var fogBottom : Float;
+		@param var fogTop : Float;
+		@param var fogHeightFalloff : Float;
+		@param var fogEnvPower : Float;
+
+		var calculatedUV : Vec2;
+		function getPosition() : Vec3 {
+			var depth = depthMap.get(calculatedUV).r;
+			var uv2 = uvToScreen(calculatedUV);
+			var temp = vec4(uv2, depth, 1) * invViewProj;
+			var originWS = temp.xyz / temp.w;
+			return originWS;
+		}
+
+		function noise( pos : Vec3 ) : Float {
+			var i = floor(pos);
+    		var f = fract(pos);
+			f = f*f*(3.0-2.0*f);
+			var uv = (i.xy+vec2(37.0,239.0)*i.z) + f.xy;
+			var rg = noiseTex.getLod( (uv+0.5) / 256.0, 0 ).yx;
+			return mix( rg.x, rg.y, f.z );
+		}
 
-			var startPos = cameraPosition;
-			if ( startPos.z > top ) {
-				if ( camDir.z > 0.0 )
-					discard;
-				startPos = startPos + (top - startPos.z) / camDir.z * camDir;
+		function noiseAt( pos : Vec3 ) : Float {
+			var amount = 0.;
+			var p = pos * 0.1 * noiseScale;
+			var t = global.time * noiseTurmoil;
+			amount += noise(p - t * vec3(0, 0, 1));
+			var tot = 1.;
+			var k = noisePersistence;
+			p *= noiseLacunarity;
+			if ( noiseOctave >= 2 ) {
+				amount += noise(p + t * vec3(0, 0, -0.6)) * k;
+				k *= noisePersistence;
+				p *= noiseLacunarity;
+				tot += k;
 			}
-			if ( startPos.z < bottom ) {
-				if ( camDir.z < 0.0 )
-					discard;
-				startPos = startPos + (bottom - startPos.z) / camDir.z * camDir;
+			if ( noiseOctave >= 3 ) {
+				amount += noise(p + t * vec3(-0.9, 0, 1.1)) * k;
+				k *= noisePersistence;
+				p *= noiseLacunarity;
+				tot += k;
 			}
-			if ( distance(startPos, cameraPosition) > distance(cameraPosition, origin) )
-				discard;
-			var d = min(maxDist, distance(startPos, origin));
-			if ( USE_DITHERING ) {
-				var dithering = ditheringNoise.getLod(calculatedUV * targetSize / ditheringSize, 0.0).r;
-				dithering *= ditheringIntensity * d / steps;
-				startPos += camDir * dithering;
+			if ( noiseOctave >= 4 ) {
+				amount += noise(p + t * vec3(0.8, 0.95,-1.2)) * k;
+				k *= noisePersistence;
+				p *= noiseLacunarity;
+				tot += k;
 			}
-			var end = startPos + d * camDir;
-
-			var density = smoothstep(0.0, 1.0, pow(distance(startPos, end) / maxDist, decayPower));
 
-			var fog = 0.0;
-			for ( i in 1...steps ) {
-				var pos = mix(startPos, end, float(i) / float(steps));
-
-				var shadowPos = pos * shadowProj;
-				var zMax = shadowPos.z.saturate();
-				var shadowUv = screenToUv(shadowPos.xy);
-				var shadowDepth = shadowMap.getLod(shadowUv.xy, 0.0).r;
-
-				// TODO : Mie scattering approximation
-				// var scattering = computeScattering(toCam);
-				fog += zMax - shadowBias > shadowDepth ? 0.0 : getFogDensity(pos.z) / float(steps);
+			if ( noiseOctave >= 5 ) {
+				amount += noise(p + t * vec3(0,-0.84,-1.3)) * k;
+				tot += k;
+				p *= noiseLacunarity;
+				k *= noisePersistence;
+				tot += k;
 			}
+			return pow(amount / tot, noiseSharpness);
+		}
 
-			var color = mix(darkColor, brightColor, fog) * lightColor;
-			density *= mix(darkOpacity, brightOpacity, fog);
+		function indirectLighting() : Vec3 {
+			return irrDiffuse.getLod(vec3(0.5), 0.0).rgb * irrPower * fogEnvPower;
+		}
 
+		function directLighting(lightColor : Vec3, lightDirection : Vec3) : Vec3 {
+			return lightColor;
+		}
 
-			pixelColor = vec4(color, density);
+		function fragment() {
+			metalness = 0.0;
+			emissive = 0.0;
+			albedoGamma = vec3(0.0);
+			var wPos = getPosition();
+			var cameraDistance = length(wPos - camera.position);
+			var camDir = normalize(wPos - camera.position);
+			view = -camDir;
+
+			if ( cameraDistance < startDistance )
+				discard;
+			var startPos = camera.position + camDir * startDistance;
+			var endPos = camera.position + camDir * min(cameraDistance, endDistance);
+			var opacity = 0.0;
+			var stepSize = 1.0 / float(steps) * length(endPos - startPos) / (endDistance - startDistance);
+			var dithering = ditheringNoise.getLod(calculatedUV * targetSize / ditheringSize, 0.0).r;
+			dithering = dithering * ditheringIntensity;
+			pixelColor.rgb = vec3(0.0);
+			var totalScattered = vec3(0.0);
+			var opticalDepth = 0.0;
+			for ( i in 0...steps ) {
+				transformedPosition = mix(startPos, endPos, (float(i) + dithering) / float(steps));
+
+				var hNorm = smoothstep(0.0, 1.0, (transformedPosition.z - fogBottom) / (fogTop - fogBottom));
+				var density = noiseAt(transformedPosition) * exp(-hNorm * fogHeightFalloff) * (1.0 - hNorm);
+				density *= stepSize;
+
+				var l = evaluateLighting();
+				var transmittance = l * exp(-opticalDepth);
+				var d = density * (1.0 - exp(-length(transmittance)));
+				opticalDepth += d * fogDensity;
+				opacity += d * fogDensity * (1.0 - opacity); 
+				totalScattered += density * transmittance;
+			}
+			pixelColor.rgb = totalScattered;
+			pixelColor.a = distanceOpacity * opacity;
 		}
 	};
 }
@@ -101,110 +149,119 @@ class VolumetricLightingShader extends hrt.shader.PbrShader {
 @:access(h3d.scene.Renderer)
 class VolumetricLighting extends RendererFX {
 
-	var pass = new h3d.pass.ScreenFx(new VolumetricLightingShader());
+	var pass = new h3d.pass.ScreenFx(new h3d.shader.ScreenShader());
 	var blurPass = new h3d.pass.Blur();
+	var vshader = new VolumetricLightingShader();
 
-	@:s public var gamma : Float = 1.0;
-	@:s public var bottom : Float = 0.0;
-	@:s public var top : Float = 10.0;
-	@:s public var fallOff : Float = 0.8;
+	@:s public var blend : h3d.mat.PbrMaterial.PbrBlend = Alpha;
+	@:s public var color : Int = 0xFFFFFF;
 	@:s public var steps : Int = 10;
 	@:s public var textureSize : Float = 0.5;
-	@:s public var blur : Float = 0.5;
-
-	@:s public var angleThreshold : Float = 90.0;
-	@:s public var minIntensity : Float = 0.0;
-	@:s public var fadeBlur : Float = 2.0;
-
-	@:s public var darkOpacity : Float = 0.0;
-	@:s public var brightOpacity : Float = 1.0;
-	@:s public var darkColor : Int = 0;
-	@:s public var brightColor : Int = 0xFFFFFF;
-
-	@:s public var ditheringNoise : String;
+	@:s public var blur : Float = 0.0;
+	@:s public var startDistance : Float = 0.0;
+	@:s public var endDistance : Float = 200.0;
+	@:s public var distanceOpacity : Float = 1.0;
 	@:s public var ditheringIntensity : Float = 1.0;
+	@:s public var ditheringNoise : String;
 
-	@:s public var maxDist : Float = 30.0;
-	@:s public var decayPower : Float = 1.0;
+	@:s public var noiseScale : Float = 1.0;
+	@:s public var noiseLacunarity : Float = 2.0;
+	@:s public var noiseSharpness : Float = 1.0;
+	@:s public var noisePersistence : Float = 0.5;
+	@:s public var noiseTurmoil : Float = 1.0;
+	@:s public var noiseOctave : Int = 1;
 
-	override function makeInstance() {
-		 super.makeInstance();
-		updateInstance();
-	}
+	@:s public var fogDensity : Float = 1.0;
+	@:s public var fogHeightFalloff : Float = 1.0;
+	@:s public var fogEnvPower : Float = 1.0;
+	@:s public var fogBottom : Float = 0.0;
+	@:s public var fogTop : Float = 200.0;
 
-	override function begin(r:h3d.scene.Renderer, step:h3d.impl.RendererFX.Step) {
+	var noiseTex : h3d.mat.Texture;
+
+	override function end(r:h3d.scene.Renderer, step:h3d.impl.RendererFX.Step) {
 		if( step == BeforeTonemapping ) {
+			var r = cast(r, h3d.scene.pbr.Renderer);
 			r.mark("VolumetricLighting");
 
-			var sun : h3d.scene.pbr.DirLight = null;
-			var light = @:privateAccess r.ctx.lights;
-			while ( light != null ) {
-				var pbrLight = Std.downcast(light, h3d.scene.pbr.DirLight);
-				if ( pbrLight != null && pbrLight.isMainLight ) {
-					sun = pbrLight;
-					break;
-				}
-				light = light.next;
-			}
-			if ( sun == null || sun.shadows == null || !sun.shadows.enabled || sun.shadows.getShadowTex() == null )
-				return;
+			if ( noiseTex == null )
+				noiseTex = makeNoiseTex();
+
 			var tex = r.allocTarget("volumetricLighting", false, textureSize, RGBA16F);
 			tex.clear(0, 0.0);
 			r.ctx.engine.pushTarget(tex);
 
-			pass.shader.maxDist = maxDist;
-			pass.shader.decayPower = decayPower;
-
-			pass.shader.gamma = gamma;
-			pass.shader.bottom = bottom;
-			pass.shader.top = top;
-			pass.shader.fallOff = bottom + (top - bottom) * fallOff;
-
-			pass.shader.darkColor = h3d.Vector.fromColor(darkColor);
-			pass.shader.brightColor = h3d.Vector.fromColor(brightColor);
-			pass.shader.lightColor.set(hxd.Math.pow(sun.color.x, gamma), hxd.Math.pow(sun.color.y, gamma), hxd.Math.pow(sun.color.z, gamma));
-			var lightDir = sun.getAbsPos().front();
-			lightDir.normalize();
-			pass.shader.lightDir.load(lightDir);
-
-			var camFront = r.ctx.camera.target.sub(r.ctx.camera.pos);
-			camFront.normalize();
-			var dot = camFront.dot(lightDir);
-			dot = hxd.Math.clamp(dot);
-			var cosAngle = Math.cos(hxd.Math.degToRad(angleThreshold));
-			var alignedFactor = (1.0 - dot) / (1.0 - cosAngle);
-			alignedFactor = hxd.Math.clamp(alignedFactor);
-			var realBlur = hxd.Math.lerp(fadeBlur, blur, alignedFactor);
-			alignedFactor = hxd.Math.lerp(minIntensity, 1.0, alignedFactor);
-			pass.shader.brightOpacity = brightOpacity * alignedFactor;
-			pass.shader.darkOpacity = darkOpacity * alignedFactor;
-
-			pass.shader.cameraInverseViewProj = r.ctx.camera.getInverseViewProj();
-			pass.shader.shadowMap = sun.shadows.getShadowTex();
-			pass.shader.shadowProj = sun.shadows.getShadowProj();
-			pass.shader.cameraPosition = r.ctx.camera.pos;
-			pass.shader.steps = steps;
-			pass.shader.ditheringIntensity = ditheringIntensity;
-			pass.shader.ditheringNoise = ditheringNoise != null ? hxd.res.Loader.currentInstance.load(ditheringNoise).toTexture() : h3d.mat.Texture.fromColor(0);
-			pass.shader.ditheringNoise.wrap = Repeat;
-			if ( ditheringNoise != null )
-				pass.shader.USE_DITHERING = true;
-			else
-				pass.shader.USE_DITHERING = false;
-			pass.shader.targetSize.set(tex.width, tex.height);
-			pass.shader.ditheringSize.set(pass.shader.ditheringNoise.width, pass.shader.ditheringNoise.height);
+			vshader.USE_INDIRECT = false;
+			if ( pass.getShader(h3d.shader.pbr.DefaultForward) == null )
+				pass.addShader(vshader);
+			var ls = cast(r.getLightSystem(), h3d.scene.pbr.LightSystem);
+			ls.lightBuffer.setBuffers(vshader);
+			vshader.depthMap = @:privateAccess r.textures.depth;
+			vshader.startDistance = startDistance;
+			vshader.endDistance = endDistance;
+			vshader.distanceOpacity = distanceOpacity;
+			vshader.steps = steps;
+			vshader.invViewProj = r.ctx.camera.getInverseViewProj();
+			vshader.color.load(h3d.Vector.fromColor(color));
+			var noise = ditheringNoise != null ? hxd.res.Loader.currentInstance.load(ditheringNoise).toTexture() : h3d.mat.Texture.fromColor(0);
+			noise.wrap = Repeat;
+			vshader.ditheringNoise = noise;
+			vshader.targetSize.set(tex.width, tex.height);
+			vshader.ditheringSize.set(vshader.ditheringNoise.width, vshader.ditheringNoise.height);
+			vshader.ditheringIntensity = ditheringIntensity;
+			vshader.noiseTex = noiseTex;
+			vshader.noiseScale = noiseScale;
+			vshader.noiseOctave = noiseOctave;
+			vshader.noiseTurmoil = noiseTurmoil;
+			vshader.noiseSharpness = noiseSharpness;
+			vshader.noisePersistence = noisePersistence;
+			vshader.noiseLacunarity = noiseLacunarity;
+			vshader.fogEnvPower = fogEnvPower;
+			vshader.fogDensity = fogDensity;
+			vshader.fogBottom = fogBottom;
+			vshader.fogTop = fogTop;
+			vshader.fogHeightFalloff = fogHeightFalloff;
 			pass.pass.setBlendMode(Alpha);
 			pass.render();
 
 			r.ctx.engine.popTarget();
 
-			if ( realBlur > 0.0 ) {
-				blurPass.radius = realBlur;
-				blurPass.apply(r.ctx, tex);
+			blurPass.radius = blur;
+			blurPass.apply(r.ctx, tex);
+
+			var b : h3d.mat.BlendMode = switch ( blend ) {
+			case None: None; 
+			case Alpha: Alpha; 
+			case Add: Add; 
+			case AlphaAdd: AlphaAdd; 
+			case Multiply: Multiply; 
+			case AlphaMultiply: AlphaMultiply; 
 			}
+			h3d.pass.Copy.run(tex, h3d.Engine.getCurrent().getCurrentTarget(), b);
+		}
+	}
 
-			h3d.pass.Copy.run(tex, h3d.Engine.getCurrent().getCurrentTarget(), Add);
+	function makeNoiseTex() : h3d.mat.Texture {
+		var rands : Array<Int> = [];
+		var rand = new hxd.Rand(0);
+		for(x in 0...256)
+			for(y in 0...256)
+				rands.push(rand.random(256));
+		var pix = hxd.Pixels.alloc(256, 256, RGBA);
+		for(x in 0...256) {
+			for(y in 0...256) {
+				var r = rands[x + y * 256];
+				var g = rands[((x - 37) & 255) + ((y - 239) & 255) * 256];
+				var off = (x + y*256) * 4;
+				pix.bytes.set(off, r);
+				pix.bytes.set(off+1, g);
+				pix.bytes.set(off+3, 255);
+			}
 		}
+		var tex = new h3d.mat.Texture(pix.width, pix.height, [], RGBA);
+		tex.uploadPixels(pix);
+		tex.wrap = Repeat;
+		return tex;
 	}
 
 	#if editor
@@ -214,35 +271,46 @@ class VolumetricLighting extends RendererFX {
 		ctx.properties.add(new hide.Element(
 			'<div class="group" name="Fog">
 				<dl>
-					<dt>Bottom</dt><dd><input type="range" min="0" max="10" field="bottom"/></dd>
-					<dt>Top</dt><dd><input type="range" min="0" max="10" field="top"/></dd>
-					<dt>Falloff</dt><dd><input type="range" min="0" max="1" field="fallOff"/></dd>
-					<dt>Decay distance</dt><dd><input type="range" min="0" max="50" field="maxDist"/></dd>
-					<dt>Decay power</dt><dd><input type="range" min="0.2" max="5" field="decayPower"/></dd>
+					<dt>Blend</dt>
+					<dd>
+						<select field="blend">
+							<option value="None">None</option>
+							<option value="Alpha">Alpha</option>
+							<option value="Add">Add</option>
+							<option value="AlphaAdd">AlphaAdd</option>
+							<option value="Multiply">Multiply</option>
+							<option value="AlphaMultiply">AlphaMultiply</option>
+						</select>
+					</dd>
+					<dt>Start distance</dt><dd><input type="range" min="0" field="startDistance"/></dd>
+					<dt>End distance</dt><dd><input type="range" min="0" field="endDistance"/></dd>
+					<dt>Distance opacity</dt><dd><input type="range" min="0" max="1" field="distanceOpacity"/></dd>
+					<dt>Density</dt><dd><input type="range" min="0" max="2" field="fogDensity"/></dd>
+					<dt>Bottom [m]</dt><dd><input type="range" min="0" max="1000" field="fogBottom"/></dd>
+					<dt>Top [m]</dt><dd><input type="range" min="0" max="1000" field="fogTop"/></dd>
+					<dt>Height falloff</dt><dd><input type="range" min="0" max="3" field="fogHeightFalloff"/></dd>
+					<dt>Env power</dt><dd><input type="range" min="0" max="2" field="fogEnvPower"/></dd>
+				</dl>
+			</div>
+			<div class="group" name="Noise">
+				<dl>
+					<dt><font color=#FF0000>Octaves</font></dt><dd><input type="range" step="1" min="1" max="4" field="noiseOctave"/></dd>
+					<dt>Scale</dt><dd><input type="range" min="0" max="100" field="noiseScale"/></dd>
+					<dt>Turmoil</dt><dd><input type="range" min="0" max="100" field="noiseTurmoil"/></dd>
+					<dt>Persistence</dt><dd><input type="range" min="0" max="1" field="noisePersistence"/></dd>
+					<dt>Lacunarity</dt><dd><input type="range" min="0" max="2" field="noiseLacunarity"/></dd>
+					<dt>Sharpness</dt><dd><input type="range" min="0" max="2" field="noiseSharpness"/></dd>
 				</dl>
 			</div>
 			<div class="group" name="Rendering">
 				<dl>
-					<dt>Steps</dt><dd><input type="range" step="1" min="0" max="255" field="steps"/></dd>
-					<dt>Texture size</dt><dd><input type="range" min="0" max="1" field="textureSize"/></dd>
+					<dt><font color=#FF0000>Steps</font></dt><dd><input type="range" step="1" min="0" max="255" field="steps"/></dd>
+					<dt><font color=#FF0000>Texture size</font></dt><dd><input type="range" min="0" max="1" field="textureSize"/></dd>
 					<dt>Blur</dt><dd><input type="range" step="1" min="0" max="100" field="blur"/></dd>
 					<dt>Blue noise</dt><dd><input type="texturepath" field="ditheringNoise"/></dd>
 					<dt>Dithering intensity</dt><dd><input type="range" min="0" max="1" field="ditheringIntensity"/></dd>
 				</dl>
 			</div>
-			<div class="group" name="Color">
-				<dt>Angle threshold</dt><dd><input type="range" min="0" max="180" field="angleThreshold"/></dd>
-				<dt>Dark opacity</dt><dd><input type="range" min="0" max="1" field="darkOpacity"/></dd>
-				<dt>Bright opacity</dt><dd><input type="range" min="0" max="1" field="brightOpacity"/></dd>
-				<dt>Dark color</dt><dd><input type="color" field="darkColor"/></dd>
-				<dt>Bright color</dt><dd><input type="color" field="brightColor"/></dd>
-				<dt>Gamma</dt><dd><input type="range" min="1" max="2" field="gamma"/></dd>
-			</div>
-			<div class="group" name="Fade">
-				<dt>Angle threshold</dt><dd><input type="range" min="0" max="180" field="angleThreshold"/></dd>
-				<dt>Min intensity</dt><dd><input type="range" min="0" max="1" field="minIntensity"/></dd>
-				<dt>Blur fading</dt><dd><input type="range" min="0" max="100" field="fadeBlur"/></dd>
-			</div>
 			'), this, function(pname) {
 				ctx.onChange(this, pname);
 		});