Screen effects¶
Full-screen effects are pixel shaders drawn over each player's finished picture of the world: built in, or ones you write. The HUD, chat and menus stay on top, untouched. Use them for:
- colour grading per area, weather or time of day;
- night vision or thermal goggles;
- being drunk, drugged or hurt;
- black and white flashbacks.
const grey = player.screenEffects.add("greyscale", { fadeMs: 1000 });
grey.set({ strength: 0.5 });
grey.remove(1000); // fade out over a second
player.screenEffects.add("shaders/underwater.hlsl", { params: { depth: 0.5 } }); // your own
player.screenEffects puts effects on one player's screen.
server.screenEffects puts them on everyone's, including players who join
later. Client scripts have the same, sagl.screenEffects.
Built in¶
Add these by name. Each starts from its defaults; give only the parameters
you want to change, and set any of them later.
player.screenEffects.add("nightvision");
player.screenEffects.add("hurt", { params: { color: [0, 0, 0.5] } }); // blue edges instead of red
| Name | Parameters (defaults) | What it does |
|---|---|---|
greyscale |
strength (1) |
Black and white: 0 is full colour, 1 grey. |
sepia |
strength (1) |
An old photo's brown. |
nightvision |
gain (3), grain (1) |
Brightened gain times and green, with grain (0 for none), scanlines and dark edges. |
thermal |
grain (1), blur (1.5), cooling (60), coldest, cold, warm, hot, hottest (colours) |
A thermal camera: people glow hot and everything else is cold, fading into the cold with distance. Dead bodies cool over cooling seconds. grain is the sensor's noise (0 for none), blur its softness in pixels. See below. |
drunk |
amount (1) |
The picture sways and doubles. |
hurt |
amount (1), color ([0.55, 0, 0]) |
color creeping in from the edges, pulsing. |
grade |
saturation (1), contrast (1), brightness (1), tint ([1, 1, 1]) |
Colour grading: 1 is as it is for each; tint multiplies the picture. |
blur |
amount (4) |
Out of focus: the blur's radius in pixels at 1080p (it scales with the screen). |
vignette |
amount (0.5), color ([0, 0, 0]) |
The edges darkened (or tinted towards color). |
fade |
amount (1), color ([0, 0, 0]) |
The world faded to color, with the HUD and chat still showing. |
dof |
focus (0), range (1), amount (8) |
Depth of field: sharp at focus metres (0: the nearest thing in the middle of the screen), and range metres either side; blurring up to amount pixels (at 1080p) away from it. The camera's focus does the same for a shot. |
tiltshift |
center (0.55), width (0.08), angle (0), amount (12), saturation (1.4) |
Tilt-shift, the miniature "toy town" look: a band across the picture stays sharp and the rest blurs more the further it is from it, with richer colours. center is where the band is (0 the top, 1 the bottom), width how far it reaches either side (as a share of the screen's height), angle tilts it (degrees), amount is the most blur in pixels (at 1080p), and saturation the colour boost (1 leaves it). It looks best from high up looking down, with a narrow field of view. |
Thermal changes how the game draws the world, the way its own thermal
goggles do. It comes on at once (fadeMs is ignored when adding it), but
fades out as usual. Only people are heat sources: vehicles and
objects show cold.
Its colours run from coldest to hottest. The world shows between
coldest and cold (bright surfaces reaching towards warm), and people
between hot and hottest, with a cooling body sliding back down. The
defaults go from near-black blue through purple, red and orange to a pale
yellow. A black and white "white hot" camera:
player.screenEffects.add("thermal", { params: {
coldest: [0, 0, 0], cold: [0.25, 0.25, 0.25], warm: [0.5, 0.5, 0.5], hot: [0.85, 0.85, 0.85], hottest: [1, 1, 1],
} });
Colours are red, green and blue, each 0 to 1. The SDK exports this list,
with the defaults, as BUILT_IN_SCREEN_EFFECTS.
Naming your own¶
define names one of your shaders, with its parameters' defaults, so it's
added by name like a built-in:
server.screenEffects.define("underwater", "shaders/underwater.hlsl", { depth: 0.5, tint: [0.2, 0.5, 0.6] });
player.screenEffects.add("underwater"); // the defaults
player.screenEffects.add("underwater", { params: { depth: 1 } }); // deeper
A name is for the whole server: server.screenEffects and every
player.screenEffects. Freeroam names its pixelate shader this way.
Writing a shader¶
A shader is an HLSL file among your client files, with a main
that gives a pixel's colour. Players' games compile it with Direct3D's
shader compiler (pixel shader 3.0).
// client-files/shaders/greyscale.hlsl
float4 main(float2 uv : TEXCOORD0) : COLOR {
float4 c = tex2D(scene, uv); // the picture so far
float g = dot(c.rgb, float3(0.299, 0.587, 0.114)); // its brightness
return float4(lerp(c.rgb, g.xxx, strength), 1); // strength: a parameter
}
| Name | Type | Description |
|---|---|---|
uv |
float2 |
The pixel's place on screen: 0,0 top left to 1,1 bottom right. |
scene |
sampler2D |
The picture so far: the world, and any effects drawn before this one. Read it with tex2D(scene, uv). |
time |
float |
Seconds, counting up (it wraps each hour), for animation. |
screenSize |
float2 |
The picture's size in pixels. |
pixelSize |
float2 |
One pixel, as a uv step (1 / screenSize): to read the pixels around this one. |
depthAt(uv) |
float |
How far away the 3D picture is at uv, in metres (along the camera's view). The sky and anything past the draw distance give the far clip. |
hasDepth |
bool |
False on the rare graphics card that can't give depth; depthAt then always gives the far clip. |
| your parameters | float to float4 |
By the names you add the effect with (below). |
The result's alpha is ignored. Fading is done for you: while an effect fades in or out, it's blended with the picture under it.
When a shader doesn't compile, or the file isn't there, the player's game
reports it to the server as a
clientScriptError, with where set to
"shader <file>" and the compiler's message, line numbers included.
The built-ins' shaders are in client/src/screen_effects.cpp
(kBuiltIns), written the same way, to start from. Freeroam has one of its
own, client-files/shaders/pixelate.hlsl. Freeroam's Debug page in the
pause menu has a button for each effect.
ScreenEffects¶
add(effect, options?)¶
Adds an effect: a built-in's name, a name you gave with
define, or a client file with the shader. A named effect starts from its
defaults and takes only its own parameters.
| Option | Type | Default | Description |
|---|---|---|---|
params |
Record<string, number \| number[]> |
none (a named effect: its defaults) | Its parameters, by the names the shader uses: a number, or 2 to 4 numbers (a vector, a colour). At most 8. Their names and sizes are fixed once added. |
order |
number |
0 |
Effects draw lowest first, each on the picture the ones before it left. |
fadeMs |
number |
0 |
Fades in over this long. |
Throws ReferenceError if it's no effect's name or client file;
RangeError for a bad parameter.
define(name, file, defaults?)¶
Names a shader of yours (a client file) with its parameters' defaults, to
add by name. Throws RangeError if the name is a
built-in's or already defined, ReferenceError if there's no such client
file.
names¶
The effects you can add by name: the built-ins, then those defined.
list, clear(fadeMs?)¶
The effects on now, and taking them all off.
save(), restore(fadeMs?), snapshot()¶
save(): ScreenEffectsSnapshot
restore(fadeMs?: number): void
snapshot(): ScreenEffectsSnapshot // { restore(fadeMs?) }
For taking over someone's screen for a while and giving it back: save()
remembers the effects on now, with their parameters. Then clear them and
add your own. restore() goes back to what was last saved, whatever
happened since (cleared, reset, others added): effects on now that weren't
saved go, and the saved ones come back as they were. They're the same
ScreenEffect objects, so code holding them carries on. The save stays,
so you can restore again.
player.screenEffects.save();
player.screenEffects.clear(300);
player.screenEffects.add("tiltshift"); // the scene's own
// ...
player.screenEffects.restore(500); // theirs back, tilt-shift gone
snapshot() is the same without touching what save() remembers: it
returns its own restore(). Mission cinematics use one to
clear each player's effects for a scene and put them back after.
A camera shot with its own focus takes over from any
dof effect: theirs doesn't draw until the shot's focus is off.
ScreenEffect¶
| Member | Description |
|---|---|
file |
What it was added as: a name or a client file. |
params |
Its parameters now. |
active |
Still on screen (not removed). |
set(params) |
Changes parameters: only those given, of those it was added with. |
remove(fadeMs?) |
Takes it off, fading out over fadeMs (default 0). |