R3D Documentation

repository·master·Indexed 19 days ago

https://github.com/bigfoot71/r3d

A 3D extension library for raylib that expands 3D capabilities in rendering, lighting, kinematics, and mesh utilities. It features support for screen shaders with post-processing pipelines, sky shaders for procedural cubemap generation, and community bindings for Odin, FreePascal, and C#. Requires raylib 5.5+, Assimp 6.0.2+, and OpenGL 3.3+.

Tokens
10.1K
Snippets
35
Records
41
Agent score
66%

What's inside R3D

  1. Structure of an R3D Surface Shader

    master

    R3D surface shaders follow a specific structure using GLSL. They support optional vertex and fragment stages, and use specific keywords for communication between stages and built-in outputs.

    Key Components:

    • #pragma usage <hints>: (Optional) Specifies pre-compiled variants.
    • #define R3D_NO_AUTO_FETCH: (Optional) Disables automatic material sampling.
    • uniform <type> <name>: Uniform variables.
    • varying <type> <name>: Varying variables used to pass data from the vertex stage to the fragment stage.
    • void vertex(): (Optional) The vertex stage. Used to modify built-ins like POSITION or NORMAL.
    • void fragment(): (Optional) The fragment stage. Used to modify built-ins like ALBEDO, ROUGHNESS, or ALPHA.
    #pragma usage <hints>           // Optional: opaque, transparent, shadow, etc.
    #define R3D_NO_AUTO_FETCH       // Optional: disable automatic material sampling
    
    uniform <type> <name>;          // Uniforms
    varying <type> <name>;          // Varyings (communication between stages)
    
    void vertex() {                 // Optional: vertex stage
        // Modify POSITION, NORMAL, etc.
    }
    
    void fragment() {               // Optional: fragment stage
        // Modify ALBEDO, ROUGHNESS, etc.
    }
  2. How Surface Shaders work in R3D

    master

    Surface shaders are a simplified shader interface for materials and decals. They abstract away the complexity of multiple render passes (opaque, transparent, shadows, etc.) by allowing you to define behavior in a single shader definition. R3D automatically handles the underlying render pipeline based on your shader code.

    A surface shader requires at least one of two optional entry points:

    • vertex(): Runs once per vertex. Used to modify vertex positions, colors, or pass data to the fragment stage.
    • fragment(): Runs once per pixel. Used to modify surface properties like albedo, roughness, or emission.
    // Example of both stages
    varying float v_height;
    
    void vertex() {
        v_height = POSITION.y;
    }
    
    void fragment() {
        ALBEDO = mix(vec3(0.0, 0.5, 0.0), vec3(1.0, 1.0, 1.0), v_height);
    }
  3. Manual Material Sampling and Auto-Fetch

    master

    By default, R3D automatically samples material textures (albedo, normal, ORM) and populates fragment-stage built-in variables.

    To disable this and sample manually, define #define R3D_NO_AUTO_FETCH at the top of your shader. This sets ALBEDO, NORMAL_MAP, and OCCLUSION/ROUGHNESS/METALNESS/SPECULAR to zero.

    Manual Sampling Functions:

    • void FetchMaterial(vec2 texCoord); - Automatically fills all built-in variables using the provided UV.
    • vec4 SampleAlbedo(vec2 texCoord);
    • vec3 SampleEmission(vec2 texCoord);
    • vec3 SampleNormal(vec2 texCoord);
    • vec4 SampleOrm(vec2 texCoord); - Returns (Occlusion, Roughness, Metalness, Specular).
    #define R3D_NO_AUTO_FETCH
    
    void fragment() {
        // Sample material at distorted UV
        vec2 distorted_uv = TEXCOORD + vec2(sin(TEXCOORD.y * 10.0) * 0.1, 0.0);
        FetchMaterial(distorted_uv);
        
        // Or sample individual textures
        // ALBEDO = SampleAlbedo(distorted_uv).rgb;
        // vec4 orm = SampleOrm(distorted_uv);
        // ROUGHNESS = orm.g;
    }
  4. How sky shaders work

    master

    Sky shaders are used to procedurally generate skybox cubemaps. Unlike screen shaders that process a rendered frame, sky shaders render each of the six faces of a cubemap from scratch. They run once per texel of a cubemap to compute the sky color for that specific direction. They are not used during normal frame rendering but are invoked explicitly to generate or update an R3D_Cubemap.

    void fragment() {
        // Simple gradient sky: blue at horizon, dark at zenith
        float t = max(EYEDIR.y, 0.0);
        COLOR = mix(vec3(0.5, 0.7, 1.0), vec3(0.1, 0.2, 0.5), t);
    }
  5. Pre-compile shader variants with `#pragma usage`

    master

    R3D compiles multiple shader variants for different render passes (opaque, transparent, shadows, etc.). By default, only the opaque variant is pre-compiled. Other variants compile on-demand, which can cause performance stuttering when a new variant is first used (e.g., when a transparent object first appears).

    To prevent stuttering, use the #pragma usage directive at the top of your shader to specify which variants should be pre-compiled. You can specify multiple hints separated by spaces.

    Available Usage Hints:

    • opaque: Opaque rendering for lit objects (default).
    • prepass: Transparent pre-pass rendering for lit objects.
    • transparent: Transparent rendering (color/alpha blending) for lit objects.
    • unlit: Unlit rendering (handles both opaque and transparent unlit objects).
    • shadow: Shadow map rendering.
    • decal: Decal rendering.
    • probe: Reflection probe rendering.
    #pragma usage opaque shadow
    
    void fragment() {
        ALBEDO = vec3(1.0, 0.0, 0.0);
        ALPHA = 0.5; // Alpha cutoff
    }
  6. Pass data between stages using Varyings

    master

    Varyings allow you to pass data from the vertex() stage to the fragment() stage. They are automatically interpolated across the triangle. You can control interpolation using qualifiers:

    • smooth: Perspective-correct interpolation (default).
    • flat: No interpolation (uses value from provoking vertex).
    • noperspective: Linear interpolation in screen space.
    flat varying int v_material_id;
    noperspective varying vec2 v_screen_uv;
    
    void vertex() {
        v_material_id = 1;
        v_screen_uv = TEXCOORD;
    }
    
    void fragment() {
        if (v_material_id == 1) {
            ALBEDO = texture(u_texture, v_screen_uv).rgb;
        }
    }
  7. Use Allman indentation style for braces

    master

    r3d uses the Allman indentation style. Braces should be placed on their own line, aligned with the enclosing statement for scopes that do not require a trailing semicolon (e.g., function bodies, if, for, while, switch blocks).

    When a semicolon follows the closing brace (such as in type declarations or initializers), the opening brace stays on the same line.

    // Allman: function body, closing brace needs no semicolon
    void DoSomething(void)
    {
        DoStuff();
    }
    
    // Same-line brace: closing brace is followed by ';'
    typedef struct {
        int x;
        int y;
    } Vector2;
    
    // Same-line brace: initializer, closing brace is followed by ';'
    int values[] = {1, 2, 3, 4};
    
    Vector2 origin = {
        .x = 0,
        .y = 0
    };
  8. Write R3D screen shaders

    master

    R3D screen shaders follow a specific GLSL structure. You define optional uniform variables and must implement a void fragment() entry point. Inside the fragment stage, you can read screen data using built-in variables and helper functions, and you must write the final pixel color to the COLOR variable.

    uniform float uTime;
    
    void fragment() {
        // Use built-in variables or helper functions
        vec3 color = SampleColor(TEXCOORD);
        
        // Write to the output
        COLOR = color * sin(uTime);
    }
  9. Declare pointers and variables correctly

    master

    To avoid common pitfalls and ensure readability:

    • Attach the * to the type, not the variable (e.g., Type* v).
    • Never declare multiple variables on a single line.
    • Use 4 spaces for indentation; do not use tabs.
    • Surround operators with spaces (except when omitting them for mathematical formula readability).
    Type* v;    // OK
    Type* a, b; // FORBIDDEN: split into separate declarations
    
    // Spacing around operators
    int result = a + b * c;
    
    // Vertical alignment is allowed for small, related groups
    int   width  = 800;
    int   height = 600;
    float scale  = 1.0f;
  10. Generate and update custom sky cubemaps

    master

    To use a sky shader, you must first generate a cubemap and then optionally update it.

    • R3D_GenCustomSky(int size, R3D_SkyShader* shader): Allocates a new cubemap and renders all six faces.
    • R3D_UpdateCustomSky(R3D_Cubemap* cubemap, R3D_SkyShader* shader): Re-renders an existing cubemap in place.

    Performance Note: Updating a cubemap every frame can be expensive. For large sizes, consider updating at a lower frequency or using a smaller resolution (e.g., 64–128).

    R3D_SkyShader* shader = R3D_LoadSkyShader("sky.glsl");
    
    // Generate once at startup and set it to the environment
    R3D_Cubemap sky = R3D_GenCustomSky(512, shader);
    R3D_GetEnvironment()->background.sky = sky;
    
    float time = 0.0f;
    
    while (!WindowShouldClose()) {
        // Update sky every frame for animated effects
        time += GetFrameTime();
        R3D_SetSkyShaderUniform(shader, "u_time", &time);
        R3D_UpdateCustomSky(&sky, shader);
    
        R3D_Begin();
        // ... draw scene with sky cubemap ...
        R3D_End();
    }
    
    R3D_UnloadSkyShader(shader);
  11. Format switch statements

    master

    In switch statements, case and default labels must not be indented relative to the switch keyword. The statements inside each case should be indented one level from the label.

    switch (value)
    {
    case 1:
        DoA();
        break;
    
    case 2:
        DoB();
        break;
    
    default:
        DoC();
        break;
    }
  12. Integrate TinyCThread into your project

    master

    TinyCThread is a minimalist, portable threading library for C modeled after the C11 standard. You can integrate it into your project using one of two methods:

    Method 1: Manual Integration

    Add tinycthread.c and tinycthread.h directly to your project source files and include the header in your code:

    #include <tinycthread.h>

    Method 2: CMake Integration

    If your project uses CMake, you can add TinyCThread as a subdirectory. This automatically handles include directories and CTest integration. Use target_link_libraries to link the tinycthread target to your executable.

    add_subdirectory(tinycthread)
    target_link_libraries(your_executable_name tinycthread)