// Continuation of LightEvaluation.hlsl. // use #define MATERIAL_INCLUDE_TRANSMISSION to include thick transmittance evaluation // use #define MATERIAL_INCLUDE_PRECOMPUTED_TRANSMISSION to apply pre-computed transmittance (or thin transmittance only) // use #define OVERRIDE_SHOULD_EVALUATE_THICK_OBJECT_TRANSMISSION to provide a new version of ShouldEvaluateThickObjectTransmission //----------------------------------------------------------------------------- // Directional and punctual lights (infinitesimal solid angle) //----------------------------------------------------------------------------- #ifndef OVERRIDE_SHOULD_EVALUATE_THICK_OBJECT_TRANSMISSION bool ShouldEvaluateThickObjectTransmission(float3 V, float3 L, PreLightData preLightData, BSDFData bsdfData, int shadowIndex) { #ifdef MATERIAL_INCLUDE_TRANSMISSION // Currently, we don't consider (NdotV < 0) as transmission. // TODO: ignore normal map? What about double sided-surfaces with one-sided normals? float NdotL = dot(bsdfData.normalWS, L); // If a material does not support transmission, it will never have this flag, and // the optimization pass of the compiler will remove all of the associated code. // However, this will take a lot more CPU time than doing the same thing using // the preprocessor. return HasFlag(bsdfData.materialFeatures, MATERIALFEATUREFLAGS_TRANSMISSION_MODE_THICK_OBJECT) && (shadowIndex >= 0.0) && (NdotL < 0.0); #else return false; #endif } #endif DirectLighting ShadeSurface_Infinitesimal(PreLightData preLightData, BSDFData bsdfData, float3 V, float3 L, float3 lightColor, float diffuseDimmer, float specularDimmer) { DirectLighting lighting; ZERO_INITIALIZE(DirectLighting, lighting); if (Max3(lightColor.r, lightColor.g, lightColor.b) > 0) { CBSDF cbsdf = EvaluateBSDF(V, L, preLightData, bsdfData); #if defined(MATERIAL_INCLUDE_TRANSMISSION) || defined(MATERIAL_INCLUDE_PRECOMPUTED_TRANSMISSION) float3 transmittance = bsdfData.transmittance; #else float3 transmittance = float3(0.0, 0.0, 0.0); #endif // If transmittance or the CBSDF's transmission components are known to be 0, // the optimization pass of the compiler will remove all of the associated code. // However, this will take a lot more CPU time than doing the same thing using // the preprocessor. lighting.diffuse = (cbsdf.diffR + cbsdf.diffT * transmittance) * lightColor * diffuseDimmer; lighting.specular = (cbsdf.specR + cbsdf.specT * transmittance) * lightColor * specularDimmer; } #ifdef DEBUG_DISPLAY if (_DebugLightingMode == DEBUGLIGHTINGMODE_LUX_METER) { // Only lighting, no BSDF. lighting.diffuse = lightColor * saturate(dot(bsdfData.normalWS, L)); } #endif return lighting; } //----------------------------------------------------------------------------- // Directional lights //----------------------------------------------------------------------------- DirectLighting ShadeSurface_Directional(LightLoopContext lightLoopContext, PositionInputs posInput, BuiltinData builtinData, PreLightData preLightData, DirectionalLightData light, BSDFData bsdfData, float3 V) { DirectLighting lighting; ZERO_INITIALIZE(DirectLighting, lighting); float3 L = -light.forward; // Is it worth evaluating the light? if ((light.lightDimmer > 0) && IsNonZeroBSDF(V, L, preLightData, bsdfData)) { float4 lightColor = EvaluateLight_Directional(lightLoopContext, posInput, light); lightColor.rgb *= lightColor.a; // Composite #ifdef MATERIAL_INCLUDE_TRANSMISSION if (ShouldEvaluateThickObjectTransmission(V, L, preLightData, bsdfData, light.shadowIndex)) { // Transmission through thick objects does not support shadowing // from directional lights. It will use the 'baked' transmittance value. lightColor *= _DirectionalTransmissionMultiplier; } else #endif { SHADOW_TYPE shadow = EvaluateShadow_Directional(lightLoopContext, posInput, light, builtinData, GetNormalForShadowBias(bsdfData)); float NdotL = dot(bsdfData.normalWS, L); // No microshadowing when facing away from light (use for thin transmission as well) shadow *= NdotL >= 0.0 ? ComputeMicroShadowing(GetAmbientOcclusionForMicroShadowing(bsdfData), NdotL, _MicroShadowOpacity) : 1.0; lightColor.rgb *= ComputeShadowColor(shadow, light.shadowTint, light.penumbraTint); } // Simulate a sphere/disk light with this hack. // Note that it is not correct with our precomputation of PartLambdaV // (means if we disable the optimization it will not have the // same result) but we don't care as it is a hack anyway. ClampRoughness(preLightData, bsdfData, light.minRoughness); lighting = ShadeSurface_Infinitesimal(preLightData, bsdfData, V, L, lightColor.rgb, light.diffuseDimmer, light.specularDimmer); } return lighting; } //----------------------------------------------------------------------------- // Punctual lights //----------------------------------------------------------------------------- #ifdef MATERIAL_INCLUDE_TRANSMISSION // Must be called after checking the results of ShouldEvaluateThickObjectTransmission(). float3 EvaluateTransmittance_Punctual(LightLoopContext lightLoopContext, PositionInputs posInput, BSDFData bsdfData, LightData light, float3 L, float4 distances) { // Using the shadow map, compute the distance from the light to the back face of the object. // TODO: SHADOW BIAS. float distBackFaceToLight = GetPunctualShadowClosestDistance(lightLoopContext.shadowContext, s_linear_clamp_sampler, posInput.positionWS, light.shadowIndex, L, light.positionRWS, light.lightType == GPULIGHTTYPE_POINT); // Our subsurface scattering models use the semi-infinite planar slab assumption. // Therefore, we need to find the thickness along the normal. // Note: based on the artist's input, dependence on the NdotL has been disabled. float distFrontFaceToLight = distances.x; float thicknessInUnits = (distFrontFaceToLight - distBackFaceToLight) /* * -NdotL */; float metersPerUnit = _WorldScalesAndFilterRadiiAndThicknessRemaps[bsdfData.diffusionProfileIndex].x; float thicknessInMeters = thicknessInUnits * metersPerUnit; float thicknessInMillimeters = thicknessInMeters * MILLIMETERS_PER_METER; // We need to make sure it's not less than the baked thickness to minimize light leaking. float dt = max(0, thicknessInMillimeters - bsdfData.thickness); float3 S = _ShapeParamsAndMaxScatterDists[bsdfData.diffusionProfileIndex].rgb; float3 exp_13 = exp2(((LOG2_E * (-1.0/3.0)) * dt) * S); // Exp[-S * dt / 3] // Approximate the decrease of transmittance by e^(-1/3 * dt * S). return bsdfData.transmittance * exp_13; } #endif DirectLighting ShadeSurface_Punctual(LightLoopContext lightLoopContext, PositionInputs posInput, BuiltinData builtinData, PreLightData preLightData, LightData light, BSDFData bsdfData, float3 V) { DirectLighting lighting; ZERO_INITIALIZE(DirectLighting, lighting); float3 L; float4 distances; // {d, d^2, 1/d, d_proj} GetPunctualLightVectors(posInput.positionWS, light, L, distances); // Is it worth evaluating the light? if ((light.lightDimmer > 0) && IsNonZeroBSDF(V, L, preLightData, bsdfData)) { float4 lightColor = EvaluateLight_Punctual(lightLoopContext, posInput, light, L, distances); lightColor.rgb *= lightColor.a; // Composite #ifdef MATERIAL_INCLUDE_TRANSMISSION if (ShouldEvaluateThickObjectTransmission(V, L, preLightData, bsdfData, light.shadowIndex)) { // Replace the 'baked' value using 'thickness from shadow'. bsdfData.transmittance = EvaluateTransmittance_Punctual(lightLoopContext, posInput, bsdfData, light, L, distances); } else #endif { // This code works for both surface reflection and thin object transmission. SHADOW_TYPE shadow = EvaluateShadow_Punctual(lightLoopContext, posInput, light, builtinData, GetNormalForShadowBias(bsdfData), L, distances); lightColor.rgb *= ComputeShadowColor(shadow, light.shadowTint, light.penumbraTint); #ifdef DEBUG_DISPLAY // The step with the attenuation is required to avoid seeing the screen tiles at the end of lights because the attenuation always falls to 0 before the tile ends. // Note: g_DebugShadowAttenuation have been setup in EvaluateShadow_Punctual if (_DebugShadowMapMode == SHADOWMAPDEBUGMODE_SINGLE_SHADOW && light.shadowIndex == _DebugSingleShadowIndex) g_DebugShadowAttenuation *= step(FLT_EPS, lightColor.a); #endif } // Simulate a sphere/disk light with this hack. // Note that it is not correct with our precomputation of PartLambdaV // (means if we disable the optimization it will not have the // same result) but we don't care as it is a hack anyway. ClampRoughness(preLightData, bsdfData, light.minRoughness); lighting = ShadeSurface_Infinitesimal(preLightData, bsdfData, V, L, lightColor.rgb, light.diffuseDimmer, light.specularDimmer); } return lighting; }