diff --git a/Examples/Program.cs b/Examples/Program.cs
index fbb310d..4d09055 100644
--- a/Examples/Program.cs
+++ b/Examples/Program.cs
@@ -129,6 +129,7 @@ public class ExampleList
// Shaders
new ExampleInfo("BasicLighting", BasicLighting.Main),
+ new ExampleInfo("BasicPbr", BasicPbr.Main),
new ExampleInfo("CustomUniform", CustomUniform.Main),
new ExampleInfo("Eratosthenes", Eratosthenes.Main),
new ExampleInfo("Fog", Fog.Main),
diff --git a/Examples/Shaders/BasicPbr.cs b/Examples/Shaders/BasicPbr.cs
new file mode 100644
index 0000000..7ac4936
--- /dev/null
+++ b/Examples/Shaders/BasicPbr.cs
@@ -0,0 +1,300 @@
+/*******************************************************************************************
+ *
+ * raylib [shaders] example - Basic PBR
+ *
+ * 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-2024 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/)
+ *
+ ********************************************************************************************/
+
+using System.Numerics;
+using Examples.Shared;
+using static Raylib_cs.Raylib;
+
+namespace Examples.Shaders;
+
+public class BasicPbr
+{
+ private const int GLSL_VERSION = 330;
+
+ public static unsafe int Main()
+ {
+ // Initialization
+ //--------------------------------------------------------------------------------------
+ const int screenWidth = 800;
+ const int screenHeight = 450;
+
+ // Enable Multi Sampling Anti Aliasing 4x (if available)
+ SetConfigFlags(ConfigFlags.Msaa4xHint);
+ InitWindow(screenWidth, screenHeight, "raylib [shaders] example - basic pbr");
+
+ // Define the camera to look into our 3d world
+ Camera3D camera = new();
+ camera.Position = new Vector3(2.0f, 4.0f, 6.0f);
+ camera.Target = new Vector3(0.0f, 0.5f, 0.0f);
+ camera.Up = new Vector3(0.0f, 1.0f, 0.0f);
+ camera.FovY = 45.0f;
+ camera.Projection = CameraProjection.Perspective;
+
+ // Load PBR shader and setup all required locations
+ var shader = LoadShader("resources/shaders/glsl330/pbr.vs", "resources/shaders/glsl330/pbr.fs");
+
+ shader.Locs[(int)ShaderLocationIndex.MapAlbedo] = 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[(int)ShaderLocationIndex.MapMetalness] = GetShaderLocation(shader, "mraMap");
+ shader.Locs[(int)ShaderLocationIndex.MapNormal] = 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[(int)ShaderLocationIndex.MapEmission] = GetShaderLocation(shader, "emissiveMap");
+ shader.Locs[(int)ShaderLocationIndex.ColorDiffuse] = GetShaderLocation(shader, "albedoColor");
+
+ // Setup additional required shader locations, including lights data
+ shader.Locs[(int)ShaderLocationIndex.VectorView] = GetShaderLocation(shader, "viewPos");
+ var lightCountLoc = GetShaderLocation(shader, "numOfLights");
+ var maxLightCount = 4;
+ SetShaderValue(shader, lightCountLoc, &maxLightCount, ShaderUniformDataType.Int);
+
+ // Setup ambient color and intensity parameters
+ var ambientIntensity = 0.02f;
+ var ambientColor = new Color(26, 32, 135, 255);
+ var ambientColorNormalized = new Vector3(ambientColor.R / 255.0F, ambientColor.G / 255.0F, ambientColor.B / 255.0F);
+ SetShaderValue(shader, GetShaderLocation(shader, "ambientColor"), &ambientColorNormalized, ShaderUniformDataType.Vec3);
+ SetShaderValue(shader, GetShaderLocation(shader, "ambient"), &ambientIntensity, ShaderUniformDataType.Float);
+
+ // Get location for shader parameters that can be modified in real time
+ var emissiveIntensityLoc = GetShaderLocation(shader, "emissivePower");
+ var emissiveColorLoc = GetShaderLocation(shader, "emissiveColor");
+ var textureTilingLoc = 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
+ var car = LoadModel("resources/models/gltf/old_car_new.glb");
+
+ // 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[(int)MaterialMapIndex.Albedo].Color = Color.White;
+ car.Materials[0].Maps[(int)MaterialMapIndex.Metalness].Value = 0.0f;
+ car.Materials[0].Maps[(int)MaterialMapIndex.Roughness].Value = 0.0f;
+ car.Materials[0].Maps[(int)MaterialMapIndex.Occlusion].Value = 1.0f;
+ car.Materials[0].Maps[(int)MaterialMapIndex.Emission].Color = new Color(255, 162, 0, 255);
+
+ // Setup materials[0].maps default textures
+ car.Materials[0].Maps[(int)MaterialMapIndex.Albedo].Texture = LoadTexture("resources/old_car_d.png");
+ car.Materials[0].Maps[(int)MaterialMapIndex.Metalness].Texture = LoadTexture("resources/old_car_mra.png");
+ car.Materials[0].Maps[(int)MaterialMapIndex.Normal].Texture = LoadTexture("resources/old_car_n.png");
+ car.Materials[0].Maps[(int)MaterialMapIndex.Emission].Texture = LoadTexture("resources/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
+ var floor = LoadModel("resources/models/gltf/plane.glb");
+ //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[(int)MaterialMapIndex.Albedo].Color = Color.White;
+ floor.Materials[0].Maps[(int)MaterialMapIndex.Metalness].Value = 0.0f;
+ floor.Materials[0].Maps[(int)MaterialMapIndex.Roughness].Value = 0.0f;
+ floor.Materials[0].Maps[(int)MaterialMapIndex.Occlusion].Value = 1.0f;
+ floor.Materials[0].Maps[(int)MaterialMapIndex.Emission].Color = Color.Black;
+
+ floor.Materials[0].Maps[(int)MaterialMapIndex.Albedo].Texture = LoadTexture("resources/road_a.png");
+ floor.Materials[0].Maps[(int)MaterialMapIndex.Metalness].Texture = LoadTexture("resources/road_mra.png");
+ floor.Materials[0].Maps[(int)MaterialMapIndex.Normal].Texture = LoadTexture("resources/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)
+ var carTextureTiling = new Vector2(0.5f, 0.5f);
+ var floorTextureTiling = new Vector2(0.5f, 0.5f);
+
+ // Create some lights
+ var lights = new PbrLight[4];
+ lights[0] = PbrLights.CreateLight(
+ 0,
+ PbrLightType.Point,
+ new Vector3(-1.0f, 1.0f, -2.0f),
+ new Vector3(0.0f, 0.0f, 0.0f),
+ Color.Yellow,
+ 4.0f,
+ shader);
+ lights[1] = PbrLights.CreateLight(1,
+ PbrLightType.Point,
+ new Vector3(2.0f, 1.0f, 1.0f),
+ new Vector3(0.0f, 0.0f, 0.0f),
+ Color.Green,
+ 3.3f,
+ shader);
+ lights[2] = PbrLights.CreateLight(
+ 2, PbrLightType.Point,
+ new Vector3(-2.0f, 1.0f, 1.0f),
+ new Vector3(0.0f, 0.0f, 0.0f),
+ Color.Red,
+ 8.3f,
+ shader);
+ lights[3] = PbrLights.CreateLight(
+ 3,
+ PbrLightType.Point,
+ new Vector3(1.0f, 1.0f, -2.0f),
+ new Vector3(0.0f, 0.0f, 0.0f),
+ Color.Black,
+ 2.0f,
+ shader);
+
+ // Setup material texture maps usage in shader
+ // NOTE: By default, the texture maps are always used
+ var usage = 1;
+ SetShaderValue(shader, GetShaderLocation(shader, "useTexAlbedo"), &usage, ShaderUniformDataType.Int);
+ SetShaderValue(shader, GetShaderLocation(shader, "useTexNormal"), &usage, ShaderUniformDataType.Int);
+ SetShaderValue(shader, GetShaderLocation(shader, "useTexMRA"), &usage, ShaderUniformDataType.Int);
+ SetShaderValue(shader, GetShaderLocation(shader, "useTexEmissive"), &usage, ShaderUniformDataType.Int);
+
+ 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
+ //----------------------------------------------------------------------------------
+ UpdateCamera(&camera, CameraMode.Orbital);
+
+ // Update the shader with the camera view vector (points towards { 0.0f, 0.0f, 0.0f })
+ var cameraPos = camera.Position;
+ SetShaderValue(shader, shader.Locs[(int)ShaderLocationIndex.VectorView], cameraPos, ShaderUniformDataType.Vec3);
+
+ // Check key inputs to enable/disable lights
+ if (IsKeyPressed(KeyboardKey.One))
+ {
+ lights[2].Enabled = !lights[2].Enabled;
+ }
+
+ if (IsKeyPressed(KeyboardKey.Two))
+ {
+ lights[1].Enabled = !lights[1].Enabled;
+ }
+
+ if (IsKeyPressed(KeyboardKey.Three))
+ {
+ lights[3].Enabled = !lights[3].Enabled;
+ }
+
+ if (IsKeyPressed(KeyboardKey.Four))
+ {
+ lights[0].Enabled = !lights[0].Enabled;
+ }
+
+ // Update light values on shader (actually, only enable/disable them)
+ for (var i = 0; i < 4; i++)
+ {
+ UpdateLight(shader, lights[i]);
+ }
+ //----------------------------------------------------------------------------------
+
+ // Draw
+ //----------------------------------------------------------------------------------
+ BeginDrawing();
+
+ ClearBackground(Color.Black);
+
+ BeginMode3D(camera);
+
+ // Set floor model texture tiling and emissive color parameters on shader
+ SetShaderValue(shader, textureTilingLoc, &floorTextureTiling, ShaderUniformDataType.Vec2);
+ var floorEmissiveColor = ColorNormalize(floor.Materials[0].Maps[(int)MaterialMapIndex.Emission].Color);
+ SetShaderValue(shader, emissiveColorLoc, &floorEmissiveColor, ShaderUniformDataType.Vec4);
+
+ DrawModel(floor, Vector3.Zero, 5.0f, Color.White); // Draw floor model
+
+ // Set old car model texture tiling, emissive color and emissive intensity parameters on shader
+ SetShaderValue(shader, textureTilingLoc, &carTextureTiling, ShaderUniformDataType.Vec2);
+ var carEmissiveColor = ColorNormalize(car.Materials[0].Maps[(int)MaterialMapIndex.Emission].Color);
+ SetShaderValue(shader, emissiveColorLoc, &carEmissiveColor, ShaderUniformDataType.Vec4);
+ var emissiveIntensity = 0.01f;
+ SetShaderValue(shader, emissiveIntensityLoc, &emissiveIntensity, ShaderUniformDataType.Float);
+
+ DrawModel(car, Vector3.Zero, 0.005f, Color.White); // Draw car model
+
+ // Draw spheres to show the lights positions
+ for (var i = 0; i < 4; i++)
+ {
+ var color = lights[i].Color;
+ var lightColor = new Color((byte)(color.X * 255), (byte)(color.Y * 255), (byte)(color.Z * 255),
+ (byte)(color.W * 255));
+
+ if (lights[i].Enabled)
+ {
+ DrawSphereEx(lights[i].Position, 0.2f, 8, 8, lightColor);
+ }
+ else
+ {
+ DrawSphereWires(lights[i].Position, 0.2f, 8, 8, ColorAlpha(lightColor, 0.3f));
+ }
+ }
+
+ EndMode3D();
+
+ DrawText("Toggle lights: [1][2][3][4]", 10, 40, 20, Color.LightGray);
+
+ DrawText("(c) Old Rusty Car model by Renafox (https://skfb.ly/LxRy)", screenWidth - 320, screenHeight - 20, 10, Color.LightGray);
+
+ DrawFPS(10, 10);
+
+ EndDrawing();
+ //----------------------------------------------------------------------------------
+ }
+
+ // De-Initialization
+ //--------------------------------------------------------------------------------------
+ // Unbind (disconnect) shader from car.material[0]
+ // to avoid UnloadMaterial() trying to unload it automatically
+
+ UnloadMaterial(car.Materials[0]);
+ car.Materials[0].Maps = null;
+ UnloadModel(car);
+
+ UnloadMaterial(floor.Materials[0]);
+ floor.Materials[0].Maps = null;
+ UnloadModel(floor);
+
+ UnloadShader(shader); // Unload Shader
+
+ CloseWindow(); // Close window and OpenGL context
+ //--------------------------------------------------------------------------------------
+
+ return 0;
+ }
+
+ private static void UpdateLight(Shader shader, PbrLight light)
+ {
+ SetShaderValue(shader, light.EnabledLoc, light.Enabled, ShaderUniformDataType.Int);
+ SetShaderValue(shader, light.TypeLoc, light.Type, ShaderUniformDataType.Int);
+
+ // Send to shader light position values
+ SetShaderValue(shader, light.PositionLoc, light.Position, ShaderUniformDataType.Vec3);
+
+ // Send to shader light target position values
+ SetShaderValue(shader, light.TargetLoc, light.Target, ShaderUniformDataType.Vec3);
+ SetShaderValue(shader, light.ColorLoc, light.Color, ShaderUniformDataType.Vec4);
+ SetShaderValue(shader, light.IntensityLoc, light.Intensity, ShaderUniformDataType.Float);
+ }
+}
diff --git a/Examples/Shared/PbrLights.cs b/Examples/Shared/PbrLights.cs
new file mode 100644
index 0000000..90d1966
--- /dev/null
+++ b/Examples/Shared/PbrLights.cs
@@ -0,0 +1,99 @@
+using static Raylib_cs.Raylib;
+using System.Numerics;
+
+namespace Examples.Shared;
+
+public struct PbrLight
+{
+ public PbrLightType Type;
+ public bool Enabled;
+ public Vector3 Position;
+ public Vector3 Target;
+ public Vector4 Color;
+ public float Intensity;
+
+ // Shader light parameters locations
+ public int TypeLoc;
+ public int EnabledLoc;
+ public int PositionLoc;
+ public int TargetLoc;
+ public int ColorLoc;
+ public int IntensityLoc;
+}
+
+public enum PbrLightType
+{
+ Directorional,
+ Point,
+ Spot
+}
+
+public class PbrLights
+{
+ public static PbrLight CreateLight(
+ int lightsCount,
+ PbrLightType type,
+ Vector3 pos,
+ Vector3 target,
+ Color color,
+ float intensity,
+ Shader shader
+ )
+ {
+ PbrLight light = new();
+
+ light.Enabled = true;
+ light.Type = type;
+ light.Position = pos;
+ light.Target = target;
+ light.Color = new Vector4(
+ color.R / 255.0f,
+ color.G / 255.0f,
+ color.B / 255.0f,
+ color.A / 255.0f
+ );
+ light.Intensity = intensity;
+
+ string enabledName = "lights[" + lightsCount + "].enabled";
+ string typeName = "lights[" + lightsCount + "].type";
+ string posName = "lights[" + lightsCount + "].position";
+ string targetName = "lights[" + lightsCount + "].target";
+ string colorName = "lights[" + lightsCount + "].color";
+ string intensityName = "lights[" + lightsCount + "].intensity";
+
+ light.EnabledLoc = GetShaderLocation(shader, enabledName);
+ light.TypeLoc = GetShaderLocation(shader, typeName);
+ light.PositionLoc = GetShaderLocation(shader, posName);
+ light.TargetLoc = GetShaderLocation(shader, targetName);
+ light.ColorLoc = GetShaderLocation(shader, colorName);
+ light.IntensityLoc = GetShaderLocation(shader, intensityName);
+
+ UpdateLightValues(shader, light);
+
+ return light;
+ }
+
+ public static void UpdateLightValues(Shader shader, PbrLight light)
+ {
+ // Send to shader light enabled state and type
+ Raylib.SetShaderValue(
+ shader,
+ light.EnabledLoc,
+ light.Enabled ? 1 : 0,
+ ShaderUniformDataType.Int
+ );
+ Raylib.SetShaderValue(shader, light.TypeLoc, (int)light.Type, ShaderUniformDataType.Int);
+
+ // Send to shader light target position values
+ Raylib.SetShaderValue(shader, light.PositionLoc, light.Position, ShaderUniformDataType.Vec3);
+
+ // Send to shader light target position values
+ Raylib.SetShaderValue(shader, light.TargetLoc, light.Target, ShaderUniformDataType.Vec3);
+
+ // Send to shader light color values
+ Raylib.SetShaderValue(shader, light.ColorLoc, light.Color, ShaderUniformDataType.Vec4);
+
+ // Send to shader light intensity values
+ Raylib.SetShaderValue(shader, light.IntensityLoc, light.Intensity, ShaderUniformDataType.Float);
+ }
+}
diff --git a/Examples/resources/models/gltf/old_car_new.glb b/Examples/resources/models/gltf/old_car_new.glb
new file mode 100644
index 0000000..119995c
Binary files /dev/null and b/Examples/resources/models/gltf/old_car_new.glb differ
diff --git a/Examples/resources/models/gltf/plane.glb b/Examples/resources/models/gltf/plane.glb
new file mode 100644
index 0000000..452e1c5
Binary files /dev/null and b/Examples/resources/models/gltf/plane.glb differ
diff --git a/Examples/resources/old_car_d.png b/Examples/resources/old_car_d.png
new file mode 100644
index 0000000..d8b3c83
Binary files /dev/null and b/Examples/resources/old_car_d.png differ
diff --git a/Examples/resources/old_car_e.png b/Examples/resources/old_car_e.png
new file mode 100644
index 0000000..23f01c0
Binary files /dev/null and b/Examples/resources/old_car_e.png differ
diff --git a/Examples/resources/old_car_mra.png b/Examples/resources/old_car_mra.png
new file mode 100644
index 0000000..0fb46b3
Binary files /dev/null and b/Examples/resources/old_car_mra.png differ
diff --git a/Examples/resources/old_car_n.png b/Examples/resources/old_car_n.png
new file mode 100644
index 0000000..11f689f
Binary files /dev/null and b/Examples/resources/old_car_n.png differ
diff --git a/Examples/resources/road_a.png b/Examples/resources/road_a.png
new file mode 100644
index 0000000..1037773
Binary files /dev/null and b/Examples/resources/road_a.png differ
diff --git a/Examples/resources/road_mra.png b/Examples/resources/road_mra.png
new file mode 100644
index 0000000..988c839
Binary files /dev/null and b/Examples/resources/road_mra.png differ
diff --git a/Examples/resources/road_n.png b/Examples/resources/road_n.png
new file mode 100644
index 0000000..a5f3548
Binary files /dev/null and b/Examples/resources/road_n.png differ
diff --git a/Examples/resources/shaders/glsl330/pbr.fs b/Examples/resources/shaders/glsl330/pbr.fs
new file mode 100644
index 0000000..d7544d3
--- /dev/null
+++ b/Examples/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))*useTexMRA;
+ 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/Examples/resources/shaders/glsl330/pbr.vs b/Examples/resources/shaders/glsl330/pbr.vs
new file mode 100644
index 0000000..94b0062
--- /dev/null
+++ b/Examples/resources/shaders/glsl330/pbr.vs
@@ -0,0 +1,48 @@
+#version 330
+
+// Input vertex attributes
+in vec3 vertexPosition;
+in vec2 vertexTexCoord;
+in vec3 vertexNormal;
+in vec3 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);
+
+ // 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.0f));
+
+ fragTexCoord = vertexTexCoord*2.0;
+ fragNormal = normalize(normalMatrix*vertexNormal);
+ vec3 fragTangent = normalize(normalMatrix*vertexTangent);
+ 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);
+}
\ No newline at end of file
diff --git a/Raylib-cs/types/AudioCallback.cs b/Raylib-cs/types/AudioCallback.cs
new file mode 100644
index 0000000..2f7b372
--- /dev/null
+++ b/Raylib-cs/types/AudioCallback.cs
@@ -0,0 +1,10 @@
+using System;
+
+namespace Raylib_cs;
+
+///
+/// Audio stream processor.
+/// Used with Raylib.AttachAudioMixedProcessor()
+/// and Raylib.DetachAudioMixedProcessor()
+///
+public delegate void AudioCallback(Span buffer);
diff --git a/Raylib-cs/types/AudioMixed.cs b/Raylib-cs/types/AudioMixed.cs
new file mode 100644
index 0000000..bf98491
--- /dev/null
+++ b/Raylib-cs/types/AudioMixed.cs
@@ -0,0 +1,23 @@
+using System;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace Raylib_cs;
+
+internal static unsafe class AudioMixed
+{
+ public static AudioCallback Callback = null;
+
+ [UnmanagedCallersOnly(CallConvs = new[]
+ {
+ typeof(CallConvCdecl),
+ })]
+ public static void Processor(void* buffer, uint frames)
+ {
+ // The buffer is stereo audio, so we need to double our frame count.
+ frames = Math.Min(frames * 2, int.MaxValue);
+
+ Span floats = new(buffer, (int)frames);
+ Callback?.Invoke(floats);
+ }
+}
diff --git a/Raylib-cs/types/Raylib.Utils.cs b/Raylib-cs/types/Raylib.Utils.cs
index c3b2f1d..46ba915 100644
--- a/Raylib-cs/types/Raylib.Utils.cs
+++ b/Raylib-cs/types/Raylib.Utils.cs
@@ -1261,6 +1261,30 @@ public static unsafe partial class Raylib
}
}
+ ///
+ /// Attach audio stream processor to the entire audio pipeline
+ ///
+ public static void AttachAudioMixedProcessor(AudioCallback processor)
+ {
+ if (AudioMixed.Callback == null)
+ {
+ AudioMixed.Callback = processor;
+ AttachAudioMixedProcessor(&AudioMixed.Processor);
+ }
+ }
+
+ ///
+ /// Detach audio stream processor from the entire audio pipeline
+ ///
+ public static void DetachAudioMixedProcessor(AudioCallback processor)
+ {
+ if (AudioMixed.Callback == processor)
+ {
+ DetachAudioMixedProcessor(&AudioMixed.Processor);
+ AudioMixed.Callback = null;
+ }
+ }
+
public static string SubText(this string input, int position, int length)
{
return input.Substring(position, Math.Min(length, input.Length));