#ifndef UTS_LIGHT_EVALUATION #define UTS_LIGHT_EVALUATION #if FP_BUFFER #define SATURATE_IF_SDR(x) (x) #define SATURATE_BASE_COLOR_IF_SDR(x) (x) #else #define SATURATE_IF_SDR(x) saturate(x) #define SATURATE_BASE_COLOR_IF_SDR(x) saturate(x) #endif #include "Packages/com.misaki.hdrp-toon/Runtime/Shaders/Includes/Lighting/UtsMaterialEvaluation.hlsl" const float rateR = 0.299; const float rateG = 0.587; const float rateB = 0.114; struct UTSLightData { float3 lightDirection; float3 lightColor; float diffuseDimmer; float specularDimmer; float3 shadowTint; float penumbraTint; SHADOW_TYPE shadowValue; }; float GetColorAttenuation(float3 lightColor) { float lightAttenuation = rateR * lightColor.r + rateG * lightColor.g + rateB * lightColor.b; return lightAttenuation; } float3 GetLimitedLightColor(float3 lightColor) { // In a hemisphere, The engery of full radiance is L * 2pi, and the engery of lambert is L * pi. We multiply by 0.5 here to apply the energy conservation. lightColor = ApplyCurrentExposureMultiplier(lightColor) * 0.5; float3 result = lerp(lightColor, normalize(lightColor), _ClampLightColor); return result; } DirectLighting UtsEvaluateBSDF_Directional(LightLoopContext lightLoopContext, PositionInputs posInput, BuiltinData builtinData, DirectionalLightData lightData, UtsBSDFData bsdfData, PreLightData preLightData, float3 V, float2 uv0) { DirectLighting lighting; ZERO_INITIALIZE(DirectLighting, lighting); float3 L = -lightData.forward; SHADOW_TYPE shadow = 1.0; // TODO: Should we disable the contact shadow? #if _RECEIVE_LIGHT_SHADOW_ON shadow = EvaluateShadow_Directional(lightLoopContext, posInput, lightData, builtinData, bsdfData.geomNormalWS); #endif if (lightData.lightDimmer > 0.0) { // TODO: Support ray traced transparent colored shadow float3 shadowColor = ComputeShadowColor(shadow, lightData.shadowTint, lightData.penumbraTint); float4 lightColor = EvaluateLight_Directional(lightLoopContext, posInput, lightData); lightColor.rgb = GetLimitedLightColor(lightColor.rgb * lightColor.a * _LightIntensityMultiplier) * shadowColor; UtsClampRoughness(preLightData, bsdfData, lightData.minRoughness); lighting = UtsShadeSurface(posInput, bsdfData, preLightData, shadow, lightColor.rgb, V, L, uv0, lightData.diffuseDimmer, lightData.specularDimmer); } return lighting; } DirectLighting UtsEvaluateBSDF_Punctual(LightLoopContext lightLoopContext, PositionInputs posInput, BuiltinData builtinData, LightData lightData, UtsBSDFData bsdfData, PreLightData preLightData, float3 V, float2 uv0) { DirectLighting lighting; ZERO_INITIALIZE(DirectLighting, lighting); float3 L; float4 distances; // {d, d^2, 1/d, d_proj} GetPunctualLightVectors(posInput.positionWS, lightData, L, distances); SHADOW_TYPE shadow = 1.0; #if _RECEIVE_LIGHT_SHADOW_ON shadow = UtsEvaluateShadow_Punctual(lightLoopContext, posInput, lightData, builtinData, UtsGetShadowNormal(bsdfData), L, distances); #endif if (lightData.lightDimmer > 0.0) { // TODO: Support ray traced transparent colored shadow float3 shadowColor = ComputeShadowColor(shadow, lightData.shadowTint, lightData.penumbraTint); float4 lightColor = EvaluateLight_Punctual(lightLoopContext, posInput, lightData, L, distances); lightColor.rgb = GetLimitedLightColor(lightColor.rgb * lightColor.a * _LightIntensityMultiplier); UtsClampRoughness(preLightData, bsdfData, lightData.minRoughness); lighting = UtsShadeSurface(posInput, bsdfData, preLightData, shadow, lightColor.rgb, V, L, uv0, lightData.diffuseDimmer, lightData.specularDimmer); } return lighting; } IndirectLighting UtsEvaluateBSDF_ScreenSpaceReflection(PositionInputs posInput, PreLightData preLightData, inout float reflectionHierarchyWeight) { IndirectLighting lighting; ZERO_INITIALIZE(IndirectLighting, lighting); if (_SSRWeight > 0.0) { // TODO: this texture is sparse (mostly black). Can we avoid reading every texel? How about using Hi-S? float4 ssrLighting = LOAD_TEXTURE2D_X(_SsrLightingTexture, posInput.positionSS); InversePreExposeSsrLighting(ssrLighting); // Apply the weight on the ssr contribution (if required) ApplyScreenSpaceReflectionWeight(ssrLighting); reflectionHierarchyWeight = ssrLighting.a * _SSRWeight; lighting.specularReflected = lerp(lighting.specularReflected, ssrLighting.rgb * preLightData.specularFGD, _SSRWeight); } return lighting; } void UtsEvaluateBSDF_BakeDiffuse(PositionInputs posInput, PreLightData preLightData, UtsBSDFData bsdfData, float3 V, inout BuiltinData builtinData, out float3 lightInReflDir) { lightInReflDir = 0.0; #if defined(PROBE_VOLUMES_L1) || defined(PROBE_VOLUMES_L2) lightInReflDir = float3(-1, -1, -1); // This variable is used with APV for reflection probe normalization - see code for LIGHTFEATUREFLAGS_ENV #endif #if defined(_PBR_MODE_OFF) || defined(_PBR_MODE_TOON) float3 normalWS = float3(0.0, 0.0, 1.0); #else float3 normalWS = bsdfData.normalWS; #endif float3 backNormalWS = -normalWS; // Reflect normal to get lighting for reflection probe tinting float3 R = reflect(-V, normalWS); #if defined(PROBE_VOLUMES_L1) || defined(PROBE_VOLUMES_L2) if (_EnableProbeVolumes) { EvaluateAdaptiveProbeVolume(GetAbsolutePositionWS(posInput.positionWS), normalWS, -backNormalWS, R, V, posInput.positionSS, builtinData.renderingLayers, builtinData.bakeDiffuseLighting, builtinData.backBakeDiffuseLighting, lightInReflDir); } else // If probe volume is disabled we fallback on the ambient probes { builtinData.bakeDiffuseLighting = EvaluateAmbientProbe(normalWS); builtinData.backBakeDiffuseLighting = EvaluateAmbientProbe(backNormalWS); } #endif #if !defined(_SURFACE_TYPE_TRANSPARENT) && !defined(SCREEN_SPACE_INDIRECT_DIFFUSE_DISABLED) if (_IndirectDiffuseMode != INDIRECTDIFFUSEMODE_OFF) { float3 ssgiLighting = LOAD_TEXTURE2D_X(_IndirectDiffuseTexture, posInput.positionSS).xyz * GetInverseCurrentExposureMultiplier(); builtinData.bakeDiffuseLighting = lerp(builtinData.bakeDiffuseLighting, ssgiLighting.rgb, _SSGIWeight); } #endif } void UtsEvaluateBSDF_MatCapDiffuse(float3 positionWS, float3 normalWS, inout BuiltinData builtinData) { float3 positionVS = mul(UNITY_MATRIX_V, float4(positionWS, 1.0)).xyz; float3 normalVS = mul(UNITY_MATRIX_V, float4(normalWS, 1.0)).xyz; float3 PcrossN = cross(normalize(positionVS), normalVS); float2 uv = PcrossN.yx; uv.x *= -1; uv = uv * 0.5 + 0.5; float lod = clamp(UNITY_SPECCUBE_LOD_STEPS + _IndirectDiffuseMatCapLod, 0.0, 10.0); builtinData.bakeDiffuseLighting = SAMPLE_TEXTURE2D_LOD(_IndirectDiffuseMatCapMap, s_trilinear_clamp_sampler, uv, lod).rgb * GetInverseCurrentExposureMultiplier(); } IndirectLighting UtsEvaluateBSDF_MatCapSpecular(float3 positionWS, UtsBSDFData bsdfData, PreLightData preLightData) { IndirectLighting lighting; ZERO_INITIALIZE(IndirectLighting, lighting); float3 positionVS = mul(UNITY_MATRIX_V, float4(positionWS, 1.0)).xyz; float3 normalVS = mul(UNITY_MATRIX_V, float4(bsdfData.normalWS, 1.0)).xyz; float3 pcrossN = cross(normalize(positionVS), normalVS); float2 uv = pcrossN.yx; uv.x *= -1; uv = uv * 0.5 + 0.5; float lod = clamp(PerceptualRoughnessToMipmapLevel(bsdfData.perceptualRoughness) + _IndirectSpecularMatCapLod, 0.0, 10.0); lighting.specularReflected = SAMPLE_TEXTURE2D_LOD(_IndirectSpecularMatCapMap, s_trilinear_clamp_sampler, uv, lod).rgb; lighting.specularReflected *= preLightData.specularFGD * GetInverseCurrentExposureMultiplier(); return lighting; } void UtsEvaluateBSDF_Ramp(PositionInputs posInput, UtsBSDFData bsdfData, inout BuiltinData builtinData) { float3 lighting = SAMPLE_TEXTURE2D_ARRAY(_IndirectDiffuseRampMap, s_trilinear_clamp_sampler, float2(_IndirectDiffuseRampPosition, 0.0), _IndirectDiffuseRampIndex).rgb; builtinData.bakeDiffuseLighting = lighting * GetInverseCurrentExposureMultiplier(); } IndirectLighting UtsEvaluateBSDF_Env(LightLoopContext lightLoopContext, PositionInputs posInput, PreLightData preLightData, EnvLightData lightData, UtsBSDFData bsdfData, int influenceShapeType, int GPUImageBasedLightingType, inout float hierarchyWeight) { IndirectLighting lighting; ZERO_INITIALIZE(IndirectLighting, lighting); float3 envLighting; float3 positionWS = posInput.positionWS; float weight = 1.0; float3 R = preLightData.iblR; if (!IsEnvIndexTexture2D(lightData.envIndex)) // ENVCACHETYPE_CUBEMAP { R = GetSpecularDominantDir(bsdfData.normalWS, R, preLightData.iblPerceptualRoughness, ClampNdotV(preLightData.NdotV)); // When we are rough, we tend to see outward shifting of the reflection when at the boundary of the projection volume // Also it appear like more sharp. To avoid these artifact and at the same time get better match to reference we lerp to original unmodified reflection. // Formula is empirical. float roughness = PerceptualRoughnessToRoughness(preLightData.iblPerceptualRoughness); R = lerp(R, preLightData.iblR, saturate(smoothstep(0, 1, roughness * roughness))); } // Note: using influenceShapeType and projectionShapeType instead of (lightData|proxyData).shapeType allow to make compiler optimization in case the type is know (like for sky) float intersectionDistance = EvaluateLight_EnvIntersection(positionWS, bsdfData.normalWS, lightData, influenceShapeType, R, weight); float3 F = preLightData.specularFGD; float4 preLD = SampleEnvWithDistanceBaseRoughness(lightLoopContext, posInput, lightData, R, preLightData.iblPerceptualRoughness, intersectionDistance); weight *= preLD.a; // Used by planar reflection to discard pixel if (GPUImageBasedLightingType == GPUIMAGEBASEDLIGHTINGTYPE_REFLECTION) { envLighting = F * preLD.rgb; // Apply the main lobe weight and update main reflection hierarchyWeight: UpdateLightingHierarchyWeights(hierarchyWeight, weight); envLighting *= weight; } envLighting *= lightData.multiplier; if (GPUImageBasedLightingType == GPUIMAGEBASEDLIGHTINGTYPE_REFLECTION) { lighting.specularReflected = envLighting; } return lighting; } void ApplyAmbientOcclusion(AmbientOcclusionFactor aoFactor, UtsBSDFData bsdfData, inout BuiltinData builtinData, inout AggregateLighting lighting) { builtinData.bakeDiffuseLighting = APPLY_WEIGHT(builtinData.bakeDiffuseLighting, lerp(bsdfData.diffuseColor * 0.2, 1.0, aoFactor.indirectAmbientOcclusion), _SSAOWeight); lighting.indirect.specularReflected = APPLY_WEIGHT(lighting.indirect.specularReflected, aoFactor.indirectSpecularOcclusion, _SSAOWeight); lighting.direct.diffuse = APPLY_WEIGHT(lighting.direct.diffuse, aoFactor.directAmbientOcclusion, _SSAOWeight); lighting.direct.specular = APPLY_WEIGHT(lighting.direct.specular, aoFactor.directSpecularOcclusion, _SSAOWeight); } void AdjustIndirectLighting(PreLightData preLightData, UtsBSDFData bsdfData, inout BuiltinData builtinData, inout AggregateLighting lighting) { builtinData.emissiveColor *= GetCurrentExposureMultiplier(); builtinData.bakeDiffuseLighting = ApplyCurrentExposureMultiplier(builtinData.bakeDiffuseLighting * bsdfData.diffuseColor * preLightData.diffuseFGD * _IndirectDiffuseIntensity); lighting.indirect.specularReflected = ApplyCurrentExposureMultiplier(lighting.indirect.specularReflected * bsdfData.fresnel0 * _IndirectSpecularIntensity); } void UtsPostEvaluateBSDF(PositionInputs posInput, PreLightData preLightData, UtsBSDFData bsdfData, BuiltinData builtinData, AggregateLighting lighting, out LightLoopOutput lightLoopOutput) { #if !defined(_INDIRECT_DIFFUSE_MODE_OFF) || !defined(_INDIRECT_SPECULAR_MODE_OFF) AmbientOcclusionFactor aoFactor; GetScreenSpaceAmbientOcclusionMultibounce(posInput.positionSS, preLightData.NdotV, bsdfData.perceptualRoughness, bsdfData.ambientOcclusion, bsdfData.specularOcclusion, bsdfData.diffuseColor, bsdfData.fresnel0, aoFactor); ApplyAmbientOcclusion(aoFactor, bsdfData, builtinData, lighting); #endif AdjustIndirectLighting(preLightData, bsdfData, builtinData, lighting); // In regular pbr, we need to multiple diffuse color here with direct diffuse lighting. However, in UTS, we have already multiplied it when evaluating the direct diffuse since we need to apply the shading color. lightLoopOutput.diffuseLighting = lighting.direct.diffuse + builtinData.bakeDiffuseLighting + builtinData.emissiveColor; lightLoopOutput.specularLighting = lighting.direct.specular + lighting.indirect.specularReflected; // Rescale the GGX to account for the multiple scattering. lightLoopOutput.specularLighting *= 1.0 + bsdfData.fresnel0 * preLightData.energyCompensation; ApplyExposureAdjustment(lightLoopOutput.diffuseLighting); ApplyExposureAdjustment(lightLoopOutput.specularLighting); } #endif