|
|
|
|
|
|
// Vertically Layered BSDF : "vlayering" |
|
|
|
// |
|
|
|
|
|
|
|
//#define VLAYERED_RECOMPUTE_PERLIGHT // TODOTODO, and to test, make it a shader_features |
|
|
|
// probably too slow but just to check also the difference it makes |
|
|
|
// Vlayer config options: |
|
|
|
|
|
|
|
//#define VLAYERED_RECOMPUTE_PERLIGHT // TODO test more, make it a shader_features |
|
|
|
// probably too slow but just to check the difference it makes |
|
|
|
//#define VLAYERED_USE_REFRACTED_ANGLES_FOR_BASE |
|
|
|
//#define VLAYERED_DIFFUSE_ENERGY_HACKED_TERM |
|
|
|
//#define VLAYERED_IBL_DESTRETCH_COMPENSATION_FOR_ANISOTROPY |
|
|
|
|
|
|
|
|
|
|
|
// Automatic: |
|
|
|
|
|
|
|
|
|
|
// TODO: if dual lobe base |
|
|
|
//#define BASE_NB_LOBES 1 |
|
|
|
#define BASE_NB_LOBES 2 |
|
|
|
//#define TOTAL_NB_LOBES (BASE_NB_LOBES+COAT_NB_LOBES) |
|
|
|
#define TOTAL_NB_LOBES 3 // TODO, for now, this will do (fix compilation errors before static pruning on some platforms) |
|
|
|
#define TOTAL_NB_LOBES (BASE_NB_LOBES+COAT_NB_LOBES) |
|
|
|
|
|
|
|
|
|
|
|
// TODO CLEANUP and put in proper define above |
|
|
|
|
|
|
|
|
|
|
#define NB_VLAYERS 3 |
|
|
|
//#define NB_TRUE_INTERFACES 2 |
|
|
|
//#define INTERFACE_TOP_IDX 0 |
|
|
|
//#define INTERFACE_BASE_IDX 1 |
|
|
|
|
|
|
|
|
|
|
|
// TODOTODO |
|
|
|
#define VLAYERED_DIFFUSE_ENERGY_HACKED_TERM |
|
|
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
|
|
// Configuration |
|
|
|
|
|
|
float GetCoatEta(in BSDFData bsdfData) |
|
|
|
{ |
|
|
|
float eta = bsdfData.coatIor / 1.0; |
|
|
|
//float eta = 1.5 / 1.0; |
|
|
|
//ieta = 1.0 / eta; |
|
|
|
return eta; |
|
|
|
} |
|
|
|
|
|
|
// We have a coat top layer: change the base fresnel0 accordingdly: |
|
|
|
bsdfData.fresnel0 = ConvertF0ForAirInterfaceToF0ForNewTopIor(bsdfData.fresnel0, bsdfData.coatIor); |
|
|
|
|
|
|
|
// We just dont clamp the roughnesses for now, but after the ComputeAdding() which will use those: |
|
|
|
// Dont clamp the roughnesses for now, ComputeAdding() will use those directly: |
|
|
|
|
|
|
|
ConvertAnisotropyToRoughness(bsdfData.perceptualRoughnessA, bsdfData.anisotropy, bsdfData.roughnessAT, bsdfData.roughnessAB); |
|
|
|
ConvertAnisotropyToRoughness(bsdfData.perceptualRoughnessB, bsdfData.anisotropy, bsdfData.roughnessBT, bsdfData.roughnessBB); |
|
|
|
bsdfData.coatRoughness = PerceptualRoughnessToRoughness(bsdfData.coatPerceptualRoughness); |
|
|
|
|
|
|
{ |
|
|
|
// roughnessT and roughnessB are clamped, and are meant to be used with punctual and directional lights. |
|
|
|
// perceptualRoughness is not clamped, and is meant to be used for IBL. |
|
|
|
// TODO: add ui inputs, +tangent map for anisotropy; |
|
|
|
// TODO: add tangent map for anisotropy; |
|
|
|
ConvertAnisotropyToClampRoughness(bsdfData.perceptualRoughnessA, bsdfData.anisotropy, bsdfData.roughnessAT, bsdfData.roughnessAB); |
|
|
|
ConvertAnisotropyToClampRoughness(bsdfData.perceptualRoughnessB, bsdfData.anisotropy, bsdfData.roughnessBT, bsdfData.roughnessBB); |
|
|
|
} |
|
|
|
|
|
|
struct PreLightData |
|
|
|
{ |
|
|
|
float NdotV; // Could be negative due to normal mapping, use ClampNdotV() |
|
|
|
float TdotV; // Stored only when VLAYERED_RECOMPUTE_PERLIGHT |
|
|
|
float BdotV; |
|
|
|
|
|
|
|
// IBL: we calculate and prefetch the pre-integrated split sum data for |
|
|
|
// all needed lobes |
|
|
|
|
|
|
// For IBLs (and analytical lights if approximation is used) |
|
|
|
|
|
|
|
float3 vLayerEnergyCoeff[NB_VLAYERS]; |
|
|
|
float vLayerPerceptualRoughness[NB_VLAYERS]; |
|
|
|
//float vLayerPerceptualRoughness[NB_VLAYERS]; |
|
|
|
// We will duplicate one entry to simplify the IBL loop |
|
|
|
// (In general it's either that or we add branches (if lobe from bottom interface or |
|
|
|
// top inteface) in the loop and make sure the compiler [unroll] - should be automatic |
|
|
|
// on a static loop - as then the compiler will remove these known branches as it unrolls. |
|
|
|
// All our loops for lobes are static so either way it should unroll and remove either |
|
|
|
// duplicated storage or the branch.) |
|
|
|
//float energyCompensation[TOTAL_NB_LOBES]; |
|
|
|
|
|
|
|
// terms during ComputeAdding (ie FGD becomes FGDinf) (but the approximation depends on f0, |
|
|
|
// our FGD is scalar, not rgb, see GetEnergyCompensationFactor.) |
|
|
|
// terms during ComputeAdding (ie FGD becomes FGDinf) (but the approximation depends on |
|
|
|
// f0, our FGD is scalar, not rgb, see GetEnergyCompensationFactor.) |
|
|
|
// Same thing for the F0: |
|
|
|
// So we will compute float3 energy factors per lobe: |
|
|
|
float3 energyFactor[TOTAL_NB_LOBES]; |
|
|
|
// We will compute float3 energy factors per lobe. |
|
|
|
// We will duplicate one entry to simplify the IBL loop (In general it's either that or |
|
|
|
// we add branches (if lobe from bottom interface or top inteface) |
|
|
|
// (All our loops for lobes are static so either way the compiler should unroll and remove |
|
|
|
// either duplicated storage or the branch.) |
|
|
|
float3 energyCompensationFactor[TOTAL_NB_LOBES]; |
|
|
|
|
|
|
|
|
|
|
|
//See VLAYERED_DIFFUSE_ENERGY_HACKED_TERM |
|
|
|
float3 diffuseEnergy; |
|
|
|
|
|
|
out float ctt, |
|
|
|
out float3 R12, out float3 T12, out float3 R21, out float3 T21, |
|
|
|
out float s_r12, out float s_t12, out float j12, |
|
|
|
// |
|
|
|
//out float s_r12_lobeB, |
|
|
|
// |
|
|
|
if(i==0) { |
|
|
|
if( i == 0 ) |
|
|
|
{ |
|
|
|
// Update energy |
|
|
|
float R0, n12; |
|
|
|
|
|
|
|
|
|
|
// Update mean |
|
|
|
float sti = sqrt(1.0 - Sq(cti)); |
|
|
|
float stt = sti / n12; |
|
|
|
if(stt <= 1.0f) { |
|
|
|
if( stt <= 1.0f ) |
|
|
|
{ |
|
|
|
// Hack: as roughness -> 1, remove the effect of changing angle also note: we never track means per se |
|
|
|
// because of symmetry, we have no azimuth, and don't consider offspecular effect as well as never |
|
|
|
// outputting final downward lobes anyway. |
|
|
|
|
|
|
//http://www.wolframalpha.com/input/?i=f(alpha)+%3D+(1.0-alpha)*(sqrt(1.0-alpha)+%2B+alpha)+alpha+%3D+0+to+1 |
|
|
|
stt = scale*stt + (1.0-scale)*sti; |
|
|
|
ctt = sqrt(1.0 - stt*stt); |
|
|
|
} else { |
|
|
|
// TER, flip sign: directions either reflected or transmitted always leave |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
// TIR, flip sign: directions either reflected or transmitted always leave |
|
|
|
// the surface. So here we have ctt instead of cti, we reverse dir by flipping sign. |
|
|
|
// Not accounted for though check implications of ctt = -1.0 |
|
|
|
// TODO |
|
|
|
|
|
|
j21 = 1.0/j12; |
|
|
|
|
|
|
|
// Case of the media layer |
|
|
|
} else if(i ==1) { |
|
|
|
} |
|
|
|
else if(i == 1) |
|
|
|
{ |
|
|
|
// Update energy |
|
|
|
R12 = float3(0.0, 0.0, 0.0); |
|
|
|
T12 = exp(- bsdfData.coatThickness * bsdfData.coatExtinction / cti); |
|
|
|
|
|
|
j21 = 1.0; |
|
|
|
|
|
|
|
// Case of the dielectric / conductor base |
|
|
|
} else { |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
// Update energy |
|
|
|
R12 = F_Schlick(bsdfData.fresnel0, cti); |
|
|
|
T12 = 0.0; |
|
|
|
|
|
|
// Iterate over the layers |
|
|
|
for(int i = 0; i < NB_VLAYERS; ++i) |
|
|
|
{ |
|
|
|
|
|
|
|
// Variables for the adding step |
|
|
|
float3 R12, T12, R21, T21; |
|
|
|
s_r12=0.0; |
|
|
|
|
|
|
_s_ri0 = (e_ri0 > 0.0) ? _s_ri0/e_ri0 : 0.0; |
|
|
|
|
|
|
|
// Store the coefficient and variance |
|
|
|
if(m_r0i > 0.0) { |
|
|
|
// TODO: cleanup and check if unroll works, then need only top layer, can use an if |
|
|
|
// and the rest of the array is not needed |
|
|
|
preLightData.vLayerEnergyCoeff[i] = m_R0i; // TODO: don't forget to lerp with lobeMix for bottom coefficient |
|
|
|
preLightData.vLayerPerceptualRoughness[i] = LinearVarianceToPerceptualRoughness(_s_r0m); |
|
|
|
} else { |
|
|
|
if(m_r0i > 0.0) |
|
|
|
{ |
|
|
|
preLightData.vLayerEnergyCoeff[i] = m_R0i; |
|
|
|
//preLightData.vLayerPerceptualRoughness[i] = LinearVarianceToPerceptualRoughness(_s_r0m); |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
preLightData.vLayerPerceptualRoughness[i] = 0.0; |
|
|
|
//preLightData.vLayerPerceptualRoughness[i] = 0.0; |
|
|
|
} |
|
|
|
|
|
|
|
// Update energy |
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
//------------------------------------------------------------- |
|
|
|
// Post compute: TODO also VLAYERED_RECOMPUTE_PERLIGHT |
|
|
|
// Post compute: |
|
|
|
//------------------------------------------------------------- |
|
|
|
// TODO: dual lobe feature option |
|
|
|
// |
|
|
|
|
|
|
// We have 6 roughnesses to process; |
|
|
|
// TODO: We could probably optimize this a bit. |
|
|
|
// |
|
|
|
// We need both bottom lobes |
|
|
|
// perceptualRoughnessA and perceptualRoughnessB |
|
|
|
// for IBLs (because anisotropy will use a hack) |
|
|
|
// We need both bottom lobes perceptualRoughnessA and |
|
|
|
// perceptualRoughnessB for IBLs (because anisotropy will use a hack) |
|
|
|
// |
|
|
|
// Then we need anisotropic roughness updates again for the 2 |
|
|
|
// bottom lobes, for analytical lights. |
|
|
|
|
|
|
// but not vLayer*[1] - this is the media "layer") |
|
|
|
|
|
|
|
// Obviously coat roughness is given without ComputeAdding calculations (nothing on top) |
|
|
|
// |
|
|
|
//preLightData.iblPerceptualRoughness[COAT_LOBE_IDX] = preLightData.vLayerPerceptualRoughness[TOP_VLAYER_IDX]; |
|
|
|
// ( preLightData.iblPerceptualRoughness[COAT_LOBE_IDX] = preLightData.vLayerPerceptualRoughness[TOP_VLAYER_IDX]; ) |
|
|
|
|
|
|
|
#ifdef VLAYERED_RECOMPUTE_PERLIGHT |
|
|
|
bool perLightOption = true; |
|
|
|
|
|
|
bool haveAnisotropy = HasFeatureFlag(bsdfData.materialFeatures, MATERIALFEATUREFLAGS_STACK_LIT_ANISOTROPY); |
|
|
|
|
|
|
|
if( !calledPerLight ) |
|
|
|
{ |
|
|
|
// First, if we're not called per light, always (regardless of perLightOption) calculate |
|
|
|
// roughness for top lobe: no adding( ) modifications, just conversion + clamping for |
|
|
|
// analytical lights. For these we also don't need to recompute these, but only the Fresnel |
|
|
|
// or FGD term are necessary in ComputeAdding, see BSDF(). |
|
|
|
preLightData.iblPerceptualRoughness[COAT_LOBE_IDX] = bsdfData.coatPerceptualRoughness; |
|
|
|
preLightData.layeredCoatRoughness = ClampRoughnessForAnalyticalLights(bsdfData.coatRoughness); |
|
|
|
} |
|
|
|
|
|
|
|
// calledPerLight and all of the bools above are static time known. |
|
|
|
// What we have to calculate is a bit messy here. |
|
|
|
// Basically, If we're in a mode where we will compute the vlayer stats per analytical light, |
|
|
|
|
|
|
// Otherwise, depending on if we have anisotropy or not, we might still have to deal with |
|
|
|
// the T and B terms to have isotropic modulation by the above layer and re-infer back a |
|
|
|
// a corrected anisotropy and scalar roughness for use with the IBL hack. |
|
|
|
// That hack adds complexity because IBLs they can't use the T and B roughnesses but at the |
|
|
|
// That hack adds complexity because IBLs can't use the T and B roughnesses but at the |
|
|
|
// roughness in the anisotropic direction. |
|
|
|
// roughness in the anisotropic direction, which is incorrect and with the hack, will appear |
|
|
|
// even more so. |
|
|
|
|
|
|
|
if( !calledPerLight && !haveAnisotropy) |
|
|
|
{ |
|
|
|
|
|
|
//s_r12 = RoughnessToLinearVariance(PerceptualRoughnessToRoughness(bsdfData.perceptualRoughnessA)); |
|
|
|
s_r12 = RoughnessToLinearVariance(bsdfData.roughnessAT); |
|
|
|
_s_r0m = s_ti0 + j0i*(s_t0i + s_r12 + m_rr*(s_r12+s_ri0)); |
|
|
|
float tmpA = _s_r0m; |
|
|
|
float varianceLobeA = _s_r0m; |
|
|
|
float tmpB = _s_r0m; |
|
|
|
float varianceLobeB = _s_r0m; |
|
|
|
preLightData.iblPerceptualRoughness[BASE_LOBEB_IDX] = LinearVarianceToPerceptualRoughness(_s_r0m); |
|
|
|
|
|
|
|
if( !perLightOption ) |
|
|
|
|
|
|
preLightData.layeredRoughnessT[0] = ClampRoughnessForAnalyticalLights(LinearVarianceToRoughness(tmpA)); |
|
|
|
preLightData.layeredRoughnessT[1] = ClampRoughnessForAnalyticalLights(LinearVarianceToRoughness(tmpB)); |
|
|
|
preLightData.layeredRoughnessT[0] = ClampRoughnessForAnalyticalLights(LinearVarianceToRoughness(varianceLobeA)); |
|
|
|
preLightData.layeredRoughnessT[1] = ClampRoughnessForAnalyticalLights(LinearVarianceToRoughness(varianceLobeB)); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
_s_r0m = s_ti0 + j0i*(s_t0i + s_r12 + m_rr*(s_r12+s_ri0)); |
|
|
|
float roughnessB = LinearVarianceToRoughness(_s_r0m); |
|
|
|
|
|
|
|
//ConvertRoughnessToAnisotropy(roughnessT, roughnessB, preLightData.iblAnisotropy[0]); |
|
|
|
#ifdef VLAYERED_IBL_DESTRETCH_COMPENSATION_FOR_ANISOTROPY |
|
|
|
ConvertRoughnessToAnisotropy(roughnessT, roughnessB, preLightData.iblAnisotropy[0]); |
|
|
|
#else |
|
|
|
#endif |
|
|
|
|
|
|
|
// We're not going to get called again per analytical light so store the result needed and used by them: |
|
|
|
// LOBEA T and B part: |
|
|
|
preLightData.layeredRoughnessT[0] = ClampRoughnessForAnalyticalLights(roughnessT); |
|
|
|
|
|
|
// We do the same for LOBEB: |
|
|
|
// ------------------------- |
|
|
|
|
|
|
|
// LOBEB roughness for analytical lights (T part) |
|
|
|
s_r12 = RoughnessToLinearVariance(bsdfData.roughnessBT); |
|
|
|
|
|
|
_s_r0m = s_ti0 + j0i*(s_t0i + s_r12 + m_rr*(s_r12+s_ri0)); |
|
|
|
roughnessB = LinearVarianceToRoughness(_s_r0m); |
|
|
|
|
|
|
|
// ConvertRoughnessToAnisotropy(roughnessT, roughnessB, preLightData.iblAnisotropy[1]); |
|
|
|
#ifdef VLAYERED_IBL_DESTRETCH_COMPENSATION_FOR_ANISOTROPY |
|
|
|
ConvertRoughnessToAnisotropy(roughnessT, roughnessB, preLightData.iblAnisotropy[1]); |
|
|
|
#else |
|
|
|
preLightData.iblPerceptualRoughness[BASE_LOBEB_IDX] = RoughnessToPerceptualRoughness((roughnessT + roughnessB)/2.0); |
|
|
|
#endif |
|
|
|
preLightData.iblPerceptualRoughness[BASE_LOBEA_IDX] = RoughnessToPerceptualRoughness((roughnessT + roughnessB)/2.0); |
|
|
|
// LOBEB T and B part: |
|
|
|
// LOBEA T and B part: |
|
|
|
} |
|
|
|
} // if( !calledPerLight && haveAnisotropy) |
|
|
|
|
|
|
|
if( calledPerLight ) |
|
|
|
{ |
|
|
|
|
|
|
_s_r0m = s_ti0 + j0i*(s_t0i + s_r12 + m_rr*(s_r12+s_ri0)); |
|
|
|
preLightData.layeredRoughnessT[0] = ClampRoughnessForAnalyticalLights(LinearVarianceToRoughness(_s_r0m)); |
|
|
|
|
|
|
|
// LOBEB roughness for analytical (T part) |
|
|
|
// LOBEB roughness for analytical lights (T part) |
|
|
|
s_r12 = RoughnessToLinearVariance(bsdfData.roughnessBT); |
|
|
|
_s_r0m = s_ti0 + j0i*(s_t0i + s_r12 + m_rr*(s_r12+s_ri0)); |
|
|
|
preLightData.layeredRoughnessT[1] = ClampRoughnessForAnalyticalLights(LinearVarianceToRoughness(_s_r0m)); |
|
|
|
|
|
|
// LOBEA roughness for analytical (B part) |
|
|
|
// LOBEA roughness for analytical lights (B part) |
|
|
|
// LOBEB roughness for analytical (B part) |
|
|
|
// LOBEB roughness for analytical lights (B part) |
|
|
|
s_r12 = RoughnessToLinearVariance(bsdfData.roughnessBB); |
|
|
|
_s_r0m = s_ti0 + j0i*(s_t0i + s_r12 + m_rr*(s_r12+s_ri0)); |
|
|
|
preLightData.layeredRoughnessB[1] = ClampRoughnessForAnalyticalLights(LinearVarianceToRoughness(_s_r0m)); |
|
|
|
|
|
|
#ifdef VLAYERED_DIFFUSE_ENERGY_HACKED_TERM |
|
|
|
// TODO |
|
|
|
// Obviously not correct since this is directional |
|
|
|
// probably too much removed, but with a non FGD term, could |
|
|
|
// actually balance out (as using FGD would lower this) |
|
|
|
// diffuseEnergy = Max3( Ti0.r, Ti0.g, Ti0.b); |
|
|
|
// Not correct since these stats are still directional probably too much |
|
|
|
// removed, but with a non FGD term, could actually balance out (as using |
|
|
|
// FGD would lower this) |
|
|
|
#else |
|
|
|
preLightData.diffuseEnergy = float3(1.0, 1.0, 1.0); |
|
|
|
#endif |
|
|
|
|
|
|
preLightData.NdotV = dot(N, V); |
|
|
|
float NdotV = ClampNdotV(preLightData.NdotV); |
|
|
|
|
|
|
|
preLightData.diffuseEnergy = float3(1.0, 1.0, 1.0); |
|
|
|
|
|
|
|
// iblPerceptualRoughness for FGD, mip, etc |
|
|
|
// iblR (fetch direction compensated dominant spec) |
|
|
|
// specularFGD (coatIblF is now in there too) |
|
|
|
// energyCompensation (for all light, apply everytime since with layering it becomes |
|
|
|
// lobe specific) |
|
|
|
// iblPerceptualRoughness (for FGD, mip, etc) |
|
|
|
// iblR (fetch direction compensated dominant spec) |
|
|
|
// specularFGD (coatIblF is now in there too) |
|
|
|
// energyCompensation (to a apply for each light sample since with layering it becomes interface specific) |
|
|
|
// |
|
|
|
// We also need for analytical lights: |
|
|
|
// |
|
|
|
|
|
|
// The later are done here only if we're not using VLAYERED_RECOMPUTE_PERLIGHT. |
|
|
|
// |
|
|
|
|
|
|
|
// The later are done in ComputeAdding at GetPreLightData time only if we're not using |
|
|
|
// VLAYERED_RECOMPUTE_PERLIGHT. |
|
|
|
// |
|
|
|
float3 iblN[TOTAL_NB_LOBES]; //ZERO_INITIALIZE(float3[TOTAL_NB_LOBES], iblN); |
|
|
|
float3 iblR[TOTAL_NB_LOBES]; //ZERO_INITIALIZE(float3[TOTAL_NB_LOBES], iblR); |
|
|
|
float3 iblN[TOTAL_NB_LOBES]; //ZERO_INITIALIZE(float3[TOTAL_NB_LOBES], iblN); |
|
|
|
float3 iblR[TOTAL_NB_LOBES]; |
|
|
|
// See the struct PreLightData, to simplify the IBL loop, we will recopy these |
|
|
|
//preLightData.fresnel0[BASE_LOBEA_IDX] = bsdfData.fresnel0; |
|
|
|
//preLightData.fresnel0[BASE_LOBEB_IDX] = bsdfData.fresnel0; |
|
|
|
|
|
|
|
//preLightData.fresnel0[COAT_LOBE_IDX] = IorToFresnel0(bsdfData.coatIor); |
|
|
|
preLightData.coatIeta = 1.0 / GetCoatEta(bsdfData); |
|
|
|
#ifdef _MATERIAL_FEATURE_COAT |
|
|
|
// -------------------------------------------------------------------- |
|
|
|
// VLAYERING: |
|
|
|
// -------------------------------------------------------------------- |
|
|
|
// Obviously coat roughness is given without ComputeAdding calculations (nothing on top) |
|
|
|
preLightData.iblPerceptualRoughness[COAT_LOBE_IDX] = bsdfData.coatPerceptualRoughness; |
|
|
|
preLightData.layeredCoatRoughness = ClampRoughnessForAnalyticalLights(bsdfData.coatRoughness); |
|
|
|
preLightData.coatIeta = 1.0 / GetCoatEta(bsdfData); |
|
|
|
|
|
|
|
// First thing we need is compute the energy coefficients and new roughnesses. |
|
|
|
// Even if configured to do it also per analytical light, we need it for IBLs too. |
|
|
|
|
|
|
float BdotV = dot(bsdfData.bitangentWS, V); |
|
|
|
|
|
|
|
#ifndef VLAYERED_RECOMPUTE_PERLIGHT |
|
|
|
// We can precalculate lambdaVs for all lights here since we're not doing ComputeAdding per light |
|
|
|
#else |
|
|
|
// Store those for eval analytical lights since we're going to |
|
|
|
// recalculate lambdaV after each ComputeAdding for each light |
|
|
|
preLightData.TdotV = TdotV; |
|
|
|
preLightData.BdotV = BdotV; |
|
|
|
#endif |
|
|
|
// 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. |
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
#ifndef VLAYERED_RECOMPUTE_PERLIGHT |
|
|
|
// We can precalculate lambdaVs for all lights here since we're not doing ComputeAdding per light |
|
|
|
preLightData.partLambdaV[COAT_LOBE_IDX] = GetSmithJointGGXPartLambdaV(NdotV, preLightData.layeredCoatRoughness); |
|
|
|
preLightData.partLambdaV[BASE_LOBEA_IDX] = GetSmithJointGGXPartLambdaV(NdotV, preLightData.layeredRoughnessT[0]); |
|
|
|
preLightData.partLambdaV[BASE_LOBEB_IDX] = GetSmithJointGGXPartLambdaV(NdotV, preLightData.layeredRoughnessT[1]); |
|
|
|
|
|
|
|
|
|
|
float specularReflectivityBase = lerp(specularReflectivity[BASE_LOBEA_IDX], specularReflectivity[BASE_LOBEB_IDX], bsdfData.lobeMix); |
|
|
|
|
|
|
|
//preLightData.energyCompensation[BASE_LOBEA_IDX] = CalculateEnergyCompensationFromSpecularReflectivity(specularReflectivityBase); |
|
|
|
//preLightData.energyCompensation[BASE_LOBEB_IDX] = preLightData.energyCompensation[BASE_LOBEA_IDX]; |
|
|
|
//preLightData.energyCompensation[COAT_LOBE_IDX] = CalculateEnergyCompensationFromSpecularReflectivity(specularReflectivity[COAT_LOBE_IDX]); |
|
|
|
|
|
|
|
preLightData.energyFactor[BASE_LOBEA_IDX] = GetEnergyCompensationFactor(specularReflectivityBase, bsdfData.fresnel0); |
|
|
|
preLightData.energyFactor[BASE_LOBEB_IDX] = GetEnergyCompensationFactor(specularReflectivityBase, bsdfData.fresnel0); |
|
|
|
preLightData.energyFactor[COAT_LOBE_IDX] = GetEnergyCompensationFactor(specularReflectivity[COAT_LOBE_IDX], IorToFresnel0(bsdfData.coatIor)); |
|
|
|
preLightData.energyCompensationFactor[BASE_LOBEA_IDX] = GetEnergyCompensationFactor(specularReflectivityBase, bsdfData.fresnel0); |
|
|
|
preLightData.energyCompensationFactor[BASE_LOBEB_IDX] = GetEnergyCompensationFactor(specularReflectivityBase, bsdfData.fresnel0); |
|
|
|
preLightData.energyCompensationFactor[COAT_LOBE_IDX] = GetEnergyCompensationFactor(specularReflectivity[COAT_LOBE_IDX], IorToFresnel0(bsdfData.coatIor)); |
|
|
|
//preLightData.energyCompensation[COAT_LOBE_IDX] = |
|
|
|
//preLightData.energyCompensation[BASE_LOBEA_IDX] = |
|
|
|
//preLightData.energyCompensation[BASE_LOBEB_IDX] = 0.0; |
|
|
|
preLightData.energyFactor[BASE_LOBEA_IDX] = |
|
|
|
preLightData.energyFactor[BASE_LOBEB_IDX] = |
|
|
|
preLightData.energyFactor[COAT_LOBE_IDX] = 1.0; |
|
|
|
preLightData.energyCompensationFactor[BASE_LOBEA_IDX] = |
|
|
|
preLightData.energyCompensationFactor[BASE_LOBEB_IDX] = |
|
|
|
preLightData.energyCompensationFactor[COAT_LOBE_IDX] = 1.0; |
|
|
|
#endif //ifdef _MATERIAL_FEATURE_COAT |
|
|
|
// -------------------------------------------------------------------- |
|
|
|
// -------------------------------------------------------------------- |
|
|
|
// To make BSDF( ) evaluation more generic, even if we're not vlayered, |
|
|
|
// we will use these: |
|
|
|
// See ConvertSurfaceDataToBSDFData : The later are already clamped if |
|
|
|
// vlayering is disabled, so could be used directly, but for later |
|
|
|
// refactoring (instead of BSDFdata A and B values, we should really |
|
|
|
// permit array definitions in the shader include attributes TODOTODO) |
|
|
|
preLightData.layeredRoughnessT[0] = bsdfData.roughnessAT; // the later are already clamped |
|
|
|
preLightData.layeredRoughnessT[0] = bsdfData.roughnessAT; |
|
|
|
preLightData.layeredRoughnessB[0] = bsdfData.roughnessAB; |
|
|
|
preLightData.layeredRoughnessT[1] = bsdfData.roughnessBT; |
|
|
|
preLightData.layeredRoughnessB[1] = bsdfData.roughnessBB; |
|
|
|
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
preLightData.partLambdaV[0] = GetSmithJointGGXPartLambdaV(NdotV, bsdfData.roughnessAT); |
|
|
|
preLightData.partLambdaV[1] = GetSmithJointGGXPartLambdaV(NdotV, bsdfData.roughnessBT); |
|
|
|
preLightData.partLambdaV[0] = GetSmithJointGGXPartLambdaV(NdotV, preLightData.layeredRoughnessT[0]); |
|
|
|
preLightData.partLambdaV[1] = GetSmithJointGGXPartLambdaV(NdotV, preLightData.layeredRoughnessT[1]); |
|
|
|
iblN[0] = iblN[1] = N; |
|
|
|
} // ...no anisotropy |
|
|
|
|
|
|
|
|
|
|
// BSDF share between directional light, punctual light and area light (reference) |
|
|
|
//----------------------------------------------------------------------------- |
|
|
|
|
|
|
|
void CalculateAnisoAngles(out float TdotH, out float TdotL, out float BdotH, out float BdotL) |
|
|
|
void CalculateAnisoAngles(BSDFData bsdfData, float3 L, float3 V, float invLenLV, out float TdotH, out float TdotL, out float BdotH, out float BdotL) |
|
|
|
{ |
|
|
|
float3 H = (L + V) * invLenLV; |
|
|
|
|
|
|
|
|
|
|
{ |
|
|
|
float3 N = bsdfData.normalWS; |
|
|
|
|
|
|
|
|
|
|
|
// Optimized math. Ref: PBR Diffuse Lighting for GGX + Smith Microsurfaces (slide 114). |
|
|
|
float LdotV = dot(L, V); |
|
|
|
float invLenLV = rsqrt(max(2.0 * LdotV + 2.0, FLT_EPS)); // invLenLV = rcp(length(L + V)), clamp to avoid rsqrt(0) = NaN |
|
|
|
|
|
|
// TODO: Proper Fresnel |
|
|
|
float3 F = F_Schlick(bsdfData.fresnel0, LdotH); |
|
|
|
|
|
|
|
// TODO: with iridescence, will be per light sample. |
|
|
|
// TODO: with iridescence, will be optionally per light sample |
|
|
|
#if 0 |
|
|
|
#if 0 |
|
|
|
// If we're going to do heavy calculations anyways, maybe we can even do that: |
|
|
|
// Use the proper angle at the bottom interface for BSDF calculations: |
|
|
|
// (tricky which one to choose, as we have the energy terms already, which are |
|
|
|
// a bit like FGD, while at the same time, we have multiple-scattering, but |
|
|
|
// if we have anisotropy, we know the later isn't accounted for in ComputeAdding. |
|
|
|
// In the IBL case, we don't have a specific incoming light direction but we |
|
|
|
// don't handle anisotropy correctly either anyway (ComputeAdding) in both case |
|
|
|
// we need to work around it. |
|
|
|
float3 H = (L + V) * invLenLV; |
|
|
|
L = CoatRefract(L, H, preLightData.coatIeta); // H stays the same |
|
|
|
// -------------------------------------------------------------------- |
|
|
|
// VLAYERING: |
|
|
|
// -------------------------------------------------------------------- |
|
|
|
#ifdef _MATERIAL_FEATURE_COAT |
|
|
|
|
|
|
|
float topLdotH = LdotH; // == VdotH) |
|
|
|
float topNdotH = NdotH; |
|
|
|
float topNdotL = NdotL; |
|
|
|
float topNdotV = NdotV; |
|
|
|
#if VLAYERED_USE_REFRACTED_ANGLES_FOR_BASE |
|
|
|
|
|
|
|
// Use the refracted angle at the bottom interface for BSDF calculations: |
|
|
|
// Seems like the more correct ones to use, but not obvious as we have the energy |
|
|
|
// coefficients already (vLayerEnergyCoeff), which are like FGD (but no deferred |
|
|
|
// FGD fetch to do here for analytical lights), so normally, we should use |
|
|
|
// an output lobe parametrization, but multiple-scattering is not accounted fully |
|
|
|
// byt ComputeAdding (by deriving azimuth dependent covariance operators). |
|
|
|
// In the IBL case, we don't have a specific incoming light direction so the light |
|
|
|
// representation matches more the correct context of a split sum approximation, |
|
|
|
// though we don't handle anisotropy correctly either anyway. |
|
|
|
// In both cases we need to work around it, so just to test: |
|
|
|
float3 H = (L + V) * invLenLV; |
|
|
|
// H stays the same so calculate it one time |
|
|
|
V = CoatRefract(V, H, preLightData.coatIeta); |
|
|
|
L = reflect(-V, H); |
|
|
|
LdotV = dot(L, V); |
|
|
|
invLenLV = rsqrt(max(2.0 * LdotV + 2.0, FLT_EPS)); // invLenLV = rcp(length(L + V)), clamp to avoid rsqrt(0) = NaN |
|
|
|
NdotH = saturate((NdotL + dot(N, V)) * invLenLV); // Do not clamp NdotV here |
|
|
|
LdotH = saturate(invLenLV * LdotV + invLenLV); |
|
|
|
NdotV = ClampNdotV(dot(N, V)); |
|
|
|
#ifndef VLAYERED_RECOMPUTE_PERLIGHT |
|
|
|
#ifdef VLAYERED_RECOMPUTE_PERLIGHT |
|
|
|
ComputeAdding(LdotH, bsdfData, preLightData, true); // Notice LdotH as interface angle for energy calculations and roughness modifications |
|
|
|
ComputeAdding(topLdotH, bsdfData, preLightData, true); |
|
|
|
// Notice topLdotH as interface angle, symmetric model parametrization (see sec. 6 and comments |
|
|
|
// on ComputeAdding) |
|
|
|
// layered*Roughness* and vLayerEnergyCoeff are now updated for the proper light direction. |
|
|
|
preLightData.partLambdaV[BASE_LOBEA_IDX] = GetSmithJointGGXAnisoPartLambdaV(TdotV, BdotV, NdotV, preLightData.layeredRoughnessT[0], preLightData.layeredRoughnessB[0]); |
|
|
|
preLightData.partLambdaV[BASE_LOBEB_IDX] = GetSmithJointGGXAnisoPartLambdaV(TdotV, BdotV, NdotV, preLightData.layeredRoughnessT[1], preLightData.layeredRoughnessB[1]); |
|
|
|
// for our LdotH, too bad, along with the "wrongly" calculated energy. |
|
|
|
// for our LdotH, too bad (similar to what we do with iridescence), along with the "wrongly" calculated energy. |
|
|
|
if (HasFeatureFlag(bsdfData.materialFeatures, MATERIALFEATUREFLAGS_LIT_ANISOTROPY)) |
|
|
|
if (HasFeatureFlag(bsdfData.materialFeatures, MATERIALFEATUREFLAGS_STACK_LIT_ANISOTROPY)) |
|
|
|
|
|
|
|
#ifdef VLAYERED_RECOMPUTE_PERLIGHT |
|
|
|
#ifdef VLAYERED_USE_REFRACTED_ANGLES_FOR_BASE |
|
|
|
// we just changed V, update those: |
|
|
|
preLightData.TdotV = dot(bsdfData.tangentWS, V); |
|
|
|
preLightData.BdotV = dot(bsdfData.bitangentWS, V); |
|
|
|
#endif |
|
|
|
preLightData.partLambdaV[BASE_LOBEA_IDX] = GetSmithJointGGXAnisoPartLambdaV(preLightData.TdotV, preLightData.BdotV, NdotV, |
|
|
|
preLightData.layeredRoughnessT[0], preLightData.layeredRoughnessB[0]); |
|
|
|
preLightData.partLambdaV[BASE_LOBEB_IDX] = GetSmithJointGGXAnisoPartLambdaV(preLightData.TdotV, preLightData.BdotV, NdotV, |
|
|
|
preLightData.layeredRoughnessT[1], preLightData.layeredRoughnessB[1]); |
|
|
|
#endif |
|
|
|
|
|
|
|
CalculateAnisoAngles(TdotH, TdotL, BdotH, BdotL); |
|
|
|
CalculateAnisoAngles(bsdfData, L, V, invLenLV, TdotH, TdotL, BdotH, BdotL); |
|
|
|
|
|
|
|
DV[BASE_LOBEA_IDX] = DV_SmithJointGGXAniso(TdotH, BdotH, NdotH, NdotV, TdotL, BdotL, NdotL, |
|
|
|
preLightData.layeredRoughnessT[0], preLightData.layeredRoughnessB[0], |
|
|
|
|
|
|
preLightData.layeredRoughnessT[1], preLightData.layeredRoughnessB[1], |
|
|
|
preLightData.partLambdaV[BASE_LOBEB_IDX]); |
|
|
|
|
|
|
|
DV[COAT_LOBE_IDX] = DV_SmithJointGGX(NdotH, NdotL, NdotV, preLightData.layeredCoatRoughness, preLightData.partLambdaV[COAT_LOBE_IDX]); |
|
|
|
DV[COAT_LOBE_IDX] = DV_SmithJointGGX(topNdotH, topNdotL, topNdotV, preLightData.layeredCoatRoughness, preLightData.partLambdaV[COAT_LOBE_IDX]); |
|
|
|
|
|
|
|
DV[COAT_LOBE_IDX] = DV_SmithJointGGX(NdotH, NdotL, NdotV, preLightData.layeredCoatRoughness, preLightData.partLambdaV[COAT_LOBE_IDX]); |
|
|
|
DV[BASE_LOBEA_IDX] = DV_SmithJointGGX(NdotH, NdotL, NdotV, preLightData.layeredRoughnessT[0], preLightData.partLambdaV[0]); |
|
|
|
DV[BASE_LOBEB_IDX] = DV_SmithJointGGX(NdotH, NdotL, NdotV, preLightData.layeredRoughnessT[1], preLightData.partLambdaV[1]); |
|
|
|
#ifdef VLAYERED_RECOMPUTE_PERLIGHT |
|
|
|
preLightData.partLambdaV[BASE_LOBEA_IDX] = GetSmithJointGGXPartLambdaV(NdotV, preLightData.layeredRoughnessT[0]); |
|
|
|
preLightData.partLambdaV[BASE_LOBEB_IDX] = GetSmithJointGGXPartLambdaV(NdotV, preLightData.layeredRoughnessT[1]); |
|
|
|
#endif |
|
|
|
DV[COAT_LOBE_IDX] = DV_SmithJointGGX(topNdotH, topNdotL, topNdotV, preLightData.layeredCoatRoughness, preLightData.partLambdaV[COAT_LOBE_IDX]); |
|
|
|
DV[BASE_LOBEA_IDX] = DV_SmithJointGGX(NdotH, NdotL, NdotV, preLightData.layeredRoughnessT[0], preLightData.partLambdaV[BASE_LOBEA_IDX]); |
|
|
|
DV[BASE_LOBEB_IDX] = DV_SmithJointGGX(NdotH, NdotL, NdotV, preLightData.layeredRoughnessT[1], preLightData.partLambdaV[BASE_LOBEB_IDX]); |
|
|
|
specularLighting = preLightData.vLayerEnergyCoeff[TOP_VLAYER_IDX] |
|
|
|
* preLightData.energyFactor[BASE_LOBEA_IDX] // either energyFactor index will do, same one. |
|
|
|
|
|
|
|
specularLighting = preLightData.vLayerEnergyCoeff[BOTTOM_VLAYER_IDX] |
|
|
|
* preLightData.energyCompensationFactor[BASE_LOBEA_IDX] // either energyCompensationFactor index will do, same one. |
|
|
|
specularLighting += preLightData.vLayerEnergyCoeff[BOTTOM_VLAYER_IDX] |
|
|
|
* preLightData.energyFactor[COAT_LOBE_IDX] |
|
|
|
specularLighting += preLightData.vLayerEnergyCoeff[TOP_VLAYER_IDX] |
|
|
|
* preLightData.energyCompensationFactor[COAT_LOBE_IDX] |
|
|
|
} |
|
|
|
|
|
|
|
#endif // ..._MATERIAL_FEATURE_COAT |
|
|
|
} // if( IsVLayeredEnabled(bsdfData) ) |
|
|
|
#endif |
|
|
|
// No vlayering : |
|
|
|
// ------------------------------------ |
|
|
|
if (HasFeatureFlag(bsdfData.materialFeatures, MATERIALFEATUREFLAGS_LIT_ANISOTROPY)) |
|
|
|
// -------------------------------------------------------------------- |
|
|
|
// NO VLAYERING: |
|
|
|
// -------------------------------------------------------------------- |
|
|
|
if (HasFeatureFlag(bsdfData.materialFeatures, MATERIALFEATUREFLAGS_STACK_LIT_ANISOTROPY)) |
|
|
|
CalculateAnisoAngles(TdotH, TdotL, BdotH, BdotL); |
|
|
|
CalculateAnisoAngles(bsdfData, L, V, invLenLV, TdotH, TdotL, BdotH, BdotL); |
|
|
|
bsdfData.roughnessAT, bsdfData.roughnessAB, preLightData.partLambdaV[0]); |
|
|
|
bsdfData.roughnessAT, bsdfData.roughnessAB, |
|
|
|
preLightData.partLambdaV[0]); |
|
|
|
bsdfData.roughnessBT, bsdfData.roughnessBB, preLightData.partLambdaV[1]); |
|
|
|
bsdfData.roughnessBT, bsdfData.roughnessBB, |
|
|
|
preLightData.partLambdaV[1]); |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
|
|
|
specularLighting = F * lerp(DV[0], DV[1], bsdfData.lobeMix); |
|
|
|
//...and energy compensation is applied at PostEvaluateBSDF when no vlayering. |
|
|
|
// TODO: config option + diffuse GGX |
|
|
|
float3 diffuseTerm = Lambert(); |
|
|
|
|
|
|
|
#ifdef VLAYERED_DIFFUSE_ENERGY_HACKED_TERM |
|
|
|
// TODO: config option + diffuse GGX |
|
|
|
float diffuseTerm = Lambert(); |
|
|
|
if( IsVLayeredEnabled(bsdfData) ) |
|
|
|
{ |
|
|
|
diffuseTerm *= preLightData.diffuseEnergy; |
|
|
|
} |
|
|
|
#endif |
|
|
|
|
|
|
|
// We don't multiply by 'bsdfData.diffuseColor' here. It's done only once in PostEvaluateBSDF(). |
|
|
|
diffuseLighting = diffuseTerm; |
|
|
|
|
|
|
{ |
|
|
|
IndirectLighting lighting; |
|
|
|
ZERO_INITIALIZE(IndirectLighting, lighting); |
|
|
|
|
|
|
|
//return lighting; |
|
|
|
// TODO: Refraction |
|
|
|
// There is no coat handling in Lit for refractions. |
|
|
|
// Here handle one lobe instead of all the others basically, or we could want it all. |
|
|
|
|
|
|
// Note that when we're not vlayered, we apply it not at each light sample but at the end, |
|
|
|
// at PostEvaluateBSDF. |
|
|
|
// Incorrect, but just for now: |
|
|
|
//L = ApplyEnergyCompensationToSpecularLighting(L, preLightData.fresnel0[i], preLightData.energyCompensation[i]); |
|
|
|
L *= preLightData.energyFactor[i]; |
|
|
|
L *= preLightData.energyCompensationFactor[i]; |
|
|
|
} |
|
|
|
envLighting += L; |
|
|
|
} |
|
|
|