Screen shaders are post-processing effects applied to the entire rendered frame. Unlike surface shaders, they operate on the final image rather than individual objects.
Screen shaders process the entire rendered frame as a 2D image. They run after all 3D rendering is complete and can read from depth, color, and geometry buffers to create post-processing effects.
void fragment() {
COLOR = SampleColor(TEXCOORD) * vec3(1.0, 0.5, 0.5); // Red tint
}
// From file
R3D_ScreenShader* shader = R3D_LoadScreenShader("vignette.glsl");
// From memory
const char* code = "void fragment() { COLOR = vec3(1.0 - SampleColor(TEXCOORD)); }";
R3D_ScreenShader* shader = R3D_LoadScreenShaderFromMemory(code);
// Don't forget to unload when done
R3D_UnloadScreenShader(shader);
Screen shaders have only one required entry point: fragment(). There is no vertex stage, and consequently, no varyings.
Runs once per screen pixel to compute the final output color.
void fragment() {
// Read input color
vec3 color = SampleColor(TEXCOORD);
// Apply effect
color = vec3(dot(color, vec3(0.299, 0.587, 0.114))); // Grayscale conversion
// Write output
COLOR = color;
}
Screen shaders provide built-in variables for screen-space operations:
| Variable | Type | Description |
|---|---|---|
TEXCOORD |
vec2 |
Normalized texture coordinates (0.0 to 1.0) |
PIXCOORD |
ivec2 |
Integer pixel coordinates (0 to resolution-1) |
TEXEL_SIZE |
vec2 |
Size of one texel (1.0 / RESOLUTION) |
RESOLUTION |
vec2 |
Screen resolution in pixels |
ASPECT |
float |
Screen aspect ratio |
COLOR |
vec3 |
Output color (write to this) |
Texture coordinates:
void fragment() {
// Sample at current screen position
COLOR = SampleColor(TEXCOORD);
}
Pixel coordinates:
void fragment() {
// Checkerboard pattern
int checker = (PIXCOORD.x / 8 + PIXCOORD.y / 8) % 2;
COLOR = SampleColor(TEXCOORD) * (checker == 0 ? 1.0 : 0.5);
}
Resolution-aware effects:
void fragment() {
// Blur using texel size for offset
vec3 color = vec3(0.0);
for (int x = -1; x <= 1; x++) {
for (int y = -1; y <= 1; y++) {
vec2 offset = vec2(x, y) * TEXEL_SIZE;
color += SampleColor(TEXCOORD + offset);
}
}
COLOR = color / 9.0; // Average of 3x3 grid
}
Screen shaders provide convenience functions for accessing frame data. Each function comes in two variants: Fetch (uses integer pixel coordinates) and Sample (uses normalized texture coordinates).
vec3 FetchColor(ivec2 pixCoord); // Fast, no filtering
vec3 SampleColor(vec2 texCoord); // Bilinear filtering
Example:
void fragment() {
// Fetch exact pixel (faster)
vec3 center = FetchColor(PIXCOORD);
// Sample with filtering (smoother)
vec3 blurred = SampleColor(TEXCOORD + vec2(0.01, 0.0));
COLOR = mix(center, blurred, 0.5);
}
Returns linear depth values from the depth buffer.
float FetchDepth(ivec2 pixCoord); // linear depth [near, far]
float SampleDepth(vec2 texCoord);
float FetchDepth01(ivec2 pixCoord); // linear depth normalized [0, 1]
float SampleDepth01(vec2 texCoord);
Example:
void fragment() {
vec3 color = SampleColor(TEXCOORD);
const float outline_size = 1.5;
vec2 px = TEXEL_SIZE * outline_size;
// Edge detection using depth
float d = SampleDepth(TEXCOORD);
float dx1 = abs(d - SampleDepth(TEXCOORD + vec2(px.x, 0)));
float dx2 = abs(d - SampleDepth(TEXCOORD - vec2(px.x, 0)));
float dy1 = abs(d - SampleDepth(TEXCOORD + vec2(0, px.y)));
float dy2 = abs(d - SampleDepth(TEXCOORD - vec2(0, px.y)));
float edge = step(0.5, max(max(dx1, dx2), max(dy1, dy2)));
COLOR = mix(color, vec3(0.0), edge);
}
Returns view-space position (camera-relative coordinates).
vec3 FetchPosition(ivec2 pixCoord);
vec3 SamplePosition(vec2 texCoord);
Example:
void fragment() {
vec3 position = SamplePosition(TEXCOORD);
// Distance from camera
float distance = length(position);
// Depth-based effect
vec3 color = SampleColor(TEXCOORD);
COLOR = color * (1.0 - smoothstep(10.0, 50.0, distance));
}
Returns view-space surface normal.
vec3 FetchNormal(ivec2 pixCoord);
vec3 SampleNormal(vec2 texCoord);
Example:
void fragment() {
vec3 normal = SampleNormal(TEXCOORD);
// Edge detection using normals
vec3 normal_right = SampleNormal(TEXCOORD + vec2(TEXEL_SIZE.x, 0.0));
float edge = length(normal - normal_right);
vec3 color = SampleColor(TEXCOORD);
COLOR = mix(color, vec3(0.0), edge * 10.0);
}
Fetch: Uses integer pixel coordinates, no filtering, faster
PIXCOORDSample: Uses normalized coordinates, bilinear filtering, smoother
TEXCOORDScreen shaders support the same uniform system as surface shaders, with the same limits and behavior.
Values:
bool, int, floatvec2, vec3, vec4mat2, mat3, mat4Samplers:
sampler1D, sampler2D, sampler3D, samplerCubeR3D_MAX_SHADER_UNIFORMS)R3D_MAX_SHADER_SAMPLERS)uniform float u_intensity;
uniform sampler2D u_lut;
void fragment() {
vec3 color = SampleColor(TEXCOORD);
// Apply color grading
color = texture(u_lut, color.rg).rgb;
// Apply intensity
COLOR = mix(SampleColor(TEXCOORD), color, u_intensity);
}
float intensity = 0.5f;
Texture2D lut = LoadTexture("color_lut.png");
R3D_SetScreenShaderUniform(shader, "u_intensity", &intensity);
R3D_SetScreenShaderSampler(shader, "u_lut", lut);
Screen shaders can be chained, R3D executes them using internal ping-pong buffers, avoiding extra buffers like with a RenderTexture.
void R3D_SetScreenShaderChain(R3D_ScreenShader** shaders, int count);
R3D_MAX_SCREEN_SHADERS (default: 8)NULL entries in the array are safely ignoredshaders = NULL or count = 0 disables all screen shaders// Create shaders
R3D_ScreenShader* outline = R3D_LoadScreenShader("outline.glsl");
R3D_ScreenShader* fisheye = R3D_LoadScreenShader("fisheye.glsl");
R3D_ScreenShader* grain = R3D_LoadScreenShader("grain.glsl");
// Set up chain
R3D_ScreenShader* chain[] = {outline, fisheye, grain};
R3D_SetScreenShaderChain(chain, 3);
// Render loop
while (!WindowShouldClose()) {
R3D_Begin();
// ... draw 3D scene ...
R3D_End(); // Screen shaders execute here
}
// Disable screen shaders (not mandatory at the end of the program)
R3D_SetScreenShaderChain(NULL, 0);
// Cleanup
R3D_UnloadScreenShader(fisheye);
R3D_UnloadScreenShader(grain);
R3D_UnloadScreenShader(outline);
FetchColor(PIXCOORD) is faster than SampleColor(TEXCOORD) when filtering isn't neededTEXEL_SIZE instead of computing 1.0 / RESOLUTIONASPECT for aspect-aware effectsvignette.glsl:
// Simple vignette effect
// u_intensity: Controls vignette strength (0.0 = none, 1.0 = full)
// u_radius: Controls vignette size (0.0 = tight, 1.0 = wide)
uniform float u_intensity;
uniform float u_radius;
void fragment() {
vec3 color = SampleColor(TEXCOORD);
// Calculate distance from center
vec2 center = TEXCOORD - vec2(0.5);
center.x *= ASPECT; // Aspect correction
float dist = length(center);
// Apply vignette
float vignette = smoothstep(u_radius, u_radius * 0.5, dist);
COLOR = color * mix(1.0, vignette, u_intensity);
}
R3D_ScreenShader* R3D_LoadScreenShader(const char* filePath);
R3D_ScreenShader* R3D_LoadScreenShaderFromMemory(const char* code);
void R3D_UnloadScreenShader(R3D_ScreenShader* shader);
void R3D_SetScreenShaderUniform(R3D_ScreenShader* shader, const char* name, const void* value);
void R3D_SetScreenShaderSampler(R3D_ScreenShader* shader, const char* name, Texture texture);
void R3D_SetScreenShaderChain(R3D_ScreenShader** shaders, int count);
uniform <type> <name>; // Optional: uniforms
void fragment() { // Required: fragment stage
// Read: TEXCOORD, PIXCOORD, RESOLUTION, TEXEL_SIZE, ASPECT
// Sample: SampleColor(), SampleDepth(), SamplePosition(), SampleNormal()
// Fetch: FetchColor(), FetchDepth(), FetchPosition(), FetchNormal()
// Write: COLOR
}
// Input (read-only)
vec2 TEXCOORD; // Normalized coordinates [0..1]
ivec2 PIXCOORD; // Integer pixel coordinates
vec2 TEXEL_SIZE; // Size of one pixel (1.0 / RESOLUTION)
vec2 RESOLUTION; // Screen resolution
float ASPECT; // Screen aspect ratio
// Output (write)
vec3 COLOR; // Final pixel color
// Color
vec3 FetchColor(ivec2 pixCoord);
vec3 SampleColor(vec2 texCoord);
// Depth (linear, near to far)
float FetchDepth(ivec2 pixCoord);
float SampleDepth(vec2 texCoord);
// Depth (linear normalized, 0 to 1)
float FetchDepth01(ivec2 pixCoord);
float SampleDepth01(vec2 texCoord);
// Position (view-space)
vec3 FetchPosition(ivec2 pixCoord);
vec3 SamplePosition(vec2 texCoord);
// Normal (view-space)
vec3 FetchNormal(ivec2 pixCoord);
vec3 SampleNormal(vec2 texCoord);