diff --git a/build.zig b/build.zig index 57e22d8..209e363 100644 --- a/build.zig +++ b/build.zig @@ -184,6 +184,16 @@ pub fn build(b: *std.Build) !void { .path = "examples/shaders/raymarching.zig", .desc = "Uses a raymarching in a shader to render shapes", }, + .{ + .name = "shaders_basic_pbr", + .path = "examples/shaders/shaders_basic_pbr.zig", + .desc = "Demonstrates physically based rendering", + }, + .{ + .name = "shaders_hybrid_render", + .path = "examples/shaders/shaders_hybrid_render.zig", + .desc = "Demonstrates hybrid rendering", + }, .{ .name = "texture_outline", .path = "examples/shaders/texture_outline.zig", diff --git a/examples/shaders/shaders_basic_pbr.zig b/examples/shaders/shaders_basic_pbr.zig new file mode 100644 index 0000000..da73a0f --- /dev/null +++ b/examples/shaders/shaders_basic_pbr.zig @@ -0,0 +1,366 @@ +// raylib [shaders] example - Basic PBR +// +// Example complexity rating: [★★★★] 4/4 +// +// Example originally created with raylib 5.0, last time updated with raylib 5.1-dev +// +// Example contributed by Afan OLOVCIC (@_DevDad) 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) 2023-2025 Afan OLOVCIC (@_DevDad) +// +// Model: "Old Rusty Car" (https://skfb.ly/LxRy) by Renafox, +// licensed under Creative Commons Attribution-NonCommercial +// (http://creativecommons.org/licenses/by-nc/4.0/) + +const rl = @import("raylib"); + +/// Casts ShaderLocationIndex to a u32 +fn uSli(sli: rl.ShaderLocationIndex) u32 { + return @intCast(@intFromEnum(sli)); +} + +/// Casts MaterialMapIndex to a u32 +fn uMmi(mmi: rl.MaterialMapIndex) u32 { + return @intCast(@intFromEnum(mmi)); +} + +/// Max dynamic lights supported by shader +const max_lights = 4; +/// Current number of dynamic lights that have been created +var light_count: u32 = 0; + +//------------------------------------------------------------------------------ +// Types and Structures Definition +//------------------------------------------------------------------------------ + +/// Light data +const Light = extern struct { + type: Type = .directional, + enabled: bool = false, + _enabled_pad1: u8 = 0, + _enabled_pad2: @Type(.{.int = .{ + .signedness = .unsigned, + .bits = @bitSizeOf(c_uint) - 16, + }}) = 0, + position: rl.Vector3 = .init(0, 0, 0), + target: rl.Vector3 = .init(0, 0, 0), + color: [4]f32 = .{ 0, 0, 0, 0 }, + intensity: f32 = 0, + + // Shader light parameters locations + loc: extern struct { + type: i32 = 0, + enabled: i32 = 0, + position: i32 = 0, + target: i32 = 0, + color: i32 = 0, + intensity: i32 = 0, + } = .{}, + + /// Light type + const Type = enum(c_uint) { + directional = 0, + point, + spot, + }; + + /// Create light with provided data + /// + /// NOTE: It updates `light_count` and is limited to `max_lights` + fn init( + t: Type, + position: rl.Vector3, + target: rl.Vector3, + color: rl.Color, + intensity: f32, + shader: rl.Shader, + ) Light { + if (light_count >= max_lights) { + return .{}; + } + const light: Light = .{ + .type = t, + .enabled = true, + .position = position, + .target = target, + .color = .{ + @as(f32, @floatFromInt(color.r)) / 255.0, + @as(f32, @floatFromInt(color.g)) / 255.0, + @as(f32, @floatFromInt(color.b)) / 255.0, + @as(f32, @floatFromInt(color.a)) / 255.0, + }, + .intensity = intensity, + + // NOTE: Shader parameters names for lights must match the requested ones + .loc = .{ + .type = rl.getShaderLocation(shader, rl.textFormat("lights[%i].type", .{ light_count })), + .enabled = rl.getShaderLocation(shader, rl.textFormat("lights[%i].enabled", .{ light_count })), + .position = rl.getShaderLocation(shader, rl.textFormat("lights[%i].position", .{ light_count })), + .target = rl.getShaderLocation(shader, rl.textFormat("lights[%i].target", .{ light_count })), + .color = rl.getShaderLocation(shader, rl.textFormat("lights[%i].color", .{ light_count })), + .intensity = rl.getShaderLocation(shader, rl.textFormat("lights[%i].intensity", .{ light_count })), + }, + }; + light.update(shader); + light_count += 1; + + return light; + } + + /// Send light properties to shader + /// + /// NOTE: Light shader locations should be available + fn update(self: Light, shader: rl.Shader) void { + rl.setShaderValue(shader, self.loc.type, &self.type, .int); + rl.setShaderValue(shader, self.loc.enabled, &self.enabled, .int); + + // Send to shader light position values + const position: [3]f32 = .{ self.position.x, self.position.y, self.position.z }; + rl.setShaderValue(shader, self.loc.position, &position, .vec3); + + // Send to shader light target position values + const target: [3]f32 = .{ self.target.x, self.target.y, self.target.z }; + rl.setShaderValue(shader, self.loc.target, &target, .vec3); + rl.setShaderValue(shader, self.loc.color, &self.color, .vec4); + rl.setShaderValue(shader, self.loc.intensity, &self.intensity, .float); + } +}; + +//---------------------------------------------------------------------------------- +// Main Entry Point +//---------------------------------------------------------------------------------- +pub fn main() anyerror!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 - basic pbr"); + defer rl.closeWindow(); // Close window and OpenGL context + + // Define the camera to look into our 3d world + var camera: rl.Camera = .{ + .position = .init(2, 2, 6), // Camera position + .target = .init(0, 0.5, 0), // Camera looking at point + .up = .init(0, 1, 0), // Camera up vector (rotation towards target) + .fovy = 45, // Camera field-of-view Y + .projection = .perspective, // Camera projection type + }; + + // Load PBR shader and setup all required locations + const shader: rl.Shader = try rl.loadShader( + "resources/shaders/glsl330/pbr.vs", + "resources/shaders/glsl330/pbr.fs", + ); + defer rl.unloadShader(shader); + + shader.locs[uSli(.map_albedo)] = rl.getShaderLocation(shader, "albedoMap"); + // WARNING: Metalness, roughness, and ambient occlusion are all packed into a MRA texture + // They are passed as to the SHADER_LOC_MAP_METALNESS location for convenience, + // shader already takes care of it accordingly + shader.locs[uSli(.map_metalness)] = rl.getShaderLocation(shader, "mraMap"); + shader.locs[uSli(.map_normal)] = rl.getShaderLocation(shader, "normalMap"); + // WARNING: Similar to the MRA map, the emissive map packs different information + // into a single texture: it stores height and emission data + // It is binded to SHADER_LOC_MAP_EMISSION location an properly processed on shader + shader.locs[uSli(.map_emission)] = rl.getShaderLocation(shader, "emissiveMap"); + shader.locs[uSli(.color_diffuse)] = rl.getShaderLocation(shader, "albedoColor"); + + // Setup additional required shader locations, including lights data + shader.locs[uSli(.vector_view)] = rl.getShaderLocation(shader, "viewPos"); + const loc_light_count: i32 = rl.getShaderLocation(shader, "numOfLights"); + const max_light_count: i32 = max_lights; + rl.setShaderValue(shader, loc_light_count, &max_light_count, .int); + + // Setup ambient color and intensity parameters + const ambient_intensity: f32 = 0.02; + const ambient_color: rl.Vector3 = blk: { + const c: rl.Color = .init(26, 32, 135, 255); + break :blk .init( + @as(f32, @floatFromInt(c.r)) / 255.0, + @as(f32, @floatFromInt(c.g)) / 255.0, + @as(f32, @floatFromInt(c.b)) / 255.0, + ); + }; + rl.setShaderValue(shader, rl.getShaderLocation(shader, "ambientColor"), &ambient_color, .vec3); + rl.setShaderValue(shader, rl.getShaderLocation(shader, "ambient"), &ambient_intensity, .float); + + // Get location for shader parameters that can be modified in real time + const loc_metallic_value = rl.getShaderLocation(shader, "metallicValue"); + const loc_roughness_value = rl.getShaderLocation(shader, "roughnessValue"); + const loc_emissive_intensity = rl.getShaderLocation(shader, "emissivePower"); + const loc_emissive_color = rl.getShaderLocation(shader, "emissiveColor"); + const loc_texture_tiling = rl.getShaderLocation(shader, "tiling"); + + // Load old car model using PBR maps and shader + // WARNING: We know this model consists of a single model.meshes[0] and + // that model.materials[0] is by default assigned to that mesh + // There could be more complex models consisting of multiple meshes and + // multiple materials defined for those meshes... but always 1 mesh = 1 material + const car: rl.Model = try .init("resources/models/old_car_new.glb"); + defer { + car.materials[0].shader = .{ .id = 0, .locs = null }; + rl.unloadMaterial(car.materials[0]); + car.materials[0].maps = null; + car.unload(); + } + + // Assign already setup PBR shader to model.materials[0], used by models.meshes[0] + car.materials[0].shader = shader; + + // Setup materials[0].maps default parameters + car.materials[0].maps[uMmi(.albedo)].color = .white; + car.materials[0].maps[uMmi(.metalness)].value = 1.0; + car.materials[0].maps[uMmi(.roughness)].value = 0.0; + car.materials[0].maps[uMmi(.occlusion)].value = 1.0; + car.materials[0].maps[uMmi(.emission)].color = .init(255, 162, 0, 255); + + // Setup materials[0].maps default textures + car.materials[0].maps[uMmi(.albedo)].texture = try .init("resources/textures/old_car_d.png"); + car.materials[0].maps[uMmi(.metalness)].texture = try .init("resources/textures/old_car_mra.png"); + car.materials[0].maps[uMmi(.normal)].texture = try .init("resources/textures/old_car_n.png"); + car.materials[0].maps[uMmi(.emission)].texture = try .init("resources/textures/old_car_e.png"); + + // Load floor model mesh and assign material parameters + // NOTE: A basic plane shape can be generated instead of being loaded from a model file + const floor: rl.Model = try .init("resources/models/plane.glb"); + defer { + floor.materials[0].shader = .{ .id = 0, .locs = null }; + rl.unloadMaterial(floor.materials[0]); + floor.materials[0].maps = null; + floor.unload(); + } + //Mesh floorMesh = GenMeshPlane(10, 10, 10, 10); + //GenMeshTangents(&floorMesh); // TODO: Review tangents generation + //Model floor = LoadModelFromMesh(floorMesh); + + // Assign material shader for our floor model, same PBR shader + floor.materials[0].shader = shader; + + floor.materials[0].maps[uMmi(.albedo)].color = .white; + floor.materials[0].maps[uMmi(.metalness)].value = 0.8; + floor.materials[0].maps[uMmi(.roughness)].value = 0.1; + floor.materials[0].maps[uMmi(.occlusion)].value = 1.0; + floor.materials[0].maps[uMmi(.emission)].color = .black; + + floor.materials[0].maps[uMmi(.albedo)].texture = try .init("resources/textures/road_a.png"); + floor.materials[0].maps[uMmi(.metalness)].texture = try .init("resources/textures/road_mra.png"); + floor.materials[0].maps[uMmi(.normal)].texture = try .init("resources/textures/road_n.png"); + + // Models texture tiling parameter can be stored in the Material struct if required (CURRENTLY NOT USED) + // NOTE: Material.params[4] are available for generic parameters storage (float) + const car_texture_tiling: rl.Vector2 = .init(0.5, 0.5); + const floor_texture_tiling: rl.Vector2 = .init(0.5, 0.5); + + // Create some lights + var lights: [max_lights]Light = .{ + .init(.point, .init(-1, 1, -2), .init(0, 0, 0), .yellow, 4, shader), + .init(.point, .init(2, 1, 1), .init(0, 0, 0), .green, 3.3, shader), + .init(.point, .init(-2, 1, 1), .init(0, 0, 0), .red, 8.3, shader), + .init(.point, .init(1, 1, -2), .init(0, 0, 0), .blue, 2, shader), + }; + + // Setup material texture maps usage in shader + // NOTE: By default, the texture maps are always used + const usage: i32 = 1; + rl.setShaderValue(shader, rl.getShaderLocation(shader, "useTexAlbedo"), &usage, .int); + rl.setShaderValue(shader, rl.getShaderLocation(shader, "useTexNormal"), &usage, .int); + rl.setShaderValue(shader, rl.getShaderLocation(shader, "useTexMRA"), &usage, .int); + rl.setShaderValue(shader, rl.getShaderLocation(shader, "useTexEmissive"), &usage, .int); + + 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 + //---------------------------------------------------------------------------------- + camera.update(.orbital); + + // Update the shader with the camera view vector (points towards { 0.0f, 0.0f, 0.0f }) + const camera_pos: [3]f32 = .{ camera.position.x, camera.position.y, camera.position.z }; + rl.setShaderValue(shader, shader.locs[uSli(.vector_view)], &camera_pos, .vec3); + + // Check key inputs to enable/disable lights + if (rl.isKeyPressed(.one)) { + lights[2].enabled = !lights[2].enabled; + } + if (rl.isKeyPressed(.two)) { + lights[1].enabled = !lights[1].enabled; + } + if (rl.isKeyPressed(.three)) { + lights[3].enabled = !lights[3].enabled; + } + if (rl.isKeyPressed(.four)) { + lights[0].enabled = !lights[0].enabled; + } + + // Update light values on shader (actually, only enable/disable them) + for (&lights) |*l| { + l.update(shader); + } + //---------------------------------------------------------------------------------- + + // Draw + //---------------------------------------------------------------------------------- + rl.beginDrawing(); + defer rl.endDrawing(); + + rl.clearBackground(.black); + { + rl.beginMode3D(camera); + defer rl.endMode3D(); + + // Set floor model texture tiling and emissive color parameters on shader + rl.setShaderValue(shader, loc_texture_tiling, &floor_texture_tiling, .vec2); + const floor_emissive_color: rl.Vector4 = rl.colorNormalize(floor.materials[0].maps[uMmi(.emission)].color); + rl.setShaderValue(shader, loc_emissive_color, &floor_emissive_color, .vec4); + + // Set floor metallic and roughness values + rl.setShaderValue(shader, loc_metallic_value, &floor.materials[0].maps[uMmi(.metalness)].value, .float); + rl.setShaderValue(shader, loc_roughness_value, &floor.materials[0].maps[uMmi(.roughness)].value, .float); + + floor.draw(.init(0, 0, 0), 5, .white); // Draw floor model + + // Set old car model texture tiling, emissive color and emissive intensity parameters on shader + rl.setShaderValue(shader, loc_texture_tiling, &car_texture_tiling, .vec2); + const car_emissive_color: rl.Vector4 = rl.colorNormalize(car.materials[0].maps[uMmi(.emission)].color); + rl.setShaderValue(shader, loc_emissive_color, &car_emissive_color, .vec4); + const emissive_intensity: f32 = 0.01; + rl.setShaderValue(shader, loc_emissive_intensity, &emissive_intensity, .float); + + // Set old car metallic and roughness values + rl.setShaderValue(shader, loc_metallic_value, &car.materials[0].maps[uMmi(.metalness)].value, .float); + rl.setShaderValue(shader, loc_roughness_value, &car.materials[0].maps[uMmi(.roughness)].value, .float); + + car.draw(.init(0, 0, 0), 0.25, .white); // Draw car model + + // Draw spheres to show the lights positions + for (&lights) |*l| { + const light_color: rl.Color = .init( + @intFromFloat(l.color[0] * 255), + @intFromFloat(l.color[1] * 255), + @intFromFloat(l.color[2] * 255), + @intFromFloat(l.color[3] * 255), + ); + + if (l.enabled) { + rl.drawSphereEx(l.position, 0.2, 8, 8, light_color); + } else { + rl.drawSphereWires(l.position, 0.2, 8, 8, rl.colorAlpha(light_color, 0.3)); + } + } + } + rl.drawText("Toggle lights: [1][2][3][4]", 10, 40, 20, .light_gray); + + rl.drawText("(c) Old Rusty Car model by Renafox (https://skfb.ly/LxRy)", + screen_width - 320, screen_height - 20, 10, .light_gray); + + rl.drawFPS(10, 10); + } +} diff --git a/examples/shaders/shaders_hybrid_render.zig b/examples/shaders/shaders_hybrid_render.zig new file mode 100644 index 0000000..06babff --- /dev/null +++ b/examples/shaders/shaders_hybrid_render.zig @@ -0,0 +1,197 @@ +// raylib [shaders] example - Hybrid Rendering +// +// Example complexity rating: [★★★★] 4/4 +// +// Example originally created with raylib 4.2, last time updated with raylib 4.2 +// +// Example contributed by Buğra Alptekin Sarı (@BugraAlptekinSari) 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) 2022-2025 Buğra Alptekin Sarı (@BugraAlptekinSari) + +const rl = @import("raylib"); +const pi = @import("std").math.pi; + +//------------------------------------------------------------------------------------ +// Declare custom Structs +//------------------------------------------------------------------------------------ + +const RayLocs = struct { + cam_pos: i32, + cam_dir: i32, + screen_center: i32, +}; + +//------------------------------------------------------------------------------------ +// Program main entry point +//------------------------------------------------------------------------------------ +pub fn main() anyerror!void { + // Initialization + //-------------------------------------------------------------------------------------- + const screen_width = 800; + const screen_height = 450; + + rl.initWindow(screen_width, screen_height, "raylib [shaders] example - write depth buffer"); + defer rl.closeWindow(); // Close window and OpenGL context + + + // This Shader calculates pixel depth and color using raymarch + const shdr_raymarch: rl.Shader = try rl.loadShader(null, "resources/shaders/glsl330/hybrid_raymarch.fs"); + defer rl.unloadShader(shdr_raymarch); + + // This Shader is a standard rasterization fragment shader with the addition of depth writing + // You are required to write depth for all shaders if one shader does it + const shdr_raster: rl.Shader = try rl.loadShader(null, "resources/shaders/glsl330/hybrid_raster.fs"); + defer rl.unloadShader(shdr_raster); + + // Declare Struct used to store camera locs. + const march_locs: RayLocs = .{ + .cam_pos = rl.getShaderLocation(shdr_raymarch, "camPos"), + .cam_dir = rl.getShaderLocation(shdr_raymarch, "camDir"), + .screen_center = rl.getShaderLocation(shdr_raymarch, "screenCenter"), + }; + + // Transfer screenCenter position to shader. Which is used to calculate ray direction. + const screen_center: rl.Vector2 = .init(screen_width / 2.0, screen_height / 2.0); + rl.setShaderValue(shdr_raymarch, march_locs.screen_center , &screen_center , .vec2); + + // Use Customized function to create writable depth texture buffer + const target: rl.RenderTexture2D = try loadRenderTextureDepthTex(screen_width, screen_height); + defer unloadRenderTextureDepthTex(target); + + // Define the camera to look into our 3d world + var camera: rl.Camera = .{ + .position = .init(0.5, 1, 1.5), // Camera position + .target = .init(0, 0.5, 0), // Camera looking at point + .up = .init(0, 1, 0), // Camera up vector (rotation towards target) + .fovy = 45, // Camera field-of-view Y + .projection = .perspective, // Camera projection type + }; + + // Camera FOV is pre-calculated in the camera Distance. + const cam_dist: f32 = 1.0 / @tan(camera.fovy * 0.5 * (pi / 180.0)); + + 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 + //---------------------------------------------------------------------------------- + camera.update(.orbital); + + // Update Camera Postion in the ray march shader. + rl.setShaderValue(shdr_raymarch, march_locs.cam_pos, &camera.position, .vec3); + + // Update Camera Looking Vector. Vector length determines FOV. + const cam_dir: rl.Vector3 = .scale(.normalize(.subtract(camera.target, camera.position)), cam_dist); + rl.setShaderValue(shdr_raymarch, march_locs.cam_dir, &cam_dir, .vec3); + //---------------------------------------------------------------------------------- + + // Draw + //---------------------------------------------------------------------------------- + // Draw into our custom render texture (framebuffer) + { + target.begin(); + defer target.end(); + + rl.clearBackground(.white); + + // Raymarch Scene + rl.gl.rlEnableDepthTest(); //Manually enable Depth Test to handle multiple rendering methods. + { + shdr_raymarch.activate(); + defer shdr_raymarch.deactivate(); + rl.drawRectangleRec(.init(0, 0, screen_width, screen_height), .white); + } + + // Rasterize Scene + { + rl.beginMode3D(camera); + defer rl.endMode3D(); + + shdr_raster.activate(); + defer shdr_raster.deactivate(); + + rl.drawCubeWiresV(.init(0, 0.5, 1), .init(1, 1, 1), .red); + rl.drawCubeV(.init(0, 0.5, 1), .init(1, 1, 1), .purple); + rl.drawCubeWiresV(.init(0, 0.5, -1), .init(1, 1, 1), .dark_green); + rl.drawCubeV(.init(0, 0.5, -1), .init(1, 1, 1), .yellow); + rl.drawGrid(10, 1); + } + } + + // Draw into screen our custom render texture + rl.beginDrawing(); + defer rl.endDrawing(); + + rl.clearBackground(.ray_white); + + target.texture.drawRec(.init(0, 0, screen_width, -screen_height), .init(0, 0), .white); + rl.drawFPS(10, 10); + } +} + +//------------------------------------------------------------------------------------ +// Define custom functions required for the example +//------------------------------------------------------------------------------------ +// Load custom render texture, create a writable depth texture buffer +fn loadRenderTextureDepthTex(width: i32, height: i32) !rl.RenderTexture2D { + const id = rl.gl.rlLoadFramebuffer(); // Load an empty framebuffer + if (id <= 0) { + return error.LoadFrameBufferFail; + } + + rl.gl.rlEnableFramebuffer(id); + defer rl.gl.rlDisableFramebuffer(); + + const pix_format: i32 = @intFromEnum(rl.gl.rlPixelFormat.rl_pixelformat_uncompressed_r8g8b8a8); + + const target: rl.RenderTexture2D = .{ + .id = id, + // Create color texture (default to RGBA) + .texture = .{ + .id = rl.gl.rlLoadTexture(null, width, height, pix_format, 1), + .width = width, + .height = height, + .format = .uncompressed_r8g8b8a8, + .mipmaps = 1, + }, + // Create depth texture buffer (instead of raylib default renderbuffer) + .depth = .{ + .id = rl.gl.rlLoadTextureDepth(width, height, false), + .width = width, + .height = height, + .format = .compressed_etc2_rgb, //DEPTH_COMPONENT_24BIT? + .mipmaps = 1, + } + }; + + // Attach color texture and depth texture to FBO + const channel0: i32 = @intFromEnum(rl.gl.rlFramebufferAttachType.rl_attachment_color_channel0); + const depth: i32 = @intFromEnum(rl.gl.rlFramebufferAttachType.rl_attachment_depth); + const texture2d: i32 = @intFromEnum(rl.gl.rlFramebufferAttachTextureType.rl_attachment_texture2d); + rl.gl.rlFramebufferAttach(target.id, target.texture.id, channel0, texture2d, 0); + rl.gl.rlFramebufferAttach(target.id, target.depth.id, depth, texture2d, 0); + + // Check if fbo is complete with attachments (valid) + if (rl.gl.rlFramebufferComplete(target.id)) { + rl.traceLog(.info, "FBO: [ID %i] Framebuffer object created successfully", .{ target.id }); + } + + return target; +} + +// Unload render texture from GPU memory (VRAM) +fn unloadRenderTextureDepthTex(target: rl.RenderTexture2D) void { + // Color texture attached to FBO is deleted + rl.gl.rlUnloadTexture(target.texture.id); + rl.gl.rlUnloadTexture(target.depth.id); + + // NOTE: Depth texture is automatically + // queried and deleted before deleting framebuffer + rl.gl.rlUnloadFramebuffer(target.id); +} diff --git a/resources/models/old_car_new.glb b/resources/models/old_car_new.glb new file mode 100644 index 0000000..119995c Binary files /dev/null and b/resources/models/old_car_new.glb differ diff --git a/resources/models/plane.glb b/resources/models/plane.glb new file mode 100644 index 0000000..452e1c5 Binary files /dev/null and b/resources/models/plane.glb differ diff --git a/resources/shaders/glsl330/hybrid_raster.fs b/resources/shaders/glsl330/hybrid_raster.fs new file mode 100644 index 0000000..547d196 --- /dev/null +++ b/resources/shaders/glsl330/hybrid_raster.fs @@ -0,0 +1,22 @@ +#version 330 + +// Input vertex attributes (from vertex shader) +in vec2 fragTexCoord; +in vec4 fragColor; + +// Input uniform values +uniform sampler2D texture0; +uniform vec4 colDiffuse; + +// Output fragment color +//out vec4 finalColor; + +// NOTE: Add your custom variables here + +void main() +{ + vec4 texelColor = texture(texture0, fragTexCoord); + + gl_FragColor = texelColor*colDiffuse*fragColor; + gl_FragDepth = gl_FragCoord.z; +} \ No newline at end of file diff --git a/resources/shaders/glsl330/hybrid_raymarch.fs b/resources/shaders/glsl330/hybrid_raymarch.fs new file mode 100644 index 0000000..b120dbc --- /dev/null +++ b/resources/shaders/glsl330/hybrid_raymarch.fs @@ -0,0 +1,284 @@ +# version 330 + +// Input vertex attributes (from vertex shader) +in vec2 fragTexCoord; +in vec4 fragColor; + +// Input uniform values +uniform sampler2D texture0; +uniform vec4 colDiffuse; + +// Custom Input Uniform +uniform vec3 camPos; +uniform vec3 camDir; +uniform vec2 screenCenter; + +#define ZERO 0 + +// https://learnopengl.com/Advanced-OpenGL/Depth-testing +float CalcDepth(in vec3 rd, in float Idist){ + float local_z = dot(normalize(camDir),rd)*Idist; + return (1.0/(local_z) - 1.0/0.01)/(1.0/1000.0 -1.0/0.01); +} + +// https://iquilezles.org/articles/distfunctions/ +float sdHorseshoe( in vec3 p, in vec2 c, in float r, in float le, vec2 w ) +{ + p.x = abs(p.x); + float l = length(p.xy); + p.xy = mat2(-c.x, c.y, + c.y, c.x)*p.xy; + p.xy = vec2((p.y>0.0 || p.x>0.0)?p.x:l*sign(-c.x), + (p.x>0.0)?p.y:l ); + p.xy = vec2(p.x,abs(p.y-r))-vec2(le,0.0); + + vec2 q = vec2(length(max(p.xy,0.0)) + min(0.0,max(p.x,p.y)),p.z); + vec2 d = abs(q) - w; + return min(max(d.x,d.y),0.0) + length(max(d,0.0)); +} + +// r = sphere's radius +// h = cutting's plane's position +// t = thickness +float sdSixWayCutHollowSphere( vec3 p, float r, float h, float t ) +{ + // Six way symetry Transformation + vec3 ap = abs(p); + if(ap.x < max(ap.y, ap.z)){ + if(ap.y < ap.z) ap.xz = ap.zx; + else ap.xy = ap.yx; + } + + vec2 q = vec2( length(ap.yz), ap.x ); + + float w = sqrt(r*r-h*h); + + return ((h*q.x0.0 ) + { + tmax = min( tmax, tp1 ); + res = vec2( tp1, 1.0 ); + } + + float t = tmin; + for( int i=0; i<70 ; i++ ) + { + if(t>tmax) break; + vec2 h = map( ro+rd*t ); + if( abs(h.x)<(0.0001*t) ) + { + res = vec2(t,h.y); + break; + } + t += h.x; + } + + return res; +} + + +// https://iquilezles.org/articles/rmshadows +float calcSoftshadow( in vec3 ro, in vec3 rd, in float mint, in float tmax ) +{ + // bounding volume + float tp = (0.8-ro.y)/rd.y; if( tp>0.0 ) tmax = min( tmax, tp ); + + float res = 1.0; + float t = mint; + for( int i=ZERO; i<24; i++ ) + { + float h = map( ro + rd*t ).x; + float s = clamp(8.0*h/t,0.0,1.0); + res = min( res, s ); + t += clamp( h, 0.01, 0.2 ); + if( res<0.004 || t>tmax ) break; + } + res = clamp( res, 0.0, 1.0 ); + return res*res*(3.0-2.0*res); +} + + +// https://iquilezles.org/articles/normalsSDF +vec3 calcNormal( in vec3 pos ) +{ + vec2 e = vec2(1.0,-1.0)*0.5773*0.0005; + return normalize( e.xyy*map( pos + e.xyy ).x + + e.yyx*map( pos + e.yyx ).x + + e.yxy*map( pos + e.yxy ).x + + e.xxx*map( pos + e.xxx ).x ); +} + +// https://iquilezles.org/articles/nvscene2008/rwwtt.pdf +float calcAO( in vec3 pos, in vec3 nor ) +{ + float occ = 0.0; + float sca = 1.0; + for( int i=ZERO; i<5; i++ ) + { + float h = 0.01 + 0.12*float(i)/4.0; + float d = map( pos + h*nor ).x; + occ += (h-d)*sca; + sca *= 0.95; + if( occ>0.35 ) break; + } + return clamp( 1.0 - 3.0*occ, 0.0, 1.0 ) * (0.5+0.5*nor.y); +} + +// https://iquilezles.org/articles/checkerfiltering +float checkersGradBox( in vec2 p ) +{ + // filter kernel + vec2 w = fwidth(p) + 0.001; + // analytical integral (box filter) + vec2 i = 2.0*(abs(fract((p-0.5*w)*0.5)-0.5)-abs(fract((p+0.5*w)*0.5)-0.5))/w; + // xor pattern + return 0.5 - 0.5*i.x*i.y; +} + +// https://www.shadertoy.com/view/tdS3DG +vec4 render( in vec3 ro, in vec3 rd) +{ + // background + vec3 col = vec3(0.7, 0.7, 0.9) - max(rd.y,0.0)*0.3; + + // raycast scene + vec2 res = raycast(ro,rd); + float t = res.x; + float m = res.y; + if( m>-0.5 ) + { + vec3 pos = ro + t*rd; + vec3 nor = (m<1.5) ? vec3(0.0,1.0,0.0) : calcNormal( pos ); + vec3 ref = reflect( rd, nor ); + + // material + col = 0.2 + 0.2*sin( m*2.0 + vec3(0.0,1.0,2.0) ); + float ks = 1.0; + + if( m<1.5 ) + { + float f = checkersGradBox( 3.0*pos.xz); + col = 0.15 + f*vec3(0.05); + ks = 0.4; + } + + // lighting + float occ = calcAO( pos, nor ); + + vec3 lin = vec3(0.0); + + // sun + { + vec3 lig = normalize( vec3(-0.5, 0.4, -0.6) ); + vec3 hal = normalize( lig-rd ); + float dif = clamp( dot( nor, lig ), 0.0, 1.0 ); + //if( dif>0.0001 ) + dif *= calcSoftshadow( pos, lig, 0.02, 2.5 ); + float spe = pow( clamp( dot( nor, hal ), 0.0, 1.0 ),16.0); + spe *= dif; + spe *= 0.04+0.96*pow(clamp(1.0-dot(hal,lig),0.0,1.0),5.0); + //spe *= 0.04+0.96*pow(clamp(1.0-sqrt(0.5*(1.0-dot(rd,lig))),0.0,1.0),5.0); + lin += col*2.20*dif*vec3(1.30,1.00,0.70); + lin += 5.00*spe*vec3(1.30,1.00,0.70)*ks; + } + // sky + { + float dif = sqrt(clamp( 0.5+0.5*nor.y, 0.0, 1.0 )); + dif *= occ; + float spe = smoothstep( -0.2, 0.2, ref.y ); + spe *= dif; + spe *= 0.04+0.96*pow(clamp(1.0+dot(nor,rd),0.0,1.0), 5.0 ); + //if( spe>0.001 ) + spe *= calcSoftshadow( pos, ref, 0.02, 2.5 ); + lin += col*0.60*dif*vec3(0.40,0.60,1.15); + lin += 2.00*spe*vec3(0.40,0.60,1.30)*ks; + } + // back + { + float dif = clamp( dot( nor, normalize(vec3(0.5,0.0,0.6))), 0.0, 1.0 )*clamp( 1.0-pos.y,0.0,1.0); + dif *= occ; + lin += col*0.55*dif*vec3(0.25,0.25,0.25); + } + // sss + { + float dif = pow(clamp(1.0+dot(nor,rd),0.0,1.0),2.0); + dif *= occ; + lin += col*0.25*dif*vec3(1.00,1.00,1.00); + } + + col = lin; + + col = mix( col, vec3(0.7,0.7,0.9), 1.0-exp( -0.0001*t*t*t ) ); + } + + return vec4(vec3( clamp(col,0.0,1.0) ),t); +} + +vec3 CalcRayDir(vec2 nCoord){ + vec3 horizontal = normalize(cross(camDir,vec3(.0 , 1.0, .0))); + vec3 vertical = normalize(cross(horizontal,camDir)); + return normalize(camDir + horizontal*nCoord.x + vertical*nCoord.y); +} + +mat3 setCamera() +{ + vec3 cw = normalize(camDir); + vec3 cp = vec3(0.0, 1.0 ,0.0); + vec3 cu = normalize( cross(cw,cp) ); + vec3 cv = ( cross(cu,cw) ); + return mat3( cu, cv, cw ); +} + +void main() +{ + vec2 nCoord = (gl_FragCoord.xy - screenCenter.xy)/screenCenter.y; + mat3 ca = setCamera(); + + // focal length + float fl = length(camDir); + vec3 rd = ca * normalize( vec3(nCoord,fl) ); + vec3 color = vec3(nCoord/2.0 + 0.5, 0.0); + float depth = gl_FragCoord.z; + { + vec4 res = render( camPos - vec3(0.0, 0.0, 0.0) , rd ); + color = res.xyz; + depth = CalcDepth(rd,res.w); + } + gl_FragColor = vec4(color , 1.0); + gl_FragDepth = depth; +} \ No newline at end of file diff --git a/resources/shaders/glsl330/pbr.fs b/resources/shaders/glsl330/pbr.fs new file mode 100644 index 0000000..ceaead3 --- /dev/null +++ b/resources/shaders/glsl330/pbr.fs @@ -0,0 +1,162 @@ +#version 330 + +#define MAX_LIGHTS 4 +#define LIGHT_DIRECTIONAL 0 +#define LIGHT_POINT 1 +#define PI 3.14159265358979323846 + +struct Light { + int enabled; + int type; + vec3 position; + vec3 target; + vec4 color; + float intensity; +}; + +// Input vertex attributes (from vertex shader) +in vec3 fragPosition; +in vec2 fragTexCoord; +in vec4 fragColor; +in vec3 fragNormal; +in vec4 shadowPos; +in mat3 TBN; + +// Output fragment color +out vec4 finalColor; + +// Input uniform values +uniform int numOfLights; +uniform sampler2D albedoMap; +uniform sampler2D mraMap; +uniform sampler2D normalMap; +uniform sampler2D emissiveMap; // r: Hight g:emissive + +uniform vec2 tiling; +uniform vec2 offset; + +uniform int useTexAlbedo; +uniform int useTexNormal; +uniform int useTexMRA; +uniform int useTexEmissive; + +uniform vec4 albedoColor; +uniform vec4 emissiveColor; +uniform float normalValue; +uniform float metallicValue; +uniform float roughnessValue; +uniform float aoValue; +uniform float emissivePower; + +// Input lighting values +uniform Light lights[MAX_LIGHTS]; +uniform vec3 viewPos; + +uniform vec3 ambientColor; +uniform float ambient; + +// Reflectivity in range 0.0 to 1.0 +// NOTE: Reflectivity is increased when surface view at larger angle +vec3 SchlickFresnel(float hDotV,vec3 refl) +{ + return refl + (1.0 - refl)*pow(1.0 - hDotV, 5.0); +} + +float GgxDistribution(float nDotH,float roughness) +{ + float a = roughness*roughness*roughness*roughness; + float d = nDotH*nDotH*(a - 1.0) + 1.0; + d = PI*d*d; + return (a/max(d,0.0000001)); +} + +float GeomSmith(float nDotV,float nDotL,float roughness) +{ + float r = roughness + 1.0; + float k = r*r/8.0; + float ik = 1.0 - k; + float ggx1 = nDotV/(nDotV*ik + k); + float ggx2 = nDotL/(nDotL*ik + k); + return ggx1*ggx2; +} + +vec3 ComputePBR() +{ + vec3 albedo = texture(albedoMap,vec2(fragTexCoord.x*tiling.x + offset.x, fragTexCoord.y*tiling.y + offset.y)).rgb; + albedo = vec3(albedoColor.x*albedo.x, albedoColor.y*albedo.y, albedoColor.z*albedo.z); + + float metallic = clamp(metallicValue, 0.0, 1.0); + float roughness = clamp(roughnessValue, 0.0, 1.0); + float ao = clamp(aoValue, 0.0, 1.0); + + if (useTexMRA == 1) + { + vec4 mra = texture(mraMap, vec2(fragTexCoord.x*tiling.x + offset.x, fragTexCoord.y*tiling.y + offset.y)); + metallic = clamp(mra.r + metallicValue, 0.04, 1.0); + roughness = clamp(mra.g + roughnessValue, 0.04, 1.0); + ao = (mra.b + aoValue)*0.5; + } + + vec3 N = normalize(fragNormal); + if (useTexNormal == 1) + { + N = texture(normalMap, vec2(fragTexCoord.x*tiling.x + offset.y, fragTexCoord.y*tiling.y + offset.y)).rgb; + N = normalize(N*2.0 - 1.0); + N = normalize(N*TBN); + } + + vec3 V = normalize(viewPos - fragPosition); + + vec3 emissive = vec3(0); + emissive = (texture(emissiveMap, vec2(fragTexCoord.x*tiling.x + offset.x, fragTexCoord.y*tiling.y + offset.y)).rgb).g*emissiveColor.rgb*emissivePower*useTexEmissive; + + // return N;//vec3(metallic,metallic,metallic); + // If dia-electric use base reflectivity of 0.04 otherwise ut is a metal use albedo as base reflectivity + vec3 baseRefl = mix(vec3(0.04), albedo.rgb, metallic); + vec3 lightAccum = vec3(0.0); // Acumulate lighting lum + + for (int i = 0; i < numOfLights; i++) + { + vec3 L = normalize(lights[i].position - fragPosition); // Compute light vector + vec3 H = normalize(V + L); // Compute halfway bisecting vector + float dist = length(lights[i].position - fragPosition); // Compute distance to light + float attenuation = 1.0/(dist*dist*0.23); // Compute attenuation + vec3 radiance = lights[i].color.rgb*lights[i].intensity*attenuation; // Compute input radiance, light energy comming in + + // Cook-Torrance BRDF distribution function + float nDotV = max(dot(N,V), 0.0000001); + float nDotL = max(dot(N,L), 0.0000001); + float hDotV = max(dot(H,V), 0.0); + float nDotH = max(dot(N,H), 0.0); + float D = GgxDistribution(nDotH, roughness); // Larger the more micro-facets aligned to H + float G = GeomSmith(nDotV, nDotL, roughness); // Smaller the more micro-facets shadow + vec3 F = SchlickFresnel(hDotV, baseRefl); // Fresnel proportion of specular reflectance + + vec3 spec = (D*G*F)/(4.0*nDotV*nDotL); + + // Difuse and spec light can't be above 1.0 + // kD = 1.0 - kS diffuse component is equal 1.0 - spec comonent + vec3 kD = vec3(1.0) - F; + + // Mult kD by the inverse of metallnes, only non-metals should have diffuse light + kD *= 1.0 - metallic; + lightAccum += ((kD*albedo.rgb/PI + spec)*radiance*nDotL)*lights[i].enabled; // Angle of light has impact on result + } + + vec3 ambientFinal = (ambientColor + albedo)*ambient*0.5; + + return (ambientFinal + lightAccum*ao + emissive); +} + +void main() +{ + vec3 color = ComputePBR(); + + // HDR tonemapping + color = pow(color, color + vec3(1.0)); + + // Gamma correction + color = pow(color, vec3(1.0/2.2)); + + finalColor = vec4(color, 1.0); +} \ No newline at end of file diff --git a/resources/shaders/glsl330/pbr.vs b/resources/shaders/glsl330/pbr.vs new file mode 100644 index 0000000..8aabb6b --- /dev/null +++ b/resources/shaders/glsl330/pbr.vs @@ -0,0 +1,48 @@ +#version 330 + +// Input vertex attributes +in vec3 vertexPosition; +in vec2 vertexTexCoord; +in vec3 vertexNormal; +in vec4 vertexTangent; +in vec4 vertexColor; + +// Input uniform values +uniform mat4 mvp; +uniform mat4 matModel; +uniform mat4 matNormal; +uniform vec3 lightPos; +uniform vec4 difColor; + +// Output vertex attributes (to fragment shader) +out vec3 fragPosition; +out vec2 fragTexCoord; +out vec4 fragColor; +out vec3 fragNormal; +out mat3 TBN; + +const float normalOffset = 0.1; + +void main() +{ + // Compute binormal from vertex normal and tangent + vec3 vertexBinormal = cross(vertexNormal, vertexTangent.xyz) * vertexTangent.w; + + // Compute fragment normal based on normal transformations + mat3 normalMatrix = transpose(inverse(mat3(matModel))); + + // Compute fragment position based on model transformations + fragPosition = vec3(matModel*vec4(vertexPosition, 1.0)); + + fragTexCoord = vertexTexCoord*2.0; + fragNormal = normalize(normalMatrix*vertexNormal); + vec3 fragTangent = normalize(normalMatrix*vertexTangent.xyz); + fragTangent = normalize(fragTangent - dot(fragTangent, fragNormal)*fragNormal); + vec3 fragBinormal = normalize(normalMatrix*vertexBinormal); + fragBinormal = cross(fragNormal, fragTangent); + + TBN = transpose(mat3(fragTangent, fragBinormal, fragNormal)); + + // Calculate final vertex position + gl_Position = mvp*vec4(vertexPosition, 1.0); +} diff --git a/resources/textures/old_car_d.png b/resources/textures/old_car_d.png new file mode 100644 index 0000000..d8b3c83 Binary files /dev/null and b/resources/textures/old_car_d.png differ diff --git a/resources/textures/old_car_e.png b/resources/textures/old_car_e.png new file mode 100644 index 0000000..23f01c0 Binary files /dev/null and b/resources/textures/old_car_e.png differ diff --git a/resources/textures/old_car_mra.png b/resources/textures/old_car_mra.png new file mode 100644 index 0000000..0fb46b3 Binary files /dev/null and b/resources/textures/old_car_mra.png differ diff --git a/resources/textures/old_car_n.png b/resources/textures/old_car_n.png new file mode 100644 index 0000000..11f689f Binary files /dev/null and b/resources/textures/old_car_n.png differ diff --git a/resources/textures/road_a.png b/resources/textures/road_a.png new file mode 100644 index 0000000..1037773 Binary files /dev/null and b/resources/textures/road_a.png differ diff --git a/resources/textures/road_mra.png b/resources/textures/road_mra.png new file mode 100644 index 0000000..988c839 Binary files /dev/null and b/resources/textures/road_mra.png differ diff --git a/resources/textures/road_n.png b/resources/textures/road_n.png new file mode 100644 index 0000000..a5f3548 Binary files /dev/null and b/resources/textures/road_n.png differ