|
|
|
|
|
|
// Required for SSS |
|
|
|
#define GBufferType0 float4 |
|
|
|
|
|
|
|
#define HAS_REFRACTION (defined(_REFRACTION_PLANE) || defined(_REFRACTION_SPHERE)) && (defined(_REFRACTION_SSRAY_PROXY) || defined(_REFRACTION_SSRAY_HIZ)) |
|
|
|
#define DEFAULT_SPECULAR_VALUE 0.04 |
|
|
|
|
|
|
|
// Needed for MATERIAL_FEATURE_MASK_FLAGS. |
|
|
|
#include "../../Lighting/LightLoop/LightLoop.cs.hlsl" |
|
|
|
|
|
|
|
|
|
|
# define IF_DEBUG(a) |
|
|
|
#endif |
|
|
|
|
|
|
|
// Vlayer config options: |
|
|
|
// 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 |
|
|
|
|
|
|
float metallic = HasFeatureFlag(surfaceData.materialFeatures, /*MATERIALFEATUREFLAGS_LIT_SPECULAR_COLOR |*/ MATERIALFEATUREFLAGS_STACK_LIT_SUBSURFACE_SCATTERING | MATERIALFEATUREFLAGS_STACK_LIT_TRANSMISSION) ? 0.0 : surfaceData.metallic; |
|
|
|
|
|
|
|
bsdfData.diffuseColor = ComputeDiffuseColor(surfaceData.baseColor, metallic); |
|
|
|
bsdfData.fresnel0 = ComputeFresnel0(surfaceData.baseColor, surfaceData.metallic, DEFAULT_SPECULAR_VALUE); |
|
|
|
bsdfData.fresnel0 = ComputeFresnel0(surfaceData.baseColor, surfaceData.metallic, IorToFresnel0(surfaceData.dielectricIor)); |
|
|
|
|
|
|
|
// Kind of obsolete without gbuffer, ie could use _MATERIAL_FEATURE* shader_features directly, but |
|
|
|
// if anything, makes the code more readable. |
|
|
|
|
|
|
float3 vLayerEnergyCoeff[NB_VLAYERS]; |
|
|
|
//float vLayerPerceptualRoughness[NB_VLAYERS]; |
|
|
|
|
|
|
|
// TODOENERGY: |
|
|
|
// For the vlayered case, fold compensation into FGD terms during ComputeAdding |
|
|
|
// (ie FGD becomes FGDinf) (but the approximation depends on f0, our FGD is scalar, |
|
|
|
// TODOENERGY: |
|
|
|
// For the vlayered case, fold compensation into FGD terms during ComputeAdding |
|
|
|
// (ie FGD becomes FGDinf) (but the approximation depends on f0, our FGD is scalar, |
|
|
|
// We will duplicate one entry to simplify the IBL loop (In general it's either that or |
|
|
|
// We will duplicate one entry to simplify the IBL loop (In general it's either that or |
|
|
|
// (All our loops for lobes are static so either way the compiler should unroll and remove |
|
|
|
// (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]; |
|
|
|
|
|
|
|
|
|
|
// EnergyCompensation: This term can no longer be used alone in our vlayered BSDF framework as it |
|
|
|
// was applied only one time indiscriminately at PostEvaluateBSDF( ) on the specular lighting which |
|
|
|
// would be wrong in our case, since the correction terms depend on the FGD of the lobe |
|
|
|
// compensation must happen at each FGD use in ComputeAdding. However, our framework is exactly |
|
|
|
// designed to handle that problem, in that if we calculate and apply proper energy coefficient |
|
|
|
// terms (should be calculated from FGDinf) and modulate each specular calculations with them, |
|
|
|
// this will actually do compensation. For now, since FGD fetches are done after ComputeAdding, |
|
|
|
// compensation must happen at each FGD use in ComputeAdding. However, our framework is exactly |
|
|
|
// designed to handle that problem, in that if we calculate and apply proper energy coefficient |
|
|
|
// terms (should be calculated from FGDinf) and modulate each specular calculations with them, |
|
|
|
// this will actually do compensation. For now, since FGD fetches are done after ComputeAdding, |
|
|
|
// we apply the factors on light samples, less correct, must be moved inside ComputeAdding. |
|
|
|
|
|
|
|
// TODO: |
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
// Case of the dielectric coating |
|
|
|
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 |
|
|
|
|
|
|
//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 |
|
|
|
} |
|
|
|
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. |
|
|
|
|
|
|
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); |
|
|
|
|
|
|
j21 = 1.0; |
|
|
|
|
|
|
|
// Case of the dielectric / conductor base |
|
|
|
} |
|
|
|
else |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
// Update energy |
|
|
|
R12 = F_Schlick(bsdfData.fresnel0, cti); |
|
|
|
|
|
|
_s_ri0 = (e_ri0 > 0.0) ? _s_ri0/e_ri0 : 0.0; |
|
|
|
|
|
|
|
// Store the coefficient and variance |
|
|
|
if(m_r0i > 0.0) |
|
|
|
if(m_r0i > 0.0) |
|
|
|
} |
|
|
|
else |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
preLightData.vLayerEnergyCoeff[i] = float3(0.0, 0.0, 0.0); |
|
|
|
//preLightData.vLayerPerceptualRoughness[i] = 0.0; |
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
//------------------------------------------------------------- |
|
|
|
// Post compute: |
|
|
|
// Post compute: |
|
|
|
//------------------------------------------------------------- |
|
|
|
// TODO: dual lobe feature option |
|
|
|
// |
|
|
|
|
|
|
// We need both bottom lobes perceptualRoughnessA and |
|
|
|
// 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 |
|
|
|
|
|
|
|
|
|
|
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 |
|
|
|
// 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); |
|
|
|
|
|
|
// a corrected anisotropy and scalar roughness for use with the IBL hack. |
|
|
|
// That hack adds complexity because IBLs can't use the T and B roughnesses but at the |
|
|
|
// same time we can't just update their scalar roughness because then it will only give them more |
|
|
|
// roughness in the anisotropic direction, which is incorrect and with the hack, will appear |
|
|
|
// roughness in the anisotropic direction, which is incorrect and with the hack, will appear |
|
|
|
// even more so. |
|
|
|
|
|
|
|
#ifdef VLAYERED_ANISOTROPY_SCALAR_ROUGHNESS |
|
|
|
|
|
|
// roughnesses, keep anisotropy the same. Whether we're being called per light or |
|
|
|
// not makes little difference on calculations so everything is made generic, |
|
|
|
// roughnesses, keep anisotropy the same. Whether we're being called per light or |
|
|
|
// not makes little difference on calculations so everything is made generic, |
|
|
|
// but if we have the perLightOption enabled, we don't finalize calculations for |
|
|
|
// analytical lights. |
|
|
|
// -------------------------------------------------------------------------------- |
|
|
|
|
|
|
preLightData.iblPerceptualRoughness[BASE_LOBEB_IDX] = LinearVarianceToPerceptualRoughness(_s_r0m); |
|
|
|
|
|
|
|
#ifdef VLAYERED_ANISOTROPY_SCALAR_ROUGHNESS_CORRECTANISO |
|
|
|
// Try to correct stretching that happens when original roughness was low, but not too much |
|
|
|
// Try to correct stretching that happens when original roughness was low, but not too much |
|
|
|
preLightData.iblAnisotropy[0] = GetModifiedAnisotropy(bsdfData.anisotropy, bsdfData.perceptualRoughnessA, |
|
|
|
PerceptualRoughnessToRoughness(bsdfData.perceptualRoughnessA), |
|
|
|
preLightData.iblAnisotropy[0] = GetModifiedAnisotropy(bsdfData.anisotropy, bsdfData.perceptualRoughnessA, |
|
|
|
PerceptualRoughnessToRoughness(bsdfData.perceptualRoughnessA), |
|
|
|
preLightData.iblAnisotropy[1] = GetModifiedAnisotropy(bsdfData.anisotropy, bsdfData.perceptualRoughnessB, |
|
|
|
PerceptualRoughnessToRoughness(bsdfData.perceptualRoughnessB), |
|
|
|
preLightData.iblAnisotropy[1] = GetModifiedAnisotropy(bsdfData.anisotropy, bsdfData.perceptualRoughnessB, |
|
|
|
PerceptualRoughnessToRoughness(bsdfData.perceptualRoughnessB), |
|
|
|
preLightData.iblPerceptualRoughness[BASE_LOBEB_IDX]); |
|
|
|
} |
|
|
|
#endif |
|
|
|
|
|
|
ConvertAnisotropyToClampRoughness(preLightData.iblPerceptualRoughness[BASE_LOBEA_IDX], preLightData.iblAnisotropy[0], |
|
|
|
ConvertAnisotropyToClampRoughness(preLightData.iblPerceptualRoughness[BASE_LOBEA_IDX], preLightData.iblAnisotropy[0], |
|
|
|
ConvertAnisotropyToClampRoughness(preLightData.iblPerceptualRoughness[BASE_LOBEB_IDX], preLightData.iblAnisotropy[1], |
|
|
|
ConvertAnisotropyToClampRoughness(preLightData.iblPerceptualRoughness[BASE_LOBEB_IDX], preLightData.iblAnisotropy[1], |
|
|
|
// Non scalar treatment of anisotropy to have the option to remove some anisotropy |
|
|
|
// Non scalar treatment of anisotropy to have the option to remove some anisotropy |
|
|
|
// -------------------------------------------------------------------------------- |
|
|
|
if( !calledPerLight && !haveAnisotropy) |
|
|
|
{ |
|
|
|
|
|
|
#ifdef VLAYERED_DIFFUSE_ENERGY_HACKED_TERM |
|
|
|
// TODO |
|
|
|
preLightData.diffuseEnergy = Ti0; |
|
|
|
// Not correct since these stats are still directional probably too much |
|
|
|
// removed, but with a non FGD term, could actually balance out (as using |
|
|
|
// 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); |
|
|
|
|
|
|
// coatRoughness, roughnessAT/AB/BT/BB (anisotropic, all are nonperceptual and *clamped*) |
|
|
|
// partLambdaV |
|
|
|
// |
|
|
|
// The later are done in ComputeAdding at GetPreLightData time only if we're not using |
|
|
|
// The later are done in ComputeAdding at GetPreLightData time only if we're not using |
|
|
|
// VLAYERED_RECOMPUTE_PERLIGHT. |
|
|
|
|
|
|
|
// TODO this can now be refactored instead of having mostly duped code down here, |
|
|
|
|
|
|
float3 iblN[TOTAL_NB_LOBES]; //ZERO_INITIALIZE(float3[TOTAL_NB_LOBES], iblN); |
|
|
|
float3 iblN[TOTAL_NB_LOBES]; //ZERO_INITIALIZE(float3[TOTAL_NB_LOBES], iblN); |
|
|
|
float3 iblR[TOTAL_NB_LOBES]; |
|
|
|
float specularReflectivity[TOTAL_NB_LOBES]; |
|
|
|
float diffuseFGD[BASE_NB_LOBES]; |
|
|
|
|
|
|
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]); |
|
|
|
#else |
|
|
|
// Store those for eval analytical lights since we're going to |
|
|
|
// 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; |
|
|
|
|
|
|
// NO VLAYERING: |
|
|
|
// -------------------------------------------------------------------- |
|
|
|
|
|
|
|
// See ConvertSurfaceDataToBSDFData : The later are already clamped if |
|
|
|
// See ConvertSurfaceDataToBSDFData : The later are already clamped if |
|
|
|
// refactoring (instead of BSDFdata A and B values, we should really |
|
|
|
// refactoring (instead of BSDFdata A and B values, we should really |
|
|
|
// permit array definitions in the shader include attributes TODOTODO) |
|
|
|
// no coat here: preLightData.layeredCoatRoughness = bsdfData.coatRoughness; |
|
|
|
preLightData.layeredRoughnessT[0] = bsdfData.roughnessAT; |
|
|
|
|
|
|
// TODO: Proper Fresnel |
|
|
|
float3 F = F_Schlick(bsdfData.fresnel0, LdotH); |
|
|
|
|
|
|
|
// TODO: with iridescence, will be optionally per light sample |
|
|
|
// TODO: with iridescence, will be optionally per light sample |
|
|
|
|
|
|
|
if( IsVLayeredEnabled(bsdfData) ) |
|
|
|
{ |
|
|
|
|
|
|
// -------------------------------------------------------------------- |
|
|
|
|
|
|
|
// Save top angles in case VLAYERED_USE_REFRACTED_ANGLES_FOR_BASE option is used |
|
|
|
// Save top angles in case VLAYERED_USE_REFRACTED_ANGLES_FOR_BASE option is used |
|
|
|
float topLdotH = LdotH; // == VdotH) |
|
|
|
float topNdotH = NdotH; |
|
|
|
float topNdotL = NdotL; |
|
|
|
|
|
|
|
|
|
|
// 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 |
|
|
|
// 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 |
|
|
|
// In the IBL case, we don't have a specific incoming light direction so the light |
|
|
|
// In the IBL case, we don't have a specific incoming light direction so the light |
|
|
|
// though we don't handle anisotropy correctly either anyway. |
|
|
|
// 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 |
|
|
|
|
|
|
//LdotH = saturate(invLenLV * LdotV + invLenLV); |
|
|
|
//NdotV = ClampNdotV(dot(N, V)); |
|
|
|
CalculateAngles(L, V, NdotL, dot(N, V), LdotV, invLenLV, NdotH, LdotH, NdotV); |
|
|
|
|
|
|
|
|
|
|
|
ComputeAdding(topLdotH, bsdfData, preLightData, true); |
|
|
|
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. |
|
|
|
|
|
|
// p9 eq(39): if we don't recompute per light, we just reuse the IBL energy terms as the fresnel terms |
|
|
|
// for our LdotH, too bad (similar to what we do with iridescence), along with the "wrongly" calculated energy. |
|
|
|
// Note that in any case, we should have used FGD terms (except for R12 at the start of the process) |
|
|
|
// Note that in any case, we should have used FGD terms (except for R12 at the start of the process) |
|
|
|
#ifdef VLAYERED_RECOMPUTE_PERLIGHT |
|
|
|
#ifdef VLAYERED_RECOMPUTE_PERLIGHT |
|
|
|
#ifdef VLAYERED_USE_REFRACTED_ANGLES_FOR_BASE |
|
|
|
// we just changed V, update those: |
|
|
|
preLightData.TdotV = dot(bsdfData.tangentWS, V); |
|
|
|
|
|
|
float 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], |
|
|
|
DV[BASE_LOBEA_IDX] = DV_SmithJointGGXAniso(TdotH, BdotH, NdotH, NdotV, TdotL, BdotL, NdotL, |
|
|
|
preLightData.layeredRoughnessT[0], preLightData.layeredRoughnessB[0], |
|
|
|
DV[BASE_LOBEB_IDX] = DV_SmithJointGGXAniso(TdotH, BdotH, NdotH, NdotV, TdotL, BdotL, NdotL, |
|
|
|
preLightData.layeredRoughnessT[1], preLightData.layeredRoughnessB[1], |
|
|
|
DV[BASE_LOBEB_IDX] = DV_SmithJointGGXAniso(TdotH, BdotH, NdotH, NdotV, TdotL, BdotL, NdotL, |
|
|
|
preLightData.layeredRoughnessT[1], preLightData.layeredRoughnessB[1], |
|
|
|
else |
|
|
|
else |
|
|
|
{ |
|
|
|
#ifdef VLAYERED_RECOMPUTE_PERLIGHT |
|
|
|
preLightData.partLambdaV[BASE_LOBEA_IDX] = GetSmithJointGGXPartLambdaV(NdotV, preLightData.layeredRoughnessT[0]); |
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
specularLighting = preLightData.vLayerEnergyCoeff[BOTTOM_VLAYER_IDX] |
|
|
|
* lerp(DV[BASE_LOBEA_IDX] * preLightData.energyCompensationFactor[BASE_LOBEA_IDX], |
|
|
|
DV[BASE_LOBEB_IDX] * preLightData.energyCompensationFactor[BASE_LOBEB_IDX], |
|
|
|
specularLighting = preLightData.vLayerEnergyCoeff[BOTTOM_VLAYER_IDX] |
|
|
|
* lerp(DV[BASE_LOBEA_IDX] * preLightData.energyCompensationFactor[BASE_LOBEA_IDX], |
|
|
|
DV[BASE_LOBEB_IDX] * preLightData.energyCompensationFactor[BASE_LOBEB_IDX], |
|
|
|
bsdfData.lobeMix); |
|
|
|
|
|
|
|
specularLighting += preLightData.vLayerEnergyCoeff[TOP_VLAYER_IDX] |
|
|
|
|
|
|
float3 H = (L + V) * invLenLV; |
|
|
|
float TdotH, TdotL, BdotH, BdotL; |
|
|
|
CalculateAnisoAngles(bsdfData, L, V, invLenLV, TdotH, TdotL, BdotH, BdotL); |
|
|
|
DV[0] = DV_SmithJointGGXAniso(TdotH, BdotH, NdotH, NdotV, TdotL, BdotL, NdotL, |
|
|
|
bsdfData.roughnessAT, bsdfData.roughnessAB, |
|
|
|
DV[0] = DV_SmithJointGGXAniso(TdotH, BdotH, NdotH, NdotV, TdotL, BdotL, NdotL, |
|
|
|
bsdfData.roughnessAT, bsdfData.roughnessAB, |
|
|
|
DV[1] = DV_SmithJointGGXAniso(TdotH, BdotH, NdotH, NdotV, TdotL, BdotL, NdotL, |
|
|
|
bsdfData.roughnessBT, bsdfData.roughnessBB, |
|
|
|
DV[1] = DV_SmithJointGGXAniso(TdotH, BdotH, NdotH, NdotV, TdotL, BdotL, NdotL, |
|
|
|
bsdfData.roughnessBT, bsdfData.roughnessBB, |
|
|
|
preLightData.partLambdaV[1]); |
|
|
|
} |
|
|
|
else |
|
|
|
|
|
|
} |
|
|
|
specularLighting = F * lerp(DV[0]*preLightData.energyCompensationFactor[BASE_LOBEA_IDX], |
|
|
|
DV[1]*preLightData.energyCompensationFactor[BASE_LOBEB_IDX], |
|
|
|
specularLighting = F * lerp(DV[0]*preLightData.energyCompensationFactor[BASE_LOBEA_IDX], |
|
|
|
DV[1]*preLightData.energyCompensationFactor[BASE_LOBEB_IDX], |
|
|
|
bsdfData.lobeMix); |
|
|
|
//...and energy compensation is applied at PostEvaluateBSDF when no vlayering. |
|
|
|
} |
|
|
|