add examples shaders_basic_pbr and shaders_hybrid_render

This commit is contained in:
Mike Will 2025-07-05 02:13:35 -04:00 committed by Nikolas
parent 125ead95a3
commit dfea9ed840
16 changed files with 1089 additions and 0 deletions

View File

@ -184,6 +184,16 @@ pub fn build(b: *std.Build) !void {
.path = "examples/shaders/raymarching.zig", .path = "examples/shaders/raymarching.zig",
.desc = "Uses a raymarching in a shader to render shapes", .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", .name = "texture_outline",
.path = "examples/shaders/texture_outline.zig", .path = "examples/shaders/texture_outline.zig",

View File

@ -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);
}
}

View File

@ -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);
}

Binary file not shown.

BIN
resources/models/plane.glb Normal file

Binary file not shown.

View File

@ -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;
}

View File

@ -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.x<w*q.y) ? length(q-vec2(w,h)) :
abs(length(q)-r) ) - t;
}
// https://iquilezles.org/articles/boxfunctions
vec2 iBox( in vec3 ro, in vec3 rd, in vec3 rad )
{
vec3 m = 1.0/rd;
vec3 n = m*ro;
vec3 k = abs(m)*rad;
vec3 t1 = -n - k;
vec3 t2 = -n + k;
return vec2( max( max( t1.x, t1.y ), t1.z ),
min( min( t2.x, t2.y ), t2.z ) );
}
vec2 opU( vec2 d1, vec2 d2 )
{
return (d1.x<d2.x) ? d1 : d2;
}
vec2 map( in vec3 pos ){
vec2 res = vec2( sdHorseshoe( pos-vec3(-1.0,0.08, 1.0), vec2(cos(1.3),sin(1.3)), 0.2, 0.3, vec2(0.03,0.5) ), 11.5 ) ;
res = opU(res, vec2( sdSixWayCutHollowSphere( pos-vec3(0.0, 1.0, 0.0), 4.0, 3.5, 0.5 ), 4.5 )) ;
return res;
}
// https://www.shadertoy.com/view/Xds3zN
vec2 raycast( in vec3 ro, in vec3 rd ){
vec2 res = vec2(-1.0,-1.0);
float tmin = 1.0;
float tmax = 20.0;
// raytrace floor plane
float tp1 = (-ro.y)/rd.y;
if( tp1>0.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;
}

View File

@ -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);
}

View File

@ -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);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 623 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 657 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 645 KiB