using System; using UnityEditor; using UnityEngine; using UnityEngine.Rendering; using UnityEngine.Rendering.HighDefinition; namespace Unity.Toonshader { [Serializable, VolumeComponentMenu("Rendering/Unity Toon Shader")] public class UTSRenderer : VolumeComponent { // flags bool m_initialized = false; bool m_srpCallbackInitialized = false; const int kAdjustmentCurvePrecision = 128; const string kCompensationPropName = "_ToonEvAdjustmentCompensation"; const string kExposureAdjustmentPropName = "_ToonEvAdjustmentCurve"; const string kExposureArrayPropName = "_ToonEvAdjustmentValueArray"; const string kExposureMinPropName = "_ToonEvAdjustmentValueMin"; const string kExposureMaxPropName = "_ToonEvAdjustmentValueMax"; const string kToonLightFilterPropName = "_ToonLightHiCutFilter"; const string kIgnoreVolumeExposurePropName = "_ToonIgnoreExposureMultiplier"; [SerializeField] internal float[] m_ExposureArray; [SerializeField] internal float m_Max, m_Min; CustomPassVolume customPassVolume = new(); UTSOutlinePass outlinePass = new(); #if UNITY_EDITOR #pragma warning restore CS0414 bool m_isCompiling = false; #endif public BoolParameter enable = new BoolParameter(false, BoolParameter.DisplayType.EnumPopup); [Space] [Header("Outline")] public BoolParameter enableOutline = new BoolParameter(false); public MinFloatParameter outlineMaxWidth = new MinFloatParameter(1.0f, 0.0f); [Space] [Header("Hair Shadow")] public BoolParameter enableHairShadow = new BoolParameter(false); public ClampedFloatParameter shadowDistance = new ClampedFloatParameter(5.0f, 0.0f, 20.0f); public ClampedFloatParameter shadowDistanceScale = new ClampedFloatParameter(0.5f, 0.0f, 1.0f); public ClampedFloatParameter shadowDepthBias = new ClampedFloatParameter(0f, 0.0f, 0.01f); public FloatParameter shadowFadeIn = new FloatParameter(45f); public FloatParameter shadowFadeOut = new FloatParameter(50f); [Space] [Header("Exposure")] public BoolParameter ignoreVolumeExposure = new BoolParameter(false); public BoolParameter lightIntensityLimiter = new BoolParameter(false); public MinFloatParameter compensation = new MinFloatParameter(0.0f, 0.0f); public BoolParameter toonEVAdjustment = new BoolParameter(false); public AnimationCurveParameter adjustmentCurve = new AnimationCurveParameter(DefaultAnimationCurve()); UTSRenderer() { displayName = "UTS Renderer"; } protected override void OnEnable() { base.OnEnable(); UTSDebugPanel.OnEnable(); Initialize(); } protected override void OnDisable() { base.OnDisable(); UTSDebugPanel.OnDisable(); Release(); } private void OnValidate() { if (!enable.value) { Release(); return; } Initialize(); if (!m_initialized) { return; } // Fail safe in case the curve is deleted / has 0 point var curve = adjustmentCurve.value; if (curve == null || curve.length == 0) { m_Min = 0f; m_Max = 0f; for (var i = 0; i < kAdjustmentCurvePrecision; i++) m_ExposureArray[i] = 0.0f; } else { m_Min = curve[0].time; m_Max = curve[curve.length - 1].time; var step = (m_Max - m_Min) / (kAdjustmentCurvePrecision - 1f); for (var i = 0; i < kAdjustmentCurvePrecision; i++) m_ExposureArray[i] = curve.Evaluate(m_Min + step * i); } #if UNITY_EDITOR // handle script recompile if (EditorApplication.isCompiling && !m_isCompiling) { // on compile begin m_isCompiling = true; // Release(); no need return; // } else if (!EditorApplication.isCompiling && m_isCompiling) { // on compile end m_isCompiling = false; } #endif Shader.SetGlobalFloatArray(kExposureArrayPropName, m_ExposureArray); Shader.SetGlobalFloat(kExposureMinPropName, m_Min); Shader.SetGlobalFloat(kExposureMaxPropName, m_Max); Shader.SetGlobalInt(kExposureAdjustmentPropName, toonEVAdjustment.value ? 1 : 0); Shader.SetGlobalInt(kToonLightFilterPropName, lightIntensityLimiter.value ? 1 : 0); Shader.SetGlobalInt(kIgnoreVolumeExposurePropName, ignoreVolumeExposure.value ? 1 : 0); Shader.SetGlobalFloat(kCompensationPropName, compensation.value); } internal static AnimationCurve DefaultAnimationCurve() { return AnimationCurve.Linear(-10f, -10f, -1.32f, -1.32f); } void Initialize() { if (m_initialized) { return; } #if UNITY_EDITOR // initializing renderer can interfere GI baking. so wait until it is completed. if (EditorApplication.isCompiling) return; #endif if (m_ExposureArray == null || m_ExposureArray.Length != kAdjustmentCurvePrecision) { m_ExposureArray = new float[kAdjustmentCurvePrecision]; } m_initialized = true; } protected override void OnDestroy() { base.OnDestroy(); Release(); } void Release() { if (m_initialized) { m_ExposureArray = null; Shader.SetGlobalFloat(kExposureMinPropName, 0); Shader.SetGlobalFloat(kExposureMaxPropName, 0); Shader.SetGlobalInt(kExposureAdjustmentPropName, 0); Shader.SetGlobalInt(kToonLightFilterPropName, 0); Shader.SetGlobalInt(kIgnoreVolumeExposurePropName, 0); Shader.SetGlobalFloat(kCompensationPropName, 0); } m_initialized = false; } private void Reset() { OnDisable(); OnEnable(); DefaultAnimationCurve(); } } }