I've been writing some custom shaders based around Unity's Standard BRDF, which is uses GGX distribution term and Smith visibility term. It looks great at either end of the smoothness scale (1 and 2 in the pic below). However, with the smoothness set to about 50%, things start to get a little ugly (3). Increasing the metalness makes it worse (4), and strongly coloured specular gets worse still, as different colour channels blow out at different rates(5).
This seems to be caused by specular term returning >1 around the highlights, probably caused by Unity's hack of multiplying specular by pi (instead of dividing diffuse) as shown in the code snippet below.
// Specular term
// HACK: theoretically we should divide diffuseTerm by Pi and not multiply specularTerm!
// BUT 1) that will make shader look significantly darker than Legacy ones
// and 2) on engine side "Non-important" lights have to be divided by Pi too in cases when they are injected into ambient SH
half roughness = PerceptualRoughnessToRoughness(perceptualRoughness);
half V = SmithJointGGXVisibilityTerm (nl, nv, roughness);
half D = GGXTerm (nh, roughness);
half specularTerm = V*D * UNITY_PI; // Torrance-Sparrow model, Fresnel is applied later
HDR rendering helps a lot, but unfortunately our project is on mobile and using LDR. I'm making a car paint shader with a clear coat, so the base coat will likely be semi-rough and semi-metallic in a lot of cases which seems to be a worst-case-scenario for this BRDF. Can anyone suggest a way to get saner specular highlights? Either some kind of rolloff for out-of-range specular values, or a different BRDF altogether?