Browse Source

Added code for Gaussian filter

BearishSun 8 years ago
parent
commit
40712d9a65

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

@@ -289,6 +289,10 @@
         {
             "Path": "ShadowProjectStencil.bsl",
             "UUID": "c8625547-f5e7-43df-85cf-4da6e1806cf9"
+        },
+        {
+            "Path": "PPGaussianBlur.bsl",
+            "UUID": "14b16378-6282-4d2d-bc4c-2d662cc98066"
         }
     ],
     "Skin": [

+ 49 - 0
Data/Raw/Engine/Shaders/PPGaussianBlur.bsl

@@ -0,0 +1,49 @@
+#include "$ENGINE$\PPBase.bslinc"
+
+technique PPGaussianBlur
+{
+	mixin PPBase;
+
+	code
+	{
+		[internal]
+		cbuffer Input
+		{
+			float2 gSampleOffsets[MAX_NUM_SAMPLES];
+			float gSampleWeights[MAX_NUM_SAMPLES];
+			uint gNumSamples;
+		}		
+
+		SamplerState gInputSamp;
+		Texture2D gInputTex;
+		
+		float4 fsmain(VStoFS input) : SV_Target0
+		{
+			// Note: Consider adding a version of this shader with unrolled loop for small number of samples
+			float4 output = 0;
+			
+			uint sampleIdx = 0;
+			for(sampleIdx = 0; sampleIdx < (gNumSamples - 1); sampleIdx += 2)
+			{
+				{
+					float2 uv = input.uv0 + gSampleOffsets[sampleIdx];
+					output += gInputTex.Sample(gInputSamp, uv) * gSampleWeights[sampleIdx + 0];
+				}
+				
+				{
+					float2 uv = input.uv0 + gSampleOffsets[sampleIdx];
+					output += gInputTex.Sample(gInputSamp, uv) * gSampleWeights[sampleIdx + 1];
+				}
+			}
+			
+			[branch]
+			if(sampleIdx < gNumSamples)
+			{
+				float2 uv = input.uv0 + gSampleOffsets[sampleIdx / 2].xy;
+				output += gInputTex.Sample(gInputSamp, uv) * gSampleWeights[sampleIdx + 0];
+			}
+			
+			return output;
+		}	
+	};
+};

+ 1 - 1
Source/BansheeVulkanRenderAPI/Include/BsVulkanCommandBuffer.h

@@ -395,7 +395,7 @@ namespace bs { namespace ct
 			bool needsBarrier : 1;
 		};
 
-		/** Checks if all the prerequisites for rendering have been made (e.g. render target and pipeline state are set. */
+		/** Checks if all the prerequisites for rendering have been made (e.g. render target and pipeline state are set.) */
 		bool isReadyForRender();
 
 		/** Marks the command buffer as submitted on a queue. */

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

@@ -303,6 +303,52 @@ namespace bs { namespace ct
 		TonemappingMat<true, true, true> mTTT;
 	};
 
+	const int MAX_BLUR_SAMPLES = 128;
+
+	BS_PARAM_BLOCK_BEGIN(GaussianBlurParamDef)
+		BS_PARAM_BLOCK_ENTRY_ARRAY(Vector2, gSampleOffsets, MAX_BLUR_SAMPLES)
+		BS_PARAM_BLOCK_ENTRY_ARRAY(float, gSampleWeights, MAX_BLUR_SAMPLES)
+		BS_PARAM_BLOCK_ENTRY(int, gNumSamples)
+	BS_PARAM_BLOCK_END
+
+	extern GaussianBlurParamDef gGaussianBlurParamDef;
+
+	/** Shader that perform Gaussian blur filtering on the provided texture. */
+	class GaussianBlurMat : public RendererMaterial<GaussianBlurMat>
+	{
+		// Direction of the Gaussian filter pass
+		enum Direction
+		{
+			DirVertical,
+			DirHorizontal
+		};
+
+		RMAT_DEF("PPGaussianBlur.bsl");
+
+	public:
+		GaussianBlurMat();
+
+		/** 
+		 * Renders the post-process effect with the provided parameters. 
+		 * 
+		 * @param[in]	source		Input texture to blur.
+		 * @param[in]	filterSize	Size of the blurring filter, in percent of the source texture. In range [0, 1].
+		 * @param[in]	destination	Output texture to which to write the blurred image to.
+		 */
+		void execute(const SPtr<Texture>& source, float filterSize, const SPtr<RenderTexture>& destination);
+
+	private:
+		/** Calculates weights and offsets for the standard distribution of the specified filter size. */
+		static UINT32 calcStdDistribution(float filterRadius, std::array<float, MAX_BLUR_SAMPLES>& weights,
+			std::array<float, MAX_BLUR_SAMPLES>& offsets);
+
+		/** Calculates the radius of the blur kernel depending on the source texture size and provided scale. */
+		static float calcKernelRadius(const SPtr<Texture>& source, float scale, Direction filterDir);
+
+		SPtr<GpuParamBlockBuffer> mParamBuffer;
+		GpuParamTexture mInputTexture;
+	};
+
 	/**
 	 * Renders post-processing effects for the provided render target.
 	 *

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

@@ -545,6 +545,173 @@ namespace bs { namespace ct
 		}
 	}
 
+	GaussianBlurParamDef gGaussianBlurParamDef;
+
+	GaussianBlurMat::GaussianBlurMat()
+	{
+		mParamBuffer = gDownsampleParamDef.createBuffer();
+
+		mParamsSet->setParamBlockBuffer("Input", mParamBuffer);
+		mParamsSet->getGpuParams()->getTextureParam(GPT_FRAGMENT_PROGRAM, "gInputTex", mInputTexture);
+	}
+
+	void GaussianBlurMat::_initDefines(ShaderDefines& defines)
+	{
+		defines.set("MAX_NUM_SAMPLES", MAX_BLUR_SAMPLES);
+	}
+
+	void GaussianBlurMat::execute(const SPtr<Texture>& source, float filterSize, const SPtr<RenderTexture>& destination)
+	{
+		const TextureProperties& srcProps = source->getProperties();
+		const RenderTextureProperties& dstProps = destination->getProperties();
+
+		Vector2 invTexSize(1.0f / srcProps.getWidth(), 1.0f / srcProps.getHeight());
+
+		std::array<float, MAX_BLUR_SAMPLES> sampleOffsets;
+		std::array<float, MAX_BLUR_SAMPLES> sampleWeights;
+
+		POOLED_RENDER_TEXTURE_DESC tempTextureDesc = POOLED_RENDER_TEXTURE_DESC::create2D(srcProps.getFormat(), 
+			dstProps.getWidth(), dstProps.getHeight(), TU_RENDERTARGET);
+		SPtr<PooledRenderTexture> tempTexture = GpuResourcePool::instance().get(tempTextureDesc);
+
+		// Horizontal pass
+		{
+			float kernelRadius = calcKernelRadius(source, filterSize, DirHorizontal);
+			UINT32 numSamples = calcStdDistribution(kernelRadius, sampleWeights, sampleOffsets);
+
+			for(UINT32 i = 0; i < numSamples; ++i)
+			{
+				gGaussianBlurParamDef.gSampleWeights.set(mParamBuffer, sampleWeights[i], i);
+
+				Vector2 offset;
+				offset.x = sampleOffsets[i] * invTexSize.x;
+				offset.y = 0.0f;
+
+				gGaussianBlurParamDef.gSampleOffsets.set(mParamBuffer, offset, i);
+			}
+
+			gGaussianBlurParamDef.gNumSamples.set(mParamBuffer, numSamples);
+
+			mInputTexture.set(source);
+
+			RenderAPI& rapi = RenderAPI::instance();
+			rapi.setRenderTarget(tempTexture->renderTexture);
+
+			gRendererUtility().setPass(mMaterial);
+			gRendererUtility().setPassParams(mParamsSet);
+			gRendererUtility().drawScreenQuad();
+		}
+
+		// Vertical pass
+		{
+			float kernelRadius = calcKernelRadius(source, filterSize, DirVertical);
+			UINT32 numSamples = calcStdDistribution(kernelRadius, sampleWeights, sampleOffsets);
+
+			for(UINT32 i = 0; i < numSamples; ++i)
+			{
+				gGaussianBlurParamDef.gSampleWeights.set(mParamBuffer, sampleWeights[i], i);
+
+				Vector2 offset;
+				offset.x = 0.0f;
+				offset.y = sampleOffsets[i] * invTexSize.y;
+
+				gGaussianBlurParamDef.gSampleOffsets.set(mParamBuffer, offset, i);
+			}
+
+			gGaussianBlurParamDef.gNumSamples.set(mParamBuffer, numSamples);
+
+			mInputTexture.set(tempTexture->texture);
+
+			RenderAPI& rapi = RenderAPI::instance();
+			rapi.setRenderTarget(destination);
+
+			gRendererUtility().setPass(mMaterial);
+			gRendererUtility().setPassParams(mParamsSet);
+			gRendererUtility().drawScreenQuad();
+		}
+
+		GpuResourcePool::instance().release(tempTexture);
+	}
+
+	UINT32 GaussianBlurMat::calcStdDistribution(float filterRadius, std::array<float, MAX_BLUR_SAMPLES>& weights, 
+		std::array<float, MAX_BLUR_SAMPLES>& offsets)
+	{
+		filterRadius = Math::clamp(filterRadius, 0.00001f, (float)(MAX_BLUR_SAMPLES - 1));
+		INT32 intFilterRadius = std::min(Math::ceilToInt(filterRadius), MAX_BLUR_SAMPLES - 1);
+
+		auto normalDistribution = [](int i, float scale)
+		{
+			float samplePos = fabs((float)i) * scale;
+			return exp(samplePos * samplePos);
+		};
+
+		// We make use of the hardware linear filtering, and therefore only generate half the number of samples.
+		// The weights and the sampling location needs to be adjusted in order to get the same results as if we
+		// perform two samples separately:
+		//
+		// Original formula is: t1*w1 + t2*w2
+		// With hardware filtering it's: (t1 + (t2 - t1) * o) * w3 
+		//	Or expanded: t1*w3 - t1*o*w3 + t2*o*w3 = t1 * (w3 - o*w3) + t2 * (o*w3)
+		//
+		// These two need to equal, which means this follows:
+		// w1 = w3 - o*w3
+		// w2 = o*w3
+		//
+		// From the second equation get the offset o:
+		// o = w2/w3
+		//
+		// From the first equation and o, get w3:
+		// w1 = w3 - w2
+		// w3 = w1 + w2
+
+		float scale = 1.0f / filterRadius;
+		UINT32 numSamples = 0;
+		float totalWeight = 0.0f;
+		for(int i = -intFilterRadius; i < intFilterRadius; i += 2)
+		{
+			float w1 = normalDistribution(i, scale);
+			float w2 = normalDistribution(i + 1, scale);
+
+			float w3 = w1 + w2;
+			float o = w2/w3; // Relative to first sample
+
+			weights[numSamples] = w3;
+			offsets[numSamples] = o;
+
+			numSamples++;
+			totalWeight += w3;
+		}
+
+		// Special case for last weight, as it doesn't have a matching pair
+		float w = normalDistribution(intFilterRadius, scale);
+		weights[numSamples] = w;
+		offsets[numSamples] = 0.0f;
+
+		numSamples++;
+		totalWeight += w;
+
+		// Normalize weights
+		float invTotalWeight = 1.0f / totalWeight;
+		for(UINT32 i = 0; i < numSamples; i++)
+			weights[i] *= invTotalWeight;
+
+		return numSamples;
+	}
+
+	float GaussianBlurMat::calcKernelRadius(const SPtr<Texture>& source, float scale, Direction filterDir)
+	{
+		scale = Math::clamp01(scale);
+
+		UINT32 length;
+		if (filterDir == DirHorizontal)
+			length = source->getProperties().getWidth();
+		else
+			length = source->getProperties().getHeight();
+
+		// Divide by two because we need the radius
+		return std::min(length * scale / 2, (float)MAX_BLUR_SAMPLES - 1);
+	}
+
 	void PostProcessing::postProcess(RendererView* viewInfo, const SPtr<RenderTargets>& renderTargets, float frameDelta)
 	{
 		auto& viewProps = viewInfo->getProperties();