← ziglang.inraylib-zigGitHub

framebuffer rendering

★★☆☆by Jack Boakes (@jackboakes)

There is no browser build of this example yet.

Is it the same as C?

//! raylib [textures] example - framebuffer rendering
//!
//! Zig-API translation of examples/textures/textures_framebuffer_rendering.c (raylib 6.2).
//! Example contributed by Jack Boakes (@jackboakes) and reviewed by Ramon Santamaria (@raysan5)
//! Example licensed under an unmodified zlib/libpng license.
//! Copyright (c) 2026 Jack Boakes (@jackboakes)

const rl = @import("raylib");

pub fn main() !void {
    // Initialization
    const screen_width = 800;
    const screen_height = 450;
    const split_width = screen_width / 2;

    rl.initWindow(screen_width, screen_height, "raylib [textures] example - framebuffer rendering");
    defer rl.closeWindow(); // Close window and OpenGL context

    // Camera to look at the 3D world
    var subject_camera: rl.Camera3D = .{
        .position = .{ .x = 5.0, .y = 5.0, .z = 5.0 },
        .target = .{ .x = 0.0, .y = 0.0, .z = 0.0 },
        .up = .{ .x = 0.0, .y = 1.0, .z = 0.0 },
        .fovy = 45.0,
        .projection = .perspective,
    };

    // Camera to observe the subject camera and 3D world
    var observer_camera: rl.Camera3D = .{
        .position = .{ .x = 10.0, .y = 10.0, .z = 10.0 },
        .target = .{ .x = 0.0, .y = 0.0, .z = 0.0 },
        .up = .{ .x = 0.0, .y = 1.0, .z = 0.0 },
        .fovy = 45.0,
        .projection = .perspective,
    };

    // Set up render textures
    const observer_target = try rl.loadRenderTexture(split_width, screen_height);
    defer rl.unloadRenderTexture(observer_target);
    const observer_source: rl.Rectangle = .{
        .x = 0.0,
        .y = 0.0,
        .width = @floatFromInt(observer_target.texture.width),
        .height = -@as(f32, @floatFromInt(observer_target.texture.height)),
    };
    const observer_dest: rl.Rectangle = .{ .x = 0.0, .y = 0.0, .width = split_width, .height = screen_height };

    const subject_target = try rl.loadRenderTexture(split_width, screen_height);
    defer rl.unloadRenderTexture(subject_target);
    const subject_width: f32 = @floatFromInt(subject_target.texture.width);
    const subject_height: f32 = @floatFromInt(subject_target.texture.height);
    const subject_source: rl.Rectangle = .{ .x = 0.0, .y = 0.0, .width = subject_width, .height = -subject_height };
    const subject_dest: rl.Rectangle = .{ .x = split_width, .y = 0.0, .width = split_width, .height = screen_height };
    const texture_aspect_ratio = subject_width / subject_height;

    // Rectangles for cropping render texture
    const capture_size: f32 = 128.0;
    const crop_source: rl.Rectangle = .{
        .x = (subject_width - capture_size) / 2.0,
        .y = (subject_height - capture_size) / 2.0,
        .width = capture_size,
        .height = -capture_size,
    };
    const crop_dest: rl.Rectangle = .{ .x = split_width + 20.0, .y = 20.0, .width = capture_size, .height = capture_size };

    rl.setTargetFPS(60);
    rl.disableCursor();

    // Main game loop
    while (!rl.windowShouldClose()) { // Detect window close button or ESC key
        // Update
        rl.updateCamera(&observer_camera, .free);
        rl.updateCamera(&subject_camera, .orbital);

        if (rl.isKeyPressed(.r)) observer_camera.target = .{ .x = 0.0, .y = 0.0, .z = 0.0 };

        // Build LHS observer view texture
        {
            rl.beginTextureMode(observer_target);
            defer rl.endTextureMode();

            rl.clearBackground(.ray_white);

            {
                rl.beginMode3D(observer_camera);
                defer rl.endMode3D();

                rl.drawGrid(10, 1.0);
                rl.drawCube(.{ .x = 0.0, .y = 0.0, .z = 0.0 }, 2.0, 2.0, 2.0, .gold);
                rl.drawCubeWires(.{ .x = 0.0, .y = 0.0, .z = 0.0 }, 2.0, 2.0, 2.0, .pink);
                drawCameraPrism(subject_camera, texture_aspect_ratio, .green);
            }

            rl.drawText("Observer View", 10, observer_target.texture.height - 30, 20, .black);
            rl.drawText("WASD + Mouse to Move", 10, 10, 20, .dark_gray);
            rl.drawText("Scroll to Zoom", 10, 30, 20, .dark_gray);
            rl.drawText("R to Reset Observer Target", 10, 50, 20, .dark_gray);
        }

        // Build RHS subject view texture
        {
            rl.beginTextureMode(subject_target);
            defer rl.endTextureMode();

            rl.clearBackground(.ray_white);

            {
                rl.beginMode3D(subject_camera);
                defer rl.endMode3D();

                rl.drawCube(.{ .x = 0.0, .y = 0.0, .z = 0.0 }, 2.0, 2.0, 2.0, .gold);
                rl.drawCubeWires(.{ .x = 0.0, .y = 0.0, .z = 0.0 }, 2.0, 2.0, 2.0, .pink);
                rl.drawGrid(10, 1.0);
            }

            rl.drawRectangleLines(
                @intFromFloat((subject_width - capture_size) / 2.0),
                @intFromFloat((subject_height - capture_size) / 2.0),
                @intFromFloat(capture_size),
                @intFromFloat(capture_size),
                .green,
            );
            rl.drawText("Subject View", 10, subject_target.texture.height - 30, 20, .black);
        }

        // Draw
        rl.beginDrawing();
        defer rl.endDrawing();

        rl.clearBackground(.black);

        // Draw observer texture LHS
        rl.drawTexturePro(observer_target.texture, observer_source, observer_dest, .{ .x = 0.0, .y = 0.0 }, 0.0, .white);

        // Draw subject texture RHS
        rl.drawTexturePro(subject_target.texture, subject_source, subject_dest, .{ .x = 0.0, .y = 0.0 }, 0.0, .white);

        // Draw the small crop overlay on top
        rl.drawTexturePro(subject_target.texture, crop_source, crop_dest, .{ .x = 0.0, .y = 0.0 }, 0.0, .white);
        rl.drawRectangleLinesEx(crop_dest, 2, .black);

        // Draw split screen divider line
        rl.drawLine(split_width, 0, split_width, screen_height, .black);
    }
}

/// Draw the camera's view prism, from its position to the plane sliced at its target.
fn drawCameraPrism(camera: rl.Camera3D, aspect: f32, color: rl.Color) void {
    const length = rl.math.vector3Distance(camera.position, camera.target);
    // Define the 4 corners of the camera's prism plane sliced at the target in Normalized Device Coordinates
    const plane_ndc = [4]rl.Vector3{
        .{ .x = -1.0, .y = -1.0, .z = 1.0 }, // Bottom Left
        .{ .x = 1.0, .y = -1.0, .z = 1.0 }, // Bottom Right
        .{ .x = 1.0, .y = 1.0, .z = 1.0 }, // Top Right
        .{ .x = -1.0, .y = 1.0, .z = 1.0 }, // Top Left
    };

    // Build the matrices
    const view = rl.getCameraMatrix(camera);
    const proj = rl.math.matrixPerspective(camera.fovy * rl.deg2rad, aspect, 0.05, length);
    // Combine view and projection so we can reverse the full camera transform
    const view_proj = rl.math.matrixMultiply(view, proj);
    // Invert the view-projection matrix to unproject points from NDC space back into world space
    const m = rl.math.matrixInvert(view_proj);

    // Transform the 4 plane corners from NDC into world space
    var corners: [4]rl.Vector3 = undefined;
    for (&corners, plane_ndc) |*corner, p| {
        // Multiply NDC position by the inverse view-projection matrix
        // This produces a homogeneous (x, y, z, w) position in world space
        const vx = m.m0 * p.x + m.m4 * p.y + m.m8 * p.z + m.m12;
        const vy = m.m1 * p.x + m.m5 * p.y + m.m9 * p.z + m.m13;
        const vz = m.m2 * p.x + m.m6 * p.y + m.m10 * p.z + m.m14;
        const vw = m.m3 * p.x + m.m7 * p.y + m.m11 * p.z + m.m15;

        corner.* = .{ .x = vx / vw, .y = vy / vw, .z = vz / vw };
    }

    // Draw the far plane sliced at the target
    for (corners, 0..) |corner, i| {
        rl.drawLine3D(corner, corners[(i + 1) % corners.len], color);
    }

    // Draw the prism lines from the far plane to the camera position
    for (corners) |corner| rl.drawLine3D(camera.position, corner, color);
}
/*******************************************************************************************
*
*   raylib [textures] example - framebuffer rendering
*
*   Example complexity rating: [★★☆☆] 2/4
*
*   Example originally created with raylib 5.6, last time updated with raylib 5.6
*
*   Example contributed by Jack Boakes (@jackboakes) and reviewed by Ramon Santamaria (@raysan5)
*
*   Example licensed under an unmodified zlib/libpng license, which is an OSI-certified,
*   BSD-like license that allows static linking with closed source software
*
*   Copyright (c) 2026 Jack Boakes (@jackboakes)
*
********************************************************************************************/

#include "raylib.h"
#include "raymath.h"

//------------------------------------------------------------------------------------
// Module Functions Declaration
//------------------------------------------------------------------------------------
static void DrawCameraPrism(Camera3D camera, float aspect, Color color);

//------------------------------------------------------------------------------------
// Program main entry point
//------------------------------------------------------------------------------------
int main(void)
{
    // Initialization
    //--------------------------------------------------------------------------------------
    const int screenWidth = 800;
    const int screenHeight = 450;
    const int splitWidth = screenWidth/2;

    InitWindow(screenWidth, screenHeight, "raylib [textures] example - framebuffer rendering");

    // Camera to look at the 3D world
    Camera3D subjectCamera = { 0 };
    subjectCamera.position = (Vector3){ 5.0f, 5.0f, 5.0f };
    subjectCamera.target = (Vector3){ 0.0f, 0.0f, 0.0f };
    subjectCamera.up = (Vector3){ 0.0f, 1.0f, 0.0f };
    subjectCamera.fovy = 45.0f;
    subjectCamera.projection = CAMERA_PERSPECTIVE;

    // Camera to observe the subject camera and 3D world
    Camera3D observerCamera = { 0 };
    observerCamera.position = (Vector3){ 10.0f, 10.0f, 10.0f };
    observerCamera.target = (Vector3){ 0.0f, 0.0f, 0.0f };
    observerCamera.up = (Vector3){ 0.0f, 1.0f, 0.0f };
    observerCamera.fovy = 45.0f;
    observerCamera.projection = CAMERA_PERSPECTIVE;

    // Set up render textures
    RenderTexture2D observerTarget = LoadRenderTexture(splitWidth, screenHeight);
    Rectangle observerSource = { 0.0f, 0.0f, (float)observerTarget.texture.width, -(float)observerTarget.texture.height };
    Rectangle observerDest = { 0.0f, 0.0f, (float)splitWidth, (float)screenHeight };

    RenderTexture2D subjectTarget = LoadRenderTexture(splitWidth, screenHeight);
    Rectangle subjectSource = { 0.0f, 0.0f, (float)subjectTarget.texture.width, -(float)subjectTarget.texture.height };
    Rectangle subjectDest = { (float)splitWidth, 0.0f, (float)splitWidth, (float)screenHeight };
    const float textureAspectRatio = (float)subjectTarget.texture.width/(float)subjectTarget.texture.height;

    // Rectangles for cropping render texture
    const float captureSize = 128.0f;
    Rectangle cropSource = { (subjectTarget.texture.width - captureSize)/2.0f, (subjectTarget.texture.height - captureSize)/2.0f, captureSize, -captureSize };
    Rectangle cropDest = { splitWidth + 20.0f, 20.0f, captureSize, captureSize};

    SetTargetFPS(60);
    DisableCursor();
    //--------------------------------------------------------------------------------------

    // Main game loop
    while (!WindowShouldClose())    // Detect window close button or ESC key
    {
        // Update
        //----------------------------------------------------------------------------------
        UpdateCamera(&observerCamera, CAMERA_FREE);
        UpdateCamera(&subjectCamera, CAMERA_ORBITAL);

        if (IsKeyPressed(KEY_R)) observerCamera.target = (Vector3){ 0.0f, 0.0f, 0.0f };

        // Build LHS observer view texture
        BeginTextureMode(observerTarget);

            ClearBackground(RAYWHITE);

            BeginMode3D(observerCamera);

                DrawGrid(10, 1.0f);
                DrawCube((Vector3){ 0.0f, 0.0f, 0.0f }, 2.0f, 2.0f, 2.0f, GOLD);
                DrawCubeWires((Vector3){ 0.0f, 0.0f, 0.0f }, 2.0f, 2.0f, 2.0f, PINK);
                DrawCameraPrism(subjectCamera, textureAspectRatio, GREEN);

            EndMode3D();

            DrawText("Observer View", 10, observerTarget.texture.height - 30, 20, BLACK);
            DrawText("WASD + Mouse to Move", 10, 10, 20, DARKGRAY);
            DrawText("Scroll to Zoom", 10, 30, 20, DARKGRAY);
            DrawText("R to Reset Observer Target", 10, 50, 20, DARKGRAY);

        EndTextureMode();

        // Build RHS subject view texture
        BeginTextureMode(subjectTarget);

            ClearBackground(RAYWHITE);

            BeginMode3D(subjectCamera);

                DrawCube((Vector3){ 0.0f, 0.0f, 0.0f }, 2.0f, 2.0f, 2.0f, GOLD);
                DrawCubeWires((Vector3){ 0.0f, 0.0f, 0.0f }, 2.0f, 2.0f, 2.0f, PINK);
                DrawGrid(10, 1.0f);

            EndMode3D();

            DrawRectangleLines((int)((subjectTarget.texture.width - captureSize)/2.0f), (int)((subjectTarget.texture.height - captureSize)/2.0f), (int)captureSize, (int)captureSize, GREEN);
            DrawText("Subject View", 10, subjectTarget.texture.height - 30, 20, BLACK);

        EndTextureMode();
        //----------------------------------------------------------------------------------

        // Draw
        //----------------------------------------------------------------------------------
        BeginDrawing();

            ClearBackground(BLACK);

            // Draw observer texture LHS
            DrawTexturePro(observerTarget.texture, observerSource, observerDest, (Vector2){0.0f, 0.0f }, 0.0f, WHITE);

            // Draw subject texture RHS
            DrawTexturePro(subjectTarget.texture, subjectSource, subjectDest, (Vector2){ 0.0f, 0.0f }, 0.0f, WHITE);

            // Draw the small crop overlay on top
            DrawTexturePro(subjectTarget.texture, cropSource, cropDest, (Vector2){ 0.0f, 0.0f }, 0.0f, WHITE);
            DrawRectangleLinesEx(cropDest, 2, BLACK);

            // Draw split screen divider line
            DrawLine(splitWidth, 0, splitWidth, screenHeight, BLACK);

        EndDrawing();
        //----------------------------------------------------------------------------------
    }

    // De-Initialization
    //--------------------------------------------------------------------------------------
    UnloadRenderTexture(observerTarget);
    UnloadRenderTexture(subjectTarget);

    CloseWindow();        // Close window and OpenGL context
    //--------------------------------------------------------------------------------------

    return 0;
}

//----------------------------------------------------------------------------------
// Module Functions Definition
//----------------------------------------------------------------------------------
static void DrawCameraPrism(Camera3D camera, float aspect, Color color)
{
    float length = Vector3Distance(camera.position, camera.target);
    // Define the 4 corners of the camera's prism plane sliced at the target in Normalized Device Coordinates
    Vector3 planeNDC[4] = {
        { -1.0f, -1.0f, 1.0f }, // Bottom Left
        {  1.0f, -1.0f, 1.0f }, // Bottom Right
        {  1.0f,  1.0f, 1.0f }, // Top Right
        { -1.0f,  1.0f, 1.0f }  // Top Left
    };

    // Build the matrices
    Matrix view = GetCameraMatrix(camera);
    Matrix proj = MatrixPerspective(camera.fovy * DEG2RAD, aspect, 0.05f, length);
    // Combine view and projection so we can reverse the full camera transform
    Matrix viewProj = MatrixMultiply(view, proj);
    // Invert the view-projection matrix to unproject points from NDC space back into world space
    Matrix inverseViewProj = MatrixInvert(viewProj);

    // Transform the 4 plane corners from NDC into world space
    Vector3 corners[4];
    for (int i = 0; i < 4; i++)
    {
        float x = planeNDC[i].x;
        float y = planeNDC[i].y;
        float z = planeNDC[i].z;

        // Multiply NDC position by the inverse view-projection matrix
        // This produces a homogeneous (x, y, z, w) position in world space
        float vx = inverseViewProj.m0*x + inverseViewProj.m4*y + inverseViewProj.m8*z + inverseViewProj.m12;
        float vy = inverseViewProj.m1*x + inverseViewProj.m5*y + inverseViewProj.m9*z + inverseViewProj.m13;
        float vz = inverseViewProj.m2*x + inverseViewProj.m6*y + inverseViewProj.m10*z + inverseViewProj.m14;
        float vw = inverseViewProj.m3*x + inverseViewProj.m7*y + inverseViewProj.m11*z + inverseViewProj.m15;

        corners[i] = (Vector3){ vx/vw, vy/vw, vz/vw };
    }

    // Draw the far plane sliced at the target
    DrawLine3D(corners[0], corners[1], color);
    DrawLine3D(corners[1], corners[2], color);
    DrawLine3D(corners[2], corners[3], color);
    DrawLine3D(corners[3], corners[0], color);

    // Draw the prism lines from the far plane to the camera position
    for (int i = 0; i < 4; i++)
    {
        DrawLine3D(camera.position, corners[i], color);
    }
}

From raylib 6.2. The original on GitHub.