#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 #define APPLY_WEIGHT(x, y, t) lerp(x, x * y, t) // not in materials int _ToonLightHiCutFilter; int _ToonEvAdjustmentCurve; float _ToonEvAdjustmentValueArray[128]; float _ToonEvAdjustmentValueMin; float _ToonEvAdjustmentValueMax; float _ToonEvAdjustmentCompensation; float _ToonIgnoreExposureMultiplier; struct UTSLightData { float3 lightDirection; float3 lightColor; float diffuseDimmer; float specularDimmer; float3 shadowTint; float penumbraTint; SHADOW_TYPE shadowValue; }; float3 ApplyCurrentExposureMultiplier(float3 color) { return color * lerp(GetCurrentExposureMultiplier(), 1, _ToonIgnoreExposureMultiplier); } float3 AccumulateUTSAggregateLighting(AggregateLighting aggregateLighting, UtsBSDFData bsdfData) { float3 directLighting = aggregateLighting.direct.diffuse + aggregateLighting.direct.specular; float3 indirectLighting = ApplyCurrentExposureMultiplier(aggregateLighting.indirect.specularReflected * bsdfData.fresnel0 * _IR_Intensity + aggregateLighting.indirect.specularTransmitted * bsdfData.diffuseColor * _ID_Intensity); return SATURATE_IF_SDR(directLighting + indirectLighting); } float3 ConvertFromEV100(float3 EV100) { float3 value = pow(2, EV100) * 2.5f; return value; } float3 ConvertToEV100(float3 value) { return log2(value * 0.4f); } float WeightSample(PositionInputs positionInput) { // Center-weighted const float2 kCenter = _ScreenParams.xy * 0.5; const float weight = pow(length((kCenter.xy - positionInput.positionSS.xy) / _ScreenParams.xy), 1.0); return 1.0 - saturate(weight); } float3 ApplyCompensation(float3 originalColor) { float3 ev100_Color = ConvertToEV100(originalColor) + _ToonEvAdjustmentCompensation * 0.5f; float3 resultColor = max(0, ConvertFromEV100(ev100_Color)); return resultColor; } float3 GetExposureAdjustedColor(float3 originalColor) { if (_ToonEvAdjustmentCurve != 0) { float3 ev100_Color = ConvertToEV100(originalColor); ev100_Color = clamp(ev100_Color, _ToonEvAdjustmentValueMin, _ToonEvAdjustmentValueMax); float3 ev100_remap = (ev100_Color - _ToonEvAdjustmentValueMin) * (128 - 1) / (_ToonEvAdjustmentValueMax - _ToonEvAdjustmentValueMin); ev100_remap = clamp(ev100_remap, 0.0, 127.0); int3 ev100_idx = (int3) ev100_remap; float3 ev100_lerp = ev100_remap - ev100_idx; float3 ev100_remapped; ev100_remapped.r = _ToonEvAdjustmentValueArray[ev100_idx.r] + (_ToonEvAdjustmentValueArray[ev100_idx.r + 1] - _ToonEvAdjustmentValueArray[ev100_idx.r]) * ev100_lerp.r; ev100_remapped.g = _ToonEvAdjustmentValueArray[ev100_idx.g] + (_ToonEvAdjustmentValueArray[ev100_idx.g + 1] - _ToonEvAdjustmentValueArray[ev100_idx.g]) * ev100_lerp.g; ev100_remapped.b = _ToonEvAdjustmentValueArray[ev100_idx.b] + (_ToonEvAdjustmentValueArray[ev100_idx.b + 1] - _ToonEvAdjustmentValueArray[ev100_idx.b]) * ev100_lerp.b; float3 resultColor = ConvertFromEV100(ev100_remapped); return resultColor; } else // else is neccessary to avoid warrnings. { return originalColor; } } float GetLightAttenuation(float3 lightColor) { float lightAttenuation = rateR * lightColor.r + rateG * lightColor.g + rateB * lightColor.b; return lightAttenuation; } float3 GetLimitedLightColor(float3 lightColor) { lightColor = ApplyCurrentExposureMultiplier(lightColor); float3 result = lerp(lightColor, saturate(lightColor), _Is_Filter_LightColor); return result; } IndirectLighting UtsEvaluateBSDF_ScreenSpaceReflection(PositionInputs posInput, PreLightData preLightData, inout float reflectionHierarchyWeight) { IndirectLighting lighting; ZERO_INITIALIZE(IndirectLighting, lighting); // 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; lighting.specularReflected = ssrLighting.rgb * preLightData.specularFGD; 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(_SURFACE_TYPE_TRANSPARENT) && !defined(SCREEN_SPACE_INDIRECT_DIFFUSE_DISABLED) if (_IndirectDiffuseMode != INDIRECTDIFFUSEMODE_OFF) { builtinData.bakeDiffuseLighting = LOAD_TEXTURE2D_X(_IndirectDiffuseTexture, posInput.positionSS).xyz * GetInverseCurrentExposureMultiplier(); } else #endif { #if defined(PROBE_VOLUMES_L1) || defined(PROBE_VOLUMES_L2) if (_EnableProbeVolumes) { // Reflect normal to get lighting for reflection probe tinting float3 R = reflect(-V, bsdfData.normalWS); #if defined(_PBR_Mode_OFF) || defined(_PBR_Mode_TOON) float3 normalWS = 0.0; float3 backNormalWS = 0.0; #else float3 normalWS = bsdfData.normalWS; float3 backNormalWS = -bsdfData.normalWS; #endif EvaluateAdaptiveProbeVolume(GetAbsolutePositionWS(posInput.positionWS), bsdfData.normalWS, -bsdfData.normalWS, 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(bsdfData.normalWS); builtinData.backBakeDiffuseLighting = EvaluateAmbientProbe(-bsdfData.normalWS); } #endif } } void UtsEvaluateMatCap(PositionInputs posInput, UtsBSDFData bsdfData, float perceptualRoughness, inout BuiltinData builtinData) { float3 positionVS = mul(UNITY_MATRIX_V, float4(posInput.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; builtinData.bakeDiffuseLighting = SAMPLE_TEXTURE2D_LOD(_MatCapMap, s_linear_clamp_sampler, uv, PerceptualRoughnessToMipmapLevel(perceptualRoughness)).rgb * GetInverseCurrentExposureMultiplier(); } void UtsEvaluateRamp(PositionInputs posInput, UtsBSDFData bsdfData, inout BuiltinData builtinData) { } 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); if (GPUImageBasedLightingType == GPUIMAGEBASEDLIGHTINGTYPE_REFRACTION) { return 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); // Don't do clear coating for refraction float3 coatR = preLightData.coatIblR; if (GPUImageBasedLightingType == GPUIMAGEBASEDLIGHTINGTYPE_REFLECTION && HasFlag(bsdfData.materialFeatures, MATERIALFEATUREFLAGS_LIT_CLEAR_COAT)) { float unusedWeight = 0.0; EvaluateLight_EnvIntersection(positionWS, bsdfData.normalWS, lightData, influenceShapeType, coatR, unusedWeight); } 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; } DirectLighting UtsEvaluateShading_Directional(LightLoopContext lightLoopContext, PositionInputs posInput, BuiltinData builtinData, DirectionalLightData lightData, UtsBSDFData bsdfData, float3 V) { DirectLighting lighting; ZERO_INITIALIZE(DirectLighting, lighting); float3 L = - lightData.forward; float NdotL = dot(bsdfData.normalWS, L); float halfLambert = 0.5 * NdotL + 0.5; SHADOW_TYPE shadow = EvaluateShadow_Directional(lightLoopContext, posInput, lightData, builtinData, bsdfData.normalWS); float systemShadows = saturate(shadow + 0.5f + _Tweak_SystemShadowsLevel > 0.0 ? shadow + 0.5f + _Tweak_SystemShadowsLevel : 0.0); float shadingGrade = lerp(halfLambert, halfLambert * systemShadows, _Set_SystemShadowsToBase ); float firstColorFeatherForMask = lerp(_1st_ShadeColor_Feather, 0.0f, max(_ComposerMaskMode, _FirstShadeOverridden)); float finalShadow = saturate((shadingGrade - (_1st_ShadeColor_Step - firstColorFeatherForMask)) / (_1st_ShadeColor_Step - (_1st_ShadeColor_Step - firstColorFeatherForMask))); // Base and 1st Shade Mask if (lightData.diffuseDimmer > 0.0 && finalShadow > 0.0) { float secondColorFeatherForMask = lerp(_2nd_ShadeColor_Feather, 0.0f, max(_SecondShadeOverridden, _ComposerMaskMode)); float shadeShadow = saturate((halfLambert - (_ShadeColor_Step - secondColorFeatherForMask)) / (_ShadeColor_Step - (_ShadeColor_Step - secondColorFeatherForMask))); // 1st and 2nd Shades Mask float3 diffuseTerm = lerp(lerp(bsdfData.secondShadingDiffuseColor, bsdfData.firstShadingDiffuseColor, shadeShadow), bsdfData.diffuseColor, finalShadow); float3 specularTerm = ComputeSpecularTerm(V, L, bsdfData) * finalShadow; float4 lightColor = EvaluateLight_Directional(lightLoopContext, posInput, lightData); lightColor.rgb *= ComputeShadowColor(systemShadows, lightData.shadowTint, lightData.penumbraTint) * lightColor.a * _Light_Intensity_Multiplier; lightColor.rgb = GetLimitedLightColor(lightColor.rgb); lighting.diffuse = diffuseTerm * lightColor.rgb * lightData.diffuseDimmer; lighting.specular += specularTerm * lightColor.rgb * lightData.specularDimmer; } return lighting; } void UtsPostEvaluateBSDF(PositionInputs posInput, PreLightData preLightData, UtsBSDFData bsdfData, BuiltinData builtinData, AggregateLighting lighting, out LightLoopOutput lightLoopOutput) { AmbientOcclusionFactor aoFactor; GetScreenSpaceAmbientOcclusionMultibounce(posInput.positionSS, preLightData.NdotV, bsdfData.perceptualRoughness, bsdfData.ambientOcclusion, bsdfData.specularOcclusion, bsdfData.diffuseColor, bsdfData.fresnel0, aoFactor); builtinData.bakeDiffuseLighting = APPLY_WEIGHT(builtinData.bakeDiffuseLighting, aoFactor.indirectAmbientOcclusion, _AO_Factor); lighting.indirect.specularReflected = APPLY_WEIGHT(lighting.indirect.specularReflected, aoFactor.indirectSpecularOcclusion, _AO_Factor); lighting.direct.diffuse = APPLY_WEIGHT(lighting.direct.diffuse, aoFactor.directAmbientOcclusion, _AO_Factor); lighting.direct.specular = APPLY_WEIGHT(lighting.direct.specular, aoFactor.directSpecularOcclusion, _AO_Factor); builtinData.bakeDiffuseLighting = ApplyCurrentExposureMultiplier(builtinData.bakeDiffuseLighting * bsdfData.diffuseColor * _ID_Intensity); lighting.indirect.specularReflected = ApplyCurrentExposureMultiplier(lighting.indirect.specularReflected * bsdfData.fresnel0 * _IR_Intensity); 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; } #endif