Something we’ve noticed at work is that the cloth shading model doesn’t seem to add any more instructions but we can’t figure out why, what’s it doing, or what the advantage is to just using our own fresnel + albedo etc. Any clarity on how the cloth shading model works would be much appreciated - thanks!
The model looks quite a bit different than the standard BRDF shader. Here’s most of the code between the ShadingModels.ush and the BRDF.ush files:
FDirectLighting ClothBxDF( FGBufferData GBuffer, half3 N, half3 V, half3 L, float Falloff, float NoL, FAreaLight AreaLight, FShadowTerms Shadow )
{
const float3 FuzzColor = saturate(GBuffer.CustomData.rgb);
const float Cloth = saturate(GBuffer.CustomData.a);
BxDFContext Context;
Init( Context, N, V, L );
SphereMaxNoH( Context, AreaLight.SphereSinAlpha, true );
Context.NoV = saturate( abs( Context.NoV ) + 1e-5 );
float3 Spec1;
if( AreaLight.bIsRect )
Spec1 = RectGGXApproxLTC( GBuffer.Roughness, GBuffer.SpecularColor, N, V, AreaLight.Rect, AreaLight.Texture );
else
Spec1 = AreaLight.FalloffColor * (Falloff * NoL) * SpecularGGX( GBuffer.Roughness, GBuffer.SpecularColor, Context, NoL, AreaLight );
// Cloth - Asperity Scattering - Inverse Beckmann Layer
float D2 = D_InvGGX( Pow4( GBuffer.Roughness ), Context.NoH );
float Vis2 = Vis_Cloth( Context.NoV, NoL );
float3 F2 = F_Schlick( FuzzColor, Context.VoH );
float3 Spec2 = AreaLight.FalloffColor * (Falloff * NoL) * (D2 * Vis2) * F2;
FDirectLighting Lighting;
Lighting.Diffuse = AreaLight.FalloffColor * (Falloff * NoL) * Diffuse_Lambert( GBuffer.DiffuseColor );
Lighting.Specular = lerp( Spec1, Spec2, Cloth );
Lighting.Transmission = 0;
return Lighting;
}
float D_InvGGX( float a2, float NoH )
{
float A = 4;
float d = ( NoH - a2 * NoH ) * NoH + a2;
return rcp( PI * (1 + A*a2) ) * ( 1 + 4 * a2*a2 / ( d*d ) );
}
float Vis_Cloth( float NoV, float NoL )
{
return rcp( 4 * ( NoL + NoV - NoL * NoV ) );
}
// [Schlick 1994, "An Inexpensive BRDF Model for Physically-Based Rendering"]
float3 F_Schlick( float3 SpecularColor, float VoH )
{
float Fc = Pow5( 1 - VoH ); // 1 sub, 3 mul
//return Fc + (1 - Fc) * SpecularColor; // 1 add, 3 mad
// Anything less than 2% is physically impossible and is instead considered to be shadowing
return saturate( 50.0 * SpecularColor.g ) * Fc + (1 - Fc) * SpecularColor;
}
That’s great thank you! I managed to track down the Vis_Cloth() function when looking through usf files but not the rest of this. Suprisingly only adds 1 extra instruction to the shader it seems which is mainly what’s confusing me, like surely it’s not that cheap?
Unless you’re using 1000+ instruction materials, for every model, I wouldn’t worry too much about throwing in some instructions here and there.
@IronicParadox This isn’t for a personal project or anything so the stuff we’re doing has to be performant, and we’re using the cloth BRDF on nearly everything. I can’t say much about what we’re doing but making sure the shaders are optimised is definitely something we need to keep ontop off.
Understandable, but my point is that it will make an extremely trivial amount of difference in performance; using cloth BRDF vs the standard BRDF. It would be so trivial that you’d probably be measuring in microseconds. Your big choke points will be overdraw, draw calls, shadows and texture bandwidth/memory.
@IronicParadox you are quite right, we are measuring in microseconds for performance because there’s a lot going on so we have to do what we can where we can. Those other considerations you’ve mentioned are already being took into consideration, but to make things easier on the whole team to hit 60fps on the target platform we all have to do what where we can, which is why I’m actually surprised that the cloth BRDF only adds a single instruction. Which I’m guessing is because it’s replacing most of the standard shader and how it’s calculating its lambertian and specular impacts etc, instead of adding them on-top using fresnel calculations in the material editor (which is what I would normally do).