// GGX
float partLambdaV;
float energyCompensation;
float clampRoughnessT; // Clamped version of bsdfData.roughnessT for analytic light
float clampRoughnessB; // Clamped version of bsdfData.roughnessB for analytic light
// Clear coat
float ccIEta;
// IBL
float3 iblDirWS; // Dominant specular direction, used for IBL in EvaluateBSDF_Env()
float iblMipLevel ;
float iblPerceptualRoughness ;
// IBL clear coat
float3 coatIblDirWS;
// Area lights (17 VGPRs)
float areaPerceptualRoughness;
// TODO: 'orthoBasisViewNormal' is just a rotation around the normal and should thus be just 1x VGPR.
float3x3 orthoBasisViewNormal; // Right-handed view-dependent orthogonal basis around the normal (6x VGPRs)
float3x3 ltcTransformDiffuse; // Inverse transformation for Lambertian or Disney Diffuse (4x VGPRs)
float NdotV = saturate(dot(N, V));
preLightData.clampNdotV = NdotV; // Caution: The handling of edge cases where N is directed away from the screen is handled during Gbuffer/forward pass, so here do nothing
// 'preLightData.clampRoughnessT ' and 'preLightData.clampRoughnessB' are clamped, and are meant to be used with punctual and directional lights.
// 'preLightData.iblPerceptualRoughness' is not clamped, and is meant to be used for IBL.
// 'bsdfData.perceptualRoughness' is still use where it make sense (SSAO, disney diffuse)
// If IBL needs the roughness value for some reason, it can be computed with PerceptualRoughnessToRoughness
// Clear coat need to modify roughness, V and NdotV for all the other precalculation below
if (bsdfData.materialId == MATERIALID_LIT_CLEAR_COAT && HasMaterialFeatureFlag(MATERIALFEATUREFLAGS_LIT_CLEAR_COAT))
{
// Modify V for following calculation
preLightData.ccRefractV = RefractNoTIR(V, N, preLightData.ccIEta);
preLightData.ccRoughness = CLEAR_COAT_ROUGHNESS; // This can be modify in punctual light evaluation in case of minRoughness usage
preLightData.ccPartLambdaV = GetSmithJointGGXPartLambdaV(NdotV, CLEAR_COAT_ROUGHNESS);
preLightData.ccPartLambdaV = GetSmithJointGGXPartLambdaV(NdotV, CLEAR_COAT_ROUGHNESS); // This will not take into account the modification by minRoughness but we are ok with this
preLightData.ccRoughnessScale = Sq(preLightData.ccIEta) * (NdotV / dot(bsdfData.normalWS, preLightData.refractV));
float ccRoughnessScale = Sq(preLightData.ccIEta) * (NdotV / dot(bsdfData.normalWS, preLightData.ccRefractV));
// Modify roughness for base layer (so it is taken into account for precomputing of PartLambdaV).
// Note that roughnessT and roughnessB are only use with punctual light (not with IBL)
float sigmaT = roughnessToVariance(bsdfData.roughnessT);
preLightData.clampRoughnessT = varianceToRoughness(sigmaT * ccRoughnessScale);
float sigmaB = roughnessToVariance(bsdfData.roughnessB);
preLightData.clampRoughnessB = varianceToRoughness(sigmaB * ccRoughnessScale);
// Our anisotropic IBL approach is solely based on the value of perceptualRoughness, so for clear coat, just update this one
// Can't share with roughnessT/roughnessB as it depends on anisotropy value
float sigmaPR = roughnessToVariance(PerceptualRoughnessToRoughness(bsdfData.perceptualRoughness));
preLightData.iblPerceptualRoughness = RoughnessToPerceptualRoughness(varianceToRoughness(sigmaPR * ccRoughnessScale));
V = preLightData.ccRefractV;
NdotV = saturate(dot(N, V));
}
else
{
preLightData.clampRoughnessT = bsdfData.roughnessT;
preLightData.clampRoughnessB = bsdfData.roughnessB;
preLightData.iblPerceptualRoughness = bsdfData.perceptualRoughness;
// For clear coat and area light we reuse the same 'IBL' algorithm
preLightData.areaPerceptualRoughness = preLightData.iblPerceptualRoughness;
preLightData.clampRoughnessT = ClampRoughnessForAnalyticalLights(preLightData.clampRoughnessT);
preLightData.clampRoughnessB = ClampRoughnessForAnalyticalLights(preLightData.clampRoughnessB);
// Now code use preLightData version of roughnessT/roughnessB for precomputation
float3 iblN, iblR;
// We avoid divergent evaluation of the GGX, as that nearly doubles the cost.
float TdotV = dot(bsdfData.tangentWS, V);
float BdotV = dot(bsdfData.bitangentWS, V);
preLightData.partLambdaV = GetSmithJointGGXAnisoPartLambdaV(TdotV, BdotV, NdotV, bsdfData.roughnessT, bsdfData.r oughnessB);
preLightData.partLambdaV = GetSmithJointGGXAnisoPartLambdaV(TdotV, BdotV, NdotV, preLightData.clampRoughnessT, preLightData.clampR oughnessB);
// For GGX aniso and IBL we have done an empirical (eye balled) approximation compare to the reference.
// We use a single fetch, and we stretch the normal to use based on various criteria.
// Reduce stretching for (perceptualRoughness < 0.2).
float stretch = abs(bsdfData.anisotropy) * saturate(5 * bsdfData.perceptualRoughness);
float stretch = abs(bsdfData.anisotropy) * saturate(5 * preLightData.iblPerceptualRoughness);
// This is a ad-hoc tweak to better match reference of anisotropic GGX.
// TODO: We need a better hack.
// TODO for PR review: Check with Evgenii that it is ok to move this so GetPreIntegratedFGD take it into account and it stay with aniso group
preLightData.iblPerceptualRoughness *= saturate(1.2 - abs(bsdfData.anisotropy));
preLightData.partLambdaV = GetSmithJointGGXPartLambdaV(NdotV, bsdfData.roughnessT);
preLightData.partLambdaV = GetSmithJointGGXPartLambdaV(NdotV, preLightData.clampRoughnessT);
// IBL
float iblRoughness = PerceptualRoughnessToRoughness(preLightData.iblPerceptualRoughness);
// Corretion of reflected direction for better handling of rough material
preLightData.iblDirWS = GetSpecularDominantDir(N, iblR, iblRoughness, NdotV);
// Handle IBL + multiscattering
// IBL
GetPreIntegratedFGD(NdotV, bsdfData.perceptualRoughness, bsdfData.fresnel0, preLightData.specularFGD, preLightData.diffuseFGD, reflectivity);
if (bsdfData.materialId == MATERIALID_LIT_CLEAR_COAT && HasMaterialFeatureFlag(MATERIALFEATUREFLAGS_LIT_CLEAR_COAT))
{
// Update the roughness and the IBL miplevel
// Bottom layer is affected by upper layer BRDF, result can't be more sharp than input (it is to mimic what a path tracer will do)
float roughness = PerceptualRoughnessToRoughness(bsdfData.perceptualRoughness);
float shininess = Sq(preLightData.ieta) * (2.0 / Sq(roughness) - 2.0);
roughness = sqrt(2.0 / (shininess + 2.0));
preLightData.iblDirWS = GetSpecularDominantDir(N, iblR, roughness, NdotV);
preLightData.iblMipLevel = PerceptualRoughnessToMipmapLevel(RoughnessToPerceptualRoughness(roughness));
}
else
{
// Note: this is a ad-hoc tweak.
// TODO: we need a better hack.
float iblPerceptualRoughness = bsdfData.perceptualRoughness * saturate(1.2 - abs(bsdfData.anisotropy));
float iblRoughness = PerceptualRoughnessToRoughness(iblPerceptualRoughness);
preLightData.iblDirWS = GetSpecularDominantDir(N, iblR, iblRoughness, NdotV);
preLightData.iblMipLevel = PerceptualRoughnessToMipmapLevel(iblPerceptualRoughness);
}
GetPreIntegratedFGD(NdotV, preLightData.iblPerceptualRoughness, bsdfData.fresnel0, preLightData.specularFGD, preLightData.diffuseFGD, reflectivity);
#ifdef LIT_USE_GGX_ENERGY_COMPENSATION
// Ref: Practical multiple scattering compensation for microfacet models.
// Area light
// UVs for sampling the LUTs
float theta = FastACosPos(NdotV); // For Area light - UVs for sampling the LUTs
float2 uv = LTC_LUT_OFFSET + LTC_LUT_SCALE * float2(bsdfData.p erceptualRoughness, theta * INV_HALF_PI);
float2 uv = LTC_LUT_OFFSET + LTC_LUT_SCALE * float2(preLightData.areaP erceptualRoughness, theta * INV_HALF_PI);
// Note we load the matrix transpose (avoid to have to transpose it in shader)
#ifdef LIT_DIFFUSE_LAMBERT_BRDF
// to match the reference for rough metals, but further darkens dielectrics.
preLightData.ltcMagnitudeFresnel = bsdfData.fresnel0 * ltcGGXFresnelMagnitudeDiff + (float3)ltcGGXFresnelMagnitude;
// refraction (forward only)
// Empirical remap to try to match a bit the refractio probe blurring for the fallback
preLightData.transmissionSSMipLevel = sqrt(bsdfData.perceptualRoughness) * uint(_GaussianPyramidColorMipSize.z);
// Empirical remap to try to match a bit the refraction probe blurring for the fallback
// Use IblPerceptualRoughness so we can handle approx of clear coat.
preLightData.transmissionSSMipLevel = sqrt(preLightData.IblPerceptualRoughness) * uint(_GaussianPyramidColorMipSize.z);
#endif
return preLightData;
// Change the Fresnel term to account for transmission through Clear Coat
F = Sq(1.0 - F);
// Hope the compiler can move this outside of the loop (we don't do it in PrelightData as we must not modify bsdfData there).
// Modify roughness for base layer (so it is taken into account for precomputing of PartLambdaV).
// Note that roughnessT and roughnessB are only use with punctual light (not with IBL)
float sigmaT = roughnessToVariance(bsdfData.roughnessT);
bsdfData.roughnessT = varianceToRoughness(sigmaT * preLightData.ccRoughnessScale);
// Note: We update perceptualRoughness from roughnessT which is not correct as roughnessT is mean for analytic light and is clamped
// however when we use clear coat, we are ok with the fact the the base layer will not be able to be perfectly smooth
bsdfData.perceptualRoughness = PerceptualRoughnessToRoughness(bsdfData.roughnessT);
float sigmaB = roughnessToVariance(bsdfData.roughnessB);
bsdfData.roughnessB = varianceToRoughness(sigmaB * preLightData.ccRoughnessScale);
// Note: The modification of the base roughness by the clear coat is already handled in the GetPrelightData() call
// Change the Light and View direction to account for IOR change
// Update the half vector accordingly
// TODO: Do comparison between this correct version and the one from isotropic and see if there is any visual difference
DV = DV_SmithJointGGXAniso(TdotH, BdotH, NdotH, NdotV, TdotL, BdotL, NdotL,
bsdfData.roughnessT, bsdfData.r oughnessB, preLightData.partLambdaV);
preLightData.clampRoughnessT, preLightData.clampR oughnessB, preLightData.partLambdaV);
DV = DV_SmithJointGGX(NdotH, NdotL, NdotV, bsdfData.r oughnessT, preLightData.partLambdaV);
DV = DV_SmithJointGGX(NdotH, NdotL, NdotV, preLightData.clampR oughnessT, preLightData.partLambdaV);
}
specularLighting += F * DV * NdotL;
// same result) but we don't care as it is a hack anyway
if (bsdfData.materialId == MATERIALID_LIT_CLEAR_COAT && HasMaterialFeatureFlag(MATERIALFEATUREFLAGS_LIT_CLEAR_COAT))
{
bsdfData.roughnessT = max(bsdfData.ccRoughness, lightData.minRoughness);
preLightData.ccRoughness = max(bsdfData.ccRoughness, lightData.minRoughness);
bsdfData.roughnessT = max(bsdfData.r oughnessT, lightData.minRoughness);
bsdfData.roughnessB = max(bsdfData.r oughnessB, lightData.minRoughness);
preLightData.clampRoughnessT = max(preLightData.clampR oughnessT, lightData.minRoughness);
preLightData.clampRoughnessB = max(preLightData.clampR oughnessB, lightData.minRoughness);
BSDF(V, L, posInput.positionWS, preLightData, bsdfData, lighting.diffuse, lighting.specular);
envLighting = IntegrateSpecularGGXIBLRef(lightLoopContext, V, preLightData, lightData, bsdfData);
// TODO: Do refraction reference (is it even possible ?)
// TODO: handle clear coat
// #ifdef LIT_DIFFUSE_LAMBERT_BRDF
// TODO: factor this code in common, so other material authoring don't require to rewrite everything,
// TODO: test the strech from Tomasz
// float roughness = PerceptualRoughnessToRoughness(bsdfData.p erceptualRoughness);
// float roughness = PerceptualRoughnessToRoughness(preLightData.IblP erceptualRoughness);
// float shrunkRoughness = AnisotropicStrechAtGrazingAngle(roughness, roughness, NdotV);
// Guideline for reflection volume: In HDRenderPipeline we separate the projection volume (the proxy of the scene) from the influence volume (what pixel on the screen is affected)
float3 F = 1.0;
// Evaluate the Clear Coat component if needed and change the BSDF roughness to match Fresnel transmission
// Evaluate the Clear Coat component if needed
F = F_Schlick(preLightData.coatFresnel0, preLightData.coatNdotV);
F = F_Schlick(CLEAR_COAT_FRESNEL0, preLightData.clampNdotV) * bsdfData.coatMask;
envLighting += F * preLD.rgb * bsdfData.coatCoverage;
envLighting += F * preLD.rgb;
// Change the Fresnel term to account for transmission through Clear Coat and reflection on the base layer.
F = Sq(-F * bsdfData.coatCoverage + 1.0);
// Change the Fresnel term to account for transmission through Clear Coat
F = Sq(1.0 - F);
float roughness = PerceptualRoughnessToRoughness(bsdfData.perceptualRoughness);
float roughness = PerceptualRoughnessToRoughness(preLightData.IblPerceptualRoughness);
float4 preLD = SampleEnv(lightLoopContext, lightData.envIndex, R, preLightData.iblMipLevel);
envLighting += F * preLD.rgb;
F *= preLightData.specularFGD;
float iblMipLevel = PerceptualRoughnessToMipmapLevel(preLightData.iblPerceptualRoughness);
float4 preLD = SampleEnv(lightLoopContext, lightData.envIndex, R, iblMipLevel);
if (GPUImageBasedLightingType == GPUIMAGEBASEDLIGHTINGTYPE_REFLECTION)
envLighting += F * preLD.rgb;
else
// specular transmisted lighting is the remaining of the reflection (let's use this approx)
// With refraction, we don't care about the clear coat value, only about the Fresnel, thus why we use 'envLighting ='
envLighting = (1.0 - F) * preLD.rgb * preLightData.transmissionTransmittance;
#endif
#endif // LIT_DISPLAY_REFERENCE_IBL
lighting.specularReflected = envLighting * preLightData.specularFGD;
lighting.specularReflected = envLighting;
// specular transmisted lighting is the remaining of the reflection (let's use this approx)
lighting.specularTransmitted = (1.0 - preLightData.specularFGD) * envLighting * preLightData.transmissionTransmittance;
lighting.specularTransmitted = envLighting * preLightData.transmissionTransmittance;
return lighting;
}