//pref
Ambient|float|0.0|0.1|2
Diffuse|float|0.0|1.3|2
Specular|float|0.0|0.3|2
DiffuseRough|float|0|0.5|1
SpecularRough|float|0.1|0.3|1
diffuseStyle|int|0|2|2
specularStyle|int|0|2|2
Mix and match diffuse (0=Lambert, 1=Minnaert, 2=Oren-Nayer) and specular (0=Blinn-Phong, 1=Ward, 2=Cook-Torrance) shading styles. Luca Vezzaro (2007) Public Domain|note
//frag
#version 120
varying vec3 vN, vV, vL;
varying vec4 vP, vClr;
uniform float Ambient, Diffuse, Specular, DiffuseRough, SpecularRough;
uniform vec4 ClipPlane;
uniform int specularStyle, diffuseStyle;
const float epsilon = 0.005;
#define PI 3.14159265

float lambertDiff (vec3 l, vec3 n) {
	return max(pow(max(dot( l, n), 0.0), DiffuseRough * 2.0), 0.0);
}

float minnaertDiff (vec3 l, vec3 n, vec3 v) {
	//http://lvezzaro.altervista.org/other-stuff/
	// Source: "The reciprocity principle in lunar photometry" by M. Minnaert
	float dotNV = max(dot(n, v), 0.0);
	// Isotropic roughness
	float sigma = DiffuseRough;
	// Empirical formulas for determining k and I from the roughness value.
	// The values are both 1 when sigma is 0 (lambertian surface).
	// k decreases with increasing roughness, but not by much as values close to zero result in an unrealistic
	// cel-shading like appearance.
	// I decreases too, but faster, since when close to zero it greatly influences shading (as long as k is large enough).
	float coeffK = 1.0 - 0.2*sigma;
	float coeffI = -sigma*sigma - sigma + 1.0;
	float normalLightPosDot = max(dot(n, l), 0.0);
    return pow(normalLightPosDot, coeffK) * pow(dotNV, max(0.0, coeffK - coeffI));
    // This is the formula proposed in: the "Pixel Shading Lab: Minnaert Shading" NVIDIA presentation. It's pretty useless as roughness gets higher:
    //diff = k * lightIntensity * lightRadiance3 * materialDiffuse3 * pow(normalLightPosDot, 1.0 - sigma) * pow(dotNV, max(epsilon, 1.0 - coeffK));
}

float orenNayarDiffuse(vec3 l, vec3 viewDirection, vec3 N, float roughness, float albedo) {
  float LdotV = dot(l, viewDirection);
  float NdotL = dot(l, N);
  float NdotV = dot(N, viewDirection);
  float s = LdotV - NdotL * NdotV;
  float t = mix(1.0, max(NdotL, NdotV), step(0.0, s));
  float sigma2 = roughness * roughness;
  float A = 1.0 + sigma2 * (albedo / (sigma2 + 0.13) + 0.5 / (sigma2 + 0.33));
  float B = 0.45 * sigma2 / (sigma2 + 0.09);
  return albedo * max(0.0, NdotL) * (A + B * s / t) / PI;
}

float blinnSpec (vec3 n, vec3 h) {
	float r = pow(SpecularRough, 4.0);
	return pow(max(0.0,dot(n,h)), 1.0/r);
}

float wardSpec (vec3 l, vec3 n, vec3 v) {
	// Source: "Programming Vertex, Geometry, and Pixel Shaders" by Engel et al.
	// Halfway vector
	vec3 h = normalize(l + v);
	float dotNV = max(dot(n, v), epsilon);
	float normalHalfwayDot = dot(h, n);
	float ms;
	float num;
	ms = max(SpecularRough * SpecularRough, epsilon);
	float slope = tan(acos(normalHalfwayDot));
	num = exp(- slope * slope / ms);
	float normalLightPosDot = max(dot(n, l), epsilon);
	float den = 4.0 * PI * ms * sqrt(normalLightPosDot * dotNV);
	return num / den;
}

float beckmannDistribution(float x, float roughness) {
  float NdotH = max(x, 0.0001);
  float cos2Alpha = NdotH * NdotH;
  float tan2Alpha = (cos2Alpha - 1.0) / cos2Alpha;
  float roughness2 = roughness * roughness;
  float denom = PI * roughness2 * cos2Alpha * cos2Alpha;
  return exp(tan2Alpha / roughness2) / denom;
}

//spec = cookSpec(l, n, v, h);
float cookSpec(vec3 l, vec3 n, vec3 v, vec3 h) {
  float roughness = SpecularRough;
  float fresnel = 0.6;
  float VdotN = max(dot(v, n), 0.0);
  float LdotN = max(dot(l, n), 0.0);
  //Half angle vector
  //Geometric term
  float NdotH = max(dot(n, h), 0.0);
  float VdotH = max(dot(v, h), 0.000001);
  float LdotH = max(dot(l, h), 0.000001);
  float G1 = (2.0 * NdotH * VdotN) / VdotH;
  float G2 = (2.0 * NdotH * LdotN) / LdotH;
  float G = min(1.0, min(G1, G2));
  //Distribution term
  float D = beckmannDistribution(NdotH, roughness);
  //Fresnel term
  float F = pow(1.0 - VdotN, fresnel);
  //Multiply terms and done
  return  G * F * D / max(PI * VdotN, 0.000001);
}

vec3 desaturate(vec3 color, float amount) {
    vec3 gray = vec3(dot(vec3(0.2126,0.7152,0.0722), color));
    return vec3(mix(color, gray, amount));
}

void main() {
	if ((ClipPlane[0] < 1.5) && (dot( ClipPlane, vP) > 0.0)) discard;
	vec3 l = normalize(vL);
	vec3 n = normalize(vN);
	vec3 v = normalize(vV);
	vec3 h = normalize(l+v);
	vec3 a = vClr.rgb;
	vec3 d = a *Diffuse;
	a *= Ambient;
	vec3 backcolor = desaturate(0.75 * a + 0.75 * d * abs(dot(l,n)), 0.5);
	float diff;
	if (diffuseStyle == 1)
		diff = minnaertDiff(l, n,v);
	else if (diffuseStyle == 2)
		diff = orenNayarDiffuse(l, v, n, DiffuseRough, Diffuse);
	else
		diff = lambertDiff(l,n);
	float spec;
	if (specularStyle == 1)
		spec = wardSpec(l, n, v) * Specular;
	else if (specularStyle == 2)
		spec = cookSpec(l, n, v, h) * pow(Specular, 0.3);
	else
		spec = blinnSpec(n,h) * Specular; //blinn
	float backface = step(0.00, n.z);
	gl_FragColor = vec4(mix(backcolor.rgb, a + d*diff + spec,  backface), 1.0);
}
//Public domain: acknowledgment is appreciated, but not required.
//  http://lvezzaro.altervista.org/other-stuff/
//Copyright 2015 Chris Dickinson and stackgl contributors, MIT License|note
//  https://github.com/stackgl/glslify/blob/master/LICENSE.md