bevy_ratatui_camera 0.14.2

A bevy plugin for rendering your bevy app to the terminal using ratatui.
Documentation
#import bevy_core_pipeline::fullscreen_vertex_shader::FullscreenVertexOutput
#import bevy_render::view::View

struct Config {
    thickness: f32,

    color_enabled: u32,
    color_threshold: f32,

    depth_enabled: u32,
    depth_threshold: f32,

    normal_enabled: u32,
    normal_threshold: f32,

    #ifdef SIXTEEN_BYTE_ALIGNMENT
    // pad to 32 bytes when structs must be 16 byte aligned
    _padding: u32,
    #endif
};

@group(0) @binding(0) var screen_texture: texture_2d<f32>;
@group(0) @binding(1) var texture_sampler: sampler;
@group(0) @binding(2) var depth_prepass_texture: texture_depth_2d;
@group(0) @binding(3) var normal_prepass_texture: texture_2d<f32>;
@group(0) @binding(4) var<uniform> view: View;
@group(0) @binding(5) var<uniform> config: Config;

var<private> thickness: f32 = 1.4;

var<private> sobel_horizontal: array<f32, 9> = array<f32, 9>(
    1.0, 0.0, -1.0,
    2.0, 0.0, -2.0,
    1.0, 0.0, -1.0,
);

var<private> sobel_vertical: array<f32, 9> = array<f32, 9>(
     1.0,  2.0,  1.0,
     0.0,  0.0,  0.0,
    -1.0, -2.0, -1.0,
);

var<private> sobel_forward: array<f32, 9> = array<f32, 9>(
     0.0,  1.0,  2.0,
    -1.0,  0.0,  1.0,
    -2.0, -1.0,  0.0,
);

var<private> sobel_backward: array<f32, 9> = array<f32, 9>(
     2.0,  1.0,  0.0,
     1.0,  0.0, -1.0,
     0.0, -1.0, -2.0,
);

var<private> neighbors: array<vec2f, 9> = array<vec2f, 9>(
    vec2f(-1.0,  1.0), vec2f(0.0,  1.0), vec2f(1.0,  1.0),
    vec2f(-1.0,  0.0), vec2f(0.0,  0.0), vec2f(1.0,  0.0),
    vec2f(-1.0, -1.0), vec2f(0.0, -1.0), vec2f(1.0, -1.0),
);

fn detect_edge_f32(samples: ptr<function, array<f32, 9>>) -> vec4f {
    var horizontal = vec3f(0.0);
    for (var i = 0; i < 9; i++) {
        horizontal += (*samples)[i] * sobel_horizontal[i];
    }
    var vertical = vec3f(0.0);
    for (var i = 0; i < 9; i++) {
        vertical += (*samples)[i] * sobel_vertical[i];
    }
    var forward = vec3f(0.0);
    for (var i = 0; i < 9; i++) {
        forward += (*samples)[i] * sobel_forward[i];
    }
    var backward = vec3f(0.0);
    for (var i = 0; i < 9; i++) {
        backward += (*samples)[i] * sobel_backward[i];
    }

    let edge = vec4f(
        sqrt(dot(horizontal, horizontal)),
        sqrt(dot(vertical, vertical)),
        sqrt(dot(forward, forward)),
        sqrt(dot(backward, backward)),
    );

    return edge;
}

fn detect_edge_vec3(samples: ptr<function, array<vec3f, 9>>) -> vec4f {
    var horizontal = vec3f(0.0);
    for (var i = 0; i < 9; i++) {
        horizontal += (*samples)[i].xyz * sobel_horizontal[i];
    }
    var vertical = vec3f(0.0);
    for (var i = 0; i < 9; i++) {
        vertical += (*samples)[i].xyz * sobel_vertical[i];
    }
    var forward = vec3f(0.0);
    for (var i = 0; i < 9; i++) {
        forward += (*samples)[i].xyz * sobel_forward[i];
    }
    var backward = vec3f(0.0);
    for (var i = 0; i < 9; i++) {
        backward += (*samples)[i].xyz * sobel_backward[i];
    }

    let edge = vec4f(
        sqrt(dot(horizontal, horizontal)),
        sqrt(dot(vertical, vertical)),
        sqrt(dot(forward, forward)),
        sqrt(dot(backward, backward)),
    );

    return edge;
}

fn prepass_depth(frag_coord: vec2f) -> f32 {
    #ifdef DEPTH_TEXTURE_SAMPLING_SUPPORTED
    return textureLoad(depth_prepass_texture, vec2i(frag_coord), 0);
    #else
    return 0.0;
    #endif
}

fn prepass_normal(frag_coord: vec2f) -> vec3f {
    return textureLoad(normal_prepass_texture, vec2i(frag_coord), 0).xyz;
}

fn detect_edge_depth(frag_coord: vec2f) -> vec4f {
    if config.depth_enabled == 0u {
        return vec4f(0.0);
    }

    var samples = array<f32, 9>();
    for (var i = 0; i < 9; i++) {
        samples[i] =  prepass_depth(frag_coord + neighbors[i] * config.thickness);
    }

    var edge = detect_edge_f32(&samples);
    if edge.x < config.depth_threshold {
        edge.x = 0.0;
    }
    if edge.y < config.depth_threshold {
        edge.y = 0.0;
    }
    if edge.z < config.depth_threshold {
        edge.z = 0.0;
    }
    if edge.w < config.depth_threshold {
        edge.w = 0.0;
    }

    return edge;
}

fn detect_edge_normal(frag_coord: vec2f) -> vec4f {
    if config.normal_enabled == 0u {
        return vec4f(0.0);
    }

    var samples = array<vec3f, 9>();
    for (var i = 0; i < 9; i++) {
        samples[i] = prepass_normal(frag_coord + neighbors[i] * config.thickness);
    }

    var edge = detect_edge_vec3(&samples);
    if edge.x < config.normal_threshold {
        edge.x = 0.0;
    }
    if edge.y < config.normal_threshold {
        edge.y = 0.0;
    }
    if edge.z < config.normal_threshold {
        edge.z = 0.0;
    }
    if edge.w < config.normal_threshold {
        edge.w = 0.0;
    }

    return edge;
}

fn detect_edge_color(frag_coord: vec2f) -> vec4f {
    if config.color_enabled == 0u {
        return vec4f(0.0);
    }

    var samples = array<vec3f, 9>();
    for (var i = 0; i < 9; i++) {
        samples[i] = textureLoad(
            screen_texture,
            vec2i(frag_coord + neighbors[i] * config.thickness),
            0,
        ).rgb;
    }

    var edge = detect_edge_vec3(&samples);
    if edge.x < config.color_threshold {
        edge.x = 0.0;
    }
    if edge.y < config.color_threshold {
        edge.y = 0.0;
    }
    if edge.z < config.color_threshold {
        edge.z = 0.0;
    }
    if edge.w < config.color_threshold {
        edge.w = 0.0;
    }

    return edge;
}

@fragment
fn fragment(in: FullscreenVertexOutput) -> @location(0) vec4f {
    let color = textureSample(screen_texture, texture_sampler, in.uv);

    let frag_coord = in.position.xy;
    let edge_color = detect_edge_color(frag_coord);
    let edge_normal = detect_edge_normal(frag_coord);
    let edge_depth = detect_edge_depth(frag_coord);

    var edge = vec4f(0.0);
    edge = max(edge, edge_color);
    edge = max(edge, edge_normal);
    edge = max(edge, edge_depth);

    return vec4f(edge.x, edge.y, edge.z, edge.w);
}