Disney PBS Light Model – Slice
Slice Image of the Disney PBS Light Model based on Brent Burley’s paper here.
You can get nodes for Godot visual shading system from other lighting models here.
Shader code
// ElSuicio, 2025.
// GODOT v4.5.1.stable.
// x.com/ElSuicio
// github.com/ElSuicio
// Contact email [interdreamsoft@gmail.com]
shader_type canvas_item;
uniform float _PhiH : hint_range(0.0, 180.0, 1e-3) = 45.0; // incident phi.
uniform float _PhiD : hint_range(0.0, 180.0, 1e-3) = 90.0;
uniform float _Brightness : hint_range(0.0, 1.0, 1e-3) = 1.0;
uniform float _Exposure : hint_range(-6.0, 6.0, 1e-3) = 0.0;
uniform float _Gamma : hint_range(1.0, 5.0, 1e-3) = 2.2;
/* Disney PBS */
// https://www.w3.org/WAI/GL/wiki/Relative_luminance
#define RELATIVE_LUMINANCE(color) float(\
0.2126 * color.r +\
0.7152 * color.g +\
0.0722 * color.b\
);
group_uniforms _Disney;
uniform vec3 _DiffuseColor : source_color = vec3(1.0);
uniform float _Roughness : hint_range(0.0, 1.0, 1e-3) = 1.0;
uniform float _Subsurface : hint_range(0.0, 1.0, 1e-3) = 0.0;
uniform float _Metallic : hint_range(0.0, 1.0, 1e-3) = 0.0;
uniform float _Specular : hint_range(0.0, 1.0, 1e-3) = 0.5;
uniform float _SpecularTint : hint_range(0.0, 1.0, 1e-3) = 0.0;
uniform float _Anisotropic : hint_range(0.0, 1.0, 1e-3) = 0.0;
uniform float _Sheen : hint_range(0.0, 1.0, 1e-3) = 0.0;
uniform float _SheenTint : hint_range(0.0, 1.0, 1e-3) = 0.5;
uniform float _Clearcoat : hint_range(0.0, 1.0, 1e-3) = 0.0;
uniform float _ClearcoatGloss : hint_range(0.0, 1.0, 1e-3) = 1.0;
vec3 to_linear(vec3 srgb)
{
vec3 a = pow((srgb + 0.055) / 1.055, vec3(2.4));
vec3 b = srgb / 12.92;
bvec3 c = lessThan(srgb, vec3(0.04045));
return mix(a, b, c);
}
float schlick_fresnel(float u)
{
float m = clamp(1.0 - u, 0.0, 1.0);
return m * m * m * m * m; // pow(m, 5).
}
vec3 BRDF(
in vec3 n,
in vec3 l,
in vec3 v,
in vec3 x,
in vec3 y
)
{
vec3 rho = to_linear(_DiffuseColor);
vec3 h = normalize(v + l);
float alpha = _Roughness * _Roughness;
float NdotL = dot(n, l); // cos(theta_l) == cos(theta_i).
float NdotV = dot(n, v); // cos(theta_v) == cos(theta_r).
float NdotH = dot(n, h); // cos(theta_h).
float HdotL = dot(h, l); // cos(theta_d).
float LdotX = dot(l, x);
float VdotX = dot(v, x);
float HdotX = dot(h, x);
float LdotY = dot(l, y);
float VdotY = dot(v, y);
float HdotY = dot(h, y);
float luminance = RELATIVE_LUMINANCE(rho);
vec3 chroma = luminance > 0.0 ? rho / luminance : vec3(1.0);
// https://media.disneyanimation.com/uploads/production/publication_asset/48/asset/s2012_pbs_disney_brdf_notes_v3.pdf
/* Burley */
float FD_l = schlick_fresnel(NdotL), FD_v = schlick_fresnel(NdotV);
float retroreflection = _Roughness * HdotL * HdotL;
float FD90 = 0.5 + 2.0 * retroreflection;
float burley = mix(1.0, FD90, FD_l) * mix(1.0, FD90, FD_v);
/* Subsurface */
float subsurface = mix(1.0, retroreflection, FD_l) * mix(1.0, retroreflection, FD_v);
subsurface = 1.25 * (subsurface * (1.0 / (NdotL + NdotV) - 0.5) + 0.5);
/* Anisotropic-GGX */
float aspect = sqrt(1.0 - _Anisotropic * 0.9);
float ax = max(1e-3, alpha / aspect);
float ay = max(1e-3, alpha * aspect);
float inv_ax = 1.0 / ax;
float inv_ay = 1.0 / ay;
/* Normal Distribution Function (GGX-Anisotropic) */
float hx = HdotX * inv_ax;
float hy = HdotY * inv_ay;
float t = hx * hx + hy * hy + NdotH * NdotH;
float D = 1.0 / (PI * ax * ay * t * t);
/* Geometric Function (Smith-GGX-Anisotropic) */
float lx = LdotX * ax;
float ly = LdotY * ay;
float vx = VdotX * ax;
float vy = VdotY * ay;
float GL = 1.0 / (NdotL + sqrt(lx * lx + ly * ly + NdotL * NdotL));
float GV = 1.0 / (NdotV + sqrt(vx * vx + vy * vy + NdotV * NdotV));
float G = GL * GV;
/* Fresnel Function */
vec3 f0 = mix(_Specular * 8e-2 * mix(vec3(1.0), chroma, _SpecularTint), rho, _Metallic);
float FH = schlick_fresnel(HdotL);
vec3 F = mix(f0, vec3(1.0), FH);
/* Sheen */
vec3 sheen = FH * _Sheen * mix(vec3(1.0), chroma, _SheenTint);
/* Clearcoat */
/* Normal Distribution Function */
float clearcoat_alpha = mix(1e-1, 1e-3, _ClearcoatGloss);
float clearcoat_alpha2 = clearcoat_alpha * clearcoat_alpha;
float Dr = (clearcoat_alpha2 - 1.0) / (PI * log(clearcoat_alpha2) * (1.0 + (clearcoat_alpha2 - 1.0) * NdotH * NdotH));
/* Geometric Function */
float GrL = 1.0 / (NdotL + sqrt(625e-4 + 9375e-4 * NdotL * NdotL));
float GrV = 1.0 / (NdotV + sqrt(625e-4 + 9375e-4 * NdotV * NdotV));
float Gr = GrL * GrV;
/* Fresnel Function */
float Fr = mix(4e-2, 1.0, FH);
vec3 f_d = ((1.0/ PI) * mix(burley, subsurface, _Subsurface) * rho + sheen) * (1.0 - _Metallic);
vec3 f_s = D * G * F + 0.25 * _Clearcoat * Dr * Gr * Fr;
return f_d + f_s;
}
group_uniforms UV;
uniform vec2 _Tiling = vec2(1.0);
uniform vec2 _Offset = vec2(0.0);
vec2 tiling_and_offset(
in vec2 st,
in vec2 tiling,
in vec2 offset
)
{
return vec2(st.x * tiling.x + offset.x, st.y * tiling.y + offset.y);
}
vec3 rotate_vector(
in vec3 vector,
in vec3 axis,
in float angle
)
{
axis = normalize(axis);
vec3 n = axis * dot(axis, vector);
return n + cos(angle) * (vector - n) + sin(angle) * cross(axis, vector);
}
void fragment() {
vec2 st = tiling_and_offset(UV, _Tiling, _Offset);
// Orthonormal vectors.
vec3 normal = vec3(0,0,1); // n.
vec3 tangent = vec3(1,0,0); // x.
vec3 bitangent = vec3(0,1,0); // y.
float phi_h = _PhiH * (PI / 180.0);
float theta_h = st.x * (PI * 0.5);
vec3 h = vec3(sin(theta_h) * cos(phi_h), sin(theta_h) * sin(phi_h), cos(theta_h));
float phi_d = _PhiD * (PI / 180.0);
float theta_d = (1.0 - st.y) * (PI * 0.5);
float sin_theta_d = sin(theta_d);
float cos_theta_d = cos(theta_d);
float sin_phi_d = sin(phi_d);
float cos_phi_d = cos(phi_d);
vec3 d = vec3(sin_theta_d * cos_phi_d, sin_theta_d * sin_phi_d, cos_theta_d);
vec3 light = rotate_vector(rotate_vector(d, bitangent, theta_h), normal, phi_h);
vec3 view = reflect(-light, h);
vec3 b = BRDF(normal, light, view, tangent, bitangent);
b *= _Brightness;
b *= pow(2.0, _Exposure);
b = pow( b, vec3( 1.0 / _Gamma ) );
COLOR = vec4(clamp(b, 0.0, 1.0), 1.0);
}


