← ziglang.inraylib-zigGitHub

normalmap rendering

★★★★by Jeremy Montgomery (@Sir_Irk)

There is no browser build of this example yet.

Is it the same as C?

//! raylib [shaders] example - normalmap rendering
//!
//! Zig-API translation of examples/shaders/shaders_normalmap_rendering.c (raylib 6.2).
//! Example contributed by Jeremy Montgomery (@Sir_Irk) and reviewed by Ramon Santamaria (@raysan5)
//! Example licensed under an unmodified zlib/libpng license.
//! Copyright (c) 2025 Jeremy Montgomery (@Sir_Irk) and Ramon Santamaria (@raysan5)

const std = @import("std");
const rl = @import("raylib");

const glsl_version = 330;

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

    rl.setConfigFlags(.{ .msaa_4x_hint = true });
    rl.initWindow(screen_width, screen_height, "raylib [shaders] example - normalmap rendering");
    defer rl.closeWindow(); // Close window and OpenGL context

    const camera: rl.Camera = .{
        .position = .{ .x = 0.0, .y = 2.0, .z = -4.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,
    };

    // Load basic normal map lighting shader
    const shader = try rl.loadShader(
        std.fmt.comptimePrint("resources/shaders/glsl{d}/normalmap.vs", .{glsl_version}),
        std.fmt.comptimePrint("resources/shaders/glsl{d}/normalmap.fs", .{glsl_version}),
    );
    defer rl.unloadShader(shader);

    // Get some required shader locations
    shader.loc(.map_normal).* = rl.getShaderLocation(shader, "normalMap");
    shader.loc(.vector_view).* = rl.getShaderLocation(shader, "viewPos");

    // NOTE: "matModel" location name is automatically assigned on shader loading,
    // no need to get the location again if using that uniform name
    //shader.loc(.matrix_model).* = rl.getShaderLocation(shader, "matModel");

    // This example uses just 1 point light
    var light_position: rl.Vector3 = .{ .x = 0.0, .y = 1.0, .z = 0.0 };
    const light_pos_loc = rl.getShaderLocation(shader, "lightPos");

    // Load a plane model that has proper normals and tangents
    var plane = try rl.loadModel("resources/models/plane.glb");
    defer rl.unloadModel(plane);

    // Set the plane model's shader and texture maps
    const material = &plane.materials.?[0];
    material.shader = shader;
    const diffuse_map = material.map(.diffuse);
    const normal_map = material.map(.normal);
    diffuse_map.texture = try rl.loadTexture("resources/tiles_diffuse.png");
    normal_map.texture = try rl.loadTexture("resources/tiles_normal.png");

    // Generate Mipmaps and use TRILINEAR filtering to help with texture aliasing
    rl.genTextureMipmaps(&diffuse_map.texture);
    rl.genTextureMipmaps(&normal_map.texture);

    rl.setTextureFilter(diffuse_map.texture, .trilinear);
    rl.setTextureFilter(normal_map.texture, .trilinear);

    // Specular exponent AKA shininess of the material
    var specular_exponent: f32 = 8.0;
    const specular_exponent_loc = rl.getShaderLocation(shader, "specularExponent");

    // Allow toggling the normal map on and off for comparison purposes
    var use_normal_map = true;
    const use_normal_map_loc = rl.getShaderLocation(shader, "useNormalMap");

    rl.setTargetFPS(60); // Set our game to run at 60 frames-per-second

    // Main game loop
    while (!rl.windowShouldClose()) { // Detect window close button or ESC key
        // Update
        // Move the light around on the X and Z axis using WASD keys
        var direction: rl.Vector3 = .{ .x = 0, .y = 0, .z = 0 };
        if (rl.isKeyDown(.w)) direction = rl.math.vector3Add(direction, .{ .x = 0.0, .y = 0.0, .z = 1.0 });
        if (rl.isKeyDown(.s)) direction = rl.math.vector3Add(direction, .{ .x = 0.0, .y = 0.0, .z = -1.0 });
        if (rl.isKeyDown(.d)) direction = rl.math.vector3Add(direction, .{ .x = -1.0, .y = 0.0, .z = 0.0 });
        if (rl.isKeyDown(.a)) direction = rl.math.vector3Add(direction, .{ .x = 1.0, .y = 0.0, .z = 0.0 });

        direction = rl.math.vector3Normalize(direction);
        light_position = rl.math.vector3Add(light_position, rl.math.vector3Scale(direction, rl.getFrameTime() * 3.0));

        // Increase/Decrease the specular exponent(shininess)
        if (rl.isKeyDown(.up)) specular_exponent = rl.math.clamp(specular_exponent + 40.0 * rl.getFrameTime(), 2.0, 128.0);
        if (rl.isKeyDown(.down)) specular_exponent = rl.math.clamp(specular_exponent - 40.0 * rl.getFrameTime(), 2.0, 128.0);

        // Toggle normal map on and off
        if (rl.isKeyPressed(.n)) use_normal_map = !use_normal_map;

        // Spin plane model at a constant rate
        plane.transform = rl.math.matrixRotateY(@as(f32, @floatCast(rl.getTime())) * 0.5);

        // Update shader values
        rl.setShaderValue(shader, light_pos_loc, light_position);
        rl.setShaderValue(shader, shader.loc(.vector_view).*, camera.position);
        rl.setShaderValue(shader, specular_exponent_loc, specular_exponent);
        const use_normal_map_value: i32 = @intFromBool(use_normal_map);
        rl.setShaderValue(shader, use_normal_map_loc, use_normal_map_value);

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

        rl.clearBackground(.ray_white);

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

            {
                rl.beginShaderMode(shader);
                defer rl.endShaderMode();

                rl.drawModel(plane, rl.math.vector3Zero(), 2.0, .white);
            }

            // Draw sphere to show light position
            rl.drawSphereWires(light_position, 0.2, 8, 8, .orange);
        }

        const text_color: rl.Color = if (use_normal_map) .dark_green else .red;
        const toggle_str = if (use_normal_map) "On" else "Off";
        rl.drawText(rl.textFormat("Use key [N] to toggle normal map: {s}", .{toggle_str}), 10, 10, 10, text_color);

        const y_offset = 24;
        rl.drawText("Use keys [W][A][S][D] to move the light", 10, 10 + y_offset * 1, 10, .black);
        rl.drawText("Use keys [Up][Down] to change specular exponent", 10, 10 + y_offset * 2, 10, .black);
        rl.drawText(rl.textFormat("Specular Exponent: {d:.2}", .{specular_exponent}), 10, 10 + y_offset * 3, 10, .blue);

        rl.drawFPS(screen_width - 90, 10);
    }
}
/*******************************************************************************************
*
*   raylib [shaders] example - normalmap rendering
*
*   Example complexity rating: [★★★★] 4/4
*
*   NOTE: This example requires raylib OpenGL 3.3 or ES2 versions for shaders support,
*        OpenGL 1.1 does not support shaders, recompile raylib to OpenGL 3.3 version
*
*   Example originally created with raylib 6.0, last time updated with raylib 6.0
*
*   Example contributed by Jeremy Montgomery (@Sir_Irk) 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) 2025 Jeremy Montgomery (@Sir_Irk) and Ramon Santamaria (@raysan5)
*
********************************************************************************************/

#include "raylib.h"

#include "raymath.h"

#if defined(PLATFORM_DESKTOP)
    #define GLSL_VERSION            330
#else   // PLATFORM_ANDROID, PLATFORM_WEB
    #define GLSL_VERSION            100
#endif

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

    SetConfigFlags(FLAG_MSAA_4X_HINT);
    InitWindow(screenWidth, screenHeight, "raylib [shaders] example - normalmap rendering");

    Camera camera = { 0 };
    camera.position = (Vector3){ 0.0f, 2.0f, -4.0f };
    camera.target = (Vector3){ 0.0f, 0.0f, 0.0f };
    camera.up = (Vector3){ 0.0f, 1.0f, 0.0f };
    camera.fovy = 45.0f;
    camera.projection = CAMERA_PERSPECTIVE;

    // Load basic normal map lighting shader
    Shader shader = LoadShader(TextFormat("resources/shaders/glsl%i/normalmap.vs", GLSL_VERSION),
                               TextFormat("resources/shaders/glsl%i/normalmap.fs", GLSL_VERSION));

    // Get some required shader locations
    shader.locs[SHADER_LOC_MAP_NORMAL] = GetShaderLocation(shader, "normalMap");
    shader.locs[SHADER_LOC_VECTOR_VIEW] = GetShaderLocation(shader, "viewPos");

    // NOTE: "matModel" location name is automatically assigned on shader loading,
    // no need to get the location again if using that uniform name
    // shader.locs[SHADER_LOC_MATRIX_MODEL] = GetShaderLocation(shader, "matModel");

    // This example uses just 1 point light
    Vector3 lightPosition = { 0.0f, 1.0f, 0.0f };
    int lightPosLoc = GetShaderLocation(shader, "lightPos");

    // Load a plane model that has proper normals and tangents
    Model plane = LoadModel("resources/models/plane.glb");

    // Set the plane model's shader and texture maps
    plane.materials[0].shader = shader;
    plane.materials[0].maps[MATERIAL_MAP_DIFFUSE].texture = LoadTexture("resources/tiles_diffuse.png");
    plane.materials[0].maps[MATERIAL_MAP_NORMAL].texture = LoadTexture("resources/tiles_normal.png");

    // Generate Mipmaps and use TRILINEAR filtering to help with texture aliasing
    GenTextureMipmaps(&plane.materials[0].maps[MATERIAL_MAP_DIFFUSE].texture);
    GenTextureMipmaps(&plane.materials[0].maps[MATERIAL_MAP_NORMAL].texture);

    SetTextureFilter(plane.materials[0].maps[MATERIAL_MAP_DIFFUSE].texture, TEXTURE_FILTER_TRILINEAR);
    SetTextureFilter(plane.materials[0].maps[MATERIAL_MAP_NORMAL].texture, TEXTURE_FILTER_TRILINEAR);

    // Specular exponent AKA shininess of the material
    float specularExponent = 8.0f;
    int specularExponentLoc = GetShaderLocation(shader, "specularExponent");

    // Allow toggling the normal map on and off for comparison purposes
    int useNormalMap = 1;
    int useNormalMapLoc = GetShaderLocation(shader, "useNormalMap");

    SetTargetFPS(60); // Set our game to run at 60 frames-per-second
    //--------------------------------------------------------------------------------------

    // Main game loop
    while (!WindowShouldClose()) // Detect window close button or ESC key
    {
        // Update
        //----------------------------------------------------------------------------------
        // Move the light around on the X and Z axis using WASD keys
        Vector3 direction = { 0 };
        if (IsKeyDown(KEY_W)) direction = Vector3Add(direction, (Vector3){ 0.0f, 0.0f, 1.0f });
        if (IsKeyDown(KEY_S)) direction = Vector3Add(direction, (Vector3){ 0.0f, 0.0f, -1.0f });
        if (IsKeyDown(KEY_D)) direction = Vector3Add(direction, (Vector3){ -1.0f, 0.0f, 0.0f });
        if (IsKeyDown(KEY_A)) direction = Vector3Add(direction, (Vector3){ 1.0f, 0.0f, 0.0f });

        direction = Vector3Normalize(direction);
        lightPosition = Vector3Add(lightPosition, Vector3Scale(direction, GetFrameTime()*3.0f));

        // Increase/Decrease the specular exponent(shininess)
        if (IsKeyDown(KEY_UP)) specularExponent = Clamp(specularExponent + 40.0f*GetFrameTime(), 2.0f, 128.0f);
        if (IsKeyDown(KEY_DOWN)) specularExponent = Clamp(specularExponent - 40.0f*GetFrameTime(), 2.0f, 128.0f);

        // Toggle normal map on and off
        if (IsKeyPressed(KEY_N)) useNormalMap = !useNormalMap;

        // Spin plane model at a constant rate
        plane.transform = MatrixRotateY((float)GetTime()*0.5f);

        // Update shader values
        float lightPos[3] = {lightPosition.x, lightPosition.y, lightPosition.z};
        SetShaderValue(shader, lightPosLoc, lightPos, SHADER_UNIFORM_VEC3);

        float camPos[3] = {camera.position.x, camera.position.y, camera.position.z};
        SetShaderValue(shader, shader.locs[SHADER_LOC_VECTOR_VIEW], camPos, SHADER_UNIFORM_VEC3);

        SetShaderValue(shader, specularExponentLoc, &specularExponent, SHADER_UNIFORM_FLOAT);

        SetShaderValue(shader, useNormalMapLoc, &useNormalMap, SHADER_UNIFORM_INT);
        //--------------------------------------------------------------------------------------

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

            ClearBackground(RAYWHITE);

            BeginMode3D(camera);

                BeginShaderMode(shader);

                    DrawModel(plane, Vector3Zero(), 2.0f, WHITE);

                EndShaderMode();

                // Draw sphere to show light position
                DrawSphereWires(lightPosition, 0.2f, 8, 8, ORANGE);

            EndMode3D();

            Color textColor = (useNormalMap) ? DARKGREEN : RED;
            const char *toggleStr = (useNormalMap) ? "On" : "Off";
            DrawText(TextFormat("Use key [N] to toggle normal map: %s", toggleStr), 10, 10, 10, textColor);

            int yOffset = 24;
            DrawText("Use keys [W][A][S][D] to move the light", 10, 10 + yOffset*1, 10, BLACK);
            DrawText("Use keys [Up][Down] to change specular exponent", 10, 10 + yOffset*2, 10, BLACK);
            DrawText(TextFormat("Specular Exponent: %.2f", specularExponent), 10, 10 + yOffset*3, 10, BLUE);

            DrawFPS(screenWidth - 90, 10);

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

    // De-Initialization
    //--------------------------------------------------------------------------------------
    UnloadShader(shader);
    UnloadModel(plane);

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

    return 0;
}

From raylib 6.2. The original on GitHub.