using System; using UnityEditor; using UnityEngine; using UnityEngine.Experimental.Rendering; using UnityEngine.Rendering; using UnityEngine.Rendering.HighDefinition; using UnityEngine.Rendering.RendererUtils; namespace Misaki.HdrpToon { [HideInInspector] internal class UTSPass : CustomPass { private const int _ADJUSTMENT_CURVE_PRECISION = 128; private const string _COMPENSATION_PROP_NAME = "_ToonEvAdjustmentCompensation"; private const string _EXPOSURE_ADJUSTMENT_PROP_NAME = "_ToonEvAdjustmentCurve"; private const string _EXPOSURE_ARRAY_PROP_NAME = "_ToonEvAdjustmentValueArray"; private const string _EXPOSURE_MIN_PROP_NAME = "_ToonEvAdjustmentValueMin"; private const string _EXPOSURE_MAX_PROP_NAME = "_ToonEvAdjustmentValueMax"; private const string _TOON_LIGHT_FILTER_PROP_NAME = "_ToonLightHiCutFilter"; private const string _IGNORE_VOLUME_EXPOSURE_PROP_NAME = "_ToonIgnoreExposureMultiplier"; private const string _HAIR_SHADOW_DISTANCE_PROP_NAME = "_HairShadowDistance"; private const string _HAIR_SHADOW_DISTANCE_SCALE_PROP_NAME = "_HairShadowDistanceScaleFactor"; private const string _HAIR_SHADOW_DEPTH_BIAS_PROP_NAME = "_HairShadowDepthBias"; private const string _HAIR_SHADOW_FADEIN_PROP_NAME = "_HairShadowFadeInDistance"; private const string _HAIR_SHADOW_FADE_OUT_PROP_NAME = "_HairShadowFadeOutDistance"; private const string _HAIR_SHADOW_RT_PROP_NAME = "_HairShadowTex"; private const string _HAIR_BLENDING_PROP_NAME = "_HairBlendingTex"; private static readonly ProfilingSampler _hairShadowProfilingSampler = new("UTS Hair Shadow"); private static readonly ProfilingSampler _hairBlendingProfilingSampler = new("UTS Hair Blending Target"); private float _max; private float _min; private float[] _exposureArray; private RTHandle _hairShadowRTHandle; private bool _needReallocateHairShadow; // TODO: Possible to avoid another RTHandle for depth? private RTHandle _hairBlendingRTHandle; private RTHandle _hairBlendingDepthRTHandle; private bool _needReallocateHairBlending; private bool _enableHairShadow; public bool EnableHairShadow { get => _enableHairShadow; set { if (_enableHairShadow == value) { return; } _enableHairShadow = value; if (_enableHairShadow) { Shader.EnableKeyword("ENABLE_UTS_HAIR_SHAOW"); } else { Shader.DisableKeyword("ENABLE_UTS_HAIR_SHAOW"); _hairShadowRTHandle?.Release(); } } } private bool _enableHairBlending; public bool EnableHairBlending { get => _enableHairBlending; set { if (_enableHairBlending == value) { return; } _enableHairBlending = value; if (_enableHairBlending) { Shader.EnableKeyword("ENABLE_UTS_HAIR_BLENDING"); } else { Shader.DisableKeyword("ENABLE_UTS_HAIR_BLENDING"); _hairBlendingRTHandle?.Release(); } } } private BufferQuality _hairShadowQuality = BufferQuality.High; internal BufferQuality HairShadowQuality { get => _hairShadowQuality; set { if (_hairShadowQuality == value) { return; } _hairShadowQuality = value; _needReallocateHairShadow = true; } } private BufferQuality _hairBlendingQuality = BufferQuality.High; internal BufferQuality HairBlendingQuality { get => _hairBlendingQuality; set { if (_hairBlendingQuality == value) { return; } _hairBlendingQuality = value; _needReallocateHairBlending = true; } } private bool ShouldReallocateHairShadowBuffer() { return _hairShadowRTHandle == null || _hairShadowRTHandle.rt == null || !_hairShadowRTHandle.rt.IsCreated() || _needReallocateHairShadow; } private void ReallocateHairShadowBuffer() { #if UNITY_EDITOR if (EditorApplication.isCompiling) { return; } #endif // Use R8 or R16 directly? var format = _hairShadowQuality switch { BufferQuality.Low => GraphicsFormat.D16_UNorm, BufferQuality.High => GraphicsFormat.D32_SFloat, _ => GraphicsFormat.R16G16B16A16_SFloat }; _hairShadowRTHandle?.Release(); _hairShadowRTHandle = RTHandles.Alloc(Vector2.one, useDynamicScale: true, format: format, isShadowMap: true, name: _HAIR_SHADOW_RT_PROP_NAME); Shader.SetGlobalTexture(_HAIR_SHADOW_RT_PROP_NAME, _hairShadowRTHandle); _needReallocateHairShadow = false; } private bool ShouldReallocateHairBlendingBuffer() { return _hairBlendingRTHandle == null || _hairBlendingRTHandle.rt == null || !_hairBlendingRTHandle.rt.IsCreated() || _needReallocateHairBlending; } private void ReallocateHairBlendingBuffer() { #if UNITY_EDITOR if (EditorApplication.isCompiling) { return; } #endif var format = _hairBlendingQuality switch { BufferQuality.Low => GraphicsFormat.A2B10G10R10_UNormPack32, BufferQuality.High => GraphicsFormat.R16G16B16A16_UNorm, _ => GraphicsFormat.A2B10G10R10_UNormPack32 }; _hairBlendingRTHandle?.Release(); _hairBlendingRTHandle = RTHandles.Alloc(Vector2.one, useDynamicScale: true, dimension: TextureXR.dimension, colorFormat: format, name: _HAIR_BLENDING_PROP_NAME); if (_hairBlendingDepthRTHandle == null || _hairBlendingDepthRTHandle.rt == null || !_hairBlendingDepthRTHandle.rt.IsCreated()) { _hairBlendingDepthRTHandle?.Release(); _hairBlendingDepthRTHandle = RTHandles.Alloc(Vector2.one, useDynamicScale: true, dimension: TextureXR.dimension, colorFormat: GraphicsFormat.D16_UNorm, name: _HAIR_BLENDING_PROP_NAME + "_depth"); } Shader.SetGlobalTexture(_HAIR_BLENDING_PROP_NAME, _hairBlendingRTHandle); _needReallocateHairBlending = false; } protected override void Setup(ScriptableRenderContext renderContext, CommandBuffer cmd) { _exposureArray = new float[_ADJUSTMENT_CURVE_PRECISION]; } private void UpdateSceneEV(UTSRenderer utsRenderer) { if (utsRenderer == null) { return; } var toonEVAdjustment = utsRenderer.toonEVAdjustment.value ? 1 : 0; var lightIntensityLimiter = utsRenderer.lightIntensityLimiter.value ? 1 : 0; var ignoreVolumeExposure = utsRenderer.ignoreVolumeExposure.value ? 1 : 0; var compensation = utsRenderer.compensation.value; if (!utsRenderer.state.value) { _min = 0; _max = 0; _exposureArray.AsSpan().Fill(0); toonEVAdjustment = 0; lightIntensityLimiter = 0; ignoreVolumeExposure = 0; compensation = 0; } else { // Fail safe in case the curve is deleted / has 0 point var curve = utsRenderer.adjustmentCurve.value; if (curve == null || curve.length == 0) { _min = 0f; _max = 0f; _exposureArray.AsSpan().Fill(0); } else { _min = curve[0].time; _max = curve[curve.length - 1].time; var step = (_max - _min) / (_ADJUSTMENT_CURVE_PRECISION - 1f); for (var i = 0; i < _ADJUSTMENT_CURVE_PRECISION; i++) { _exposureArray[i] = curve.Evaluate(_min + step * i); } } } Shader.SetGlobalFloatArray(_EXPOSURE_ARRAY_PROP_NAME, _exposureArray); Shader.SetGlobalFloat(_EXPOSURE_MIN_PROP_NAME, _min); Shader.SetGlobalFloat(_EXPOSURE_MAX_PROP_NAME, _max); Shader.SetGlobalInt(_EXPOSURE_ADJUSTMENT_PROP_NAME, toonEVAdjustment); Shader.SetGlobalInt(_TOON_LIGHT_FILTER_PROP_NAME, lightIntensityLimiter); Shader.SetGlobalInt(_IGNORE_VOLUME_EXPOSURE_PROP_NAME, ignoreVolumeExposure); Shader.SetGlobalFloat(_COMPENSATION_PROP_NAME, compensation); } private void RenderHairShadow(ref CustomPassContext ctx, UTSRenderer utsRenderer) { if (!_enableHairShadow || utsRenderer == null || !utsRenderer.state.value || !utsRenderer.enableHairShadow.value) { return; } if (ShouldReallocateHairShadowBuffer()) { ReallocateHairShadowBuffer(); return; } using (new ProfilingScope(ctx.cmd, _hairShadowProfilingSampler)) { CoreUtils.SetRenderTarget(ctx.cmd, _hairShadowRTHandle, ClearFlag.Depth); var result = new RendererListDesc(UtsShaderPassName.hairShadowCasterPassId, ctx.cullingResults, ctx.hdCamera.camera) { renderQueueRange = GetRenderQueueRange(RenderQueueType.AllOpaque), sortingCriteria = SortingCriteria.CommonOpaque, excludeObjectMotionVectors = false, }; CoreUtils.DrawRendererList(ctx.renderContext, ctx.cmd, ctx.renderContext.CreateRendererList(result)); Shader.SetGlobalFloat(_HAIR_SHADOW_DISTANCE_PROP_NAME, utsRenderer.shadowDistance.value); Shader.SetGlobalFloat(_HAIR_SHADOW_DISTANCE_SCALE_PROP_NAME, utsRenderer.shadowDistanceScale.value); Shader.SetGlobalFloat(_HAIR_SHADOW_DEPTH_BIAS_PROP_NAME, utsRenderer.shadowDepthBias.value); Shader.SetGlobalFloat(_HAIR_SHADOW_FADEIN_PROP_NAME, utsRenderer.shadowFadeIn.value); Shader.SetGlobalFloat(_HAIR_SHADOW_FADE_OUT_PROP_NAME, utsRenderer.shadowFadeOut.value); } } private void RenderHairBlending(ref CustomPassContext ctx) { if (!_enableHairBlending) { return; } if (ShouldReallocateHairBlendingBuffer()) { ReallocateHairBlendingBuffer(); return; } using (new ProfilingScope(ctx.cmd, _hairBlendingProfilingSampler)) { CoreUtils.SetRenderTarget(ctx.cmd, _hairBlendingRTHandle, _hairBlendingDepthRTHandle, ClearFlag.Color | ClearFlag.Depth); var result = new RendererListDesc(UtsShaderPassName.hairBlendingTargetPassId, ctx.cullingResults, ctx.hdCamera.camera) { renderQueueRange = GetRenderQueueRange(RenderQueueType.AllOpaque), sortingCriteria = SortingCriteria.CommonOpaque, excludeObjectMotionVectors = false, }; CoreUtils.DrawRendererList(ctx.renderContext, ctx.cmd, ctx.renderContext.CreateRendererList(result)); } } protected override void Execute(CustomPassContext ctx) { var utsRenderer = ctx.hdCamera.volumeStack.GetComponent(); UpdateSceneEV(utsRenderer); RenderHairShadow(ref ctx, utsRenderer); RenderHairBlending(ref ctx); } protected override void Cleanup() { _exposureArray = null; _hairShadowRTHandle?.Release(); _hairBlendingRTHandle?.Release(); _hairBlendingDepthRTHandle?.Release(); } } }