Last active
December 12, 2025 21:38
-
-
Save w0wca7a/728d56863a20850c57c4c8bad9e33d9c to your computer and use it in GitHub Desktop.
Custom post processing image effect in Stride game engine thats working in Game studio and can be configured in Graphics compositor. Used shader https://www.shadertoy.com/view/wllBDM. You must have Stride extension in VS for shader keys generation.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| using System; | |
| using System.ComponentModel; | |
| using Stride.Core; | |
| using Stride.Graphics; | |
| using Stride.Rendering; | |
| using Stride.Core.Annotations; | |
| using Stride.Rendering.Compositing; | |
| using Stride.Rendering.Images; | |
| namespace MyGame.PostEffect | |
| { | |
| /// <summary> | |
| /// A default bundle of <see cref="ImageEffect"/>. | |
| /// </summary> | |
| [DataContract("CustomPostProcessingEffects")] | |
| [Display("Custom Post-processing effects")] | |
| public sealed class CustomPostProcessingEffects : ImageEffect, IImageEffectRenderer, IPostProcessingEffects | |
| { | |
| public MyEffectExample MyEffect{ get; set; } | |
| [DataMember(70)] | |
| [Category] | |
| public ColorTransformGroup ColorTransforms => colorTransformsGroup; | |
| [DataMember(-100), Display(Browsable = false)] | |
| [NonOverridable] | |
| public Guid Id { get; set; } = Guid.NewGuid(); | |
| private ColorTransformGroup colorTransformsGroup; | |
| public CustomPostProcessingEffects(IServiceRegistry services) : this(RenderContext.GetShared(services)) { } | |
| public CustomPostProcessingEffects() | |
| { | |
| colorTransformsGroup = new ColorTransformGroup(); | |
| MyEffect = new MyEffectExample(); | |
| } | |
| public CustomPostProcessingEffects(RenderContext context) : this() { Initialize(context); } | |
| public bool RequiresVelocityBuffer => true; | |
| public bool RequiresNormalBuffer => true; | |
| public bool RequiresSpecularRoughnessBuffer => false; | |
| public void Collect(RenderContext context) { } | |
| protected override void InitializeCore() | |
| { | |
| base.InitializeCore(); | |
| MyEffect = ToLoadAndUnload(MyEffect); | |
| colorTransformsGroup = ToLoadAndUnload(colorTransformsGroup); | |
| } | |
| public void Draw(RenderDrawContext drawContext, RenderOutputValidator outputValidator, Span<Texture> inputs, Texture inputDepthStencil, Texture outputTarget) | |
| { | |
| var colorIndex = outputValidator.Find<ColorTargetSemantic>(); | |
| if (colorIndex < 0) return; | |
| SetInput(0, inputs[colorIndex]); | |
| SetInput(1, inputDepthStencil); | |
| var normalsIndex = outputValidator.Find<NormalTargetSemantic>(); | |
| if (normalsIndex >= 0) | |
| { | |
| SetInput(2, inputs[normalsIndex]); | |
| } | |
| var specularRoughnessIndex = outputValidator.Find<SpecularColorRoughnessTargetSemantic>(); | |
| if (specularRoughnessIndex >= 0) | |
| { | |
| SetInput(3, inputs[specularRoughnessIndex]); | |
| } | |
| var reflectionIndex0 = outputValidator.Find<OctahedronNormalSpecularColorTargetSemantic>(); | |
| var reflectionIndex1 = outputValidator.Find<EnvironmentLightRoughnessTargetSemantic>(); | |
| if (reflectionIndex0 >= 0 && reflectionIndex1 >= 0) | |
| { | |
| SetInput(4, inputs[reflectionIndex0]); | |
| SetInput(5, inputs[reflectionIndex1]); | |
| } | |
| var velocityIndex = outputValidator.Find<VelocityTargetSemantic>(); | |
| if (velocityIndex != -1) | |
| { | |
| SetInput(6, inputs[velocityIndex]); | |
| } | |
| SetOutput(outputTarget); | |
| Draw(drawContext); | |
| } | |
| 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; | |
| if (MyEffect.Enabled) | |
| { | |
| // blurred output | |
| var myEffectOut = NewScopedRenderTarget2D(currentInput.Width, currentInput.Height, currentInput.Format); | |
| // color input | |
| MyEffect.SetInput(0, currentInput); | |
| // velocity input | |
| MyEffect.SetInput(1, GetInput(6)); | |
| // depth input | |
| MyEffect.SetInput(2, inputDepthTexture); | |
| MyEffect.SetOutput(myEffectOut); | |
| MyEffect.Draw(context); | |
| currentInput = myEffectOut; | |
| } | |
| var toneOutput = output; | |
| // Color transform group pass (tonemap, color grading) | |
| var lastEffect = colorTransformsGroup.Enabled ? (ImageEffect)colorTransformsGroup : Scaler; | |
| lastEffect.SetInput(currentInput); | |
| lastEffect.SetOutput(toneOutput); | |
| lastEffect.Draw(context); | |
| } | |
| public void DisableAll() | |
| { | |
| MyEffect.Enabled = false; | |
| colorTransformsGroup.Enabled = false; | |
| } | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| using Stride.Core; | |
| using Stride.Core.Annotations; | |
| using Stride.Games; | |
| using Stride.Graphics; | |
| using Stride.Rendering; | |
| using Stride.Rendering.Images; | |
| namespace MyGame.PostEffect | |
| { | |
| [DataContract("MyEffectExample")] | |
| public class MyEffectExample : ImageEffect | |
| { | |
| [DataMember] | |
| [DataMemberRange(0, 6, 0.1, 0.2, 1)] | |
| public float ScanSpeedAdd { get; set; } = 6.0f; | |
| [DataMember] | |
| [DataMemberRange(0, 0.5, 0.1, 0.15, 1)] | |
| public float ScanlineSize { get; set; } = 0.1f; | |
| [DataMember] | |
| [DataMemberRange(0, 1, 0.1, 0.2, 1)] | |
| public float WhiteIntensity { get; set; } = 0.8f; | |
| [DataMember] | |
| [DataMemberRange(0, 1, 0.1, 0.2, 1)] | |
| public float AnaglyphIntensity { get; set; } = 0.5f; | |
| private ImageEffectShader OldMonitorScanlines; | |
| protected override void InitializeCore() | |
| { | |
| base.InitializeCore(); | |
| OldMonitorScanlines = ToLoadAndUnload(new ImageEffectShader("OldMonitorScanlines")); | |
| } | |
| protected override void DrawCore(RenderDrawContext context) | |
| { | |
| var GlobalTime = Services.GetService<IGame>().UpdateTime.Elapsed.TotalMilliseconds; | |
| //inputs | |
| Texture colorBuffer = GetSafeInput(0); | |
| //Texture velocityBuffer = GetSafeInput(1); | |
| //Texture depthBuffer = GetSafeInput(2); | |
| //output | |
| Texture outputBuffer = GetSafeOutput(0); | |
| OldMonitorScanlines.SetInput(0, colorBuffer); | |
| OldMonitorScanlines.SetOutput(outputBuffer); | |
| OldMonitorScanlines.Parameters.Set(OldMonitorScanlinesKeys.iTime, (float)GlobalTime); | |
| OldMonitorScanlines.Parameters.Set(OldMonitorScanlinesKeys.scanSpeedAdd, ScanSpeedAdd); | |
| OldMonitorScanlines.Parameters.Set(OldMonitorScanlinesKeys.lineCut, ScanlineSize); | |
| OldMonitorScanlines.Parameters.Set(OldMonitorScanlinesKeys.whiteIntensity, WhiteIntensity); | |
| OldMonitorScanlines.Parameters.Set(OldMonitorScanlinesKeys.anaglyphIntensity, AnaglyphIntensity); | |
| OldMonitorScanlines.Draw(context); | |
| } | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // Author: devrique. | |
| // Code: https://www.shadertoy.com/view/wllBDM | |
| // Name: Old Monitor Scanlines. | |
| // Description: Screen scanlines, like some kind of retro monitor. | |
| // I got the inspiration to do it playing Stay. :) | |
| shader OldMonitorScanlines : ImageEffectShader | |
| { | |
| // Set to 0.0 to stop animation. | |
| // Only integer numbers with float format, or else the animation cuts! | |
| float scanSpeedAdd = 6.0f; | |
| float iTime; // we must use Services.GetService<IGame>().UpdateTime becouse Global.Time will not work | |
| // Change this value to change scanline size (> = smaller lines). | |
| float lineCut = 0.1f; | |
| // Reduce 'anaglyphIntensity' value to reduce eye stress. | |
| // Adding this two values should result in 1.0. | |
| float whiteIntensity = 0.8f; | |
| float anaglyphIntensity = 0.5f; | |
| // Anaglyph colors. | |
| float3 col_r = float3(0.0f, 1.0f, 1.0f); | |
| float3 col_l = float3(1.0f, 0.0f, 0.0f); | |
| stage override float4 Shading() | |
| { | |
| // Normalized pixel coordinates (from 0 to 1). | |
| float2 uv = streams.TexCoord; | |
| float2 uv_right = float2(uv.x + 0.01, uv.y + 0.01); | |
| float2 uv_left = float2(uv.x - 0.01, uv.y - 0.01); | |
| // Black screen. | |
| float3 col = float3(0.0, 0.0, 0.0); // ve3(0.0) in orig | |
| // Measure speed. | |
| float scanSpeed = (frac(iTime) * 2.5 / 40.0) * scanSpeedAdd; // fract in orig | |
| // Generate scanlines. | |
| float3 scanlines = float3(1.0, 1.0, 1.0) * abs(cos((uv.y + scanSpeed) * 100.0)) - lineCut; | |
| // Generate anaglyph scanlines. | |
| float3 scanlines_right = col_r * abs(cos((uv_right.y + scanSpeed) * 100.0)) - lineCut; | |
| float3 scanlines_left = col_l * abs(cos((uv_left.y + scanSpeed) * 100.0)) - lineCut; | |
| // First try; a strange mess. | |
| //vec3 scanlines = cos(cos(sqrt(uv.y)*tan(iTime / 10000.0) * 100.0 * 10.0) * vec3(1.0) * 100.0); | |
| col = smoothstep(0.1, 0.7, scanlines * whiteIntensity) | |
| + smoothstep(0.1, 0.7, scanlines_right * anaglyphIntensity) | |
| + smoothstep(0.1, 0.7, scanlines_left * anaglyphIntensity); | |
| // Deform test (WIP, thanks to 'ddoodm' for its Simple Fisheye Distortion!). | |
| float2 eyefishuv = (uv - 0.5) * 2.5; | |
| float deform = (1.0 - eyefishuv.y*eyefishuv.y) * 0.02 * eyefishuv.x; | |
| //deform = 0.0; | |
| // Add texture to visualize better the effect. | |
| //float4 texture1 = texture(iChannel0, float2(uv.x - deform*0.95, uv.y)); | |
| float4 texture0 = Texture0.Sample(Sampler, float2(uv.x - deform*0.95, uv.y)); | |
| // Add vignette effect. | |
| float bottomRight = pow(uv.x, uv.y * 100.0); | |
| float bottomLeft = pow(1.0 - uv.x, uv.y * 100.0); | |
| float topRight = pow(uv.x, (1.0 - uv.y) * 100.0); | |
| //float topLeft = pow(uv.y, uv.x * 100.0); // in original | |
| float topLeft = pow(1.0 - uv.x, (1.0 - uv.y) * 100.0); // fixed vigneting in left top corner | |
| float screenForm = bottomRight | |
| + bottomLeft | |
| + topRight | |
| + topLeft; | |
| // Invert screenForm color. | |
| float3 col2 = 1.0-float3(screenForm, screenForm, screenForm); | |
| // Output to screen. | |
| // Invert last 0.1 and 1.0 positions for image processing. | |
| float4 fragColor = texture0 + float4((smoothstep(0.1, 0.9, col) * 0.1), 1.0); | |
| fragColor = float4(fragColor.rgb * col2, fragColor.a); | |
| return fragColor; | |
| } | |
| }; |
Author
w0wca7a
commented
Dec 12, 2025


Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment