#define GBUFFER_LIT_ANISOTROPIC_UPPER_BOUND 12
#define CLEAR_COAT_IOR 1.5
#define CLEAR_COAT_IETA (1.0 / CLEAR_COAT_IOR)
#define CLEAR_COAT_IETA (1.0 / CLEAR_COAT_IOR) // IETA is the inverse eta which is the ratio of IOR of two interface
#define CLEAR_COAT_PERCEPTUAL_ROUGHNESS 0.031622
#define CLEAR_COAT_ROUGHNESS (CLEAR_COAT_PERCEPTUAL_ROUGHNESS * CLEAR_COAT_PERCEPTUAL_ROUGHNESS) // 0.001
#define CLEAR_COAT_ROUGHNESS 0.001
#define CLEAR_COAT_PERCEPTUAL_ROUGHNESS RoughnessToPerceptualRoughness(CLEAR_COAT_ROUGHNESS)
//-----------------------------------------------------------------------------
// Configuration
// Approx to deal with roughness appearance of base layer (should appear rougher)
float coatRoughnessScale = Sq(ieta);
float sigma = r oughnessToVariance(PerceptualRoughnessToRoughness(bsdfData.perceptualRoughness));
bsdfData.perceptualRoughness = RoughnessToPerceptualRoughness(v arianceToRoughness(sigma * coatRoughnessScale));
// fresnel0 is deduced from interface between air and material (a ssume to be 1.5 in Unity, or a metal).
float sigma = R oughnessToVariance(PerceptualRoughnessToRoughness(bsdfData.perceptualRoughness));
bsdfData.perceptualRoughness = RoughnessToPerceptualRoughness(V arianceToRoughness(sigma * coatRoughnessScale));
// Fresnel0 is deduced from interface between air and material (A ssume to be 1.5 in Unity, or a metal).
bsdfData.fresnel0 = Fresnel0ReajustFor 15(bsdfData.fresnel0);
bsdfData.fresnel0 = ConvertF0ForAirInterfaceToF0ForClearCoat 15(bsdfData.fresnel0);
}
void FillMaterialTransparencyData(float3 baseColor, float metallic, float ior, float3 transmittanceColor, float atDistance, float thickness, float transmittanceMask, inout BSDFData bsdfData)
bsdfData.enableSpecularColor = surfaceData.enableSpecularColor;
bsdfData.enableSubsurfaceScattering = surfaceData.enableSubsurfaceScattering;
bsdfData.enableTransmission = surfaceData.enableTransmission;
bsdfData.enableAnisotropy = surfaceData.enableAnisotropy;
bsdfData.enableAnisotropy = surfaceData.enableAnisotropy;
bsdfData.enableIridescence = surfaceData.enableIridescence;
bsdfData.enableClearCoat = surfaceData.enableClearCoat;
float ltcMagnitudeCoatFresnel;
// Refraction
float3 transmission RefractV; // refracted view vector after exiting the shape
float3 transmission PositionWS; // start of the refracted ray after exiting the shape
float3 transmission Transmittance; // transmittance due to absorption
float transmissionS SMipLevel; // mip level of the screen space gaussian pyramid for rough refraction
float3 transparent RefractV; // refracted view vector after exiting the shape
float3 transparent PositionWS; // start of the refracted ray after exiting the shape
float3 transparent Transmittance; // transmittance due to absorption
float transparent SMipLevel; // mip level of the screen space gaussian pyramid for rough refraction
};
PreLightData GetPreLightData(float3 V, PositionInputs posInput, BSDFData bsdfData)
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.iblPerceptualRoughness = bsdfData.perceptualRoughness;
// Clear coat need to modify roughness, V and NdotV for all the other precalculation below
if (bsdfData.enableClearCoat)
{
preLightData.coatPartLambdaV = GetSmithJointGGXPartLambdaV(NdotV, CLEAR_COAT_ROUGHNESS);
preLightData.ltcTransformCoat._m00_m02_m11_m20 = SAMPLE_TEXTURE2D_ARRAY_LOD(_LtcData, s_linear_clamp_sampler, uv, LTC_GGX_MATRIX_INDEX, 0);
ltcMagnitude = SAMPLE_TEXTURE2D_ARRAY_LOD(_LtcData, s_linear_clamp_sampler, uv, LTC_MULTI_GGX_FRESNEL_DISNEY_DIFFUSE_INDEX, 0).rgb;
ltcGGXFresnelMagnitudeDiff = ltcMagnitude.r; // The difference of magnitudes of GGX and Fresnel
ltcGGXFresnelMagnitude = ltcMagnitude.g;
preLightData.ltcMagnitudeCoatFresnel = (CLEAR_COAT_F0 * ltcGGXFresnelMagnitudeDiff + (float3) ltcGGXFresnelMagnitude) * bsdfData.coatMask;
ltcGGXFresnelMagnitudeDiff = ltcMagnitude.r; // The difference of magnitudes of GGX and Fresnel
ltcGGXFresnelMagnitude = ltcMagnitude.g;
preLightData.ltcMagnitudeCoatFresnel = (CLEAR_COAT_F0 * ltcGGXFresnelMagnitudeDiff + ltcGGXFresnelMagnitude) * bsdfData.coatMask;
preLightData.transmission RefractV = refraction.rayWS;
preLightData.transmission PositionWS = refraction.positionWS;
preLightData.transmission Transmittance = exp(-bsdfData.absorptionCoefficient * refraction.dist);
preLightData.transparent RefractV = refraction.rayWS;
preLightData.transparent PositionWS = refraction.positionWS;
preLightData.transparent Transmittance = exp(-bsdfData.absorptionCoefficient * refraction.dist);
preLightData.transmissionS SMipLevel = sqrt(preLightData.iblPerceptualRoughness) * uint(_GaussianPyramidColorMipSize.z);
preLightData.transparent SMipLevel = sqrt(preLightData.iblPerceptualRoughness) * uint(_GaussianPyramidColorMipSize.z);
#endif
return preLightData;
// BSDF share between directional light, punctual light and area light (reference)
//-----------------------------------------------------------------------------
// This function apply BSDF * cos (required to deal with clear coating)
// This function apply BSDF
void BSDF( float3 V, float3 L, float3 positionWS, PreLightData preLightData, BSDFData bsdfData,
out float3 diffuseLighting,
out float3 specularLighting)
// Note: The modification of the base roughness and fresnel0 by the clear coat is already handled in FillMaterialClearCoatData
// Scale diffuse for energy conservation (use NdotL as an approximation)
// Very coarse attempt at doing energy conservation for the diffuse layer based on NdotL. No science.
diffuseLighting *= F_Schlick(CLEAR_COAT_F0, NdotL);
}
}
// Simulate a sphere light with this hack
// Note that it is not correct with our pre-computation of PartLambdaV (mean if we disable the optimization we will not have the
// same result) but we don't care as it is a hack anyway
if (bsdfData.enableClearCoat)
{
bsdfData.coatRoughness = max(bsdfData.coatRoughness, lightData.minRoughness);
}
bsdfData.coatRoughness = max(bsdfData.coatRoughness, lightData.minRoughness);
bsdfData.roughnessT = max(bsdfData.roughnessT, lightData.minRoughness);
bsdfData.roughnessB = max(bsdfData.roughnessB, lightData.minRoughness);
// Evaluate the diffuse part
ltcValue = LTCEvaluate(P1, P2, B, preLightData.ltcTransformDiffuse);
ltcValue *= lightData.diffuseScale;
// We don't multiply by 'bsdfData.diffuseColor' here. It's done only once in PostEvaluateBSDF().
lighting.diffuse = preLightData.ltcMagnitudeDiffuse * ltcValue;
// The matrix multiplication should not generate any extra ALU on GCN.
// TODO: double evaluation is very inefficient! This is a temporary solution.
ltcValue = LTCEvaluate(P1, P2, B, mul(flipMatrix, k_identity3x3));
ltcValue *= lightData.diffuseScale;
// We use diffuse lighting for accumulation since it is going to be blurred during the SSS pass.
// We don't multiply by 'bsdfData.diffuseColor' here. It's done only once in PostEvaluateBSDF().
lighting.diffuse += bsdfData.transmittance * ltcValue;
ltcValue = LTCEvaluate(P1, P2, B, preLightData.ltcTransformSpecular);
ltcValue *= lightData.specularScale;
lighting.specular = preLightData.ltcMagnitudeFresnel * ltcValue;
// Evaluate the coat part
lighting.specular *= (1.0 - preLightData.ltcMagnitudeCoatFresnel);
ltcValue = LTCEvaluate(P1, P2, B, preLightData.ltcTransformCoat);
ltcValue *= lightData.specularScale;
lighting.diffuse *= lightData.color * lightData.diffuseScale;
lighting.specular *= lightData.color * lightData.specularScale;;
lighting.diffuse *= lightData.color;
lighting.specular *= lightData.color;
#endif // LIT_DISPLAY_REFERENCE_AREA
return lighting;
// Evaluate the diffuse part
// Polygon irradiance in the transformed configuration.
ltcValue = PolygonIrradiance(mul(lightVerts, preLightData.ltcTransformDiffuse));
ltcValue *= lightData.diffuseScale;
// We don't multiply by 'bsdfData.diffuseColor' here. It's done only once in PostEvaluateBSDF().
lighting.diffuse = preLightData.ltcMagnitudeDiffuse * ltcValue;
// Polygon irradiance in the transformed configuration.
// TODO: double evaluation is very inefficient! This is a temporary solution.
ltcValue = PolygonIrradiance(mul(lightVerts, ltcTransform));
ltcValue *= lightData.diffuseScale;
// We use diffuse lighting for accumulation since it is going to be blurred during the SSS pass.
// We don't multiply by 'bsdfData.diffuseColor' here. It's done only once in PostEvaluateBSDF().
lighting.diffuse += bsdfData.transmittance * ltcValue;
// Polygon irradiance in the transformed configuration.
ltcValue = PolygonIrradiance(mul(lightVerts, preLightData.ltcTransformSpecular));
ltcValue *= lightData.specularScale;
lighting.specular += preLightData.ltcMagnitudeFresnel * ltcValue;
// Evaluate the coat part
lighting.specular *= (1.0 - preLightData.ltcMagnitudeCoatFresnel);
ltcValue = PolygonIrradiance(mul(lightVerts, preLightData.ltcTransformCoat));
ltcValue *= lightData.specularScale;
lighting.diffuse *= lightData.color * lightData.diffuseScale;
lighting.specular *= lightData.color * lightData.specularScale;
lighting.diffuse *= lightData.color;
lighting.specular *= lightData.color;
#endif // LIT_DISPLAY_REFERENCE_AREA
return lighting;
// a. Get the corresponding color depending on the roughness from the gaussian pyramid of the color buffer
// b. Multiply by the transmittance for absorption (depends on the optical depth)
float3 refractedBackPointWS = EstimateRaycast(V, posInput, preLightData.transmissionPositionWS, preLightData.transmission RefractV);
float3 refractedBackPointWS = EstimateRaycast(V, posInput, preLightData.transparentPositionWS, preLightData.transparent RefractV);
// Calculate screen space coordinates of refracted point in back plane
float2 refractedBackPointNDC = ComputeNormalizedDeviceCoordinates(refractedBackPointWS, UNITY_MATRIX_VP);
}
// Map the roughness to the correct mip map level of the color pyramid
lighting.specularTransmitted = SAMPLE_TEXTURE2D_LOD(_GaussianPyramidColorTexture, s_trilinear_clamp_sampler, refractedBackPointNDC, preLightData.transmissionS SMipLevel).rgb;
lighting.specularTransmitted = SAMPLE_TEXTURE2D_LOD(_GaussianPyramidColorTexture, s_trilinear_clamp_sampler, refractedBackPointNDC, preLightData.transparent SMipLevel).rgb;
lighting.specularTransmitted *= preLightData.transmission Transmittance;
lighting.specularTransmitted *= preLightData.transparent Transmittance;
float weight = 1.0;
UpdateLightingHierarchyWeights(hierarchyWeight, weight); // Shouldn't be needed, but safer in case we decide to change hierarchy priority
if (GPUImageBasedLightingType == GPUIMAGEBASEDLIGHTINGTYPE_REFRACTION)
{
positionWS = preLightData.transmission PositionWS;
R = preLightData.transmission RefractV;
positionWS = preLightData.transparent PositionWS;
R = preLightData.transparent RefractV;
}
// In Unity the cubemaps are capture with the localToWorld transform of the component.
R = (positionWS + projectionDistance * R) - lightData.positionWS;
// Test again for clear code
if (bsdfData.enableClearCoat)
if (GPUImageBasedLightingType == GPUIMAGEBASEDLIGHTINGTYPE_REFLECTION && bsdfData.enableClearCoat)
{
dirLS = mul(coatR, worldToLocal);
projectionDistance = SphereRayIntersectSimple(positionLS, dirLS, sphereOuterDistance);
// TODO: add distance based roughness
// Test again for clear code
if (bsdfData.enableClearCoat)
if (GPUImageBasedLightingType == GPUIMAGEBASEDLIGHTINGTYPE_REFLECTION && bsdfData.enableClearCoat)
{
dirLS = mul(coatR, worldToLocal);
projectionDistance = BoxRayIntersectSimple(positionLS, dirLS, -boxOuterDistance, boxOuterDistance);
}
else
{
// No clear coat support with refraction
envLighting = (1.0 - F) * preLD.rgb * preLightData.transmissionTransmittance;
// Don't account for clear coat to save ALU
envLighting = (1.0 - F) * preLD.rgb * preLightData.transparentTransmittance;
}
#endif // LIT_DISPLAY_REFERENCE_IBL
if (GPUImageBasedLightingType == GPUIMAGEBASEDLIGHTINGTYPE_REFLECTION)
lighting.specularReflected = envLighting;
else
lighting.specularTransmitted = envLighting * preLightData.transmission Transmittance;
lighting.specularTransmitted = envLighting * preLightData.transparent Transmittance;
return lighting;
}