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?

  • $\begingroup$ Can you not just replace the "Standard" shader a copy in which the hack is fixed? $\endgroup$
    – Dan Hulme
    Jul 21, 2017 at 10:35
  • $\begingroup$ 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. $\endgroup$
    – russ
    Jul 22, 2017 at 7:11

1 Answer 1


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.

  • $\begingroup$ 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/… $\endgroup$
    – Alan Wolfe
    Jul 22, 2017 at 0:34
  • $\begingroup$ 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. $\endgroup$
    – russ
    Jul 22, 2017 at 7:16

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.