Creative Cinema / Film Framing

EDIT: In my haste to upload, and tiredness. I just realised I uploaded “Master_Film_v5” and not “Master_Film_v7”. These are somewhat night and day versions of the same shader.

This is the second of my five / six plugins . . . . .

I don’t know how technically accurate the dimensions are, and as I only have a laptop at hand, I don’t know how this scales across different screen sizes and ratios.

This brings a bit of cinematic/small-screen magic to our Godot projects. I hope it helps and inspires others.
Things I might add if I get back around to it . . . . dynamic light leaks, dust, scratches, the usual jazz.

As well as the obvious film inspired ratios there’s also some neat creative framing oppertunities in the manual controls.

Shader code
shader_type canvas_item;

uniform sampler2D screen_texture : hint_screen_texture, repeat_disable, filter_nearest;

group_uniforms Preset_Framing;
uniform int ratio_preset : hint_enum(
    "Manual",
    "Square ( 1  | 1 )",
    "Lighthouse ( 1.19  | 1 )",
    "Flicker ( 1.33  | 1 )",
    "Academy ( 1.37  | 1 )",
    "Euro ( 1.66  | 1 )",
    "US ( 1.85  | 1 )",
    "CinemaScope ( 2.35  | 1 )",
    "Panavision ( 2.39  | 1 )",
    "Polyvision ( 4.0  | 1 )"
) = 0; 
/** Preset index that selects a target aspect ratio (or Manual) to automatically calculate letterbox/pillarbox framing. */

group_uniforms Manual_Adjustments;
/** Toggles visibility of the framing bars (letterboxing/pillarboxing). */
uniform bool frame_visible = true; 
/** Solid color used for the framing bars when they are visible. */
uniform vec4 frame_color : source_color = vec4(0.0, 0.0, 0.0, 1.0);
/** Manual fractional height of the top framing bar (used directly when ratio_preset is Manual). */ 
uniform float top_bar    : hint_range(0.0, 0.5, 0.001) = 0.0; 
/** Manual fractional height of the bottom framing bar (used directly when ratio_preset is Manual). */
uniform float bottom_bar : hint_range(0.0, 0.5, 0.001) = 0.0; 
/** Manual fractional width of the left framing bar (used directly when ratio_preset is Manual). */
uniform float left_bar   : hint_range(0.0, 0.5, 0.001) = 0.0; 
/** Manual fractional width of the right framing bar (used directly when ratio_preset is Manual). */
uniform float right_bar  : hint_range(0.0, 0.5, 0.001) = 0.0; 

group_uniforms Vignette;
/** Toggles the vignette darkening effect applied to the image. */
uniform bool vignette_on = true;
/** When true the vignette automatically adapts to the framed image area (recommended).  
    When false it uses the old full-screen behaviour. */
uniform bool adaptive_vignette = true;
/** Controls how far the vignette extends from the edges toward the center. */
uniform float vignette_intensity : hint_range(0.0, 1.0, 0.001) = 0.5;
/** Controls the overall strength of the vignette color blend. */ 
uniform float vignette_opacity   : hint_range(0.0, 1.0, 0.001) = 0.5; 
/** Color overlaid by the vignette effect (typically black). */
uniform vec4 vignette_color      : source_color = vec4(0.0, 0.0, 0.0, 1.0);
/** Normalized coordinates of the vignette center *relative to the framed image area* (0.5, 0.5 = center of the visible picture). */ 
uniform vec2 vignette_center     = vec2(0.5, 0.5); 

group_uniforms Dynamic_Camera;
/** Toggles dynamic camera simulation (gate weave + handheld shake) applied only to the image UV. */
uniform bool dynamic_on = false; 

/** Gate weave style: A = Simplified wave (original), B = discrete random pulse per film frame. */
uniform int gate_weave_style : hint_enum("A - Simplified", "B - Developed") = 0;
/** Frequency of the gate weave oscillation (only used when A - Simplified is selected). */
uniform float gate_weave_speed  : hint_range(0.0, 50.0, 0.1) = 15.0; 
/** Intensity of the vertical film-gate weave jitter (UV offset, works in both styles). */
uniform float gate_weave_amount : hint_range(0.0, 0.05, 0.0001) = 0.002; 
/** Simulated film frame rate used only when B - Developed is selected. */
uniform float film_fps : hint_range(1.0, 120.0, 0.1) = 24.0; 

/** Additional intensity modulation for the discrete gate weave pulse (makes option B feel more mechanical). */
uniform float pulse_intensity : hint_range(0.0, 2.0, 0.01) = 0.22;
/** Rate at which the gate-weave pulse intensity changes (multiplier of film_fps). Randomised via hash. */
uniform float pulse_rate : hint_range(0.2, 1.0, 0.05) = 0.5; 

/** Brightness flicker intensity for the old projector lamp effect (only in style B). */
uniform float projector_flicker_intensity : hint_range(0.0, 0.3, 0.001) = 0.08;
/** Rate at which the projector lamp brightness pulses (multiplier of film_fps). Fully randomised. */
uniform float projector_flicker_rate : hint_range(0.2, 2.0, 0.05) = 1.0; 

/** Frequency of the handheld camera shake motion. */
uniform float handheld_shake_speed  : hint_range(0.0, 5.0, 0.1) = 1.0; 
/** Intensity of the handheld camera shake motion. */
uniform float handheld_shake_amount : hint_range(0.0, 0.1, 0.001) = 0.005; 

float hash(float n) { return fract(sin(n) * 43758.5453123); }

void fragment() {
    // Fixed UV for bars + vignette
    vec2 uv_mask  = SCREEN_UV;
    // Drifting UV for the image
    vec2 uv_image = SCREEN_UV;

    // --- Dynamic Camera (image only) ---
    if (dynamic_on) {
        float jitter;
        if (gate_weave_style == 0) {          // A - Simplified
            jitter = sin(TIME * gate_weave_speed)
                   * gate_weave_amount
                   * hash(floor(TIME * 20.0));
        } else {                              // B - Developed (gate weave + mechanical pulse)
            float frame_index = floor(TIME * film_fps);
            float base_jitter = (hash(frame_index) * 2.0 - 1.0) * gate_weave_amount;

            // Gate-weave strength pulse
            float pulse_index = floor(TIME * film_fps * pulse_rate);
            float pulse = 1.0 + (hash(pulse_index) * 2.0 - 1.0) * pulse_intensity;

            jitter = base_jitter * pulse;
        }

        vec2 sway = vec2(
            sin(TIME * handheld_shake_speed * 0.7),
            cos(TIME * handheld_shake_speed * 1.3)
        ) * handheld_shake_amount;

        uv_image += vec2(sway.x, jitter + sway.y);
    }

    uv_image = clamp(uv_image, 0.0, 1.0);

    // --- Aspect Ratio / Framing (uses uv_mask) ---
    vec2 screen_res = 1.0 / SCREEN_PIXEL_SIZE;
    float screen_aspect = screen_res.x / screen_res.y;

    float target_ratio = 0.0;
    if      (ratio_preset == 1) target_ratio = 1.0;
    else if (ratio_preset == 2) target_ratio = 1.19;
    else if (ratio_preset == 3) target_ratio = 1.33;
    else if (ratio_preset == 4) target_ratio = 1.37;
    else if (ratio_preset == 5) target_ratio = 1.66;
    else if (ratio_preset == 6) target_ratio = 1.85;
    else if (ratio_preset == 7) target_ratio = 2.35;
    else if (ratio_preset == 8) target_ratio = 2.39;
    else if (ratio_preset == 9) target_ratio = 4.00;

    float f_top = top_bar;
    float f_bot = bottom_bar;
    float f_l   = left_bar;
    float f_r   = right_bar;

    if (target_ratio > 0.0) {
        if (screen_aspect > target_ratio) {
            float inset = (1.0 - (target_ratio / screen_aspect)) * 0.5;
            f_l = inset; f_r = inset;
        } else {
            float inset = (1.0 - (screen_aspect / target_ratio)) * 0.5;
            f_top = inset; f_bot = inset;
        }
    }

    bool is_framed =
        (uv_mask.y < f_top) || (uv_mask.y > 1.0 - f_bot) ||
        (uv_mask.x < f_l)   || (uv_mask.x > 1.0 - f_r);

    // --- Sample drifting image ---
    vec4 tex = texture(screen_texture, uv_image);
    vec3 final_rgb = tex.rgb;

    // --- Vignette (adapts to framed area when adaptive_vignette is true) ---
    if (vignette_on) {
        if (adaptive_vignette && !is_framed) {
            // Adaptive vignette (bias-free, equal on all sides)
            float visible_w = 1.0 - f_l - f_r + 0.000001;
            float visible_h = 1.0 - f_top - f_bot + 0.000001;
            vec2 image_uv = vec2(
                (uv_mask.x - f_l) / visible_w,
                (uv_mask.y - f_top) / visible_h
            );

            vec2 vignette_uv = image_uv - vignette_center;

            float image_aspect = screen_aspect * visible_w / visible_h;
            float max_dist = 0.5 * sqrt(image_aspect * image_aspect + 1.0);
            vec2 aspect_uv = (vignette_uv * vec2(image_aspect, 1.0)) / max_dist;

            float dist = length(aspect_uv);
            float v_grad = smoothstep(0.8 - vignette_intensity, 1.0, dist);
            final_rgb = mix(final_rgb, vignette_color.rgb, v_grad * vignette_opacity);
        } 
        else {
            // Old full-screen vignette (fallback)
            vec2 aspect_uv = (uv_mask - vignette_center) * vec2(screen_aspect, 1.0);
            float dist = length(aspect_uv);
            float v_grad = smoothstep(0.8 - vignette_intensity, 1.0, dist);
            final_rgb = mix(final_rgb, vignette_color.rgb, v_grad * vignette_opacity);
        }
    }

    // === Projector lamp brightness flicker (only in style B) ===
    if (dynamic_on && gate_weave_style == 1) {
        float flicker_index = floor(TIME * film_fps * projector_flicker_rate);
        float flicker = 1.0 + (hash(flicker_index) * 2.0 - 1.0) * projector_flicker_intensity;
        final_rgb *= flicker;
    }

    // --- Output with bar visibility toggle ---
    if (frame_visible && is_framed) {
        COLOR = frame_color;
    } else {
        COLOR = vec4(final_rgb, tex.a);
    }
}
Live Preview
Tags
film, Framing, Projector, vignette
The shader code and all code snippets in this post are under CC0 license and can be used freely without the author's permission. Images and videos, and assets depicted in those, do not fall under this license. For more info, see our License terms.

More from lil_sue

Related shaders

guest

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments