Skip to content

Instantly share code, notes, and snippets.

@galek
Created November 4, 2015 11:10
Show Gist options
  • Save galek/53557375251e1a942dfa to your computer and use it in GitHub Desktop.
Save galek/53557375251e1a942dfa to your computer and use it in GitHub Desktop.
PBR GLSL SHADER
in vec2 v_texcoord; // texture coords
in vec3 v_normal; // normal
in vec3 v_binormal; // binormal (for TBN basis calc)
in vec3 v_pos; // pixel view space position
out vec4 color;
layout(std140) uniform Transforms
{
mat4x4 world_matrix; // object's world position
mat4x4 view_matrix; // view (camera) transform
mat4x4 proj_matrix; // projection matrix
mat3x3 normal_matrix; // normal transformation matrix ( transpose(inverse(W * V)) )
};
layout(std140) uniform Material
{
vec4 material; // x - metallic, y - roughness, w - "rim" lighting
vec4 albedo; // constant albedo color, used when textures are off
};
uniform samplerCube envd; // prefiltered env cubemap
uniform sampler2D tex; // base texture (albedo)
uniform sampler2D norm; // normal map
uniform sampler2D spec; // "factors" texture (G channel used as roughness)
uniform sampler2D iblbrdf; // IBL BRDF normalization precalculated tex
#define PI 3.1415926
// constant light position, only one light source for testing (treated as point light)
const vec4 light_pos = vec4(-2, 3, -2, 1);
// handy value clamping to 0 - 1 range
float saturate(in float value)
{
return clamp(value, 0.0, 1.0);
}
// phong (lambertian) diffuse term
float phong_diffuse()
{
return (1.0 / PI);
}
// compute fresnel specular factor for given base specular and product
// product could be NdV or VdH depending on used technique
vec3 fresnel_factor(in vec3 f0, in float product)
{
return mix(f0, vec3(1.0), pow(1.01 - product, 5.0));
}
// following functions are copies of UE4
// for computing cook-torrance specular lighting terms
float D_blinn(in float roughness, in float NdH)
{
float m = roughness * roughness;
float m2 = m * m;
float n = 2.0 / m2 - 2.0;
return (n + 2.0) / (2.0 * PI) * pow(NdH, n);
}
float D_beckmann(in float roughness, in float NdH)
{
float m = roughness * roughness;
float m2 = m * m;
float NdH2 = NdH * NdH;
return exp((NdH2 - 1.0) / (m2 * NdH2)) / (PI * m2 * NdH2 * NdH2);
}
float D_GGX(in float roughness, in float NdH)
{
float m = roughness * roughness;
float m2 = m * m;
float d = (NdH * m2 - NdH) * NdH + 1.0;
return m2 / (PI * d * d);
}
float G_schlick(in float roughness, in float NdV, in float NdL)
{
float k = roughness * roughness * 0.5;
float V = NdV * (1.0 - k) + k;
float L = NdL * (1.0 - k) + k;
return 0.25 / (V * L);
}
// simple phong specular calculation with normalization
vec3 phong_specular(in vec3 V, in vec3 L, in vec3 N, in vec3 specular, in float roughness)
{
vec3 R = reflect(-L, N);
float spec = max(0.0, dot(V, R));
float k = 1.999 / (roughness * roughness);
return min(1.0, 3.0 * 0.0398 * k) * pow(spec, min(10000.0, k)) * specular;
}
// simple blinn specular calculation with normalization
vec3 blinn_specular(in float NdH, in vec3 specular, in float roughness)
{
float k = 1.999 / (roughness * roughness);
return min(1.0, 3.0 * 0.0398 * k) * pow(NdH, min(10000.0, k)) * specular;
}
// cook-torrance specular calculation
vec3 cooktorrance_specular(in float NdL, in float NdV, in float NdH, in vec3 specular, in float roughness)
{
#ifdef COOK_BLINN
float D = D_blinn(roughness, NdH);
#endif
#ifdef COOK_BECKMANN
float D = D_beckmann(roughness, NdH);
#endif
#ifdef COOK_GGX
float D = D_GGX(roughness, NdH);
#endif
float G = G_schlick(roughness, NdV, NdL);
float rim = mix(1.0 - roughness * material.w * 0.9, 1.0, NdV);
return (1.0 / rim) * specular * G * D;
}
void main() {
// point light direction to point in view space
vec3 local_light_pos = (view_matrix * (/*world_matrix */ light_pos)).xyz;
// light attenuation
float A = 20.0 / dot(local_light_pos - v_pos, local_light_pos - v_pos);
// L, V, H vectors
vec3 L = normalize(local_light_pos - v_pos);
vec3 V = normalize(-v_pos);
vec3 H = normalize(L + V);
vec3 nn = normalize(v_normal);
vec3 nb = normalize(v_binormal);
mat3x3 tbn = mat3x3(nb, cross(nn, nb), nn);
vec2 texcoord = v_texcoord;
// normal map
#if USE_NORMAL_MAP
// tbn basis
vec3 N = tbn * (texture2D(norm, texcoord).xyz * 2.0 - 1.0);
#else
vec3 N = nn;
#endif
// albedo/specular base
#if USE_ALBEDO_MAP
vec3 base = texture2D(tex, texcoord).xyz;
#else
vec3 base = albedo.xyz;
#endif
// roughness
#if USE_ROUGHNESS_MAP
float roughness = texture2D(spec, texcoord).y * material.y;
#else
float roughness = material.y;
#endif
// material params
float metallic = material.x;
// mix between metal and non-metal material, for non-metal
// constant base specular factor of 0.04 grey is used
vec3 specular = mix(vec3(0.04), base, metallic);
// diffuse IBL term
// I know that my IBL cubemap has diffuse pre-integrated value in 10th MIP level
// actually level selection should be tweakable or from separate diffuse cubemap
mat3x3 tnrm = transpose(normal_matrix);
vec3 envdiff = textureCubeLod(envd, tnrm * N, 10).xyz;
// specular IBL term
// 11 magic number is total MIP levels in cubemap, this is simplest way for picking
// MIP level from roughness value (but it's not correct, however it looks fine)
vec3 refl = tnrm * reflect(-V, N);
vec3 envspec = textureCubeLod(
envd, refl, max(roughness * 11.0, textureQueryLod(envd, refl).y)
).xyz;
// compute material reflectance
float NdL = max(0.0, dot(N, L));
float NdV = max(0.001, dot(N, V));
float NdH = max(0.001, dot(N, H));
float HdV = max(0.001, dot(H, V));
float LdV = max(0.001, dot(L, V));
// fresnel term is common for any, except phong
// so it will be calcuated inside ifdefs
#ifdef PHONG
// specular reflectance with PHONG
vec3 specfresnel = fresnel_factor(specular, NdV);
vec3 specref = phong_specular(V, L, N, specfresnel, roughness);
#endif
#ifdef BLINN
// specular reflectance with BLINN
vec3 specfresnel = fresnel_factor(specular, HdV);
vec3 specref = blinn_specular(NdH, specfresnel, roughness);
#endif
#ifdef COOK
// specular reflectance with COOK-TORRANCE
vec3 specfresnel = fresnel_factor(specular, HdV);
vec3 specref = cooktorrance_specular(NdL, NdV, NdH, specfresnel, roughness);
#endif
specref *= vec3(NdL);
// diffuse is common for any model
vec3 diffref = (vec3(1.0) - specfresnel) * phong_diffuse() * NdL;
// compute lighting
vec3 reflected_light = vec3(0);
vec3 diffuse_light = vec3(0); // initial value == constant ambient light
// point light
vec3 light_color = vec3(1.0) * A;
reflected_light += specref * light_color;
diffuse_light += diffref * light_color;
// IBL lighting
vec2 brdf = texture2D(iblbrdf, vec2(roughness, 1.0 - NdV)).xy;
vec3 iblspec = min(vec3(0.99), fresnel_factor(specular, NdV) * brdf.x + brdf.y);
reflected_light += iblspec * envspec;
diffuse_light += envdiff * (1.0 / PI);
// final result
vec3 result =
diffuse_light * mix(base, vec3(0.0), metallic) +
reflected_light;
color = vec4(result, 1);
}
@azizzaeny
Copy link

hi... galek
could we discuss how this pipeline works...

i came a cross to looks how pbr engine doing internally when we given some texture maps... eg: (marmoset, UE, 3DO, CRYE, Quixel, substance)

some of they use reflecitivity, specular, AO or Cavity... even used microsuface maps

where to start and how this composite code works..
could you explain this nice gist..?

@joelseverin
Copy link

Line 54 should probably read
return mix(pow(1.01 - product, 5.0), vec3(1.0), f0);
as mix is defined as mix(x, y, a) and a is the interpolating factor.

@theidealist
Copy link

What's the license?

@galek
Copy link
Author

galek commented Apr 18, 2021

Hello, sorry for the long time of reply, a lot of work - license is MIT

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment