# Dealing with blown-out highlights in PBR

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?

• Can you not just replace the "Standard" shader a copy in which the hack is fixed? – Dan Hulme Jul 21 '17 at 10:35
• It seems like they multiply the BRDF by pi to maintain backwards compatibility with existing shaders using Blinn-Phong and their existing spherical harmonics system. With a bit more research I'm not sure this is the problem, more as Stefan says below, highlights will realistically be much brighter than diffuse light, and LDR just doesn't have the bandwidth for it. Looking at GGX renders with various material values on Google images, it seems to be an issue that goes beyond unity. – russ Jul 22 '17 at 7:11

Using physically based BRDFs only makes sense if your entire pipeline is built for physical units - the extreme range of values can't be displayed properly without some form of tone mapping. You didn't include that part of the code but from the looks of it I'd say you're doing a simple clamp() followed by linear->sRGB conversion, which causes the bad highlights.

Since you're not ready to run your entire frame in HDR, you could apply something simple like a sigmoid operator to just the specular component. This may look a bit off next to other BRDFs but should make those highlights much more pleasant.

• Indeed. A simple operator to make HDR colors to LDR is a simplified reinhard operator which is this: colorOut = colorIn / (colorIn + 1.0). It's not the best, but it's easy. It makes it so the color will never quite reach 1.0 no matter how large a number it is. For more information, check this out: imdoingitwrong.wordpress.com/2010/08/19/… – Alan Wolfe Jul 22 '17 at 0:34
• Thanks for the suggestions guys. I guess what I need is something like a soft-knee compressor as used in audio processing - linear below a threshold and with a strong reinhard-like rolloff above it. Not sure what such a function looks like mathematically though. I'm gonna play around with it over the weekend and see what I can come up with. – russ Jul 22 '17 at 7:16