rusting_engine 0.1.45

A high-performance Vulkano-based 3D engine with GPU physics.
#version 450

// COMPUTE SHADER - Frustum Culling

// This compute shader performs frustum culling on the GPU.
// It checks each instance's bounding sphere against the view-projection frustum.
// Instances that are visible are written to the visible indices buffer.

// Each workgroup processes 256 instances in parallel
layout(local_size_x = 256) in;

// Per-instance data from the physics buffer
struct InstanceData {
    mat4 model;              // Model matrix (also contains position in column 3)
    vec4 color;              // Color and emissive
    vec4 mat_props;         // Material properties
    vec4 velocity;         // Physics velocity
    vec4 angular_velocity;  // Angular velocity
    vec4 physic_props;     // Physics properties
};

// INPUT BUFFER - Instance data (read from)

// Read-only buffer with all instance transforms
layout(set = 0, binding = 0) readonly buffer Instances { 
    InstanceData data[]; 
} inst_in;

// OUTPUT BUFFERS - Results (written to)

// Write-only buffer storing indices of visible instances
layout(set = 0, binding = 1) writeonly buffer VisibleIndices { 
    uint data[]; 
} indices_out;

// Indirect buffer for draw command - only visible instances are drawn
layout(set = 0, binding = 2) buffer IndirectBuffer {
    uint indexCount;      // Number of indices (not used by draw call)
    uint instanceCount;   // Number of VISIBLE instances (updated by this shader)
    uint firstIndex;      // First index to draw
    int  vertexOffset;   // Vertex buffer offset
    uint firstInstance;  // Base instance ID for draw
} indirect_out;

// PUSH CONSTANTS - Per-dispatch parameters

layout(push_constant) uniform CullParams {
    mat4 view_proj;       // Combined view-projection matrix
    uint batch_offset;   // Starting instance ID for this batch
    uint batch_count;    // Total instances to cull in this dispatch
    uint visible_list_offset;  // Offset into visible list to write
} pc;

// MAIN ENTRY POINT


void main() {
    // Get global thread ID (0 to batch_count-1)
    uint local_id = gl_GlobalInvocationID.x;
    
    // Early exit if beyond the batch count
    if (local_id >= pc.batch_count) return;

    // Calculate instance index in the buffer
    uint global_id = local_id + pc.batch_offset;

    // Extract object center position from the model matrix (column 3, rows 0-2)
    vec3 center = inst_in.data[global_id].model[3].xyz;

    // Calculate the bounding sphere radius of this object
    // The radius is the half of the largest axis of the model matrix
    float sx = length(inst_in.data[global_id].model[0].xyz);
    float sy = length(inst_in.data[global_id].model[1].xyz);
    float sz = length(inst_in.data[global_id].model[2].xyz);
    float radius = max(sx, max(sy, sz)) * 1.5;

    // Transform the center to clip space using view-projection matrix
    vec4 clip = pc.view_proj * vec4(center, 1.0);

    // ============================================================================
    // FRUSTUM CULLING TEST
    // ============================================================================
    
    // First check: is the object in front of the camera?
    bool visible = clip.w >= -radius;

    // Second check: is the bounding sphere inside the NDC frustum?
    if (visible) {
        // Prevent div-by-zero or inversion when exactly at clip plane
        float w_safe = max(abs(clip.w), 0.001);

        // Convert to normalized device coordinates (-1 to 1)
        vec3 ndc = clip.xyz / w_safe;

        // Add a margin based on the object radius (larger objects need more margin)
        float ndc_margin = radius / w_safe;

        // Test against the 6 frustum planes
        // Allow object to extend slightly beyond frustum (ndc_margin)
        visible = ndc.x >= -(1.0 + ndc_margin) && ndc.x <= (1.0 + ndc_margin)
               && ndc.y >= -(1.0 + ndc_margin) && ndc.y <= (1.0 + ndc_margin)
               && ndc.z >= -ndc_margin          && ndc.z <= (1.0 + ndc_margin);
    }

    // WRITE VISIBLE INSTANCE

    // If visible, reserve a slot and write the instance index
    if (visible) {
        // Atomically increment the instance count to get a unique slot
        uint slot = atomicAdd(indirect_out.instanceCount, 1);
        
        // Write this instance's index to the visible list
        indices_out.data[slot + pc.visible_list_offset] = global_id;
    }
}