Technical
Writing Shaders
An effect is one .cvfx file. No build step, no toolchain, no SDK — write it in a text editor, drop it in, and it shows up in the post-processing list with its own controls.
Getting a file into the app
Any of these work:
- Import… in the launcher's Post-processing section, or in the in-stream menu (Tab).
- Drag the file onto the window.
- Drop it in the shaders folder yourself and press Reload.
The folder is:
~/Library/Application Support/Uncapped/Uncapped/shaders/A copy of the built-in grade is written there as color-grade.cvfx.example the first time the app runs. It's the best thing to read first — a real multi-pass effect rather than a toy.
What an effect can and can't do
Effects run on the finished video frame, in image space. Colour grading, bloom, sharpening, grain, scanlines, vignettes, chromatic aberration and CRT emulation all work.
Depth-dependent effects can't be ported here at all. SSAO, depth of field and ray-traced GI read a game's depth buffer. Uncapped receives an HDMI stream — flat pixels, with the 3D information gone long before capture. No version of the app can add that back.
The smallest effect
//! name: Saturation
uniform float Amount : default=1.0, min=0.0, max=2.0, label="Saturation";
float4 effect(float2 uv) {
float4 c = sample_input(uv);
c.rgb = lerp(float3(luminance(c.rgb)), c.rgb, Amount);
return c;
}A file with no //! pass: lines is a single pass, and the function must be called effect.
Metadata
//! name: Colour & Bloom what the menu shows
//! author: someone
//! description: One line under the checkbox.
//! pass: blurH scale=0.5 declares a passUnknown //! directives are ignored, so a file written for a later version still loads.
Uniforms become controls
uniform <type> <Name> : default=..., min=..., max=..., label="...";| Types | float, float2, float3, float4, int, bool |
|---|---|
| default | 0.5, or (1.0, 0.95, 0.9) — a single value fills every component |
| min / max | Slider range. Ignored for bool. |
| label | What the menu shows. Defaults to the uniform's name. |
| widget=color | Gives a float3/float4 a colour picker instead of sliders. |
Each one becomes a widget automatically — an effect ships its own UI. Values are saved per effect and survive edits to the file: renaming a uniform resets that one and leaves the rest alone.
Built in for you
| sample_input(uv) | The image coming into this effect. |
|---|---|
| sample_pass(name, uv) | The output of an earlier pass in this effect. |
| resolution | Size in pixels of the pass being rendered. |
| texel | 1.0 / resolution — one pixel step, for blurs and offsets. |
| luminance(rgb) | Perceptual brightness. |
| lerp, frac | HLSL spellings, if you prefer them to mix and fract. |
Everything else is Metal Shading Language: float2/3/4, saturate, clamp, dot, pow, smoothstep, min, max, swizzles, for, if.
Helper functions can read uniforms and sample textures, just like pass functions:
uniform float Amount : default=0.5, min=0.0, max=1.0;
float falloff(float2 uv) { // reads Amount directly
float2 d = uv - 0.5;
return 1.0 - Amount * dot(d, d) * 2.2;
}
float4 effect(float2 uv) {
return sample_input(uv) * falloff(uv);
}Multiple passes
Declare them in order. Each may render at reduced scale, and may sample any pass declared before it. The last pass is what the next effect — or the screen — receives.
//! pass: bright scale=0.5
//! pass: blurH scale=0.5
//! pass: blurV scale=0.5
//! pass: composite
float4 bright(float2 uv) { ... }
float4 composite(float2 uv) {
float3 base = sample_input(uv).rgb; // still the original image
float3 bloom = sample_pass(blurV, uv).rgb; // an earlier pass
return float4(base + bloom, 1.0);
}sample_input always means the image that entered this effect, in every pass — which is what lets a composite pass blend the blurred result back over the original. Sampling a later pass returns black rather than garbage.
When it doesn't compile
The effect stays in the menu, greyed out, with the compiler's message attached — the app keeps running and the picture is untouched. The line number is for the generated shader, not your file, so it'll be offset by the boilerplate wrapping your code; the message and the quoted source line still point at the mistake.
Two constraints worth knowing, both consequences of how little this format parses:
- A
uniformdeclaration must be one line, ending in;. - Uniform names become variables in your shader, so don't declare a local with the same name.
Effects run per presented frame
With frame generation at 2×, the chain runs twice per captured frame — a generated frame is graded too, or it would flicker against the real ones. Budget accordingly: an expensive effect costs double at 2×, triple at 3×.
Publishing it here
Host the .cvfx file anywhere that serves it over https — a GitHub repo, a Gist, Drive — then sign in and hit Publish on the library. Give it a name, a line or two on what it does and what it looks best on, and a screenshot, which you upload rather than link. People can like it, and the most-liked sit at the top. You can edit or remove your own entry at any time.