Warning
This is an Advanced guide. It assumes reasonable familiarity with both Keysight and any other relevant tools such as OBS, and how to navigate your file directory.
Welcome! This guide will explain how to use a special rendering mode in Keysight to achieve perfect semi-transparency inside OBS, with no use of luma/chroma keying and no nasty artifacts! This filter will even disable dynamic masking of effects and show Keysight as a single transparency when you enter the menu, in case you wanted to enter the menu while streaming.
Requirements
- OBS version 28.0+
- Windows: this Shaderfilter v1.21 plugin folder⬇
- MacOS: this Shaderfilter v1.21 plugin installer⬇
- This special shader text file⬇
Warning
You cannot use the more recent v2+ releases of OBS Shaderfilter for this! They changed some internals on how colour is sampled and how variables are shown to you, and I haven't bothered porting the filter over to the new version and writing this guide to support both.
Warning
You cannot use Streamlabs OBS for this, due to a lack of plugin support. Consider switching to proper OBS, it really is a lot better and not hard to mirror the functionality of Streamlabs OBS!
TL;DR
- Install Shaderfilter v1.X for OBS
- Enable the (Advanced) Scene > Semi-transparency tab in Keysight
- Inside that tab, enable Mask mode plus toggle
- Easy, less good way:
-
- In OBS, add a "User-defined shader" to the Keysight source
-
- Load shader from file, point it at the downloaded
Keysight mask mode+.txt
file
- Load shader from file, point it at the downloaded
- Harder, perfect way:
-
- Make two scenes in OBS, called "Keysight Glow" and "Keysight Geo"
-
- Add the same Keysight source to both scenes
-
- On each scene, add a "User-defined shader" filter and point it at the downloaded
Keysight mask mode+.txt
file
- On each scene, add a "User-defined shader" filter and point it at the downloaded
-
- On the "Keysight Glow" shader filter, uncheck "Show geometry" and "Use opacity on glow"
-
- On the "Keysight Geometry" shader filter, uncheck "Show glow"
-
- Anywhere you want Keysight, import both "Keysight Glow" and "Keysight Geo" as a scene source with Glow on top
-
- Right click on "Keysight Glow" and set blending mode to "add"
Process
Installing the Shaderfilter plugin
Windows
- Make sure OBS is closed
- Open the downloaded zip
- Open another file browser window (
Ctrl+N
) and navigate to the OBS install. This is usuallyC:\Program Files\obs-studio
- Copy the two folders inside the zip over into the OBS install directory by dragging them
MacOS
- Simply double click the downloaded .pkg file!
Configuring Keysight
- Enable the Advanced menu mode if you haven't already
- Navigate to (Advanced) Scene > Semi-transparency
- Enable the toggle on this tab
- Inside the tab, enable Mask mode plus
- Done!
You can also toggle semi-transparency using M
on your keyboard. You'll know it's working if, when you close the menu, the colours are really weird and very washed out for the background, and very dark for anything like notes or the piano. You should also see a tiny white square in the top left while in the menu.
The provided toggles in Keysight can be used to mask out different components. For example, using using the Scene > Overlay to add a logo to your presets, you may want to enable Mask overlay. Alternatively, you could get creative and do something like inverse masking where the Note objects are not masked but everything else is, allowing notes to reveal something underneath Keysight.
OBS setup (easy, less perfect)
- Add a Game capture or Window capture source and point it at Keysight if you have not already
- Add a "User-defined shader" filter to that source (this is the filter added by the plugin. If you don't see it, the plugin has not been installed correctly)
- Tick the Load shader text from file option
- Click Browse and navigate to wherever you saved the Keysight mask mode+.txt⬇ shader file. I recommend not just keeping it in your Downloads folder! Move it somewhere for safe-keeping first
- Done! Keysight should now be semi-transparent.
This method is much faster to get set up, and looks completely fine under 99% of circumstances (and I've never seen anyone complain about the 1% of times it isn't). Still, I do recommend using the longer method outlined below if you want Keysight to look at good as possible while being overlaid onto other sources.
The issue this method has is giving dark fringes to some semi-transparent Keysight elements like particles when overlaid on a bright background. This is a worst-case scenario example, with a very bright background and very coloured semi-transparent elements). Compare the appearance of the Light bar and Tendril Particles as they overlay the background image:
OBS setup (harder, but perfect)
- Create two new OBS scenes: "Keysight Geometry" and "Keysight Glow"
- Add the same Keysight capture (either Game or Window) to both scenes
- Add a "User-defined shader" filter to each SCENE. Do not add any filters to the Keysight source itself!
- On both filters, click Browse and navigate to wherever you saved the Keysight mask mode+.txt⬇ shader file
Danger
Right click on the SCENE and add the filter there! Yes I am using giant angry red boxes because everyone f$%&ing puts the filter on the Keysight source and wonders why it doesn't work properly >:(
- On the "Keysight Geometry" scene's shader filter, untick Show glow on the filter options
- On the "Keysight Glow" scene's shader filter, untick Show geometry and Use opacity on glow
- In any other scene you want to have Keysight showing, import both "Keysight Geometry" and "Keysight Glow" as scene sources
- Right click on the "Keysight Glow" scene source and set the "Blend mode" to "Add"
- Done!
This method ensures that brightly coloured semi-transparent elements in Keysight do not cause dark fringes on bright backgrounds. It does, however, force you to import two sources and edit their transforms individually in any scenes you want Keysight to appear in.
Theory
This is one of the more ridiculous hacky things I got working with Keysight, so I wanted to share some behind-the-scenes!
Info
This section of the guide contains zero useful information, and is only worth reading if you're curious about why the colours are so strange while semi-transparency is active in Keysight, and how the whole thing came about.
How it works
The core problem to semi-transparency is that we are restricted to just using the Red/Green/Blue colour channels; Unreal Engine will not output an Alpha channel to the screen. While we do want things to be halfway transparent eventually (like particles), the first hurdle is to get a binary (0 or 1) mask encoded into our colour channels somehow. Then, any non-masked semi-transparent things need to apply their brightness as an opacity.
Ancient history
Waaaay back in the day (before June 24th 2021), "Mask Mode" would render the scene twice and look like this:
And then you'd use two scenes to isolate each half and stretch them back out, and then use StreamFX to multiply a colour channel from the mask scene against the opacity of the main scene.
This was awful.
You lost 1/2 your vertical resolution, the dark fringes were insane, and it was enormously fiddly and complicated to implement in OBS. It was also expensive to render!
Mask mode shader v1
On June 24th 2021, Keysight 1.3.0 released with an updated Mask Mode, born from an idea of: "what if I could encode the binary mask onto a colour channel directly, and ditch the stupid splitscreen thing?"
And it worked! It's still the default mode in Keysight too, as the later Mask Mode+ was added almost 4 years later and I didn't want any existing Mask Mode users to suddenly have a broken Keysight implementation without going and hitting a toggle in the settings somewhere.
The binary mask is encoded onto the blue channel, essentially multiplying the blue channel of the render by 0.5 and then adding 0.5 anywhere there is geometry that needs to be masked. On the OBS-side, a filter then detects if a pixel is above or below 0.5 to get a masking value, and corrects the colours back to what they should be.
Info
The actual multiplier used is 0.495, and the threshold is 0.4975. This gives a bit of necessary wiggle room. I didn't actually think this idea would work at first due to assuming OBS reads the source's colours as 8-bits per channel (and thus taking up one bit out of the blue channel and having to threshold it would give you horrible colour banding), but fortunately OBS seems to read colour as a raw float and you sort of have infinite colour depth to mess around with things before it gets encoded to standard 8-bit sRGB.
Challenges
This approach does have some flaws:
- If you re-scale the source, the filter that masks and corrects colours is now operating on pixels that are not 1:1 with Keysight's output. This gives you blue fringes around geometry quite often due to the threshold on the blue channel occuring on "smeared" pixels through resampling. I saw this all the time, either because people would just freely manipulate the Keysight source like any other or because Keysight was rendering to a different resolution to their OBS scene's canvas (eg. 1440p render to 1080p canvas).
- If you open the menu while in Mask Mode, the menu doesn't have any colour adjustment and Mask Mode is now "correcting" colours that should not be corrected. This results in horrible menu colours if you ever opened the menu while on stream and in Mask Mode.
- I really didn't expect this one, but since there's a keyboard shortcut of
M
to toggle Mask Mode, I saw several instances of people turning on Mask Mode without realising and just assuming that's how Keysight's colours were supposed to look! And then were confused as to why their notes were always blued, and their particles always yellowed.
Mask mode plus
Mask Mode Plus went live on the beta branch on April 3rd 2025. As part of working on the 1.6.3g update, I wanted to fix those longstanding issues above and I'd had some ideas floating around for a long while on how to do it.
More obvious colour modification
With the Mask mode plus toggle enabled, the same blue-channel masking is now applied in the inverse to all colour channels. This means semi-transparent areas (eg. the backdrop) are always at least a middle-grey in brightness, and then geometry is darker than expected. This was to make the mask mode colour modification much more noticeable and undesirable if you accidentally turned it on!
Menu colours
To fix the menu colours, I added a tiny patch in the very top left of the screen that is black when the menu is closed, and white when the menu is open. This essentially acts as a little flag for the shader filter in OBS to know if the menu is open or not. The filter even copies the colours of adjacent pixels to the patch and applies them to that corner, completely hiding it from view to anyone just looking at the OBS output. While the menu is open, Mask mode plus colour modification is disabled in Keysight and colour correction is also disabled in OBS, and instead displays a given single opacity for the entire window. This allows you to safely open the menu while live, and either show everything perfectly clearly or completely hide the menu (and Keysight scene) from viewers while you mess with stuff!
Borders on re-scaled sources
Finally, to fix the border issue on non-1:1 Keysight sources, the masking analysis uses a customised sampling UV that forces all pixels to use their original full-scale pixel location. This one is really hard to explain and have it make sense to a lay audience, so if you know about UVs and image sampling, just go read the source code to see how it works!
Info
The fixed-borders actually got back-ported to the Mask Mode v1 shader packaged with Keysight, on the offchance that people were pointing their shader filters to this file and I could silently fix that issue without anyone having to do anything.
Source code
The following is the raw HLSL-based shader code for Mask Mode+.
Info
Disclaimer: I consider myself pretty good with materials and shaders in a node-based graphical editor like Unreal Engine or Blender, but I am almost clueless when it comes to writing "real" shader code. The following is likely horrifically un-optimised and very "unrolled" and silly. But it works! And that's the important bit.
//Version 2.0.0 (2025-04-03)
//Made by twitch.tv/Egglyberts (creator of Keysight)
//Integrates with Mask Mode Plus in Keysight to provide perfect semi-transparency
//Works best if implemented as two sources, with glow additive blended and geometry normal blended
//See guide for more info: https://egglyberts.live/keysight/wiki/guide_adv_mask_mode/
uniform bool Show_geometry = true;
uniform bool Show_glow = true;
uniform bool Use_opacity_on_glow = true;
uniform float Menu_opacity = 1.0;
float4 mainImage( VertData v_in ) : TARGET
{
//Threshold is halfway between multiply value in Keysight and 0.5
float threshold = 0.4975;
float mult = 0.495;
float menu_pixels = 2.0;
float4 raw = image.Sample(textureSampler, v_in.uv);
float3 base = (raw.rgb);
float mask;
float alpha = 1.0;
float add;
//Set additive mode bool as easy variable to call
if(Use_opacity_on_glow == true)
add = 0.0;
else
add = 1.0;
//This is dumb but hey, it works
float4 menu_sample = image.Sample(textureSampler, uv_pixel_interval);
float menu = menu_sample.r;
//Force point-sampling to avoid outlines due to masking resampling
float2 intuv = (v_in.uv + (uv_pixel_interval * 0.5)) * (1/uv_pixel_interval);
intuv = round(intuv);
intuv = (intuv * uv_pixel_interval) - (uv_pixel_interval * 0.5);
//Remove the menu square by sampling adjacent to it for that little patch
float2 menu_hide_uv;
if((intuv.x <= (uv_pixel_interval.x * menu_pixels * 2.0)) && (intuv.y <= (uv_pixel_interval.y * menu_pixels * 2.0)))
menu_hide_uv = (intuv + (uv_pixel_interval.x * menu_pixels * 2.0, uv_pixel_interval.y * menu_pixels));
else
menu_hide_uv = intuv;
base = image.Sample(textureSampler, menu_hide_uv);
//Extract mask
if(base.r >= threshold)
mask = 0.0;
else
mask = 1.0;
//Bypass masking if menu is open
if(menu >= 0.5)
alpha = Menu_opacity;
else
//Begin masking
{
base = base + (0.5 * mask);
base = base - (0.5);
base = base * (1 / mult);
}
//Get max-brightness version
float bright_alpha = max(base.r, max(base.g, base.b));
bright_alpha = clamp(bright_alpha - mask, 0.0, 1.0);
float pix_mult = 1.0/bright_alpha;
float3 bright = base * pix_mult;
//Get glow with additive behaviour
float3 glow = lerp(bright, base, add);
if(Show_glow == false)
glow = (glow * 0.0);
bright_alpha = clamp((bright_alpha + add), 0.0, 1.0);
if(Show_glow == false)
bright_alpha = (bright_alpha * 0.0);
glow = clamp(glow, 0.0, 1.0);
//Get geometry
float3 geo = base * mask;
geo = clamp(geo, 0.0, 1.0);
if(Show_geometry == false)
geo = geo * 0.0;
//Mix glow and geo
float3 mix = (geo + glow);
float final_alpha = (Show_geometry * mask) + bright_alpha;
//Do menu switching
mix = lerp(mix, base, menu);
final_alpha = lerp(final_alpha, Menu_opacity, menu);
return float4(mix, final_alpha);
}