protected override void DrawCore(RenderDrawContext context) { var input = GetInput(0); var output = GetOutput(0); if (input == null || output == null) { return; } var inputDepthTexture = GetInput(1); // Depth // Update the parameters for this post effect if (!Enabled) { if (input != output) { Scaler.SetInput(input); Scaler.SetOutput(output); Scaler.Draw(context); } return; } // If input == output, than copy the input to a temporary texture if (input == output) { var newInput = NewScopedRenderTarget2D(input.Width, input.Height, input.Format); context.CommandList.Copy(input, newInput); input = newInput; } var currentInput = input; // Draw outline before AA if (Outline.Enabled && inputDepthTexture != null) { // Outline var outlineOutput = NewScopedRenderTarget2D(input.Width, input.Height, input.Format); Outline.SetColorDepthInput(currentInput, inputDepthTexture, context.RenderContext.RenderView.NearClipPlane, context.RenderContext.RenderView.FarClipPlane); Outline.SetOutput(outlineOutput); Outline.Draw(context); currentInput = outlineOutput; } var fxaa = Antialiasing as FXAAEffect; bool aaFirst = Bloom != null && Bloom.StableConvolution; bool needAA = Antialiasing != null && Antialiasing.Enabled; // do AA here, first. (hybrid method from Karis2013) if (aaFirst && needAA) { // do AA: if (fxaa != null) { fxaa.InputLuminanceInAlpha = true; } var bufferIndex = 1; if (Antialiasing.RequiresDepthBuffer) { Antialiasing.SetInput(bufferIndex++, inputDepthTexture); } bool requiresVelocityBuffer = Antialiasing.RequiresVelocityBuffer; if (requiresVelocityBuffer) { Antialiasing.SetInput(bufferIndex++, GetInput(6)); } var aaSurface = NewScopedRenderTarget2D(input.Width, input.Height, input.Format); if (Antialiasing.NeedRangeDecompress) { // explanation: // The Karis method (Unreal Engine 4.1x), uses a hybrid pipeline to execute AA. // The AA is usually done at the end of the pipeline, but we don't benefit from // AA for the posteffects, which is a shame. // The Karis method, executes AA at the beginning, but for AA to be correct, it must work post tonemapping, // and even more in fact, in gamma space too. Plus, it waits for the alpha=luma to be a "perceptive luma" so also gamma space. // in our case, working in gamma space created monstruous outlining artefacts around eggageratedely strong constrasted objects (way in hdr range). // so AA works in linear space, but still with gamma luma, as a light tradeoff to supress artefacts. // create a 16 bits target for FXAA: // render range compression & perceptual luma to alpha channel: rangeCompress.SetInput(currentInput); rangeCompress.SetOutput(aaSurface); rangeCompress.Draw(context); Antialiasing.SetInput(0, aaSurface); Antialiasing.SetOutput(currentInput); Antialiasing.Draw(context); // reverse tone LDR to HDR: rangeDecompress.SetInput(currentInput); rangeDecompress.SetOutput(aaSurface); rangeDecompress.Draw(context); } else { Antialiasing.SetInput(0, currentInput); Antialiasing.SetOutput(aaSurface); Antialiasing.Draw(context); } currentInput = aaSurface; } if (AmbientOcclusion.Enabled && inputDepthTexture != null) { // Ambient Occlusion var aoOutput = NewScopedRenderTarget2D(input.Width, input.Height, input.Format); AmbientOcclusion.SetColorDepthInput(currentInput, inputDepthTexture); AmbientOcclusion.SetOutput(aoOutput); AmbientOcclusion.Draw(context); currentInput = aoOutput; } if (LocalReflections.Enabled && inputDepthTexture != null) { var normalsBuffer = GetInput(2); var specularRoughnessBuffer = GetInput(3); if (normalsBuffer != null && specularRoughnessBuffer != null) { // Local reflections var rlrOutput = NewScopedRenderTarget2D(input.Width, input.Height, input.Format); LocalReflections.SetInputSurfaces(currentInput, inputDepthTexture, normalsBuffer, specularRoughnessBuffer); LocalReflections.SetOutput(rlrOutput); LocalReflections.Draw(context); currentInput = rlrOutput; } } if (DepthOfField.Enabled && inputDepthTexture != null) { // DoF var dofOutput = NewScopedRenderTarget2D(input.Width, input.Height, input.Format); DepthOfField.SetColorDepthInput(currentInput, inputDepthTexture); DepthOfField.SetOutput(dofOutput); DepthOfField.Draw(context); currentInput = dofOutput; } if (Fog.Enabled && inputDepthTexture != null) { // Fog var fogOutput = NewScopedRenderTarget2D(input.Width, input.Height, input.Format); Fog.SetColorDepthInput(currentInput, inputDepthTexture, context.RenderContext.RenderView.NearClipPlane, context.RenderContext.RenderView.FarClipPlane); Fog.SetOutput(fogOutput); Fog.Draw(context); currentInput = fogOutput; } // Luminance pass (only if tone mapping is enabled) // TODO: This is not super pluggable to have this kind of dependencies. Check how to improve this var toneMap = colorTransformsGroup.Transforms.Get <ToneMap>(); if (colorTransformsGroup.Enabled && toneMap != null && toneMap.Enabled) { Texture luminanceTexture = null; if (toneMap.UseLocalLuminance) { const int localLuminanceDownScale = 3; // The luminance chain uses power-of-two intermediate targets, so it expects to output to one as well var lumWidth = Math.Min(MathUtil.NextPowerOfTwo(currentInput.Size.Width), MathUtil.NextPowerOfTwo(currentInput.Size.Height)); lumWidth = Math.Max(1, lumWidth / 2); var lumSize = new Size3(lumWidth, lumWidth, 1).Down2(localLuminanceDownScale); luminanceTexture = NewScopedRenderTarget2D(lumSize.Width, lumSize.Height, PixelFormat.R16_Float, 1); luminanceEffect.SetOutput(luminanceTexture); } luminanceEffect.EnableLocalLuminanceCalculation = toneMap.UseLocalLuminance; luminanceEffect.SetInput(currentInput); luminanceEffect.Draw(context); // Set this parameter that will be used by the tone mapping colorTransformsGroup.Parameters.Set(LuminanceEffect.LuminanceResult, new LuminanceResult(luminanceEffect.AverageLuminance, luminanceTexture)); } if (BrightFilter.Enabled && (Bloom.Enabled || LightStreak.Enabled || LensFlare.Enabled)) { Texture brightTexture = NewScopedRenderTarget2D(currentInput.Width, currentInput.Height, currentInput.Format, 1); // Bright filter pass BrightFilter.SetInput(currentInput); BrightFilter.SetOutput(brightTexture); BrightFilter.Draw(context); // Bloom pass if (Bloom.Enabled) { Bloom.SetInput(brightTexture); Bloom.SetOutput(currentInput); Bloom.Draw(context); } // Light streak pass if (LightStreak.Enabled) { LightStreak.SetInput(brightTexture); LightStreak.SetOutput(currentInput); LightStreak.Draw(context); } // Lens flare pass if (LensFlare.Enabled) { LensFlare.SetInput(brightTexture); LensFlare.SetOutput(currentInput); LensFlare.Draw(context); } } bool aaLast = needAA && !aaFirst; var toneOutput = aaLast ? NewScopedRenderTarget2D(input.Width, input.Height, input.Format) : output; // When FXAA is enabled we need to detect whether the ColorTransformGroup should output the Luminance into the alpha or not var luminanceToChannelTransform = colorTransformsGroup.PostTransforms.Get <LuminanceToChannelTransform>(); if (fxaa != null) { if (luminanceToChannelTransform == null) { luminanceToChannelTransform = new LuminanceToChannelTransform { ColorChannel = ColorChannel.A }; colorTransformsGroup.PostTransforms.Add(luminanceToChannelTransform); } // Only enabled when FXAA is enabled and InputLuminanceInAlpha is true luminanceToChannelTransform.Enabled = fxaa.Enabled && fxaa.InputLuminanceInAlpha; } else if (luminanceToChannelTransform != null) { luminanceToChannelTransform.Enabled = false; } // Color transform group pass (tonemap, color grading) var lastEffect = colorTransformsGroup.Enabled ? (ImageEffect)colorTransformsGroup : Scaler; lastEffect.SetInput(currentInput); lastEffect.SetOutput(toneOutput); lastEffect.Draw(context); // do AA here, last, if not already done. if (aaLast) { Antialiasing.SetInput(toneOutput); Antialiasing.SetOutput(output); Antialiasing.Draw(context); } }