Better Environment BRDF approximation?

Has anyone worked out a better Environment BRDF approximation for URP?

Currently it’s using the same formula from the legacy pipeline, which tends to wash out the scene, especially with rough surfaces which end up with a subtle sheen to them, due to the blurred reflection mip being applied too strongly.

In a separate project I’ve switched to a precomputed term stored in a LUT, similar to HDRP and this helped greatly with environment reflections.

There are several approximations from other engines, however none of these look correct in URP and seem to make the issue worse. I assume this is because they all use different BRDFs and roughness mappings. However I’m not sure how to map these to URP’s BRDF.

https://www.unrealengine.com/en-US/blog/physically-based-shading-on-mobile

The above approximations are fairly complex formulas, Unity’s approximation is very simple and I think the result is quite poor because of this:

brdfData.grazingTerm = saturate(smoothness + reflectivity);

// Computes the specular term for EnvironmentBRDF
half3 EnvironmentBRDFSpecular(BRDFData brdfData, half fresnelTerm)
{
    float surfaceReduction = 1.0 / (brdfData.roughness2 + 1.0);
    return surfaceReduction * lerp(brdfData.specular, brdfData.grazingTerm, fresnelTerm);
}

If anyone has worked out any improvements and wants to share them, that would be great!

Otherwise, perhaps this is something the URP team can look into to make URP’s rendering more in-line with other programs/engines.

PS I’m not using shader graph at all, so integrating custom lighting formulas into my project isn’t an issue.

4 Likes

I’ve created my own Amplify shader template that does image based lighting differently from what URP does. Part of it uses a LUT too like HDRP but it has a few other things like a different way to compute the reflection vector used to sample the environment, different specular occlusion term, etc. I based most of the image based lighting work on the Frostbite engine paper linked below and a few other references. Both links below have sample code you can look at.

Frostbite
Filament

All if not most of the functions I used to precompute the LUT are available in the core SRP package these days so maybe some of that work will make it to URP in the future.

2 Likes

I’ve taken the same approach in another project with a custom SRP. (Eg a lookup table, also computed from some functions in the Core RP)

However this is a mobile project and I was hoping to use an analytical approximation that fits the BRDF that URP uses, without requiring a lookup. However I’m not entirely sure how to derive it myself so I was wondering if anyone has done it already.

BlackOps2 approximation works best, and it’s pretty cheap.

1 Like

Thanks for that, I had a quick look at it a little while ago, but took another look and implemented it myself. Environment reflections aren’t as washed out now.

I’m not entirely sure if their glossiness mapping matches Unity’s smoothness response, but the results seem to have helped either way.

For anyone else interested, the code is below, taken from page 16 of: https://blog.selfshadow.com/publications/s2013-shading-course/lazarov/s2013_pbs_black_ops_2_notes.pdf

float3 EnvironmentBRDF(float g, float NoV, float3 rf0)
{
    float4 t = float4(1 / 0.96, 0.475, (0.0275 - 0.25 * 0.04) / 0.96, 0.25);
    t *= float4(g, g, g, g);
    t += float4(0, 0, (0.015 - 0.75 * 0.04) / 0.96, 0.75);
    float a0 = t.x * min(t.y, exp2(-9.28 * NoV)) + t.z;
    float a1 = t.w;
    return saturate(lerp(a0, a1, rf0));
}

You’ll have to use a custom shader and replace the “EnvironmentBRDFSpecular” call in the EnvironmentBRDF function.

Would be cool if the URP team can derive a similar approximation for the current gloss remapping.
The URP environment BRDF is pretty outdated (Was introduced in Unity 5), and makes URP less accurate with tools like Substance, creating workflow issues. (Eg back and forth tweaking of smoothness maps to get things looking shiny but not washed out)

2 Likes

Hi, I’ve tried replicating your changes and I don’t think it makes much of a difference. The biggest (and only) difference that you saw might have been just the removal of surfaceReduction multiplier from this func compared to original EnvironmentBRDF, which i don’t think is correct, as it pretty much just removes “roughness” as a parameter, making roughness 0 or 1 everywhere, and that is the only visual diff that you saw. As it stands this EnvironmentBRDF function on it’s own is very close to just the original code’s lerp(specular, grazingTerm, Pow4/*5?*/(1.0 - NoV)).
I might be completely wrong though.

----Edit-------
It might be a little bit better:
Original/New


Here’s my code, I’m modifying the built-in pipeline instead of URP, but it’s pretty much the same calculations.

float3 IndirectSpecularProcessing_Original(float3 rf0specColor, float rf90glossinessColor, float NoV)
{
    return FresnelLerp(rf0specColor, rf90glossinessColor, NoV);
}

float3 IndirectSpecularProcessing_New(float3 rf0specColor, float rf90glossinessColor, float NoV)
{
    float4 t = float4(1 / 0.96, 0.475, (0.0275 - 0.25 * 0.04) / 0.96, 0.25);
    t *= float4(rf90glossinessColor, rf90glossinessColor, rf90glossinessColor, rf90glossinessColor);
    t += float4(0, 0, (0.015 - 0.75 * 0.04) / 0.96, 0.75);
    float a0 = t.x * min(t.y, exp2(-9.28 * NoV)) + t.z;
    float a1 = t.w;
    return saturate(lerp(a0, a1, rf0specColor));
}

UnityStandardBRDF.cginc modified and passed to copies of DeferredShading.shader and DeferredReflections.shader, which are in turn set in project settings as deferred shaders. Don't forget to set it as the first include (#include "UnityStandardBRDFCustom.cginc"), or it won't override properly.
At end of BRDF1_Unity_PBS():

float3 color =   diffColor * (gi.diffuse + light.color * diffuseTerm)
                    + specularTerm * light.color * FresnelTerm(specColor, lh)
                    + /*FresnelLerp(specColor, grazingTerm, nv)*/IndirectSpecularProcessing_New(specColor, grazingTerm, nv) * surfaceReduction * gi.specular;

I’m curious though, how the new BRDF looks like now? in proper assets i mean. .

Bump! Did anyone ever figure out how to convert these calculations to URP?

2 Likes