// This files include various function uses to evaluate lights // To use deferred directional shadow with cascaded shadow map, // it is required to define USE_DEFERRED_DIRECTIONAL_SHADOWS before including this files //----------------------------------------------------------------------------- // Directional Light evaluation helper //----------------------------------------------------------------------------- float3 EvaluateCookie_Directional(LightLoopContext lightLoopContext, DirectionalLightData lightData, float3 lightToSample) { // Translate and rotate 'positionWS' into the light space. // 'lightData.right' and 'lightData.up' are pre-scaled on CPU. float3x3 lightToWorld = float3x3(lightData.right, lightData.up, lightData.forward); float3 positionLS = mul(lightToSample, transpose(lightToWorld)); // Perform orthographic projection. float2 positionCS = positionLS.xy; // Remap the texture coordinates from [-1, 1]^2 to [0, 1]^2. float2 positionNDC = positionCS * 0.5 + 0.5; // We let the sampler handle clamping to border. return SampleCookie2D(lightLoopContext, positionNDC, lightData.cookieIndex, lightData.tileCookie); } // None of the outputs are premultiplied. void EvaluateLight_Directional(LightLoopContext lightLoopContext, PositionInputs posInput, DirectionalLightData lightData, BakeLightingData bakeLightingData, float3 N, float3 L, out float3 color, out float attenuation) { float3 positionWS = posInput.positionWS; float shadow = 1.0; float shadowMask = 1.0; color = lightData.color; attenuation = 1.0; // Note: no volumetric attenuation along shadow rays for directional lights UNITY_BRANCH if (lightData.cookieIndex >= 0) { float3 lightToSample = positionWS - lightData.positionWS; float3 cookie = EvaluateCookie_Directional(lightLoopContext, lightData, lightToSample); color *= cookie; } #ifdef SHADOWS_SHADOWMASK // shadowMaskSelector.x is -1 if there is no shadow mask // Note that we override shadow value (in case we don't have any dynamic shadow) shadow = shadowMask = (lightData.shadowMaskSelector.x >= 0.0) ? dot(bakeLightingData.bakeShadowMask, lightData.shadowMaskSelector) : 1.0; #endif UNITY_BRANCH if (lightData.shadowIndex >= 0) { #ifdef USE_DEFERRED_DIRECTIONAL_SHADOWS shadow = LOAD_TEXTURE2D(_DeferredShadowTexture, posInput.positionSS).x; #else shadow = GetDirectionalShadowAttenuation(lightLoopContext.shadowContext, positionWS, N, lightData.shadowIndex, L, posInput.positionSS); #endif #ifdef SHADOWS_SHADOWMASK float fade = saturate(posInput.linearDepth * lightData.fadeDistanceScaleAndBias.x + lightData.fadeDistanceScaleAndBias.y); // See comment in EvaluateBSDF_Punctual shadow = lightData.dynamicShadowCasterOnly ? min(shadowMask, shadow) : shadow; shadow = lerp(shadow, shadowMask, fade); // Caution to lerp parameter: fade is the reverse of shadowDimmer // Note: There is no shadowDimmer when there is no shadow mask #endif } attenuation *= shadow; } //----------------------------------------------------------------------------- // Punctual Light evaluation helper //----------------------------------------------------------------------------- float4 EvaluateCookie_Punctual(LightLoopContext lightLoopContext, LightData lightData, float3 lightToSample) { int lightType = lightData.lightType; // Translate and rotate 'positionWS' into the light space. // 'lightData.right' and 'lightData.up' are pre-scaled on CPU. float3x3 lightToWorld = float3x3(lightData.right, lightData.up, lightData.forward); float3 positionLS = mul(lightToSample, transpose(lightToWorld)); float4 cookie; UNITY_BRANCH if (lightType == GPULIGHTTYPE_POINT) { cookie.rgb = SampleCookieCube(lightLoopContext, positionLS, lightData.cookieIndex); cookie.a = 1; } else { // Perform orthographic or perspective projection. float perspectiveZ = (lightType != GPULIGHTTYPE_PROJECTOR_BOX) ? positionLS.z : 1.0; float2 positionCS = positionLS.xy / perspectiveZ; bool isInBounds = Max3(abs(positionCS.x), abs(positionCS.y), 1.0 - positionLS.z) <= 1.0; // Remap the texture coordinates from [-1, 1]^2 to [0, 1]^2. float2 positionNDC = positionCS * 0.5 + 0.5; // Manually clamp to border (black). cookie.rgb = SampleCookie2D(lightLoopContext, positionNDC, lightData.cookieIndex, false); cookie.a = isInBounds ? 1 : 0; } return cookie; } // None of the outputs are premultiplied. // distances = {d, d^2, 1/d, d_proj}, where d_proj = dot(lightToSample, lightData.forward). void EvaluateLight_Punctual(LightLoopContext lightLoopContext, PositionInputs posInput, LightData lightData, BakeLightingData bakeLightingData, float3 N, float3 L, float3 lightToSample, float4 distances, out float3 color, out float attenuation) { float3 positionWS = posInput.positionWS; float shadow = 1.0; float shadowMask = 1.0; color = lightData.color; attenuation = SmoothPunctualLightAttenuation(distances, lightData.invSqrAttenuationRadius, lightData.angleScale, lightData.angleOffset); #if (SHADEROPTIONS_VOLUMETRIC_LIGHTING_PRESET != 0) // TODO: sample the extinction from the density V-buffer. float distVol = (lightData.lightType == GPULIGHTTYPE_PROJECTOR_BOX) ? distances.w : distances.x; attenuation *= TransmittanceHomogeneousMedium(_GlobalExtinction, distVol); #endif // Projector lights always have cookies, so we can perform clipping inside the if(). UNITY_BRANCH if (lightData.cookieIndex >= 0) { float4 cookie = EvaluateCookie_Punctual(lightLoopContext, lightData, lightToSample); color *= cookie.rgb; attenuation *= cookie.a; } #ifdef SHADOWS_SHADOWMASK // shadowMaskSelector.x is -1 if there is no shadow mask // Note that we override shadow value (in case we don't have any dynamic shadow) shadow = shadowMask = (lightData.shadowMaskSelector.x >= 0.0) ? dot(bakeLightingData.bakeShadowMask, lightData.shadowMaskSelector) : 1.0; #endif UNITY_BRANCH if (lightData.shadowIndex >= 0) { // TODO: make projector lights cast shadows. shadow = GetPunctualShadowAttenuation(lightLoopContext.shadowContext, positionWS, N, lightData.shadowIndex, L, distances.x, posInput.positionSS); #ifdef SHADOWS_SHADOWMASK // Note: Legacy Unity have two shadow mask mode. ShadowMask (ShadowMask contain static objects shadow and ShadowMap contain only dynamic objects shadow, final result is the minimun of both value) // and ShadowMask_Distance (ShadowMask contain static objects shadow and ShadowMap contain everything and is blend with ShadowMask based on distance (Global distance setup in QualitySettigns)). // HDRenderPipeline change this behavior. Only ShadowMask mode is supported but we support both blend with distance AND minimun of both value. Distance is control by light. // The following code do this. // The min handle the case of having only dynamic objects in the ShadowMap // The second case for blend with distance is handled with ShadowDimmer. ShadowDimmer is define manually and by shadowDistance by light. // With distance, ShadowDimmer become one and only the ShadowMask appear, we get the blend with distance behavior. shadow = lightData.dynamicShadowCasterOnly ? min(shadowMask, shadow) : shadow; shadow = lerp(shadowMask, shadow, lightData.shadowDimmer); #else shadow = lerp(1.0, shadow, lightData.shadowDimmer); #endif } attenuation *= shadow; } // Environment map share function #include "Reflection/VolumeProjection.hlsl" void EvaluateLight_EnvIntersection(float3 positionWS, float3 normalWS, EnvLightData lightData, int influenceShapeType, inout float3 R, inout float weight) { // 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) // However we add the constrain that the shape of the projection and influence volume is the same (i.e if we have a sphere shape projection volume, we have a shape influence). // It allow to have more coherence for the dynamic if in shader code. // Users can also chose to not have any projection, in this case we use the property minProjectionDistance to minimize code change. minProjectionDistance is set to huge number // that simulate effect of no shape projection float3x3 worldToIS = WorldToInfluenceSpace(lightData); // IS: Influence space float3 positionIS = WorldToInfluencePosition(lightData, worldToIS, positionWS); float3 dirIS = mul(R, worldToIS); float3x3 worldToPS = WorldToProxySpace(lightData); // PS: Proxy space float3 positionPS = WorldToProxyPosition(lightData, worldToPS, positionWS); float3 dirPS = mul(R, worldToPS); float projectionDistance = 0; // Process the projection // In Unity the cubemaps are capture with the localToWorld transform of the component. // This mean that location and orientation matter. So after intersection of proxy volume we need to convert back to world. if (influenceShapeType == ENVSHAPETYPE_SPHERE) { projectionDistance = IntersectSphereProxy(lightData, dirPS, positionPS); // We can reuse dist calculate in LS directly in WS as there is no scaling. Also the offset is already include in lightData.capturePositionWS float3 capturePositionWS = lightData.capturePositionWS; R = (positionWS + projectionDistance * R) - capturePositionWS; weight = InfluenceSphereWeight(lightData, normalWS, positionWS, positionIS, dirIS); } else if (influenceShapeType == ENVSHAPETYPE_BOX) { projectionDistance = IntersectBoxProxy(lightData, dirPS, positionPS); // No need to normalize for fetching cubemap // We can reuse dist calculate in LS directly in WS as there is no scaling. Also the offset is already include in lightData.capturePositionWS float3 capturePositionWS = lightData.capturePositionWS; R = (positionWS + projectionDistance * R) - capturePositionWS; weight = InfluenceBoxWeight(lightData, normalWS, positionWS, positionIS, dirIS); } // Smooth weighting weight = Smoothstep01(weight); weight *= lightData.weight; } // Ambient occlusion struct AmbientOcclusionFactor { float3 indirectAmbientOcclusion; float3 directAmbientOcclusion; float3 indirectSpecularOcclusion; }; void GetScreenSpaceAmbientOcclusion(float2 positionSS, float NdotV, float perceptualRoughness, float ambientOcclusionFromData, float specularOcclusionFromData, out AmbientOcclusionFactor aoFactor) { // Note: When we ImageLoad outside of texture size, the value returned by Load is 0 (Note: On Metal maybe it clamp to value of texture which is also fine) // We use this property to have a neutral value for AO that doesn't consume a sampler and work also with compute shader (i.e use ImageLoad) // We store inverse AO so neutral is black. So either we sample inside or outside the texture it return 0 in case of neutral // Ambient occlusion use for indirect lighting (reflection probe, baked diffuse lighting) #ifndef _SURFACE_TYPE_TRANSPARENT float indirectAmbientOcclusion = 1.0 - LOAD_TEXTURE2D(_AmbientOcclusionTexture, positionSS).x; // Ambient occlusion use for direct lighting (directional, punctual, area) float directAmbientOcclusion = lerp(1.0, indirectAmbientOcclusion, _AmbientOcclusionParam.w); #else float indirectAmbientOcclusion = 1.0; float directAmbientOcclusion = 1.0; #endif float roughness = PerceptualRoughnessToRoughness(perceptualRoughness); float specularOcclusion = GetSpecularOcclusionFromAmbientOcclusion(ClampNdotV(NdotV), indirectAmbientOcclusion, roughness); aoFactor.indirectSpecularOcclusion = lerp(_AmbientOcclusionParam.rgb, float3(1.0, 1.0, 1.0), min(specularOcclusionFromData, specularOcclusion)); aoFactor.indirectAmbientOcclusion = lerp(_AmbientOcclusionParam.rgb, float3(1.0, 1.0, 1.0), min(ambientOcclusionFromData, indirectAmbientOcclusion)); aoFactor.directAmbientOcclusion = lerp(_AmbientOcclusionParam.rgb, float3(1.0, 1.0, 1.0), directAmbientOcclusion); } void GetScreenSpaceAmbientOcclusionMultibounce(float2 positionSS, float NdotV, float perceptualRoughness, float ambientOcclusionFromData, float specularOcclusionFromData, float3 diffuseColor, float3 fresnel0, out AmbientOcclusionFactor aoFactor) { // Use GTAOMultiBounce approximation for ambient occlusion (allow to get a tint from the diffuseColor) // Note: When we ImageLoad outside of texture size, the value returned by Load is 0 (Note: On Metal maybe it clamp to value of texture which is also fine) // We use this property to have a neutral value for AO that doesn't consume a sampler and work also with compute shader (i.e use ImageLoad) // We store inverse AO so neutral is black. So either we sample inside or outside the texture it return 0 in case of neutral // Ambient occlusion use for indirect lighting (reflection probe, baked diffuse lighting) #ifndef _SURFACE_TYPE_TRANSPARENT float indirectAmbientOcclusion = 1.0 - LOAD_TEXTURE2D(_AmbientOcclusionTexture, positionSS).x; // Ambient occlusion use for direct lighting (directional, punctual, area) float directAmbientOcclusion = lerp(1.0, indirectAmbientOcclusion, _AmbientOcclusionParam.w); #else float indirectAmbientOcclusion = 1.0; float directAmbientOcclusion = 1.0; #endif float roughness = PerceptualRoughnessToRoughness(perceptualRoughness); float specularOcclusion = GetSpecularOcclusionFromAmbientOcclusion(ClampNdotV(NdotV), indirectAmbientOcclusion, roughness); aoFactor.indirectSpecularOcclusion = GTAOMultiBounce(min(specularOcclusionFromData, specularOcclusion), fresnel0); aoFactor.indirectAmbientOcclusion = GTAOMultiBounce(min(ambientOcclusionFromData, indirectAmbientOcclusion), diffuseColor); aoFactor.directAmbientOcclusion = GTAOMultiBounce(directAmbientOcclusion, diffuseColor); }