Przeglądaj źródła

Added importance sampling to SSR (non-temporal for now)

BearishSun 8 lat temu
rodzic
commit
2f7eef36ea

+ 4 - 0
Data/Raw/Engine/DataList.json

@@ -151,6 +151,10 @@
         {
             "Path": "TemporalResolve.bslinc",
             "UUID": "b0ce01c2-3326-445e-bf2b-35cf8d9d6e1b"
+        },
+        {
+            "Path": "ImportanceSampling.bslinc",
+            "UUID": "52a46920-860e-4d05-a7c3-34e926b02664"
         }
     ],
     "Shaders": [

+ 56 - 0
Data/Raw/Engine/Includes/ColorSpace.bslinc

@@ -33,5 +33,61 @@ mixin ColorSpace
 		{
 			return 0.299f * input.r + 0.587f * input.g + 0.114f * input.b;
 		}
+		
+		////////////////////// HDR SCALING HELPER FUNCTIONS ///////////////////
+		// HDR scaling methods use luminance based scaling, instead of a tonemap operator, as
+		// described here:
+		// http://graphicrants.blogspot.hr/2013/12/tone-mapping.html
+		float HDRScaleWeight(float luma, float exposureScale)
+		{
+			return rcp(1 + luma * exposureScale);
+		}
+		
+		float HDRScaleWeightInv(float luma, float exposureScale)
+		{
+			return rcp(1 - luma * exposureScale);
+		}		
+		
+		float3 HDRScaleRGB(float3 v, float exposureScale)
+		{
+			float luma = LuminanceRGB(v);
+			
+			return v * HDRScaleWeight(luma, exposureScale);
+		}
+		
+		float3 HDRScaleY(float3 v, float exposureScale)
+		{
+			float luma = v.r;
+			
+			return v * HDRScaleWeight(luma, exposureScale);
+		}
+		
+		float3 HDRScaleG(float3 v, float exposureScale)
+		{
+			float luma = v.g;
+			
+			return v * HDRScaleWeight(luma, exposureScale);
+		}
+
+		float3 HDRScaleRGBInv(float3 v, float exposureScale)
+		{
+			float luma = LuminanceRGB(v);
+			
+			return v * HDRScaleWeightInv(luma, exposureScale);
+		}
+		
+		float3 HDRScaleYInv(float3 v, float exposureScale)
+		{
+			float luma = v.r;
+			
+			return v * HDRScaleWeightInv(luma, exposureScale);
+		}
+		
+		float3 HDRScaleGInv(float3 v, float exposureScale)
+		{
+			float luma = v.g;
+			
+			return v * HDRScaleWeightInv(luma, exposureScale);
+		}				
 	};
 };

+ 57 - 0
Data/Raw/Engine/Includes/ImportanceSampling.bslinc

@@ -0,0 +1,57 @@
+mixin ImportanceSampling
+{
+	code
+	{
+		#define PI 3.1415926
+	
+		float radicalInverse(uint bits)  
+		{
+			// Reverse bits. Algorithm from Hacker's Delight.
+			bits = (bits << 16u) | (bits >> 16u);
+			bits = ((bits & 0x55555555u) << 1u) | ((bits & 0xAAAAAAAAu) >> 1u);
+			bits = ((bits & 0x33333333u) << 2u) | ((bits & 0xCCCCCCCCu) >> 2u);
+			bits = ((bits & 0x0F0F0F0Fu) << 4u) | ((bits & 0xF0F0F0F0u) >> 4u);
+			bits = ((bits & 0x00FF00FFu) << 8u) | ((bits & 0xFF00FF00u) >> 8u);
+			
+			// Normalizes unsigned int in range [0, 4294967295] to [0, 1]
+			return float(bits) * 2.3283064365386963e-10;
+		}
+		
+		float2 hammersleySequence(uint i, uint count)
+		{
+			float2 output;
+			output.x = i / (float)count;
+			output.y = radicalInverse(i);
+			
+			return output;
+		}
+		
+		// Returns cos(theta) in x and phi in y
+		float2 importanceSampleGGX(float2 e, float roughness4)
+		{
+			// See GGXImportanceSample.nb for derivation (essentially, take base GGX, normalize it,
+			// generate PDF, split PDF into marginal probability for theta and conditional probability
+			// for phi. Plug those into the CDF, invert it.)				
+			float cosTheta = sqrt((1.0f - e.x) / (1.0f + (roughness4 - 1.0f) * e.x));
+			float phi = 2.0f * PI * e.y;
+			
+			return float2(cosTheta, phi);
+		}
+		
+		float3 sphericalToCartesian(float cosTheta, float sinTheta, float phi)
+		{
+			float3 output;
+			output.x = sinTheta * cos(phi);
+			output.y = sinTheta * sin(phi);
+			output.z = cosTheta;
+			
+			return output;
+		}
+		
+		float pdfGGX(float cosTheta, float sinTheta, float roughness4)
+		{
+			float d = (cosTheta*roughness4 - cosTheta) * cosTheta + 1;
+			return roughness4 * cosTheta * sinTheta / (d*d*PI);
+		}
+	};
+};

+ 0 - 56
Data/Raw/Engine/Includes/TemporalResolve.bslinc

@@ -205,63 +205,7 @@ mixin TemporalResolve
 		{
 			return val * 2.0f;
 		}
-		
-		////////////////////// HDR SCALING HELPER FUNCTIONS ///////////////////
-		// HDR scaling methods use luminance based scaling, instead of a tonemap operator, as
-		// described here:
-		// http://graphicrants.blogspot.hr/2013/12/tone-mapping.html
-		float HDRScaleWeight(float luma, float exposureScale)
-		{
-			return rcp(1 + luma * exposureScale);
-		}
-		
-		float HDRScaleWeightInv(float luma, float exposureScale)
-		{
-			return rcp(1 - luma * exposureScale);
-		}		
-		
-		float3 HDRScaleRGB(float3 v, float exposureScale)
-		{
-			float luma = LuminanceRGB(v);
-			
-			return v * HDRScaleWeight(luma, exposureScale);
-		}
-		
-		float3 HDRScaleY(float3 v, float exposureScale)
-		{
-			float luma = v.r;
-			
-			return v * HDRScaleWeight(luma, exposureScale);
-		}
-		
-		float3 HDRScaleG(float3 v, float exposureScale)
-		{
-			float luma = v.g;
-			
-			return v * HDRScaleWeight(luma, exposureScale);
-		}
 
-		float3 HDRScaleRGBInv(float3 v, float exposureScale)
-		{
-			float luma = LuminanceRGB(v);
-			
-			return v * HDRScaleWeightInv(luma, exposureScale);
-		}
-		
-		float3 HDRScaleYInv(float3 v, float exposureScale)
-		{
-			float luma = v.r;
-			
-			return v * HDRScaleWeightInv(luma, exposureScale);
-		}
-		
-		float3 HDRScaleGInv(float3 v, float exposureScale)
-		{
-			float luma = v.g;
-			
-			return v * HDRScaleWeightInv(luma, exposureScale);
-		}		
-		
 		////////////////////// HELPER TONEMAP/COLOR SPACE DEFINES /////////////////////
 		// Automatically scale HDR values based on luminance, if enabled
 		#if TEMPORAL_TONEMAP

+ 86 - 28
Data/Raw/Engine/Shaders/PPSSRTrace.bsl

@@ -1,6 +1,8 @@
 #include "$ENGINE$\PPBase.bslinc"
 #include "$ENGINE$\GBufferInput.bslinc"
 #include "$ENGINE$\PerCameraData.bslinc"
+#include "$ENGINE$\ImportanceSampling.bslinc"
+#include "$ENGINE$\ColorSpace.bslinc"
 
 #define HI_Z 1
 #include "$ENGINE$\RayMarch.bslinc"
@@ -11,6 +13,8 @@ technique PPSSRTrace
 	mixin PerCameraData;
 	mixin GBufferInput;
 	mixin RayMarch;
+	mixin ImportanceSampling;
+	mixin ColorSpace;
 
 	stencil
 	{
@@ -28,6 +32,8 @@ technique PPSSRTrace
 			float2 gHiZUVToScreenUV;
 			int2 gHiZSize;
 			int gHiZNumMips;
+			float gIntensity;
+			float2 gRoughnessScaleBias;
 		}
 		
 		Texture2D gSceneColor;
@@ -54,31 +60,28 @@ technique PPSSRTrace
 		
 			SurfaceData surfData = getGBufferData(input.uv0);
 			float3 P = NDCToWorld(input.screenPos, surfData.depth);
-			float3 V = normalize(P - gViewOrigin);
+			float3 V = normalize(gViewOrigin - P);
 			float3 N = surfData.worldNormal.xyz;
 			
+			float roughness = surfData.roughness;
+			
+			
+			roughness = 0.3f;//DEBUG ONLY
+			
+			
+			
+			float roughness2 = roughness * roughness;
+			float roughness4 = roughness2 * roughness2;
+			
 			// TODO - DEBUG ONLY - Only handle reflections on up facing surfaces
 			if(dot(N, float3(0,1,0)) < 0.8)
 				return gSceneColor.Sample(gSceneColorSamp, input.uv0);	
 			else
 				N = float3(0,1,0);
-			
-			// TODO - Allow number of steps and rays be customized using a quality level
-			//  - And HiZ vs linear search
-			
-			// TODO - Use Hammersley + random to generate ray directions based on GGX BRDF
-			//  - Clip BRDF lobe? And renormalize PDF?
-			// TODO - Reject rays pointing under the surface
-			
-			// Eliminate rays pointing towards the viewer. They won't hit anything, plus they can screw up precision
-			// and cause ray step offset to be too small, causing self-intersections.
-			float3 R = normalize(reflect(V, N));
-			if(dot(R, gViewDir) < 0.0f)
-				return 0.0f;
-			
+
 			// Jitter ray offset in 4x4 tile, in order to avoid stairstep artifacts
 			uint pixelIdx = mortonCode4x4((uint)pixelPos.x, (uint)pixelPos.y);
-			float jitterOffset = (pixelIdx & 15) / 15.0f - 0.5f; // TODO - Also add per-frame jitter
+			float jitterOffset = (pixelIdx & 15) / 15.0f - 0.5f; // TODO - Also add per-frame jitter			
 			
 			RayMarchParams rayMarchParams;
 			rayMarchParams.bufferSize = gHiZSize;
@@ -86,24 +89,79 @@ technique PPSSRTrace
 			rayMarchParams.NDCToHiZUV = gNDCToHiZUV;
 			rayMarchParams.HiZUVToScreenUV = gHiZUVToScreenUV;
 			rayMarchParams.rayOrigin = P;
-			rayMarchParams.rayDir = R;
-			rayMarchParams.jitterOffset = jitterOffset;
+			rayMarchParams.jitterOffset = jitterOffset;			
 			
-			// TODO - Fade based on roughness
+			int NUM_RAYS = 64; // DEBUG ONLY
 			
-			float4 rayHit = rayMarch(gHiZ, gDepthBufferSamp, rayMarchParams);
-			if(rayHit.w < 1.0f) // Hit
+			float4 sum = 0;
+			[loop]
+			for(int i = 0; i < NUM_RAYS; ++i)
 			{
-				float4 output = gSceneColor.Sample(gSceneColorSamp, rayHit.xy);
+				// TODO - Add per-frame random? (for temporal filtering)
+				float2 random = hammersleySequence(i, NUM_RAYS);
+				float2 sphericalH = importanceSampleGGX(random, roughness4);
 				
-				// Fade out near screen edges
-				float2 rayHitNDC = UVToNDC(rayHit.xy);
-				float2 vignette = saturate(abs(rayHitNDC) * 5.0f - 4.0f);
-	
-				return output * (1.0f - dot(vignette, vignette));
+				float cosTheta = sphericalH.x;
+				float phi = sphericalH.y;
+				float sinTheta = sqrt(1.0f - cosTheta * cosTheta);
+				
+				float3 H = sphericalToCartesian(cosTheta, sinTheta, phi);
+
+				// Transform H to world space
+				float3 up = abs(H.z) < 0.999 ? float3(0, 0, 1) : float3(1, 0, 0);
+				float3 tangentX = normalize(cross(up, N));
+				float3 tangentY = cross(N, tangentX);
+				
+				H = tangentX * H.x + tangentY * H.y + N * H.z; 
+				float3 R = 2 * dot( V, H ) * H - V;
+
+				// Eliminate rays pointing towards the viewer. They won't hit anything, plus they can screw up precision
+				// and cause ray step offset to be too small, causing self-intersections.
+				R = normalize(R); // Note: Normalization required?
+				if(dot(R, gViewDir) < 0.0f)
+					continue;
+					
+				// Eliminate rays pointing below the surface
+				if(dot(R, N) < 0.0005f)
+					continue;
+					
+				rayMarchParams.rayDir = R;
+
+				float4 rayHit = rayMarch(gHiZ, gDepthBufferSamp, rayMarchParams);
+				if(rayHit.w < 1.0f) // Hit
+				{
+					float4 color = gSceneColor.Sample(gSceneColorSamp, rayHit.xy);
+					color.a = 1.0f; // Marks the pixel as SSR
+					
+					// Fade out near screen edges
+					float2 rayHitNDC = UVToNDC(rayHit.xy);
+					float2 vignette = saturate(abs(rayHitNDC) * 5.0f - 4.0f);
+		
+					color = color * saturate(1.0f - dot(vignette, vignette));
+					
+					// Note: Not accounting for PDF here since we don't evaluate BRDF until later. Looks good though.
+					
+					// Tonemap the color to get a nicer visual average
+					color.rgb /= (1 + LuminanceRGB(color.rgb));
+					
+					sum += color;
+				}
 			}
 			
-			return 0.0f;
+			// Divide by total number of rays, instead of actual number of rays that passed the test. This scales down the
+			// contribution for pixels for which many rays failed the test and might not be accurate.
+			float4 output = sum / NUM_RAYS; 
+			
+			// Move back to high range (reverse tonemap)
+			output.rgb /= (1 - LuminanceRGB(output.rgb));
+			
+			// Fade based on roughness
+			float fadeValue = 1.0f - saturate(surfData.roughness * gRoughnessScaleBias.x + gRoughnessScaleBias.y);
+			
+			output *= fadeValue;
+			output *= gIntensity;
+			
+			return output;
 		}	
 	};
 };

+ 2 - 52
Data/Raw/Engine/Shaders/ReflectionCubeImportanceSample.bsl

@@ -1,65 +1,15 @@
 #include "$ENGINE$\PPBase.bslinc"
 #include "$ENGINE$\ReflectionCubemapCommon.bslinc"
+#include "$ENGINE$\ImportanceSampling.bslinc"
 
 technique ReflectionCubeImportanceSample
 {
 	mixin PPBase;
 	mixin ReflectionCubemapCommon;
+	mixin ImportanceSampling;
 
 	code
 	{
-		#define PI 3.1415926
-	
-		float radicalInverse(uint bits)  
-		{
-			// Reverse bits. Algorithm from Hacker's Delight.
-			bits = (bits << 16u) | (bits >> 16u);
-			bits = ((bits & 0x55555555u) << 1u) | ((bits & 0xAAAAAAAAu) >> 1u);
-			bits = ((bits & 0x33333333u) << 2u) | ((bits & 0xCCCCCCCCu) >> 2u);
-			bits = ((bits & 0x0F0F0F0Fu) << 4u) | ((bits & 0xF0F0F0F0u) >> 4u);
-			bits = ((bits & 0x00FF00FFu) << 8u) | ((bits & 0xFF00FF00u) >> 8u);
-			
-			// Normalizes unsigned int in range [0, 4294967295] to [0, 1]
-			return float(bits) * 2.3283064365386963e-10;
-		}
-		
-		float2 hammersleySequence(uint i, uint count)
-		{
-			float2 output;
-			output.x = i / (float)count;
-			output.y = radicalInverse(i);
-			
-			return output;
-		}
-		
-		// Returns cos(theta) in x and phi in y
-		float2 importanceSampleGGX(float2 e, float roughness4)
-		{
-			// See GGXImportanceSample.nb for derivation (essentially, take base GGX, normalize it,
-			// generate PDF, split PDF into marginal probability for theta and conditional probability
-			// for phi. Plug those into the CDF, invert it.)				
-			float cosTheta = sqrt((1.0f - e.x) / (1.0f + (roughness4 - 1.0f) * e.x));
-			float phi = 2.0f * PI * e.y;
-			
-			return float2(cosTheta, phi);
-		}
-		
-		float3 sphericalToCartesian(float cosTheta, float sinTheta, float phi)
-		{
-			float3 output;
-			output.x = sinTheta * cos(phi);
-			output.y = sinTheta * sin(phi);
-			output.z = cosTheta;
-			
-			return output;
-		}
-		
-		float pdfGGX(float cosTheta, float sinTheta, float roughness4)
-		{
-			float d = (cosTheta*roughness4 - cosTheta) * cosTheta + 1;
-			return roughness4 * cosTheta * sinTheta / (d*d*PI);
-		}
-		
 		cbuffer Input
 		{
 			int gCubeFace;

+ 1 - 1
Documentation/Manuals/Native/User/bsl.md

@@ -2,7 +2,7 @@ BSL syntax			{#bsl}
 ===============
 [TOC]
 
-All shaders in Banshee are written in BSL (Banshee Shading Language). The core of the language is based on HLSL (High Level Shading Language), with various extensions to make development easier. In this manual we will not cover HLSL syntax, nor talk about shaders in general, and will instead focus on the functionality specific to BSL.
+All shaders in Banshee are written in BSL (Banshee Shading Language). The core of the language is based on HLSL (High Level Shading Language), with various extensions to make development easier. In this manual we will not cover HLSL syntax, nor talk about shaders in general, and will instead focus on the functionality specific to BSL. If you are not familiar with the concept of a shader, or HLSL syntax, it is suggested you learn about them before continuing.
 
 # Basics 
 

+ 2 - 0
Source/RenderBeast/Include/BsPostProcessing.h

@@ -807,6 +807,8 @@ namespace bs { namespace ct
 		BS_PARAM_BLOCK_ENTRY(Vector2, gHiZUVToScreenUV)
 		BS_PARAM_BLOCK_ENTRY(Vector2I, gHiZSize)
 		BS_PARAM_BLOCK_ENTRY(int, gHiZNumMips)
+		BS_PARAM_BLOCK_ENTRY(float, gIntensity)
+		BS_PARAM_BLOCK_ENTRY(Vector2, gRoughnessScaleBias)
 	BS_PARAM_BLOCK_END
 
 	extern SSRTraceParamDef gSSRTraceParamDef;

+ 5 - 0
Source/RenderBeast/Source/BsPostProcessing.cpp

@@ -1702,11 +1702,16 @@ namespace bs { namespace ct
 		HiZUVToScreenUV.x = hiZProps.getWidth() / (float)viewRect.width;
 		HiZUVToScreenUV.y = hiZProps.getHeight() / (float)viewRect.height;
 
+		// Used for roughness fading
+		Vector2 roughnessScaleBias = calcRoughnessFadeScaleBias(settings.maxRoughness);
+
 		Vector2I bufferSize(viewRect.width, viewRect.height);
 		gSSRTraceParamDef.gHiZSize.set(mParamBuffer, bufferSize);
 		gSSRTraceParamDef.gHiZNumMips.set(mParamBuffer, hiZProps.getNumMipmaps());
 		gSSRTraceParamDef.gNDCToHiZUV.set(mParamBuffer, ndcToHiZUV);
 		gSSRTraceParamDef.gHiZUVToScreenUV.set(mParamBuffer, HiZUVToScreenUV);
+		gSSRTraceParamDef.gIntensity.set(mParamBuffer, settings.intensity);
+		gSSRTraceParamDef.gRoughnessScaleBias.set(mParamBuffer, roughnessScaleBias);
 
 		SPtr<GpuParamBlockBuffer> perView = view.getPerViewBuffer();
 		mParamsSet->setParamBlockBuffer("PerCamera", perView);