Your preferred or desired BRDF?

Started by
50 comments, last by Hodgman 11 years, 1 month ago

After fixing the above and also changing my formula for converting specular power to the anisotropy values, here are better results of Ashikhmin-Shirley.

attachicon.gifAsh2.png

attachicon.gifAsh3.png

attachicon.gifAsh0.png

L. Spiro

The new highlights are much better smile.png

SlimDX | Ventspace Blog | Twitter | Diverse teams make better games. I am currently hiring capable C++ engine developers in Baltimore, MD.
Advertisement

If I had to pick one, it would be GGX. But for any non-trivial application you'll typically need more than one. At the very least you'll need dedicated skin and hair BRDF's to go with it, and you'll want anisotropy for a lot of materials as well.

Either way the BRDF itself isn't usually the tricky or expensive part for real-time graphics, it's...

A. Coming up with a good overall shading model and toolset that allows your artists to understand the parameters they're authoring and efficiently author many many materials using those parameters


B. Figuring out how to apply your BRDF to more than just point lights and directional lights

and

C. Making it not alias like crazy without ruining your BRDF

I've taken to Cook-Torrance with the GGX distribution and Smith geometry factor (thanks CryZe) for specular, and the qualitative version of Oren-Nayar for diffuse.

I was mucking about with this in the BRDF explorer, and the fresnel factor didn't seem to be behaving right; even at front-on angles (L==V) there would always be a highlight, even when Ks was 0. I replaced your exp(-6 * LdotH) with pow(1-LdotH, 5) and it seems more correct now.

To help me compare it with the other BRDF's that come with BRDF explorer, I also divided everything by PI, which I'm not sure is correct, but seemed to make it behave more like the other BRDF's, and I divided the final result by NdotL, so that I could let BRDF explorer multiply by NdotL itself.

http://pastebin.com/c36FtdX5


I've taken to Cook-Torrance with the GGX distribution and Smith geometry factor (thanks CryZe) for specular, and the qualitative version of Oren-Nayar for diffuse.

I was mucking about with this in the BRDF explorer, and the fresnel factor didn't seem to be behaving right; even at front-on angles (L==V) there would always be a highlight, even when Ks was 0. I replaced your exp(-6 * LdotH) with pow(1-LdotH, 5) and it seems more correct now.

To help me compare it with the other BRDF's that come with BRDF explorer, I also divided everything by PI, which I'm not sure is correct, but seemed to make it behave more like the other BRDF's, and I divided the final result by NdotL, so that I could let BRDF explorer multiply by NdotL itself.

http://pastebin.com/c36FtdX5

There's really not a lot of difference between the two fresnel approximations. I graphed the two side-by-side here:

[attachment=13873:fresnel.png]

The one that uses exp() essentially kicks in slightly sooner and is more gradual. For materials with an IOR value of ~1.4 (average dialectics) this seems to be slightly closer to the full fresnel equation, and I'm guessing it's not any more expensive to evaluate on modern GPUs.

As for PI and NdotL, I went ahead and rewrote the unoptimized version of the shader:


analytic

::begin parameters
color Diffuse 1 0 0
color Specular 1 1 1
float DiffuseScale 0 1 0.5
float SpecularScale 0 0.999 .028
float Roughness 0.005 2 0.2
::end parameters

::begin shader

vec3 BRDF( vec3 L, vec3 V, vec3 N, vec3 X, vec3 Y )
{
    float PI = 3.14159265358979323846;
    vec3 Kd = Diffuse * DiffuseScale;
    vec3 Ks = Specular * SpecularScale;

    vec3 H = normalize(L + V);
    float NdotL = clamp(dot(N, L), 0, 1);
    float NdotV = dot(N, V);
    float NdotH = dot(N, H);
    float LdotH = dot(L, H);

    float a_2 = Roughness * Roughness;
    float NdotL_2 = NdotL * NdotL;
    float NdotV_2 = NdotV * NdotV;
    float NdotH_2 = NdotH * NdotH;
    float OneMinusNdotL_2 = 1.0 - NdotL_2;
    float OneMinusNdotV_2 = 1.0 - NdotV_2;

    vec3 Fd = 1.0 - Ks;

    float gamma = clamp(dot(V - N * NdotV, L - N * NdotL), 0, 1);
    float A = 1.0 - 0.5 * (a_2 / (a_2 + 0.33));
    float B = 0.45 * (a_2 / (a_2 + 0.09));
    float C = sqrt(OneMinusNdotL_2 * OneMinusNdotV_2) / max(NdotL, NdotV);

    vec3 Rd = Kd / PI * Fd * (A + B * gamma * C);

    float D = a_2 / (PI * pow(NdotH_2 * (a_2 - 1.0) + 1.0, 2.0));

    vec3 Fs = Ks + Fd * exp(-6 * LdotH);

    float G1_1 = 2.0 / (1.0 + sqrt(1.0 + a_2 * (OneMinusNdotL_2 / NdotL_2)));
    float G1_2 = 2.0 / (1.0 + sqrt(1.0 + a_2 * (OneMinusNdotV_2 / NdotV_2)));
    float G = G1_1 * G1_2;

    vec3 Rs = (D * Fs * G) / (4 * NdotL * NdotV);

    return (Rd + Rs) * NdotL; //remove NdotL and let BRDF Explorer handle that
}

::end shader

You can see there is a factor of PI located in the calculation of Rd. Kd over PI is essentially the Lambert BRDF. The factor of PI is necessary for energy conservation. A factor of PI also shows up in the calculating of D. This is part of the normalization of the GGX distribution. When calculating Rs you see the familiar Cook-Torrance equation. Finally, Rd and Rs are summed and then multiplied by NdotL. This NdotL is not a part of either the specular or diffuse BRDFs, but the lighting equation. The version I posted before is identical to this, only I have removes terms that cancel out in order to get rid of unnecessary shader instructions. I also removed PI from both diffuse and specular BRDFs, since it's not really necessary for video games. The only affect it has is that your lights appear to be PI times brighter.

At least that is my current understanding. I'm still very new to the concepts behind lighting.

Edit: So I suppose it would make sense to remove the final NdotL, since this shader represents only the BRDF and not the final pixel color. Presumably BRDF Explorer is automatically multiplying the result by NdotL.

If you want to compare fresnel approximations you can use this:


analytic

::begin parameters
color Specular 1 1 1
float SpecularScale 0 0.999 .028
bool Schlick 0
::end parameters

::begin shader

vec3 Fresnel(float CosTheta, vec3 Ks)
{
    vec3 n2 = (1.0 + sqrt(Ks)) / (1.0 - sqrt(Ks));
    vec3 SinTheta = sqrt(1 - CosTheta * CosTheta);

    vec3 SinThetaT = SinTheta / n2;
    vec3 CosThetaT = sqrt(1 - SinThetaT * SinThetaT);

    vec3 n2CosThetaT = n2 * CosThetaT;
    vec3 n2CosTheta = n2 * CosTheta;

    vec3 RsSqrt = (CosTheta - n2CosThetaT) / (CosTheta + n2CosThetaT);
    vec3 Rs = RsSqrt * RsSqrt;

    vec3 RpSqrt = (n2CosTheta - CosThetaT) / (n2CosTheta + CosThetaT);
    vec3 Rp = RpSqrt * RpSqrt;

    return (Rs + Rp) / 2;
}

vec3 BRDF( vec3 L, vec3 V, vec3 N, vec3 X, vec3 Y )
{
    vec3 Ks = Specular * SpecularScale;

    float NdotV = dot(N, V);

    vec3 Full = Fresnel(NdotV, Ks);

    vec3 Fs;

    if(Schlick)
        Fs = Ks + (1 - Ks) * pow(1.0 - NdotV, 5);
    else
        Fs = Ks + (1 - Ks) * exp(-6 * NdotV);

    return abs(Full - Fs);
}

::end shader

Edit: So I suppose it would make sense to remove the final NdotL, since this shader represents only the BRDF and not the final pixel color. Presumably BRDF Explorer is automatically multiplying the result by NdotL.

Yep, the BRDF needs both the PI and the division by NDotL, while the implementation in a standard shader doesn't need those.

What's the computation for the X and Y parameters that BRDF Explorer uses for aniso distributions? Are they tangent/bitangent vectors?

SlimDX | Ventspace Blog | Twitter | Diverse teams make better games. I am currently hiring capable C++ engine developers in Baltimore, MD.

Yep, the BRDF needs both the PI and the division by NDotL, while the implementation in a standard shader doesn't need those.

Shaders do need NdotL. Division by PI should be precalculated and sent to the shader—IE the light values the shader receives should already have been divided by PI.

What's the computation for the X and Y parameters that BRDF Explorer uses for aniso distributions? Are they tangent/bitangent vectors?

Yes, but in screen space (in pixel shaders) they often resolve to the directions [1,0,0] and [0,1,0]. Remember, they depend on your eyes, not the orientation of the object.

L. Spiro

I restore Nintendo 64 video-game OST’s into HD! https://www.youtube.com/channel/UCCtX_wedtZ5BoyQBXEhnVZw/playlists?view=1&sort=lad&flow=grid

Yes, but in screen space (in pixel shaders) they often resolve to the directions [1,0,0] and [0,1,0]. Remember, they depend on your eyes, not the orientation of the object.

What's the computation for the X and Y parameters that BRDF Explorer uses for aniso distributions? Are they tangent/bitangent vectors?

I don't quite understand what you mean. Are you saying that the X and Y parameters are down to artistic choice, or that they actually mathematically resolve to those vectors?

SlimDX | Ventspace Blog | Twitter | Diverse teams make better games. I am currently hiring capable C++ engine developers in Baltimore, MD.

What's the computation for the X and Y parameters that BRDF Explorer uses for aniso distributions? Are they tangent/bitangent vectors?

Yeah, you can peek into their "shaderTemplates" files:


   vec3 normal = normalize( gl_TexCoord[0].xyz );
    vec3 tangent = normalize( cross( vec3(0,1,0), normal ) );
    vec3 bitangent = normalize( cross( normal, tangent ) );

These are then passed into your BRDF function as N, X and Y.

[edit]Oh man this new IPB text box is screwing up hardcore lately... It just deleted half my post that proceeded a code block, again...

Shaders do need NdotL

He was talking about dividing by NdotL.

BRDF explorer will multiply by NdotL outside of the BRDF, so if you've included NdotL inside your BRDF (as we usually do in games), then you need to divide by NdotL at the end to cancel it out.

This topic is closed to new replies.

Advertisement