// Naive approach: 4 passes. (Reference version) private void DrawCoreNaive(RenderContext context) { var originalTexture = GetSafeInput(0); var outputTexture = GetSafeOutput(0); var tapNumber = 2 * tapCount - 1; directionalBlurEffect.Parameters.Set(DepthAwareDirectionalBlurKeys.Count, tapCount); directionalBlurEffect.Parameters.Set(DepthAwareDirectionalBlurKeys.TotalTap, tapNumber); directionalBlurEffect.Parameters.Set(DepthAwareDirectionalBlurUtilKeys.Radius, Radius); directionalBlurEffect.Parameters.Set(DepthAwareDirectionalBlurUtilKeys.TapWeights, tapWeights); // Blur in one direction var blurAngle = Phase; directionalBlurEffect.Parameters.Set(DepthAwareDirectionalBlurUtilKeys.Direction, new Vector2((float)Math.Cos(blurAngle), (float)Math.Sin(blurAngle))); var firstBlurTexture = NewScopedRenderTarget2D(originalTexture.Description); directionalBlurEffect.SetInput(0, originalTexture); directionalBlurEffect.SetOutput(firstBlurTexture); directionalBlurEffect.Draw(context, "McIntoshBokehPass1_tap{0}_radius{1}", tapNumber, (int)Radius); // Diagonal blur A blurAngle = MathUtil.Pi / 3f + Phase; directionalBlurEffect.Parameters.Set(DepthAwareDirectionalBlurUtilKeys.Direction, new Vector2((float)Math.Cos(blurAngle), (float)Math.Sin(blurAngle))); var diagonalBlurA = NewScopedRenderTarget2D(originalTexture.Description); directionalBlurEffect.SetInput(0, firstBlurTexture); directionalBlurEffect.SetOutput(diagonalBlurA); directionalBlurEffect.Draw(context, "McIntoshBokehPass2A_tap{0}_radius{1}", tapNumber, (int)Radius); // Diagonal blur B blurAngle = -MathUtil.Pi / 3f + Phase; directionalBlurEffect.Parameters.Set(DepthAwareDirectionalBlurUtilKeys.Direction, new Vector2((float)Math.Cos(blurAngle), (float)Math.Sin(blurAngle))); var diagonalBlurB = NewScopedRenderTarget2D(originalTexture.Description); directionalBlurEffect.SetInput(0, firstBlurTexture); directionalBlurEffect.SetOutput(diagonalBlurB); directionalBlurEffect.Draw(context, "McIntoshBokehPass2B_tap{0}_radius{1}", tapNumber, (int)Radius); // Final pass outputting the min of A and B finalCombineEffect.SetInput(0, diagonalBlurA); finalCombineEffect.SetInput(1, diagonalBlurB); finalCombineEffect.SetOutput(outputTexture); finalCombineEffect.Draw(context, name: "McIntoshBokehPassCombine"); }
protected override void DrawCore(RenderContext context) { // Update the weight array if necessary if (weightsDirty || tapCount == 0) { weightsDirty = false; Vector2[] gaussianWeights = GaussianUtil.Calculate1D((int)Radius, 2f, true); tapCount = gaussianWeights.Length; tapWeights = new float[tapCount]; for (int i = 0; i < tapCount; i++) { tapWeights[i] = gaussianWeights[i].Y; } } var originalTexture = GetSafeInput(0); var outputTexture = GetSafeOutput(0); var tapNumber = 2 * tapCount - 1; directionalBlurEffect.Parameters.Set(DepthAwareDirectionalBlurKeys.Count, tapCount); directionalBlurEffect.Parameters.Set(DepthAwareDirectionalBlurKeys.TotalTap, tapNumber); directionalBlurEffect.Parameters.Set(DepthAwareDirectionalBlurUtilKeys.Radius, Radius); directionalBlurEffect.Parameters.Set(DepthAwareDirectionalBlurUtilKeys.TapWeights, tapWeights); // Blur in one direction var blurAngle = 0f; directionalBlurEffect.Parameters.Set(DepthAwareDirectionalBlurUtilKeys.Direction, new Vector2((float)Math.Cos(blurAngle), (float)Math.Sin(blurAngle))); var firstBlurTexture = NewScopedRenderTarget2D(originalTexture.Description); directionalBlurEffect.SetInput(0, originalTexture); directionalBlurEffect.SetOutput(firstBlurTexture); directionalBlurEffect.Draw(context, "GaussianBokehPass1_tap{0}_radius{1}", tapNumber, (int)Radius); // Second blur pass to ouput the final result blurAngle = MathUtil.PiOverTwo; directionalBlurEffect.Parameters.Set(DepthAwareDirectionalBlurUtilKeys.Direction, new Vector2((float)Math.Cos(blurAngle), (float)Math.Sin(blurAngle))); directionalBlurEffect.SetInput(0, firstBlurTexture); directionalBlurEffect.SetOutput(outputTexture); directionalBlurEffect.Draw(context, "GaussianBokehPass2_tap{0}_radius{1}", tapNumber, (int)Radius); }
// Optimized approach: 2 passes. private void DrawCoreOptimized(RenderContext context) { var originalTexture = GetSafeInput(0); var outputTexture = GetSafeOutput(0); var tapNumber = 2 * tapCount - 1; directionalBlurEffect.Parameters.Set(DepthAwareDirectionalBlurKeys.Count, tapCount); directionalBlurEffect.Parameters.Set(DepthAwareDirectionalBlurKeys.TotalTap, tapNumber); directionalBlurEffect.Parameters.Set(DepthAwareDirectionalBlurUtilKeys.Radius, Radius); directionalBlurEffect.Parameters.Set(DepthAwareDirectionalBlurUtilKeys.TapWeights, tapWeights); // Blur in one direction var blurAngle = Phase; directionalBlurEffect.Parameters.Set(DepthAwareDirectionalBlurUtilKeys.Direction, new Vector2((float)Math.Cos(blurAngle), (float)Math.Sin(blurAngle))); var firstBlurTexture = NewScopedRenderTarget2D(originalTexture.Description); directionalBlurEffect.SetInput(0, originalTexture); directionalBlurEffect.SetOutput(firstBlurTexture); directionalBlurEffect.Draw(context, "McIntoshBokehPass1_tap{0}_radius{1}", tapNumber, (int)Radius); // Calculates the 2 diagonal blurs and keep the min of them var diagonalBlurAngleA = MathUtil.Pi / 3f + Phase; var diagonalBlurAngleB = -MathUtil.Pi / 3f + Phase; optimizedEffect.SetInput(0, firstBlurTexture); optimizedEffect.SetOutput(outputTexture); optimizedEffect.Parameters.Set(DepthAwareDirectionalBlurKeys.Count, tapCount); optimizedEffect.Parameters.Set(DepthAwareDirectionalBlurKeys.TotalTap, tapNumber); optimizedEffect.Parameters.Set(DepthAwareDirectionalBlurUtilKeys.Radius.ComposeWith("directionalBlurA"), Radius); optimizedEffect.Parameters.Set(DepthAwareDirectionalBlurUtilKeys.Direction.ComposeWith("directionalBlurA"), new Vector2((float)Math.Cos(diagonalBlurAngleA), (float)Math.Sin(diagonalBlurAngleA))); optimizedEffect.Parameters.Set(DepthAwareDirectionalBlurUtilKeys.TapWeights.ComposeWith("directionalBlurA"), tapWeights); optimizedEffect.Parameters.Set(DepthAwareDirectionalBlurUtilKeys.Radius.ComposeWith("directionalBlurB"), Radius); optimizedEffect.Parameters.Set(DepthAwareDirectionalBlurUtilKeys.Direction.ComposeWith("directionalBlurB"), new Vector2((float)Math.Cos(diagonalBlurAngleB), (float)Math.Sin(diagonalBlurAngleB))); optimizedEffect.Parameters.Set(DepthAwareDirectionalBlurUtilKeys.TapWeights.ComposeWith("directionalBlurB"), tapWeights); optimizedEffect.Draw(context, "McIntoshBokehPass2_BlurABCombine_tap{0}_radius{1}", tapNumber, (int)Radius); }
// Naive approach: 6 passes protected void DrawCoreNaive(RenderContext context) { var originalTexture = GetSafeInput(0); var outputTexture = GetSafeOutput(0); if (rhombiTapOffsetsDirty) { calculateRhombiOffsets(); } var tapNumber = 2 * tapCount - 1; directionalBlurEffect.Parameters.Set(DepthAwareDirectionalBlurKeys.Count, tapCount); directionalBlurEffect.Parameters.Set(DepthAwareDirectionalBlurKeys.TotalTap, tapNumber); directionalBlurEffect.Parameters.Set(DepthAwareDirectionalBlurUtilKeys.Radius, Radius); directionalBlurEffect.Parameters.Set(DepthAwareDirectionalBlurUtilKeys.TapWeights, tapWeights); directionalBlurEffect.Parameters.Set(DepthAwareDirectionalBlurUtilKeys.CoCReference, CoCStrength); // Vertical blur var blurAngle = MathUtil.PiOverTwo + Phase; directionalBlurEffect.Parameters.Set(DepthAwareDirectionalBlurUtilKeys.Direction, new Vector2((float)Math.Cos(blurAngle), (float)Math.Sin(blurAngle))); var verticalBlurTexture = NewScopedRenderTarget2D(originalTexture.Description); directionalBlurEffect.SetInput(0, originalTexture); directionalBlurEffect.SetOutput(verticalBlurTexture); directionalBlurEffect.Draw(context, "TripleRhombiBokeh_RhombiABVertical_tap{0}_radius{1}", tapNumber, (int)Radius); // Rhombi A (top left) blurAngle = 7f * MathUtil.Pi / 6f + Phase; directionalBlurEffect.Parameters.Set(DepthAwareDirectionalBlurUtilKeys.Direction, new Vector2((float)Math.Cos(blurAngle), (float)Math.Sin(blurAngle))); var rhombiA = NewScopedRenderTarget2D(originalTexture.Description); directionalBlurEffect.SetInput(0, verticalBlurTexture); directionalBlurEffect.SetOutput(rhombiA); directionalBlurEffect.Draw(context, "TripleRhombiBokeh_RhombiA_tap{0}_radius{1}", tapNumber, (int)Radius); // Rhombi B (top right) blurAngle = -MathUtil.Pi / 6f + Phase; directionalBlurEffect.Parameters.Set(DepthAwareDirectionalBlurUtilKeys.Direction, new Vector2((float)Math.Cos(blurAngle), (float)Math.Sin(blurAngle))); var rhombiB = NewScopedRenderTarget2D(originalTexture.Description); directionalBlurEffect.SetInput(0, verticalBlurTexture); directionalBlurEffect.SetOutput(rhombiB); directionalBlurEffect.Draw(context, "TripleRhombiBokeh_RhombiB_tap{0}_radius{1}", tapNumber, (int)Radius); //Rhombi C (bottom) blurAngle = 7f * MathUtil.Pi / 6f + Phase; directionalBlurEffect.Parameters.Set(DepthAwareDirectionalBlurUtilKeys.Direction, new Vector2((float)Math.Cos(blurAngle), (float)Math.Sin(blurAngle))); var rhombiCTmp = NewScopedRenderTarget2D(originalTexture.Description); directionalBlurEffect.SetInput(0, originalTexture); directionalBlurEffect.SetOutput(rhombiCTmp); directionalBlurEffect.Draw(context, "TripleRhombiBokeh_RhombiCTmp_tap{0}_radius{1}", tapNumber, (int)Radius); blurAngle = -MathUtil.Pi / 6f + Phase; directionalBlurEffect.Parameters.Set(DepthAwareDirectionalBlurUtilKeys.Direction, new Vector2((float)Math.Cos(blurAngle), (float)Math.Sin(blurAngle))); var rhombiC = NewScopedRenderTarget2D(originalTexture.Description); directionalBlurEffect.SetInput(0, rhombiCTmp); directionalBlurEffect.SetOutput(rhombiC); directionalBlurEffect.Draw(context, "TripleRhombiBokeh_RhombiC_tap{0}_radius{1}", tapNumber, (int)Radius); // Final pass outputting the average of the 3 blurs finalCombineEffect.SetInput(0, rhombiA); finalCombineEffect.SetInput(1, rhombiB); finalCombineEffect.SetInput(2, rhombiC); finalCombineEffect.SetOutput(outputTexture); finalCombineEffect.Parameters.Set(TripleRhombiCombineShaderKeys.RhombiTapOffsets, rhombiTapOffsets); finalCombineEffect.Draw(context, name: "TripleRhombiBokehCombine"); }
protected override void DrawCore(RenderContext context) { var originalColorBuffer = GetSafeInput(0); var originalDepthBuffer = GetSafeInput(1); var outputTexture = GetSafeOutput(0); if (configurationDirty) { SetupTechnique(); } // Preparation phase: create different downscaled versions of the original image, later needed by the bokeh blur shaders. // TODO use ImageMultiScaler instead? downscaledSources.Clear(); // First we linearize the depth and compute the CoC map based on the user lens configuration. // Render target will contain "CoC"(16 bits) "Linear depth"(16bits). var cocLinearDepthTexture = GetScopedRenderTarget(originalColorBuffer.Description, 1f, PixelFormat.R16G16_Float); var farPlane = context.Parameters.Get(CameraKeys.FarClipPlane); var depthAreas = DOFAreas; if (AutoFocus) { // TODO replace this by physical camera parameters (aperture, focus distance...) var diffToTarget = (autoFocusDistanceTarget - autoFocusDistanceCurrent); var maxAmplitude = farPlane * 0.2f; diffToTarget = MathUtil.Clamp(diffToTarget, -maxAmplitude, maxAmplitude); autoFocusDistanceCurrent = autoFocusDistanceCurrent + 0.1f * diffToTarget; if (autoFocusDistanceCurrent < 1f) { autoFocusDistanceCurrent = 1f; } depthAreas = new Vector4(0.5f, autoFocusDistanceCurrent, autoFocusDistanceCurrent, autoFocusDistanceCurrent + farPlane * 0.5f); } coclinearDepthMapEffect.SetInput(0, originalDepthBuffer); coclinearDepthMapEffect.SetOutput(cocLinearDepthTexture); coclinearDepthMapEffect.Parameters.Set(CircleOfConfusionKeys.depthAreas, depthAreas); coclinearDepthMapEffect.Draw(context, "CoC_LinearDepth"); if (AutoFocus) { // Reads the center depth of the previous frame and use it as a new target // TODO single pixel is really small, average some disk area instead? pointDepthShader.Parameters.Set(PointDepthKeys.Coordinate, new Vector2(0.5f, 0.5f)); pointDepthShader.SetInput(cocLinearDepthTexture); pointDepthShader.SetOutput(depthCenter1x1); pointDepthShader.Draw("Center Depth"); depthReadBack.SetInput(depthCenter1x1); depthReadBack.Draw("Center_Depth_Readback"); var centerDepth = depthReadBack.Result[0]; autoFocusDistanceTarget = centerDepth; } // Find the smallest downscale we should go down to. var maxDownscale = 0; foreach (var cocLevel in cocLevels) { if (cocLevel.downscaleFactor > maxDownscale) { maxDownscale = cocLevel.downscaleFactor; } } // Create a series of downscale, with anti-bleeding treatment for (int i = 0; i <= maxDownscale; i++) { var downSizedTexture = originalColorBuffer; if (i > 0) { downSizedTexture = GetScopedRenderTarget(originalColorBuffer.Description, 1f / (float)Math.Pow(2f, i), originalColorBuffer.Description.Format); textureScaler.SetInput(0, downscaledSources[i - 1]); textureScaler.SetOutput(downSizedTexture); textureScaler.Draw(context, "DownScale_Factor{0}", i); } downscaledSources[i] = downSizedTexture; } // We create a blurred version of the CoC map. // This is useful to avoid silhouettes appearing when the CoC changes abruptly. var blurredCoCTexture = NewScopedRenderTarget2D(cocLinearDepthTexture.Description); cocMapBlur.Radius = 6f / 720f * cocLinearDepthTexture.Description.Height; // 6 pixels at 720p cocMapBlur.SetInput(0, cocLinearDepthTexture); cocMapBlur.SetOutput(blurredCoCTexture); cocMapBlur.Draw(context, "CoC_BlurredMap"); // Creates all the levels with different CoC strengths. // (Skips level with CoC 0 which is always the original buffer.) combineLevelsEffect.Parameters.Set(CombineLevelsFromCoCKeys.LevelCount, cocLevels.Count); combineLevelsEffect.SetInput(0, cocLinearDepthTexture); combineLevelsEffect.SetInput(1, blurredCoCTexture); combineLevelsEffect.SetInput(2, originalColorBuffer); combineLevelsFrontEffect.Parameters.Set(CombineLevelsFromCoCKeys.LevelCount, cocLevels.Count); combineLevelsFrontEffect.SetInput(0, cocLinearDepthTexture); combineLevelsFrontEffect.SetInput(1, blurredCoCTexture); combineLevelsFrontEffect.SetInput(2, originalColorBuffer); float previousCoC = 0f; for (int i = 1; i < cocLevels.Count; i++) { // We render a blurred version of the original scene into a downscaled render target. // Blur strength depends on the current level CoC value. var levelConfig = cocLevels[i]; var textureToBlur = downscaledSources[levelConfig.downscaleFactor]; float downscaleFactor = 1f / (float)(Math.Pow(2f, levelConfig.downscaleFactor)); var blurOutput = GetScopedRenderTarget(originalColorBuffer.Description, downscaleFactor, originalColorBuffer.Description.Format); var blurOutputFront = NewScopedRenderTarget2D(blurOutput.Description); float blurRadius = (MaxBokehSize * BokehSizeFactor) * levelConfig.CoCValue * downscaleFactor * originalColorBuffer.Width; if (blurRadius < 1f) { blurRadius = 1f; } //--------------------------------- // Far out-of-focus //--------------------------------- // Pre-process the layer for the current CoC // This removes areas which might wrongly bleed into our image when blurring. var alphaTextureToBlur = NewScopedRenderTarget2D(textureToBlur.Description); thresholdAlphaCoC.Parameters.Set(ThresholdAlphaCoCKeys.CoCReference, previousCoC); thresholdAlphaCoC.Parameters.Set(ThresholdAlphaCoCKeys.CoCCurrent, levelConfig.CoCValue); thresholdAlphaCoC.SetInput(0, textureToBlur); thresholdAlphaCoC.SetInput(1, cocLinearDepthTexture); thresholdAlphaCoC.SetOutput(alphaTextureToBlur); thresholdAlphaCoC.Draw(context, "Alphaize_Far_{0}", i); textureToBlur = alphaTextureToBlur; // TODO Quality up: make the opaque areas "bleed" into the areas we just made transparent // Apply the bokeh blur effect BokehBlur levelBlur = levelConfig.blurEffect; levelBlur.CoCStrength = levelConfig.CoCValue; levelBlur.Radius = blurRadius; // This doesn't generate garbage if the radius value doesn't change. levelBlur.SetInput(0, textureToBlur); levelBlur.SetOutput(blurOutput); levelBlur.Draw(context, "CoC_LoD_Layer_Far_{0}", i); combineLevelsEffect.SetInput(i + 2, blurOutput); //--------------------------------- // Near out-of-focus //--------------------------------- // Negates CoC values and makes background objects transparent thresholdAlphaCoCFront.Parameters.Set(ThresholdAlphaCoCFrontKeys.CoCReference, previousCoC); thresholdAlphaCoCFront.Parameters.Set(ThresholdAlphaCoCFrontKeys.CoCCurrent, levelConfig.CoCValue); thresholdAlphaCoCFront.SetInput(0, downscaledSources[levelConfig.downscaleFactor]); thresholdAlphaCoCFront.SetInput(1, cocLinearDepthTexture); thresholdAlphaCoCFront.SetOutput(alphaTextureToBlur); thresholdAlphaCoCFront.Draw(context, "Alphaize_Near_{0}", i); textureToBlur = alphaTextureToBlur; // Apply the bokeh blur effect levelBlur.SetInput(0, textureToBlur); levelBlur.SetOutput(blurOutputFront); levelBlur.Draw(context, "CoC_LoD_Layer_Near_{0}", i); combineLevelsFrontEffect.SetInput(i + 2, blurOutputFront); previousCoC = levelConfig.CoCValue; } // Far out-of-focus: each pixel, depending on its CoC, interpolates its color from // the original color buffer and blurred buffer(s). combineLevelsEffect.Parameters.Set(CombineLevelsFromCoCShaderKeys.CoCLevelValues, combineShaderCocLevelValues); combineLevelsEffect.SetOutput(outputTexture); combineLevelsEffect.Draw(context, "CoCLevelCombineInterpolation"); // Finally add front out-of-focus objects on the top of the scene // TODO Quality up: instead of merging all the layers for each pixel, merge only // the relevant layer(s) closest to the pixel CoC. GraphicsDevice.SetBlendState(GraphicsDevice.BlendStates.AlphaBlend); combineLevelsFrontEffect.SetOutput(outputTexture); combineLevelsFrontEffect.Draw(context, "CoCLevelCombineInterpolationFront"); GraphicsDevice.SetBlendState(GraphicsDevice.BlendStates.Default); // Release any reference downscaledSources.Clear(); }