Skip to content

Instantly share code, notes, and snippets.

@w0wca7a
Last active December 12, 2025 21:38
Show Gist options
  • Select an option

  • Save w0wca7a/728d56863a20850c57c4c8bad9e33d9c to your computer and use it in GitHub Desktop.

Select an option

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.
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;
}
}
}
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);
}
}
}
// 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;
}
};
@w0wca7a
Copy link
Author

w0wca7a commented Dec 12, 2025

1
2

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