/** * Copyright(c) Live2D Inc. All rights reserved. * * Use of this source code is governed by the Live2D Open Software license * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. */ using Live2D.Cubism.Core; using Live2D.Cubism.Framework; using Live2D.Cubism.Rendering.URP.RenderingInterceptor; using System; using UnityEngine; using Object = UnityEngine.Object; namespace Live2D.Cubism.Rendering { /// /// Controls rendering of a . /// [ExecuteInEditMode, CubismDontMoveOnReimport] public sealed partial class CubismRenderController : MonoBehaviour, ICubismUpdatable { /// /// Model opacity. /// /// /// This is turned into a field to be available to s... /// [SerializeField, HideInInspector] public float Opacity = 1f; /// /// backing field. /// [SerializeField, HideInInspector] private float _lastOpacity; /// /// Last model opacity. /// private float LastOpacity { get { return _lastOpacity; } set { _lastOpacity = value; } } /// /// backing field. /// [SerializeField, HideInInspector] private bool _isOverriddenModelMultiplyColors; /// /// Whether to override with multiply color from the model. /// public bool MultiplyColorEnabled { get { return _isOverriddenModelMultiplyColors; } set { _isOverriddenModelMultiplyColors = value; } } /// /// backing field. /// [SerializeField, HideInInspector] private bool _isOverriddenModelScreenColors; /// /// Whether to override with screen color from the model. /// public bool ScreenColorEnabled { get { return _isOverriddenModelScreenColors; } set { _isOverriddenModelScreenColors = value; } } /// /// backing field. /// [SerializeField, HideInInspector] private Color _modelMultiplyColor; /// /// Multiply colors used throughout the model. /// public Color ModelMultiplyColor { get { return _modelMultiplyColor; } set { _modelMultiplyColor = value; } } /// /// backing field. /// [SerializeField, HideInInspector] private Color _modelScreenColor; /// /// Screen colors used throughout the model. /// public Color ModelScreenColor { get { return _modelScreenColor; } set { _modelScreenColor = value; } } /// /// Sorting layer name. /// public string SortingLayer { get { return UnityEngine.SortingLayer.IDToName(SortingLayerId); } set { SortingLayerId = UnityEngine.SortingLayer.NameToID(value); } } /// /// backing field. /// [SerializeField, HideInInspector] private int _sortingLayerId; /// /// Sorting layer Id. /// public int SortingLayerId { get { return _sortingLayerId; } set { if (value == _sortingLayerId) { return; } _sortingLayerId = value; // Apply sorting layer. var renderers = Renderers; for (var i = 0; i < renderers.Length; ++i) { renderers[i].OnControllerSortingLayerDidChange(_sortingLayerId); } SortRenderers(); } } /// /// backing field. /// [SerializeField, HideInInspector] private CubismSortingMode _sortingMode; /// /// sorting. /// public CubismSortingMode SortingMode { get { return _sortingMode; } set { // Return early if same value given. if (value == _sortingMode) { return; } _sortingMode = value; // Flip sorting. var renderers = Renderers; for (var i = 0; i < renderers.Length; ++i) { renderers[i].OnControllerSortingModeDidChange(_sortingMode); } SortRenderers(); } } /// /// Order in sorting layer. /// [SerializeField, HideInInspector] private int _sortingOrder; /// /// Order in sorting layer. /// public int SortingOrder { get { return _sortingOrder; } set { // Return early in case same value given. if (value == _sortingOrder) { return; } _sortingOrder = value; // Apply new sorting order. var renderers = Renderers; for (var i = 0; i < renderers.Length; ++i) { renderers[i].OnControllerSortingOrderDidChange(SortingOrder); } SortRenderers(); } } /// /// [Optional] Camera to face. /// [SerializeField] public Camera CameraToFace; /// /// backing field. /// [SerializeField, HideInInspector] private Object _drawOrderHandler; /// /// Draw order handler proxy object. /// public Object DrawOrderHandler { get { return _drawOrderHandler; } set { _drawOrderHandler = value.ToNullUnlessImplementsInterface(); } } /// /// backing field. /// [NonSerialized] private ICubismDrawOrderHandler _drawOrderHandlerInterface; /// /// Listener for draw order changes. /// private ICubismDrawOrderHandler DrawOrderHandlerInterface { get { if (_drawOrderHandlerInterface == null) { _drawOrderHandlerInterface = DrawOrderHandler.GetInterface(); } return _drawOrderHandlerInterface; } } /// /// backing field. /// [SerializeField, HideInInspector] private Object _opacityHandler; /// /// Opacity handler proxy object. /// public Object OpacityHandler { get { return _opacityHandler; } set { _opacityHandler = value.ToNullUnlessImplementsInterface(); } } /// /// backing field. /// private ICubismOpacityHandler _opacityHandlerInterface; /// /// Listener for opacity changes. /// private ICubismOpacityHandler OpacityHandlerInterface { get { if (_opacityHandlerInterface == null) { _opacityHandlerInterface = OpacityHandler.GetInterface(); } return _opacityHandlerInterface; } } /// /// backing field. /// [SerializeField, HideInInspector] private Object _multiplyColorHandler; /// /// Opacity handler proxy object. /// public Object MultiplyColorHandler { get { return _multiplyColorHandler; } set { _multiplyColorHandler = value.ToNullUnlessImplementsInterface(); } } /// /// backing field. /// private ICubismBlendColorHandler _multiplyColorHandlerInterface; /// /// Listener for blend color changes. /// private ICubismBlendColorHandler MultiplyColorHandlerInterface { get { if (_multiplyColorHandlerInterface == null) { _multiplyColorHandlerInterface = MultiplyColorHandler?.GetInterface(); } return _multiplyColorHandlerInterface; } } /// /// backing field. /// [SerializeField, HideInInspector] private Object _screenColorHandler; /// /// Screen color handler proxy object. /// public Object ScreenColorHandler { get { return _screenColorHandler; } set { _screenColorHandler = value.ToNullUnlessImplementsInterface(); } } /// /// backing field. /// private ICubismBlendColorHandler _screenColorHandlerInterface; /// /// Listener for blend color changes. /// private ICubismBlendColorHandler ScreenColorHandlerInterface { get { if (_screenColorHandlerInterface == null) { _screenColorHandlerInterface = ScreenColorHandler?.GetInterface(); } return _screenColorHandlerInterface; } } /// /// The value to offset the s by. /// /// /// You only need to adjust this value when using perspective cameras. /// [SerializeField, HideInInspector] private float _depthOffset = 0.00001f; /// /// Depth offset used when sorting by depth. /// public float DepthOffset { get { return _depthOffset; } set { // Return if same value given. if (Mathf.Abs(value - _depthOffset) < Mathf.Epsilon) { return; } // Store value. _depthOffset = value; // Apply it. var renderers = Renderers; for (var i = 0; i < renderers.Length; ++i) { renderers[i].OnControllerDepthOffsetDidChange(_depthOffset); } } } /// /// 's backing field. /// [NonSerialized] private CubismModel _cubismModel; /// /// Model the controller belongs to. /// public CubismModel Model { get { if (_cubismModel == null) { _cubismModel = this.FindCubismModel(); } return _cubismModel; } } /// /// backing field. /// private Transform _drawablesRootTransform; /// /// Root transform of all s of the model. /// private Transform DrawablesRootTransform { get { if (_drawablesRootTransform == null) { _drawablesRootTransform = Model.Drawables[0].transform.parent; } return _drawablesRootTransform; } } /// /// s backing field. /// [SerializeField] private CubismRenderer[] _renderers; /// /// s. /// public CubismRenderer[] Renderers { get { if (_renderers == null) { TryInitialize(); } return _renderers; } private set { _renderers = value; } } /// /// multiply color buffer. /// private Color[] _newMultiplyColors; /// /// screen color buffer. /// private Color[] _newScreenColors; /// /// Model has update controller component. /// [HideInInspector] public bool HasUpdateController { get; set; } /// /// s backing field. /// private bool _isInitialized = false; /// /// Is renderers initialized. /// [HideInInspector] public bool IsInitialized { get { return _isInitialized; } private set { _isInitialized = value; } } /// /// Makes sure all s have s attached to them. /// public void TryInitialize() { // If the number of drawables has changed, // discard the cached renderers so they are rebuilt to match the new drawable count. if (_renderers != null && Model && Model.Drawables != null) { var newCount = Model.Drawables.Length; if (Model.Offscreens != null) { newCount += Model.Offscreens.Length; } if (_renderers.Length != newCount) { _renderers = null; } } // Try to get renderers. var renderers = _renderers; TryInitializeRenderers(renderers); if (_renderers == null || _renderers.Length < 1) { return; } // Make sure renderers are initialized. for (var i = 0; i < Renderers.Length; ++i) { var targetRenderer = Renderers[i]; targetRenderer.TryInitialize(this); if (!HasRootPartOffscreen) { continue; } HasRootPartOffscreen = CheckHasRootPartOffscreen(targetRenderer); } // Initialize sorting layer. // We set the backing field here directly because we pull the sorting layer directly from the renderer. _sortingLayerId = Renderers[0] .MeshRenderer .sortingLayerID; OnAfterRenderersInitialize(Renderers); IsInitialized = true; } /// /// Updates opacity if necessary. /// private void UpdateOpacity() { // Return if same value given. if (Mathf.Abs(Opacity - LastOpacity) < Mathf.Epsilon) { return; } // Store value. Opacity = Mathf.Clamp(Opacity, 0f, 1f); LastOpacity = Opacity; // Apply opacity. var applyOpacityToRenderers = (OpacityHandlerInterface == null || Opacity > (1f - Mathf.Epsilon)); if (applyOpacityToRenderers && Renderers != null) { var renderers = Renderers; for (var i = 0; i < renderers.Length; ++i) { renderers[i].OnModelOpacityDidChange(Opacity); } } // Call handler. if (OpacityHandlerInterface != null) { OpacityHandlerInterface.OnOpacityDidChange(this, Opacity); } } /// /// Updates Blend Colors if necessary. /// private void UpdateDrawableBlendColors() { if (Renderers == null || !IsInitialized) { return; } var isMultiplyColorUpdated = false; var isScreenColorUpdated = false; if ((_newMultiplyColors?.Length ?? 0) != Renderers.Length) { _newMultiplyColors = new Color[Renderers.Length]; } if ((_newScreenColors?.Length ?? 0) != Renderers.Length) { _newScreenColors = new Color[Renderers.Length]; } for (var i = 0; i < Renderers.Length; i++) { if (!Renderers[i]) { continue; } var isUseUserMultiplyColor = (Renderers[i].DrawObjectMultiplyColorEnabled || MultiplyColorEnabled); if (isUseUserMultiplyColor) { // If you switch from a setting that uses the color of the model, revert to the color that was retained. if (!Renderers[i].LastIsUseUserMultiplyColor) { Renderers[i].MultiplyColor = Renderers[i].LastMultiplyColor; Renderers[i].ApplyMultiplyColor(); isMultiplyColorUpdated = true; } else if (Renderers[i].LastMultiplyColor != Renderers[i].MultiplyColor) { Renderers[i].ApplyMultiplyColor(); isMultiplyColorUpdated = true; } Renderers[i].LastMultiplyColor = Renderers[i].MultiplyColor; } else if (Renderers[i].LastIsUseUserMultiplyColor) { Renderers[i].MultiplyColor = Renderers[i].LastMultiplyColor; Renderers[i].ApplyMultiplyColor(); isMultiplyColorUpdated = true; } _newMultiplyColors[i] = Renderers[i].MultiplyColor; Renderers[i].LastIsUseUserMultiplyColor = isUseUserMultiplyColor; var isUseUserScreenColor = (Renderers[i].DrawObjectScreenColorEnabled || ScreenColorEnabled); if (isUseUserScreenColor) { // If you switch from a setting that uses the color of the model, revert to the color that was retained. if (!Renderers[i].LastIsUseUserScreenColors) { Renderers[i].ScreenColor = Renderers[i].LastScreenColor; Renderers[i].ApplyScreenColor(); isScreenColorUpdated = true; } else if (Renderers[i].LastScreenColor != Renderers[i].ScreenColor) { Renderers[i].ApplyScreenColor(); isScreenColorUpdated = true; } Renderers[i].LastScreenColor = Renderers[i].ScreenColor; } else if (Renderers[i].LastIsUseUserScreenColors) { Renderers[i].ScreenColor = Renderers[i].LastScreenColor; Renderers[i].ApplyScreenColor(); isScreenColorUpdated = true; } _newScreenColors[i] = Renderers[i].ScreenColor; Renderers[i].LastIsUseUserScreenColors = isUseUserScreenColor; } if (MultiplyColorHandler != null && isMultiplyColorUpdated) { MultiplyColorHandlerInterface.OnBlendColorDidChange(this, _newMultiplyColors); } if (ScreenColorHandler != null && isScreenColorUpdated) { ScreenColorHandlerInterface.OnBlendColorDidChange(this, _newScreenColors); } } /// /// Updates from direction changes. /// internal void UpdateDidChangeSortingFromZ(Vector3 cameraPosition) { // Return early if not sorting by depth. if (!SortingMode.SortByDepth()) { return; } for (var i = 0; i < Renderers?.Length; i++) { var cubismRenderer = Renderers[i]; if (!cubismRenderer) { continue; } // Check if direction updated from last sorted. DidChangeSorting |= cubismRenderer.DidUpdateDirectionFromLastSorted(cameraPosition); } } /// /// Called by cubism update controller. Order to invoke OnLateUpdate. /// public int ExecutionOrder { get { return CubismUpdateExecutionOrder.CubismRenderController; } } /// /// Called by cubism update controller. Needs to invoke OnLateUpdate on Editing. /// public bool NeedsUpdateOnEditing { get { return true; } } /// /// Called by cubism update controller. Applies billboarding. /// public void OnLateUpdate() { // Fail silently... if (!enabled) { return; } // Update opacity if necessary. UpdateOpacity(); // Updates Blend Colors if necessary. UpdateDrawableBlendColors(); // Return early in case no camera is to be faced. if (CameraToFace == null) { return; } // Face camera. DrawablesRootTransform.rotation = (Quaternion.LookRotation(CameraToFace.transform.forward, Vector3.up)); } #region Unity Event Handling /// /// Called by Unity. /// private void Start() { // Get cubism update controller. HasUpdateController = (GetComponent() != null); } /// /// Called by Unity. Enables listening to render data updates. /// private void OnEnable() { // Fail silently. if (!Model) { return; } CurrentOffscreenUnmanagedIndex = -1; // Make sure renderers are available. if (!IsInitialized) { Model.Revive(); TryInitialize(); } // Register listener. Model.OnDynamicDrawableData += OnDynamicDrawableData; #if UNITY_EDITOR if (!Application.isPlaying) { Model.ForceUpdateNow(); for (var drawableIndex = 0; drawableIndex < DrawableRenderers.Length; drawableIndex++) { DrawableRenderers[drawableIndex].SwapMeshes(); } } #endif if (GetComponent() != null) { // Do not register at common controller when a rendering interceptor is attached. return; } // Register at common controller. CubismRenderControllerGroup.GetInstance().AddRenderController(this); } /// /// Called by Unity. Disables listening to render data updates. /// private void OnDisable() { // Fail silently. if (!Model) { return; } // Deregister listener. Model.OnDynamicDrawableData -= OnDynamicDrawableData; // Deregister at common controller. CubismRenderControllerGroup.GetInstance().RemoveRenderController(this); } #endregion #region Cubism Event Handling /// /// Called by Unity. /// private void LateUpdate() { if (!HasUpdateController) { OnLateUpdate(); } } /// /// Called whenever new render data is available. /// /// Model with new render data. /// New render data. private void OnDynamicDrawableData(CubismModel sender, CubismDynamicDrawableData[] data) { // Get drawables. var drawables = sender.Drawables; var renderers = DrawableRenderers; // Handle render data changes. for (var dataIndex = 0; dataIndex < data.Length; ++dataIndex) { var rendererIndex = Array.FindIndex(renderers, cubismRenderer => cubismRenderer.Drawable.UnmanagedIndex == dataIndex); // Skip if no renderer found. if (rendererIndex < 0) { continue; } // Controls whether mesh buffers are to be swapped. var swapMeshes = false; // Update visibility if last SwapInfo flag is true. renderers[rendererIndex].UpdateVisibility(); // Update render order if last SwapInfo flags is true. renderers[rendererIndex].UpdateRenderOrder(); // Skip completely non-dirty data. if (!data[dataIndex].IsAnyDirty) { continue; } // Update visibility. if (data[dataIndex].IsVisibilityDirty) { renderers[rendererIndex].OnDrawableVisiblityDidChange(data[dataIndex].IsVisible); swapMeshes = true; } // Update render order. if (data[dataIndex].IsRenderOrderDirty) { renderers[rendererIndex].OnDrawableRenderOrderDidChange(data[dataIndex].RenderOrder); DidChangeDrawableRenderOrder = true; swapMeshes = true; } // Update opacity. if (data[dataIndex].IsOpacityDirty) { renderers[rendererIndex].OnDrawableOpacityDidChange(data[dataIndex].Opacity); swapMeshes = true; } // Update vertex positions. if (data[dataIndex].AreVertexPositionsDirty) { renderers[rendererIndex].OnDrawableVertexPositionsDidChange(data[dataIndex].VertexPositions); swapMeshes = true; } // Swap buffers if necessary. // [INV] Swapping only half of the meshes might improve performance even. Would that be visually feasible? if (swapMeshes) { renderers[rendererIndex].SwapMeshes(); } } // Pass draw order changes to handler (if available). var drawOrderHandler = DrawOrderHandlerInterface; if (drawOrderHandler != null) { for (var i = 0; i < data.Length; ++i) { if (data[i].IsDrawOrderDirty) { drawOrderHandler.OnDrawOrderDidChange(this, drawables[i], data[i].DrawOrder); } } } var isMultiplyColorUpdated = false; var isScreenColorUpdated = false; _newMultiplyColors ??= new Color[renderers.Length]; _newScreenColors ??= new Color[renderers.Length]; var newMultiplyColors = _newMultiplyColors; var newScreenColors = _newScreenColors; for (var dataIndex = 0; dataIndex < data.Length; ++dataIndex) { var rendererIndex = Array.FindIndex(renderers, cubismRenderer => cubismRenderer.Drawable.UnmanagedIndex == dataIndex); // Skip if no renderer found. if (rendererIndex < 0) { continue; } var isUseModelMultiplyColor = !(renderers[rendererIndex].DrawObjectMultiplyColorEnabled || MultiplyColorEnabled); // Skip processing when not using model colors. if (data[dataIndex].IsBlendColorDirty && isUseModelMultiplyColor) { renderers[rendererIndex].ApplyMultiplyColor(); isMultiplyColorUpdated = true; } newMultiplyColors[rendererIndex] = renderers[rendererIndex].MultiplyColor; } for (var dataIndex = 0; dataIndex < data.Length; ++dataIndex) { var rendererIndex = Array.FindIndex(renderers, cubismRenderer => cubismRenderer.Drawable.UnmanagedIndex == dataIndex); // Skip if no renderer found. if (rendererIndex < 0) { continue; } var isUseModelScreenColor = !(renderers[rendererIndex].DrawObjectScreenColorEnabled || ScreenColorEnabled); // Skip processing when not using model colors. if (data[dataIndex].IsBlendColorDirty && isUseModelScreenColor) { renderers[rendererIndex].ApplyScreenColor(); isScreenColorUpdated = true; } newScreenColors[rendererIndex] = renderers[rendererIndex].ScreenColor; } // Pass blend color changes to handler (if available). var multiplyColorHandlerInterface = MultiplyColorHandlerInterface; var screenColorHandlerInterface = ScreenColorHandlerInterface; if (MultiplyColorHandler != null && isMultiplyColorUpdated) { multiplyColorHandlerInterface.OnBlendColorDidChange(this, newMultiplyColors); } if (ScreenColorHandler != null && isScreenColorUpdated) { screenColorHandlerInterface.OnBlendColorDidChange(this, newScreenColors); } } #endregion } }