소스 검색

Review shader examples

Ray 6 년 전
부모
커밋
245cf2400e

+ 23 - 21
examples/models/models_material_pbr.c

@@ -17,6 +17,12 @@
 #define RLIGHTS_IMPLEMENTATION
 #include "rlights.h"
 
+#if defined(PLATFORM_DESKTOP)
+    #define GLSL_VERSION            330
+#else   // PLATFORM_RPI, PLATFORM_ANDROID, PLATFORM_WEB
+    #define GLSL_VERSION            100
+#endif
+
 #define CUBEMAP_SIZE         512        // Cubemap texture size
 #define IRRADIANCE_SIZE       32        // Irradiance texture size
 #define PREFILTERED_SIZE     256        // Prefiltered HDR environment texture size
@@ -114,10 +120,8 @@ static Material LoadMaterialPBR(Color albedo, float metalness, float roughness)
 {
     Material mat = { 0 };       // NOTE: All maps textures are set to { 0 }
     
-    #define     PATH_PBR_VS     "resources/shaders/pbr.vs"      // Path to physically based rendering vertex shader
-    #define     PATH_PBR_FS     "resources/shaders/pbr.fs"      // Path to physically based rendering fragment shader
-   
-    mat.shader = LoadShader(PATH_PBR_VS, PATH_PBR_FS);
+    mat.shader = LoadShader(FormatText("resources/shaders/glsl%i/pbr.vs", GLSL_VERSION), 
+                            FormatText("resources/shaders/glsl%i/pbr.fs", GLSL_VERSION));
     
     // Get required locations points for PBR material
     // NOTE: Those location names must be available and used in the shader code
@@ -144,23 +148,21 @@ static Material LoadMaterialPBR(Color albedo, float metalness, float roughness)
     mat.maps[MAP_ROUGHNESS].texture = LoadTexture("resources/pbr/trooper_roughness.png");
     mat.maps[MAP_OCCLUSION].texture = LoadTexture("resources/pbr/trooper_ao.png");
     
-    // Set environment maps
-    #define     PATH_CUBEMAP_VS         "resources/shaders/cubemap.vs"          // Path to equirectangular to cubemap vertex shader
-    #define     PATH_CUBEMAP_FS         "resources/shaders/cubemap.fs"          // Path to equirectangular to cubemap fragment shader
-    #define     PATH_SKYBOX_VS          "resources/shaders/skybox.vs"           // Path to skybox vertex shader
-    #define     PATH_IRRADIANCE_FS      "resources/shaders/irradiance.fs"       // Path to irradiance (GI) calculation fragment shader
-    #define     PATH_PREFILTER_FS       "resources/shaders/prefilter.fs"        // Path to reflection prefilter calculation fragment shader
-    #define     PATH_BRDF_VS            "resources/shaders/brdf.vs"     // Path to bidirectional reflectance distribution function vertex shader 
-    #define     PATH_BRDF_FS            "resources/shaders/brdf.fs"     // Path to bidirectional reflectance distribution function fragment shader
-    
-    Shader shdrCubemap = LoadShader(PATH_CUBEMAP_VS, PATH_CUBEMAP_FS);
-    printf("Loaded shader: cubemap\n");
-    Shader shdrIrradiance = LoadShader(PATH_SKYBOX_VS, PATH_IRRADIANCE_FS);
-    printf("Loaded shader: irradiance\n");
-    Shader shdrPrefilter = LoadShader(PATH_SKYBOX_VS, PATH_PREFILTER_FS);
-    printf("Loaded shader: prefilter\n");
-    Shader shdrBRDF = LoadShader(PATH_BRDF_VS, PATH_BRDF_FS);
-    printf("Loaded shader: brdf\n");
+    // Load equirectangular to cubemap shader
+    Shader shdrCubemap = LoadShader(FormatText("resources/shaders/glsl%i/cubemap.vs", GLSL_VERSION), 
+                                    FormatText("resources/shaders/glsl%i/cubemap.fs", GLSL_VERSION));
+
+    // Load irradiance (GI) calculation shader
+    Shader shdrIrradiance = LoadShader(FormatText("resources/shaders/glsl%i/skybox.vs", GLSL_VERSION), 
+                                       FormatText("resources/shaders/glsl%i/irradiance.fs", GLSL_VERSION));
+
+    // Load reflection prefilter calculation shader
+    Shader shdrPrefilter = LoadShader(FormatText("resources/shaders/glsl%i/skybox.vs", GLSL_VERSION), 
+                                      FormatText("resources/shaders/glsl%i/prefilter.fs", GLSL_VERSION));
+
+    // Load bidirectional reflectance distribution function shader 
+    Shader shdrBRDF = LoadShader(FormatText("resources/shaders/glsl%i/brdf.vs", GLSL_VERSION), 
+                                 FormatText("resources/shaders/glsl%i/brdf.fs", GLSL_VERSION));
     
     // Setup required shader locations
     SetShaderValue(shdrCubemap, GetShaderLocation(shdrCubemap, "equirectangularMap"), (int[1]){ 0 }, UNIFORM_INT);

+ 9 - 1
examples/models/models_skybox.c

@@ -11,6 +11,12 @@
 
 #include "raylib.h"
 
+#if defined(PLATFORM_DESKTOP)
+    #define GLSL_VERSION            330
+#else   // PLATFORM_RPI, PLATFORM_ANDROID, PLATFORM_WEB
+    #define GLSL_VERSION            100
+#endif
+
 int main()
 {
     // Initialization
@@ -29,7 +35,9 @@ int main()
     
     // Load skybox shader and set required locations
     // NOTE: Some locations are automatically set at shader loading
-    skybox.materials[0].shader = LoadShader("resources/shaders/skybox.vs", "resources/shaders/skybox.fs");
+    skybox.materials[0].shader = LoadShader(FormatText("resources/shaders/glsl%i/skybox.vs", GLSL_VERSION),
+                                            FormatText("resources/shaders/glsl%i/skybox.fs", GLSL_VERSION));
+                                            
     SetShaderValue(skybox.materials[0].shader, GetShaderLocation(skybox.materials[0].shader, "environmentMap"), (int[1]){ MAP_CUBEMAP }, UNIFORM_INT);
 
     // Load cubemap shader and setup required shader locations

+ 0 - 0
examples/models/resources/shaders/brdf.fs → examples/models/resources/shaders/glsl100/brdf.fs


+ 0 - 0
examples/models/resources/shaders/brdf.vs → examples/models/resources/shaders/glsl100/brdf.vs


+ 0 - 0
examples/models/resources/shaders/cubemap.fs → examples/models/resources/shaders/glsl100/cubemap.fs


+ 0 - 0
examples/models/resources/shaders/cubemap.vs → examples/models/resources/shaders/glsl100/cubemap.vs


+ 0 - 0
examples/models/resources/shaders/irradiance.fs → examples/models/resources/shaders/glsl100/irradiance.fs


+ 0 - 0
examples/models/resources/shaders/pbr.fs → examples/models/resources/shaders/glsl100/pbr.fs


+ 0 - 0
examples/models/resources/shaders/pbr.vs → examples/models/resources/shaders/glsl100/pbr.vs


+ 0 - 0
examples/models/resources/shaders/prefilter.fs → examples/models/resources/shaders/glsl100/prefilter.fs


+ 0 - 0
examples/models/resources/shaders/skybox.fs → examples/models/resources/shaders/glsl100/skybox.fs


+ 0 - 0
examples/models/resources/shaders/skybox.vs → examples/models/resources/shaders/glsl100/skybox.vs


+ 133 - 0
examples/models/resources/shaders/glsl330/brdf.fs

@@ -0,0 +1,133 @@
+/*******************************************************************************************
+*
+*   BRDF LUT Generation - Bidirectional reflectance distribution function fragment shader
+*
+*   REF: https://github.com/HectorMF/BRDFGenerator
+*
+*   Copyright (c) 2017 Victor Fisac
+*
+**********************************************************************************************/
+
+#version 330
+
+
+// Input vertex attributes (from vertex shader)
+in vec2 fragTexCoord;
+
+// Constant values
+const float PI = 3.14159265359;
+const uint MAX_SAMPLES = 1024u;
+
+// Output fragment color
+out vec4 finalColor;
+
+vec2 Hammersley(uint i, uint N);
+float RadicalInverseVdC(uint bits);
+float GeometrySchlickGGX(float NdotV, float roughness);
+float GeometrySmith(vec3 N, vec3 V, vec3 L, float roughness);
+vec3 ImportanceSampleGGX(vec2 Xi, vec3 N, float roughness);
+vec2 IntegrateBRDF(float NdotV, float roughness);
+
+float RadicalInverseVdC(uint bits)
+{
+    bits = (bits << 16u) | (bits >> 16u);
+    bits = ((bits & 0x55555555u) << 1u) | ((bits & 0xAAAAAAAAu) >> 1u);
+    bits = ((bits & 0x33333333u) << 2u) | ((bits & 0xCCCCCCCCu) >> 2u);
+    bits = ((bits & 0x0F0F0F0Fu) << 4u) | ((bits & 0xF0F0F0F0u) >> 4u);
+    bits = ((bits & 0x00FF00FFu) << 8u) | ((bits & 0xFF00FF00u) >> 8u);
+    return float(bits) * 2.3283064365386963e-10; // / 0x100000000
+}
+
+// Compute Hammersley coordinates
+vec2 Hammersley(uint i, uint N)
+{
+	return vec2(float(i)/float(N), RadicalInverseVdC(i));
+}
+
+// Integrate number of importance samples for (roughness and NoV)
+vec3 ImportanceSampleGGX(vec2 Xi, vec3 N, float roughness)
+{
+	float a = roughness*roughness;
+	float phi = 2.0 * PI * Xi.x;
+	float cosTheta = sqrt((1.0 - Xi.y)/(1.0 + (a*a - 1.0)*Xi.y));
+	float sinTheta = sqrt(1.0 - cosTheta*cosTheta);
+
+	// Transform from spherical coordinates to cartesian coordinates (halfway vector)
+	vec3 H = vec3(cos(phi)*sinTheta, sin(phi)*sinTheta, cosTheta);
+
+	// Transform from tangent space H vector to world space sample vector
+	vec3 up = ((abs(N.z) < 0.999) ? vec3(0.0, 0.0, 1.0) : vec3(1.0, 0.0, 0.0));
+	vec3 tangent = normalize(cross(up, N));
+	vec3 bitangent = cross(N, tangent);
+	vec3 sampleVec = tangent*H.x + bitangent*H.y + N*H.z;
+
+	return normalize(sampleVec);
+}
+
+float GeometrySchlickGGX(float NdotV, float roughness)
+{
+    // For IBL k is calculated different
+    float k = (roughness*roughness)/2.0;
+
+    float nom = NdotV;
+    float denom = NdotV*(1.0 - k) + k;
+
+    return nom/denom;
+}
+
+// Compute the geometry term for the BRDF given roughness squared, NoV, NoL
+float GeometrySmith(vec3 N, vec3 V, vec3 L, float roughness)
+{
+    float NdotV = max(dot(N, V), 0.0);
+    float NdotL = max(dot(N, L), 0.0);
+    float ggx2 = GeometrySchlickGGX(NdotV, roughness);
+    float ggx1 = GeometrySchlickGGX(NdotL, roughness);
+
+    return ggx1*ggx2;
+}
+
+vec2 IntegrateBRDF(float NdotV, float roughness)
+{
+    float A = 0.0;
+    float B = 0.0;
+    vec3 V = vec3(sqrt(1.0 - NdotV*NdotV), 0.0, NdotV);
+    vec3 N = vec3(0.0, 0.0, 1.0);
+
+    for (uint i = 0u; i < MAX_SAMPLES; i++)
+    {
+        // Generate a sample vector that's biased towards the preferred alignment direction (importance sampling)
+        
+        vec2 Xi = Hammersley(i, MAX_SAMPLES);       // Compute a Hammersely coordinate
+        vec3 H = ImportanceSampleGGX(Xi, N, roughness); // Integrate number of importance samples for (roughness and NoV)
+        vec3 L = normalize(2.0*dot(V, H)*H - V);    // Compute reflection vector L
+        
+        float NdotL = max(L.z, 0.0);                // Compute normal dot light
+        float NdotH = max(H.z, 0.0);                // Compute normal dot half
+        float VdotH = max(dot(V, H), 0.0);          // Compute view dot half
+
+        if (NdotL > 0.0)
+        {
+            float G = GeometrySmith(N, V, L, roughness);    // Compute the geometry term for the BRDF given roughness squared, NoV, NoL
+            float GVis = (G*VdotH)/(NdotH*NdotV);   // Compute the visibility term given G, VoH, NoH, NoV, NoL
+            float Fc = pow(1.0 - VdotH, 5.0);       // Compute the fresnel term given VoH
+
+            A += (1.0 - Fc)*GVis;                   // Sum the result given fresnel, geometry, visibility
+            B += Fc*GVis;
+        }
+    }
+
+    // Calculate brdf average sample
+    A /= float(MAX_SAMPLES);
+    B /= float(MAX_SAMPLES);
+
+    return vec2(A, B);
+}
+
+void main()
+{
+    // Calculate brdf based on texture coordinates
+    vec2 brdf = IntegrateBRDF(fragTexCoord.x, fragTexCoord.y);
+
+    // Calculate final fragment color
+    finalColor = vec4(brdf.r, brdf.g, 0.0, 1.0);
+}

+ 25 - 0
examples/models/resources/shaders/glsl330/brdf.vs

@@ -0,0 +1,25 @@
+/*******************************************************************************************
+*
+*   rPBR [shader] - Bidirectional reflectance distribution function vertex shader
+*
+*   Copyright (c) 2017 Victor Fisac
+*
+**********************************************************************************************/
+
+#version 330
+
+// Input vertex attributes
+in vec3 vertexPosition;
+in vec2 vertexTexCoord;
+
+// Output vertex attributes (to fragment shader)
+out vec2 fragTexCoord;
+
+void main()
+{
+    // Calculate fragment position based on model transformations
+    fragTexCoord = vertexTexCoord;
+
+    // Calculate final vertex position
+    gl_Position = vec4(vertexPosition, 1.0);
+}

+ 38 - 0
examples/models/resources/shaders/glsl330/cubemap.fs

@@ -0,0 +1,38 @@
+/*******************************************************************************************
+*
+*   rPBR [shader] - Equirectangular to cubemap fragment shader
+*
+*   Copyright (c) 2017 Victor Fisac
+*
+**********************************************************************************************/
+
+#version 330
+
+// Input vertex attributes (from vertex shader)
+in vec3 fragPosition;
+
+// Input uniform values
+uniform sampler2D equirectangularMap;
+
+// Output fragment color
+out vec4 finalColor;
+
+vec2 SampleSphericalMap(vec3 v)
+{
+    vec2 uv = vec2(atan(v.z, v.x), asin(v.y));
+    uv *= vec2(0.1591, 0.3183);
+    uv += 0.5;
+    return uv;
+}
+
+void main()
+{
+    // Normalize local position 
+    vec2 uv = SampleSphericalMap(normalize(fragPosition));
+
+    // Fetch color from texture map
+    vec3 color = texture(equirectangularMap, uv).rgb;
+
+    // Calculate final fragment color
+    finalColor = vec4(color, 1.0);
+}

+ 28 - 0
examples/models/resources/shaders/glsl330/cubemap.vs

@@ -0,0 +1,28 @@
+/*******************************************************************************************
+*
+*   rPBR [shader] - Equirectangular to cubemap vertex shader
+*
+*   Copyright (c) 2017 Victor Fisac
+*
+**********************************************************************************************/
+
+#version 330
+
+// Input vertex attributes
+in vec3 vertexPosition;
+
+// Input uniform values
+uniform mat4 projection;
+uniform mat4 view;
+
+// Output vertex attributes (to fragment shader)
+out vec3 fragPosition;
+
+void main()
+{
+    // Calculate fragment position based on model transformations
+    fragPosition = vertexPosition;
+
+    // Calculate final vertex position
+    gl_Position = projection*view*vec4(vertexPosition, 1.0);
+}

+ 58 - 0
examples/models/resources/shaders/glsl330/irradiance.fs

@@ -0,0 +1,58 @@
+/*******************************************************************************************
+*
+*   rPBR [shader] - Irradiance cubemap fragment shader
+*
+*   Copyright (c) 2017 Victor Fisac
+*
+**********************************************************************************************/
+
+#version 330
+
+// Input vertex attributes (from vertex shader)
+in vec3 fragPosition;
+
+// Input uniform values
+uniform samplerCube environmentMap;
+
+// Constant values
+const float PI = 3.14159265359f;
+
+// Output fragment color
+out vec4 finalColor;
+
+void main()
+{
+    // The sample direction equals the hemisphere's orientation
+    vec3 normal = normalize(fragPosition);
+
+    vec3 irradiance = vec3(0.0);  
+
+    vec3 up = vec3(0.0, 1.0, 0.0);
+    vec3 right = cross(up, normal);
+    up = cross(normal, right);
+
+    float sampleDelta = 0.025f;
+    float nrSamples = 0.0f; 
+
+    for (float phi = 0.0; phi < 2.0*PI; phi += sampleDelta)
+    {
+        for (float theta = 0.0; theta < 0.5*PI; theta += sampleDelta)
+        {
+            // Spherical to cartesian (in tangent space)
+            vec3 tangentSample = vec3(sin(theta)*cos(phi), sin(theta)*sin(phi), cos(theta));
+            
+            // tangent space to world
+            vec3 sampleVec = tangentSample.x*right + tangentSample.y*up + tangentSample.z*normal; 
+
+            // Fetch color from environment cubemap
+            irradiance += texture(environmentMap, sampleVec).rgb*cos(theta)*sin(theta);
+            nrSamples++;
+        }
+    }
+
+    // Calculate irradiance average value from samples
+    irradiance = PI*irradiance*(1.0/float(nrSamples));
+
+    // Calculate final fragment color
+    finalColor = vec4(irradiance, 1.0);
+}

+ 298 - 0
examples/models/resources/shaders/glsl330/pbr.fs

@@ -0,0 +1,298 @@
+/*******************************************************************************************
+*
+*   rPBR [shader] - Physically based rendering fragment shader
+*
+*   Copyright (c) 2017 Victor Fisac
+*
+**********************************************************************************************/
+
+#version 330
+
+#define     MAX_REFLECTION_LOD      4.0
+#define     MAX_DEPTH_LAYER         20
+#define     MIN_DEPTH_LAYER         10
+
+#define     MAX_LIGHTS              4
+#define     LIGHT_DIRECTIONAL       0
+#define     LIGHT_POINT             1
+
+struct MaterialProperty {
+    vec3 color;
+    int useSampler;
+    sampler2D sampler;
+};
+
+struct Light {
+    int enabled;
+    int type;
+    vec3 position;
+    vec3 target;
+    vec4 color;
+};
+
+// Input vertex attributes (from vertex shader)
+in vec3 fragPosition;
+in vec2 fragTexCoord;
+in vec3 fragNormal;
+in vec3 fragTangent;
+in vec3 fragBinormal;
+
+// Input material values
+uniform MaterialProperty albedo;
+uniform MaterialProperty normals;
+uniform MaterialProperty metalness;
+uniform MaterialProperty roughness;
+uniform MaterialProperty occlusion;
+uniform MaterialProperty emission;
+uniform MaterialProperty height;
+
+// Input uniform values
+uniform samplerCube irradianceMap;
+uniform samplerCube prefilterMap;
+uniform sampler2D brdfLUT;
+
+// Input lighting values
+uniform Light lights[MAX_LIGHTS];
+
+// Other uniform values
+uniform int renderMode;
+uniform vec3 viewPos;
+vec2 texCoord;
+
+// Constant values
+const float PI = 3.14159265359;
+
+// Output fragment color
+out vec4 finalColor;
+
+vec3 ComputeMaterialProperty(MaterialProperty property);
+float DistributionGGX(vec3 N, vec3 H, float roughness);
+float GeometrySchlickGGX(float NdotV, float roughness);
+float GeometrySmith(vec3 N, vec3 V, vec3 L, float roughness);
+vec3 fresnelSchlick(float cosTheta, vec3 F0);
+vec3 fresnelSchlickRoughness(float cosTheta, vec3 F0, float roughness);
+vec2 ParallaxMapping(vec2 texCoords, vec3 viewDir);
+
+vec3 ComputeMaterialProperty(MaterialProperty property)
+{
+    vec3 result = vec3(0.0, 0.0, 0.0);
+
+    if (property.useSampler == 1) result = texture(property.sampler, texCoord).rgb;
+    else result = property.color;
+
+    return result;
+}
+
+float DistributionGGX(vec3 N, vec3 H, float roughness)
+{
+    float a = roughness*roughness;
+    float a2 = a*a;
+    float NdotH = max(dot(N, H), 0.0);
+    float NdotH2 = NdotH*NdotH;
+
+    float nom = a2;
+    float denom = (NdotH2*(a2 - 1.0) + 1.0);
+    denom = PI*denom*denom;
+
+    return nom/denom;
+}
+
+float GeometrySchlickGGX(float NdotV, float roughness)
+{
+    float r = (roughness + 1.0);
+    float k = r*r/8.0;
+
+    float nom = NdotV;
+    float denom = NdotV*(1.0 - k) + k;
+
+    return nom/denom;
+}
+float GeometrySmith(vec3 N, vec3 V, vec3 L, float roughness)
+{
+    float NdotV = max(dot(N, V), 0.0);
+    float NdotL = max(dot(N, L), 0.0);
+    float ggx2 = GeometrySchlickGGX(NdotV, roughness);
+    float ggx1 = GeometrySchlickGGX(NdotL, roughness);
+
+    return ggx1*ggx2;
+}
+
+vec3 fresnelSchlick(float cosTheta, vec3 F0)
+{
+    return F0 + (1.0 - F0)*pow(1.0 - cosTheta, 5.0);
+}
+
+vec3 fresnelSchlickRoughness(float cosTheta, vec3 F0, float roughness)
+{
+    return F0 + (max(vec3(1.0 - roughness), F0) - F0)*pow(1.0 - cosTheta, 5.0);
+}
+
+vec2 ParallaxMapping(vec2 texCoords, vec3 viewDir)
+{
+    // Calculate the number of depth layers and calculate the size of each layer
+    float numLayers = mix(MAX_DEPTH_LAYER, MIN_DEPTH_LAYER, abs(dot(vec3(0.0, 0.0, 1.0), viewDir)));  
+    float layerDepth = 1.0/numLayers;
+
+    // Calculate depth of current layer
+    float currentLayerDepth = 0.0;
+
+    // Calculate the amount to shift the texture coordinates per layer (from vector P)
+    // Note: height amount is stored in height material attribute color R channel (sampler use is independent)
+    vec2 P = viewDir.xy*height.color.r; 
+    vec2 deltaTexCoords = P/numLayers;
+
+    // Store initial texture coordinates and depth values
+    vec2 currentTexCoords = texCoords;
+    float currentDepthMapValue = texture(height.sampler, currentTexCoords).r;
+
+    while (currentLayerDepth < currentDepthMapValue)
+    {
+        // Shift texture coordinates along direction of P
+        currentTexCoords -= deltaTexCoords;
+
+        // Get depth map value at current texture coordinates
+        currentDepthMapValue = texture(height.sampler, currentTexCoords).r;
+
+        // Get depth of next layer
+        currentLayerDepth += layerDepth;  
+    }
+
+    // Get texture coordinates before collision (reverse operations)
+    vec2 prevTexCoords = currentTexCoords + deltaTexCoords;
+
+    // Get depth after and before collision for linear interpolation
+    float afterDepth = currentDepthMapValue - currentLayerDepth;
+    float beforeDepth = texture(height.sampler, prevTexCoords).r - currentLayerDepth + layerDepth;
+
+    // Interpolation of texture coordinates
+    float weight = afterDepth/(afterDepth - beforeDepth);
+    vec2 finalTexCoords = prevTexCoords*weight + currentTexCoords*(1.0 - weight);
+
+    return finalTexCoords;
+}
+
+void main()
+{
+    // Calculate TBN and RM matrices
+    mat3 TBN = transpose(mat3(fragTangent, fragBinormal, fragNormal));
+
+    // Calculate lighting required attributes
+    vec3 normal = normalize(fragNormal);
+    vec3 view = normalize(viewPos - fragPosition);
+    vec3 refl = reflect(-view, normal);
+
+    // Check if parallax mapping is enabled and calculate texture coordinates to use based on height map
+    // NOTE: remember that 'texCoord' variable must be assigned before calling any ComputeMaterialProperty() function
+    if (height.useSampler == 1) texCoord = ParallaxMapping(fragTexCoord, view);
+    else texCoord = fragTexCoord;   // Use default texture coordinates
+
+    // Fetch material values from texture sampler or color attributes
+    vec3 color = ComputeMaterialProperty(albedo);
+    vec3 metal = ComputeMaterialProperty(metalness);
+    vec3 rough = ComputeMaterialProperty(roughness);
+    vec3 emiss = ComputeMaterialProperty(emission);
+    vec3 ao = ComputeMaterialProperty(occlusion);
+
+    // Check if normal mapping is enabled
+    if (normals.useSampler == 1)
+    {
+        // Fetch normal map color and transform lighting values to tangent space
+        normal = ComputeMaterialProperty(normals);
+        normal = normalize(normal*2.0 - 1.0);
+        normal = normalize(normal*TBN);
+
+        // Convert tangent space normal to world space due to cubemap reflection calculations
+        refl = normalize(reflect(-view, normal));
+    }
+
+    // Calculate reflectance at normal incidence
+    vec3 F0 = vec3(0.04);
+    F0 = mix(F0, color, metal.r);
+
+    // Calculate lighting for all lights
+    vec3 Lo = vec3(0.0);
+    vec3 lightDot = vec3(0.0);
+
+    for (int i = 0; i < MAX_LIGHTS; i++)
+    {
+        if (lights[i].enabled == 1)
+        {
+            // Calculate per-light radiance
+            vec3 light = vec3(0.0);
+            vec3 radiance = lights[i].color.rgb;
+            if (lights[i].type == LIGHT_DIRECTIONAL) light = -normalize(lights[i].target - lights[i].position);
+            else if (lights[i].type == LIGHT_POINT)
+            {
+                light = normalize(lights[i].position - fragPosition);
+                float distance = length(lights[i].position - fragPosition);
+                float attenuation = 1.0/(distance*distance);
+                radiance *= attenuation;
+            }
+
+            // Cook-torrance BRDF
+            vec3 high = normalize(view + light);
+            float NDF = DistributionGGX(normal, high, rough.r);
+            float G = GeometrySmith(normal, view, light, rough.r);
+            vec3 F = fresnelSchlick(max(dot(high, view), 0.0), F0);
+            vec3 nominator = NDF*G*F;
+            float denominator = 4*max(dot(normal, view), 0.0)*max(dot(normal, light), 0.0) + 0.001;
+            vec3 brdf = nominator/denominator;
+
+            // Store to kS the fresnel value and calculate energy conservation
+            vec3 kS = F;
+            vec3 kD = vec3(1.0) - kS;
+
+            // Multiply kD by the inverse metalness such that only non-metals have diffuse lighting
+            kD *= 1.0 - metal.r;
+
+            // Scale light by dot product between normal and light direction
+            float NdotL = max(dot(normal, light), 0.0);
+
+            // Add to outgoing radiance Lo
+            // Note: BRDF is already multiplied by the Fresnel so it doesn't need to be multiplied again
+            Lo += (kD*color/PI + brdf)*radiance*NdotL*lights[i].color.a;
+            lightDot += radiance*NdotL + brdf*lights[i].color.a;
+        }
+    }
+
+    // Calculate ambient lighting using IBL
+    vec3 F = fresnelSchlickRoughness(max(dot(normal, view), 0.0), F0, rough.r);
+    vec3 kS = F;
+    vec3 kD = 1.0 - kS;
+    kD *= 1.0 - metal.r;
+
+    // Calculate indirect diffuse
+    vec3 irradiance = texture(irradianceMap, fragNormal).rgb;
+    vec3 diffuse = color*irradiance;
+
+    // Sample both the prefilter map and the BRDF lut and combine them together as per the Split-Sum approximation
+    vec3 prefilterColor = textureLod(prefilterMap, refl, rough.r*MAX_REFLECTION_LOD).rgb;
+    vec2 brdf = texture(brdfLUT, vec2(max(dot(normal, view), 0.0), rough.r)).rg;
+    vec3 reflection = prefilterColor*(F*brdf.x + brdf.y);
+
+    // Calculate final lighting
+    vec3 ambient = (kD*diffuse + reflection)*ao;
+
+    // Calculate fragment color based on render mode
+    vec3 fragmentColor = ambient + Lo + emiss;                              // Physically Based Rendering
+
+    if (renderMode == 1) fragmentColor = color;                             // Albedo
+    else if (renderMode == 2) fragmentColor = normal;                       // Normals
+    else if (renderMode == 3) fragmentColor = metal;                        // Metalness
+    else if (renderMode == 4) fragmentColor = rough;                        // Roughness
+    else if (renderMode == 5) fragmentColor = ao;                           // Ambient Occlusion
+    else if (renderMode == 6) fragmentColor = emiss;                        // Emission
+    else if (renderMode == 7) fragmentColor = lightDot;                     // Lighting
+    else if (renderMode == 8) fragmentColor = kS;                           // Fresnel
+    else if (renderMode == 9) fragmentColor = irradiance;                   // Irradiance
+    else if (renderMode == 10) fragmentColor = reflection;                  // Reflection
+
+    // Apply HDR tonemapping
+    fragmentColor = fragmentColor/(fragmentColor + vec3(1.0));
+
+    // Apply gamma correction
+    fragmentColor = pow(fragmentColor, vec3(1.0/2.2));
+
+    // Calculate final fragment color
+    finalColor = vec4(fragmentColor, 1.0);
+}

+ 49 - 0
examples/models/resources/shaders/glsl330/pbr.vs

@@ -0,0 +1,49 @@
+/*******************************************************************************************
+*
+*   rPBR [shader] - Physically based rendering vertex shader
+*
+*   Copyright (c) 2017 Victor Fisac
+*
+**********************************************************************************************/
+
+#version 330
+
+// Input vertex attributes
+in vec3 vertexPosition;
+in vec2 vertexTexCoord;
+in vec3 vertexNormal;
+in vec4 vertexTangent;
+
+// Input uniform values
+uniform mat4 mvp;
+uniform mat4 matModel;
+
+// Output vertex attributes (to fragment shader)
+out vec3 fragPosition;
+out vec2 fragTexCoord;
+out vec3 fragNormal;
+out vec3 fragTangent;
+out vec3 fragBinormal;
+
+void main()
+{
+    // Calculate binormal from vertex normal and tangent
+    vec3 vertexBinormal = cross(vertexNormal, vec3(vertexTangent));
+
+    // Calculate fragment normal based on normal transformations
+    mat3 normalMatrix = transpose(inverse(mat3(matModel)));
+
+    // Calculate fragment position based on model transformations
+    fragPosition = vec3(matModel*vec4(vertexPosition, 1.0f));
+
+    // Send vertex attributes to fragment shader
+    fragTexCoord = vertexTexCoord;
+    fragNormal = normalize(normalMatrix*vertexNormal);
+    fragTangent = normalize(normalMatrix*vec3(vertexTangent));
+    fragTangent = normalize(fragTangent - dot(fragTangent, fragNormal)*fragNormal);
+    fragBinormal = normalize(normalMatrix*vertexBinormal);
+    fragBinormal = cross(fragNormal, fragTangent);
+
+    // Calculate final vertex position
+    gl_Position = mvp*vec4(vertexPosition, 1.0);
+}

+ 120 - 0
examples/models/resources/shaders/glsl330/prefilter.fs

@@ -0,0 +1,120 @@
+/*******************************************************************************************
+*
+*   rPBR [shader] - Prefiltered environment for reflections fragment shader
+*
+*   Copyright (c) 2017 Victor Fisac
+*
+**********************************************************************************************/
+
+#version 330
+#define     MAX_SAMPLES             1024u
+#define     CUBEMAP_RESOLUTION      1024.0
+
+// Input vertex attributes (from vertex shader)
+in vec3 fragPosition;
+
+// Input uniform values
+uniform samplerCube environmentMap;
+uniform float roughness;
+
+// Constant values
+const float PI = 3.14159265359f;
+
+// Output fragment color
+out vec4 finalColor;
+
+float DistributionGGX(vec3 N, vec3 H, float roughness);
+float RadicalInverse_VdC(uint bits);
+vec2 Hammersley(uint i, uint N);
+vec3 ImportanceSampleGGX(vec2 Xi, vec3 N, float roughness);
+
+float DistributionGGX(vec3 N, vec3 H, float roughness)
+{
+    float a = roughness*roughness;
+    float a2 = a*a;
+    float NdotH = max(dot(N, H), 0.0);
+    float NdotH2 = NdotH*NdotH;
+
+    float nom   = a2;
+    float denom = (NdotH2*(a2 - 1.0) + 1.0);
+    denom = PI*denom*denom;
+
+    return nom/denom;
+}
+
+float RadicalInverse_VdC(uint bits)
+{
+     bits = (bits << 16u) | (bits >> 16u);
+     bits = ((bits & 0x55555555u) << 1u) | ((bits & 0xAAAAAAAAu) >> 1u);
+     bits = ((bits & 0x33333333u) << 2u) | ((bits & 0xCCCCCCCCu) >> 2u);
+     bits = ((bits & 0x0F0F0F0Fu) << 4u) | ((bits & 0xF0F0F0F0u) >> 4u);
+     bits = ((bits & 0x00FF00FFu) << 8u) | ((bits & 0xFF00FF00u) >> 8u);
+     return float(bits) * 2.3283064365386963e-10; // / 0x100000000
+}
+
+vec2 Hammersley(uint i, uint N)
+{
+	return vec2(float(i)/float(N), RadicalInverse_VdC(i));
+}
+
+vec3 ImportanceSampleGGX(vec2 Xi, vec3 N, float roughness)
+{
+	float a = roughness*roughness;
+	float phi = 2.0 * PI * Xi.x;
+	float cosTheta = sqrt((1.0 - Xi.y)/(1.0 + (a*a - 1.0)*Xi.y));
+	float sinTheta = sqrt(1.0 - cosTheta*cosTheta);
+
+	// Transform from spherical coordinates to cartesian coordinates (halfway vector)
+	vec3 H = vec3(cos(phi)*sinTheta, sin(phi)*sinTheta, cosTheta);
+
+	// Transform from tangent space H vector to world space sample vector
+	vec3 up = ((abs(N.z) < 0.999) ? vec3(0.0, 0.0, 1.0) : vec3(1.0, 0.0, 0.0));
+	vec3 tangent = normalize(cross(up, N));
+	vec3 bitangent = cross(N, tangent);
+	vec3 sampleVec = tangent*H.x + bitangent*H.y + N*H.z;
+
+	return normalize(sampleVec);
+}
+
+void main()
+{
+    // Make the simplyfying assumption that V equals R equals the normal 
+    vec3 N = normalize(fragPosition);
+    vec3 R = N;
+    vec3 V = R;
+
+    vec3 prefilteredColor = vec3(0.0);
+    float totalWeight = 0.0;
+
+    for (uint i = 0u; i < MAX_SAMPLES; i++)
+    {
+        // Generate a sample vector that's biased towards the preferred alignment direction (importance sampling)
+        vec2 Xi = Hammersley(i, MAX_SAMPLES);
+        vec3 H = ImportanceSampleGGX(Xi, N, roughness);
+        vec3 L  = normalize(2.0*dot(V, H)*H - V);
+
+        float NdotL = max(dot(N, L), 0.0);
+        if(NdotL > 0.0)
+        {
+            // Sample from the environment's mip level based on roughness/pdf
+            float D = DistributionGGX(N, H, roughness);
+            float NdotH = max(dot(N, H), 0.0);
+            float HdotV = max(dot(H, V), 0.0);
+            float pdf = D*NdotH/(4.0*HdotV) + 0.0001; 
+
+            float resolution = CUBEMAP_RESOLUTION;
+            float saTexel  = 4.0*PI/(6.0*resolution*resolution);
+            float saSample = 1.0/(float(MAX_SAMPLES)*pdf + 0.0001);
+            float mipLevel = ((roughness == 0.0) ? 0.0 : 0.5*log2(saSample/saTexel)); 
+
+            prefilteredColor += textureLod(environmentMap, L, mipLevel).rgb*NdotL;
+            totalWeight += NdotL;
+        }
+    }
+
+    // Calculate prefilter average color
+    prefilteredColor = prefilteredColor/totalWeight;
+
+    // Calculate final fragment color
+    finalColor = vec4(prefilteredColor, 1.0);
+}

+ 31 - 0
examples/models/resources/shaders/glsl330/skybox.fs

@@ -0,0 +1,31 @@
+/*******************************************************************************************
+*
+*   rPBR [shader] - Background skybox fragment shader
+*
+*   Copyright (c) 2017 Victor Fisac
+*
+**********************************************************************************************/
+
+#version 330
+
+// Input vertex attributes (from vertex shader)
+in vec3 fragPosition;
+
+// Input uniform values
+uniform samplerCube environmentMap;
+
+// Output fragment color
+out vec4 finalColor;
+
+void main()
+{
+    // Fetch color from texture map
+    vec3 color = texture(environmentMap, fragPosition).rgb;
+
+    // Apply gamma correction
+    color = color/(color + vec3(1.0));
+    color = pow(color, vec3(1.0/2.2));
+
+    // Calculate final fragment color
+    finalColor = vec4(color, 1.0);
+}

+ 32 - 0
examples/models/resources/shaders/glsl330/skybox.vs

@@ -0,0 +1,32 @@
+/*******************************************************************************************
+*
+*   rPBR [shader] - Background skybox vertex shader
+*
+*   Copyright (c) 2017 Victor Fisac
+*
+**********************************************************************************************/
+
+#version 330
+
+// Input vertex attributes
+in vec3 vertexPosition;
+
+// Input uniform values
+uniform mat4 projection;
+uniform mat4 view;
+
+// Output vertex attributes (to fragment shader)
+out vec3 fragPosition;
+
+void main()
+{
+    // Calculate fragment position based on model transformations
+    fragPosition = vertexPosition;
+
+    // Remove translation from the view matrix
+    mat4 rotView = mat4(mat3(view));
+    vec4 clipPos = projection*rotView*vec4(vertexPosition, 1.0);
+
+    // Calculate final vertex position
+    gl_Position = clipPos.xyww;
+}