protected override void DrawCore(RenderDrawContext context) { // Inputs: Texture colorBuffer = GetSafeInput(0); Texture depthBuffer = GetSafeInput(1); Texture normalsBuffer = GetSafeInput(2); Texture specularRoughnessBuffer = GetSafeInput(3); // Output: Texture outputBuffer = GetSafeOutput(0); // Prepare var temporalCache = Prepare(context, outputBuffer); FlushCache(context.RenderContext.Time.FrameCount); // Get temporary buffers var rayTraceBuffersSize = GetBufferResolution(outputBuffer, RayTracePassResolution); var resolveBuffersSize = GetBufferResolution(outputBuffer, ResolvePassResolution); Texture rayTraceBuffer = NewScopedRenderTarget2D(rayTraceBuffersSize.Width, rayTraceBuffersSize.Height, RayTraceTargetFormat, 1); Texture resolveBuffer = NewScopedRenderTarget2D(resolveBuffersSize.Width, resolveBuffersSize.Height, ReflectionsFormat, 1); // Check if resize depth Texture smallerDepthBuffer = depthBuffer; if (DepthResolution != ResolutionMode.Full) { // Smaller depth buffer improves ray tracing performance. var depthBuffersSize = GetBufferResolution(depthBuffer, DepthResolution); smallerDepthBuffer = NewScopedRenderTarget2D(depthBuffersSize.Width, depthBuffersSize.Height, PixelFormat.R32_Float, 1); depthPassShader.SetInput(0, depthBuffer); depthPassShader.SetOutput(smallerDepthBuffer); depthPassShader.Draw(context, "Downscale Depth"); } // Blur Pass Texture blurPassBuffer; if (UseColorBufferMips) { // Note: using color buffer mips maps helps with reducing artifacts // and improves resolve pass performance (faster color texture lookups, less cache misses) // Also for high surface roughness values it adds more blur to the reflection tail which looks more realistic. // Get temp targets var colorBuffersSize = new Size2(outputBuffer.Width / 2, outputBuffer.Height / 2); int colorBuffersMips = Texture.CalculateMipMapCount(MipMapCount.Auto, colorBuffersSize.Width, colorBuffersSize.Height); Texture colorBuffer0 = NewScopedRenderTarget2D(colorBuffersSize.Width, colorBuffersSize.Height, ReflectionsFormat, colorBuffersMips); Texture colorBuffer1 = NewScopedRenderTarget2D(colorBuffersSize.Width / 2, colorBuffersSize.Height / 2, ReflectionsFormat, colorBuffersMips - 1); int colorBuffer1MipOffset = 1; // For colorBuffer1 we could use one mip less (optimized) // Cache per color buffer mip views int colorBuffer0Mips = colorBuffer0.MipLevels; if (cachedColorBuffer0Mips == null || cachedColorBuffer0Mips.Length != colorBuffer0Mips || cachedColorBuffer0Mips[0].ParentTexture != colorBuffer0) { cachedColorBuffer0Mips?.ForEach(view => view?.Dispose()); cachedColorBuffer0Mips = new Texture[colorBuffer0Mips]; for (int mipIndex = 0; mipIndex < colorBuffer0Mips; mipIndex++) { cachedColorBuffer0Mips[mipIndex] = colorBuffer0.ToTextureView(ViewType.Single, 0, mipIndex); } } int colorBuffer1Mips = colorBuffer1.MipLevels; if (cachedColorBuffer1Mips == null || cachedColorBuffer1Mips.Length != colorBuffer1Mips || cachedColorBuffer1Mips[0].ParentTexture != colorBuffer1) { cachedColorBuffer1Mips?.ForEach(view => view?.Dispose()); cachedColorBuffer1Mips = new Texture[colorBuffer1Mips]; for (int mipIndex = 0; mipIndex < colorBuffer1Mips; mipIndex++) { cachedColorBuffer1Mips[mipIndex] = colorBuffer1.ToTextureView(ViewType.Single, 0, mipIndex); } } // Clone scene frame to mip 0 of colorBuffer0 Scaler.SetInput(0, colorBuffer); Scaler.SetOutput(cachedColorBuffer0Mips[0]); Scaler.Draw(context, "Copy frame"); // Downscale with gaussian blur for (int mipLevel = 1; mipLevel < colorBuffersMips; mipLevel++) { // Blur H var srcMip = cachedColorBuffer0Mips[mipLevel - 1]; var dstMip = cachedColorBuffer1Mips[mipLevel - colorBuffer1MipOffset]; blurPassShaderH.SetInput(0, srcMip); blurPassShaderH.SetOutput(dstMip); blurPassShaderH.Draw(context, "Blur H"); // Blur V srcMip = dstMip; dstMip = cachedColorBuffer0Mips[mipLevel]; blurPassShaderV.SetInput(0, srcMip); blurPassShaderV.SetOutput(dstMip); blurPassShaderV.Draw(context, "Blur V"); } blurPassBuffer = colorBuffer0; } else { // Don't use color buffer with mip maps blurPassBuffer = colorBuffer; cachedColorBuffer0Mips?.ForEach(view => view?.Dispose()); cachedColorBuffer1Mips?.ForEach(view => view?.Dispose()); } // Ray Trace Pass rayTracePassShader.SetInput(0, colorBuffer); rayTracePassShader.SetInput(1, smallerDepthBuffer); rayTracePassShader.SetInput(2, normalsBuffer); rayTracePassShader.SetInput(3, specularRoughnessBuffer); rayTracePassShader.SetOutput(rayTraceBuffer); rayTracePassShader.Draw(context, "Ray Trace"); // Resolve Pass resolvePassShader.SetInput(0, blurPassBuffer); resolvePassShader.SetInput(1, ResolvePassResolution == ResolutionMode.Full ? depthBuffer : smallerDepthBuffer); resolvePassShader.SetInput(2, normalsBuffer); resolvePassShader.SetInput(3, specularRoughnessBuffer); resolvePassShader.SetInput(4, rayTraceBuffer); resolvePassShader.SetOutput(resolveBuffer); resolvePassShader.Draw(context, "Resolve"); // Temporal Pass Texture reflectionsBuffer = resolveBuffer; if (TemporalEffect) { var temporalSize = outputBuffer.Size; temporalCache.Resize(GraphicsDevice, ref temporalSize); Texture temporalBuffer0 = NewScopedRenderTarget2D(temporalSize.Width, temporalSize.Height, ReflectionsFormat, 1); temporalPassShader.SetInput(0, resolveBuffer); temporalPassShader.SetInput(1, temporalCache.TemporalBuffer); temporalPassShader.SetInput(2, depthBuffer); temporalPassShader.SetOutput(temporalBuffer0); temporalPassShader.Draw(context, "Temporal"); context.CommandList.Copy(temporalBuffer0, temporalCache.TemporalBuffer); // TODO: use Texture.Swap from ContentStreaming branch to make it faster! reflectionsBuffer = temporalCache.TemporalBuffer; } // Combine Pass combinePassShader.SetInput(0, colorBuffer); combinePassShader.SetInput(1, depthBuffer); combinePassShader.SetInput(2, normalsBuffer); combinePassShader.SetInput(3, specularRoughnessBuffer); combinePassShader.SetInput(4, reflectionsBuffer); combinePassShader.SetOutput(outputBuffer); combinePassShader.Draw(context, "Combine"); #if SSLR_DEBUG if (DebugMode != DebugModes.None) { // Debug preview of temp targets switch (DebugMode) { case DebugModes.RayTrace: Scaler.SetInput(0, rayTraceBuffer); break; case DebugModes.Resolve: Scaler.SetInput(0, resolveBuffer); break; case DebugModes.Temporal: if (temporalCache != null) { Scaler.SetInput(0, temporalCache.TemporalBuffer); } break; } Scaler.SetOutput(outputBuffer); Scaler.Draw(context); } #endif }
// Naive approach: 6 passes protected void DrawCoreNaive(RenderDrawContext 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.EffectInstance.UpdateEffect(context.GraphicsDevice); 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(RenderDrawContext contextParameters) { var input = GetInput(0); var output = GetOutput(0) ?? input; if (input == null || StreakCount == 0 || IterationCount == 0) { return; } // Downscale to 1 / 4 var halfSize = input.Size.Down2(); var halfSizeRenderTarget = NewScopedRenderTarget2D(halfSize.Width, halfSize.Height, input.Format); Scaler.SetInput(input); Scaler.SetOutput(halfSizeRenderTarget); Scaler.Draw(contextParameters, "Downsize to 0.5"); var fourthSize = halfSize.Down2(); var fourthSizeRenderTarget = NewScopedRenderTarget2D(fourthSize.Width, fourthSize.Height, input.Format); Scaler.SetInput(halfSizeRenderTarget); Scaler.SetOutput(fourthSizeRenderTarget); Scaler.Draw(contextParameters, "Downsize to 0.25"); var originalDownsize = fourthSizeRenderTarget; // Put all the streaks in an accumulation buffer var accumulationBuffer = NewScopedRenderTarget2D(fourthSizeRenderTarget.Description); // 2 scratch textures to ping-pong between var scratchTextureA = NewScopedRenderTarget2D(fourthSizeRenderTarget.Description); var scratchTextureB = NewScopedRenderTarget2D(fourthSizeRenderTarget.Description); var writeToScratchA = true; Vector2 direction; Texture currentInput = null, currentOutput = null; Vector3 colorAberration; colorAberration.X = (float)MathUtil.Lerp(1.0, ColorAberrationCoefficients.X, ColorAberrationStrength); colorAberration.Y = (float)MathUtil.Lerp(1.0, ColorAberrationCoefficients.Y, ColorAberrationStrength); colorAberration.Z = (float)MathUtil.Lerp(1.0, ColorAberrationCoefficients.Z, ColorAberrationStrength); lightStreakEffect.Parameters.Set(LightStreakShaderKeys.ColorAberrationCoefficients, colorAberration); for (int streak = 0; streak < StreakCount; streak++) { // Treats one streak // Direction vector float angle = MathUtil.DegreesToRadians(Phase) + streak * MathUtil.TwoPi / StreakCount; direction.X = (float)Math.Cos(angle); direction.Y = (float)Math.Sin(angle); // Extends the length recursively for (int level = 0; level < IterationCount; level++) { // Calculates weights and attenuation factors for all the taps float totalWeight = 0; float passLength = (float)Math.Pow(TapsPerIteration, level); for (int i = 0; i < TapsPerIteration; i++) { tapOffsetsWeights[i].X = i * passLength; tapOffsetsWeights[i].Y = (float)Math.Pow(MathUtil.Lerp(0.7f, 1.0f, Attenuation), i * passLength); totalWeight += tapOffsetsWeights[i].Y; } // Normalizes the weights for (int i = 0; i < TapsPerIteration; i++) { tapOffsetsWeights[i].Y /= totalWeight; } currentInput = writeToScratchA ? scratchTextureB : scratchTextureA; if (level == 0) { currentInput = originalDownsize; } currentOutput = writeToScratchA ? scratchTextureA : scratchTextureB; lightStreakEffect.Parameters.Set(LightStreakKeys.Count, TapsPerIteration); lightStreakEffect.Parameters.Set(LightStreakKeys.AnamorphicCount, AnamorphicOffsetsWeights.Length); lightStreakEffect.Parameters.Set(LightStreakShaderKeys.TapOffsetsWeights, tapOffsetsWeights); lightStreakEffect.Parameters.Set(LightStreakShaderKeys.AnamorphicOffsetsWeight, AnamorphicOffsetsWeights); lightStreakEffect.Parameters.Set(LightStreakShaderKeys.Direction, direction); lightStreakEffect.SetInput(0, currentInput); lightStreakEffect.SetOutput(currentOutput); lightStreakEffect.Draw(contextParameters, lightStreakDebugStrings[(streak * IterationCount) + level]); writeToScratchA = !writeToScratchA; } // Writes this streak to the accumulation buffer if (streak > 0) { combiner.BlendState = BlendStates.Additive; } combiner.SetInput(0, currentOutput); combiner.Factors[0] = (1f / StreakCount) * 0.2f * Amount; combiner.SetOutput(accumulationBuffer); ((RendererBase)combiner).Draw(contextParameters); combiner.BlendState = BlendStates.Default; } // All the light streaks have been drawn to the accumulation buffer. // Upscales and blurs the accumulation buffer. var accumulationUpscaled = NewScopedRenderTarget2D(halfSizeRenderTarget.Description); Scaler.SetInput(accumulationBuffer); Scaler.SetOutput(accumulationUpscaled); ((RendererBase)Scaler).Draw(contextParameters); blur.Radius = 3; blur.SetInput(accumulationUpscaled); blur.SetOutput(accumulationUpscaled); ((RendererBase)blur).Draw(contextParameters); // Adds the result to the original color buffer. Scaler.BlendState = BlendStates.Additive; Scaler.SetInput(accumulationUpscaled); Scaler.SetOutput(output); ((RendererBase)Scaler).Draw(contextParameters); Scaler.BlendState = BlendStates.Default; }