Преглед изворни кода

Added code for generating 2D LUT of pre-integrated G and F factors used for microfacet BRDF in environment sampling

BearishSun пре 8 година
родитељ
комит
6beeeae538

+ 1 - 1
Data/Raw/Engine/Shaders/ReflectionCubeImportanceSample.bsl

@@ -101,7 +101,7 @@ Technique
 				float4 sum = 0;
 				float4 sum = 0;
 				for(uint i = 0; i < NUM_SAMPLES; i++)
 				for(uint i = 0; i < NUM_SAMPLES; i++)
 				{
 				{
-					float random = hammersleySequence(i, NUM_SAMPLES);
+					float2 random = hammersleySequence(i, NUM_SAMPLES);
 					float2 sphericalH = importanceSampleGGX(random, roughness4);
 					float2 sphericalH = importanceSampleGGX(random, roughness4);
 					
 					
 					float cosTheta = sphericalH.x;
 					float cosTheta = sphericalH.x;

+ 10 - 0
Source/RenderBeast/Include/BsLightRendering.h

@@ -87,6 +87,16 @@ namespace bs { namespace ct
 		/** Binds all the active lights. */
 		/** Binds all the active lights. */
 		void setLights(const GPULightData& lightData);
 		void setLights(const GPULightData& lightData);
 
 
+		/** 
+		 * Generates a 2D 2-channel texture containing a pre-integrated G and F factors of the microfactet BRDF. This is an
+		 * approximation used for image based lighting, so we can avoid sampling environment maps for each light. Works in
+		 * tandem with the importance sampled reflection cubemaps.
+		 * 
+		 * (u, v) = (NoV, roughness) 
+		 * (r, g) = (scale, bias)
+		 */
+		static SPtr<Texture> generatePreintegratedEnvBRDF();
+
 		static const UINT32 TILE_SIZE;
 		static const UINT32 TILE_SIZE;
 	private:
 	private:
 		UINT32 mSampleCount;
 		UINT32 mSampleCount;

+ 133 - 0
Source/RenderBeast/Source/BsLightRendering.cpp

@@ -178,6 +178,139 @@ namespace bs { namespace ct
 		mLightOffsets[2] = mLightOffsets[1] + lightData.getNumSpotLights();
 		mLightOffsets[2] = mLightOffsets[1] + lightData.getNumSpotLights();
 	}
 	}
 
 
+	// Reverse bits functions used for Hammersley sequence
+	float reverseBits(UINT32 bits)
+	{
+		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);
+
+		return float(bits) * 2.3283064365386963e-10; // 0x100000000
+	}
+
+	void hammersleySequence(UINT32 i, UINT32 count, float& e0, float& e1)
+	{
+		e0 = i / (float)count;
+		e1 = reverseBits(i);
+	}
+
+	Vector3 sphericalToCartesian(float cosTheta, float sinTheta, float phi)
+	{
+		Vector3 output;
+		output.x = sinTheta * cos(phi);
+		output.y = sinTheta * sin(phi);
+		output.z = cosTheta;
+
+		return output;
+	}
+
+	// Generates an angle in spherical coordinates, importance sampled for the specified roughness based on some uniformly
+	// distributed random variables in range [0, 1].
+	void importanceSampleGGX(float e0, float e1, float roughness4, float& cosTheta, float& phi)
+	{
+		// 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.)				
+		cosTheta = sqrt((1.0f - e0) / (1.0f + (roughness4 - 1.0f) * e1));
+		phi = 2.0f * Math::PI * e1;
+	}
+
+	float calcMicrofacetShadowingSmithGGX(float roughness4, float NoV, float NoL)
+	{
+		// Note: See lighting shader for derivation. Includes microfacet BRDF divisor.
+		float g1V = NoV + sqrt(NoV * (NoV - NoV * roughness4) + roughness4);
+		float g1L = NoL + sqrt(NoL * (NoL - NoL * roughness4) + roughness4);
+		return 1.0f / (g1V * g1L);
+	}
+
+	SPtr<Texture> TiledDeferredLighting::generatePreintegratedEnvBRDF()
+	{
+		TEXTURE_DESC desc;
+		desc.type = TEX_TYPE_2D;
+		desc.format = PF_FLOAT16_RG;
+		desc.width = 128;
+		desc.height = 32;
+		
+		SPtr<Texture> texture = Texture::create(desc);
+		PixelData pixelData = texture->lock(GBL_WRITE_ONLY_DISCARD);
+
+		UINT16* rawPixels = (UINT16*)pixelData.getData();
+		UINT32 rowPitch = pixelData.getRowPitch() * PixelUtil::getNumElemBytes(desc.format);
+
+		for (UINT32 y = 0; y < desc.height; y++)
+		{
+			float roughness = (float)(y + 0.5f) / desc.height;
+			float m = roughness * roughness;
+			float m2 = m*m;
+
+			for (UINT32 x = 0; x < desc.width; x++)
+			{
+				float NoV = (float)(x + 0.5f) / desc.width;
+
+				Vector3 V;
+				V.x = sqrt(1.0f - NoV * NoV); // sine
+				V.y = 0.0f;
+				V.z = NoV;
+
+				// These are the two integrals resulting from the second part of the split-sum approximation. Described in
+				// Epic's 2013 paper:
+				//    http://blog.selfshadow.com/publications/s2013-shading-course/karis/s2013_pbs_epic_notes_v2.pdf
+				float scale = 0.0f;
+				float offset = 0.0f;
+
+				// We use the same importance sampling function we use for reflection cube importance sampling, only we
+				// sample G and F, instead of D factors of the microfactet BRDF. See GGXImportanceSample.nb for derivation.
+				constexpr UINT32 NumSamples = 128;
+				for (UINT32 i = 0; i < NumSamples; i++)
+				{
+					float e0, e1;
+					hammersleySequence(i, NumSamples, e0, e1);
+
+					float cosTheta, phi;
+					importanceSampleGGX(e0, e1, m2, cosTheta, phi);
+
+					float sinTheta = sqrt(1.0f - cosTheta * cosTheta);
+					Vector3 H = sphericalToCartesian(cosTheta, sinTheta, phi);
+					Vector3 L = 2.0f * Vector3::dot(V, H) * H - V;
+
+					float VoH = std::max(Vector3::dot(V, H), 0.0f);
+					float NoL = std::max(L.z, 0.0f); // N assumed (0, 0, 1)
+					float NoH = std::max(H.z, 0.0f); // N assumed (0, 0, 1)
+
+					// Set second part of the split sum integral is split into two parts:
+					//   F0*I[G * (1 - (1 - v.h)^5) * cos(theta)] + I[G * (1 - v.h)^5 * cos(theta)] (F0 * scale + bias)
+
+					// We calculate the fresnel scale (1 - (1 - v.h)^5) and bias ((1 - v.h)^5) parts
+					float fc = pow(1.0f - VoH, 5.0f);
+					float fresnelScale = 1.0f - fc;
+					float fresnelOffset = fc;
+
+					// We calculate the G part
+					float G = calcMicrofacetShadowingSmithGGX(m2, NoV, NoL);
+
+					// Note: PDF?
+					if (NoL > 0.0f)
+					{
+						scale += NoL * G * fresnelScale;
+						offset += NoL * G * fresnelOffset;
+					}
+				}
+
+				scale /= NumSamples;
+				offset /= NumSamples;
+
+				UINT16* dest = rawPixels + x * 2 + y * rowPitch;
+				dest[0] = (UINT16)(Math::clamp01(scale) * 65535.0f + 0.5f);
+				dest[1] = (UINT16)(Math::clamp01(offset) * 65535.0f + 0.5f);
+			}
+		}
+
+		texture->unlock();
+
+		return texture;
+	}
+
 	template<int MSAA_COUNT>
 	template<int MSAA_COUNT>
 	TTiledDeferredLightingMat<MSAA_COUNT>::TTiledDeferredLightingMat()
 	TTiledDeferredLightingMat<MSAA_COUNT>::TTiledDeferredLightingMat()
 		:mInternal(mMaterial, mParamsSet, MSAA_COUNT)
 		:mInternal(mMaterial, mParamsSet, MSAA_COUNT)