/** * 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; using System; using System.Collections.Generic; using Live2D.Cubism.Rendering.URP.RenderingInterceptor; using UnityEngine; using UnityEngine.Rendering; namespace Live2D.Cubism.Rendering { /// /// Partial class that describes processing related to the rendering method added in Cubism 5.3 and later. /// public partial class CubismRenderController { #region Values /// /// Material property block for the model. /// private MaterialPropertyBlock _properties; /// /// Whether the model has a root part offscreen. /// internal bool HasRootPartOffscreen = true; /// /// 's backing field. /// [SerializeField, HideInInspector] private bool _hasMask; /// /// Is the model using masks? /// public bool HasMask { get { return _hasMask; } set { _hasMask = value; } } /// /// Current frame buffer used for rendering. /// Offscreen rendering uses this to render the model. /// public RenderTexture CurrentFrameBuffer { get; set; } /// /// Index of the current offscreen owner in the unmanaged array. /// public int CurrentOffscreenUnmanagedIndex { get; set; } #region Sorting /// /// 's backing field. /// [SerializeField, HideInInspector] private int _groupedSortingIndex; /// /// Sorting index for grouped rendering. /// public int GroupedSortingIndex { get { return _groupedSortingIndex; } set { // Remove from the common rendering controller's groups. CubismRenderControllerGroup.GetInstance().RemoveRenderControllerFromGroups(this, true); _groupedSortingIndex = value; // Re-add to the common rendering controller's groups. CubismRenderControllerGroup.GetInstance().AddRenderControllerGroups(this); } } /// /// Whether the render order of the s did change. /// internal bool DidChangeDrawableRenderOrder; /// /// Whether the sorting order of the s did change. /// internal bool DidChangeSorting; [NonSerialized] private CubismRenderer[] _sortedRenderers; /// /// Sorted s for using . /// public CubismRenderer[] SortedRenderers { get { if (_sortedRenderers == null) { SortRenderers(); } return _sortedRenderers; } private set { _sortedRenderers = value; } } /// /// Sorts the by their draw object render order. /// private void SortRenderers() { if (Renderers == null) { return; } for (var i = 0; i < Renderers.Length; i++) { if (Renderers[i].DrawObjectType != CubismModelTypes.DrawObjectType.Offscreen) { continue; } Renderers[i].SetDrawObjectRenderOrder( Model.AllDrawObjectsRenderOrder[ DrawableRenderers.Length + Renderers[i].Offscreen.UnmanagedIndex]); } var renderers = new List(Renderers); SortBySortingOrder(renderers); _sortedRenderers = renderers.ToArray(); } /// /// Sorts the given renderers by their sorting order. /// /// Renderers to sort. private void SortBySortingOrder(List renderers) { renderers.Sort(CompareBySortingOrder); } /// /// Compares two s by their sorting order. /// /// First renderer. /// Second renderer. /// private int CompareBySortingOrder(CubismRenderer a, CubismRenderer b) { return a.MeshRenderer.sortingOrder - b.MeshRenderer.sortingOrder; } #endregion /// /// 's backing field. /// [NonSerialized] private CubismRenderer[] _drawableRenderers; /// /// Drawable s. /// public CubismRenderer[] DrawableRenderers { get { if (_drawableRenderers == null) { _drawableRenderers = Model.Drawables.GetComponentsMany(); } return _drawableRenderers; } private set { _drawableRenderers = value; } } /// /// 's backing field. /// private CubismRenderer[] _offscreenRenderers; /// /// Offscreen s. /// public CubismRenderer[] OffscreenRenderers { get { if (_offscreenRenderers == null && Model?.Offscreens != null) { _offscreenRenderers = Model.Offscreens.GetComponentsMany(); } return _offscreenRenderers; } private set { _offscreenRenderers = value; } } #endregion /// /// Initializes the render controller. /// /// Array of renderers to initialize. private void TryInitializeRenderers(CubismRenderer[] renderers) { // HACK: It doesn't work correctly when placed inside a function. if (renderers != null && renderers.Length != 0) { return; } // Initialize renderers. renderers = Array.Empty(); // Create renders and apply it to backing field... var drawables = Model.Drawables; var drawableRenderers = drawables.AddComponentEach(); Array.Resize(ref renderers, drawableRenderers.Length); Array.Copy(drawableRenderers, renderers, drawableRenderers.Length); for (var index = 0; index < renderers.Length; index++) { var targetRenderer = renderers[index]; targetRenderer.DrawObjectType = CubismModelTypes.DrawObjectType.Drawable; targetRenderer.Drawable = drawables[index]; if (!HasRootPartOffscreen) { continue; } HasRootPartOffscreen = CheckHasRootPartOffscreen(targetRenderer); } // Store drawable renderers. DrawableRenderers = drawableRenderers; var offscreens = Model.Offscreens; if (offscreens != null) { var offscreenRenderers = offscreens.AddComponentEach(); Array.Resize(ref renderers, renderers.Length + offscreenRenderers.Length); Array.Copy(offscreenRenderers, 0, renderers, renderers.Length - offscreenRenderers.Length, offscreenRenderers.Length); for (var index = drawableRenderers.Length; index < renderers.Length; ++index) { var targetRenderer = renderers[index]; targetRenderer.DrawObjectType = CubismModelTypes.DrawObjectType.Offscreen; targetRenderer.Offscreen = offscreens[index - drawableRenderers.Length]; if (!HasRootPartOffscreen) { continue; } HasRootPartOffscreen = CheckHasRootPartOffscreen(targetRenderer); } // Store offscreen renderers. OffscreenRenderers = offscreenRenderers; } // Store renderers. Renderers = renderers; } /// /// Called after all s are initialized. /// /// private void OnAfterRenderersInitialize(CubismRenderer[] renderers) { // Set the render order and call `OnAfterAllRendererInitialize` method for each renderer. for (var i = 0; i < renderers.Length; i++) { var initRenderer = renderers[i]; switch (initRenderer.DrawObjectType) { case CubismModelTypes.DrawObjectType.Drawable: initRenderer.SetDrawObjectRenderOrder(Model.AllDrawObjectsRenderOrder[initRenderer.Drawable.UnmanagedIndex]); break; case CubismModelTypes.DrawObjectType.Offscreen: initRenderer.SetDrawObjectRenderOrder(Model.AllDrawObjectsRenderOrder[DrawableRenderers.Length + initRenderer.Offscreen.UnmanagedIndex]); break; default: Debug.LogWarning($"Unknown draw object type: {initRenderer.DrawObjectType} for renderer: {initRenderer.name}"); break; } initRenderer.OnAfterAllRendererInitialize(); } } /// /// Does the model have an offscreen for the root part? /// /// The to check. /// True if it has one, false if it does not. private bool CheckHasRootPartOffscreen(CubismRenderer targetRenderer) { var parentPartIndex = -1; switch (targetRenderer.DrawObjectType) { case CubismModelTypes.DrawObjectType.Drawable: parentPartIndex = targetRenderer.Drawable.ParentPartIndex; break; case CubismModelTypes.DrawObjectType.Offscreen: parentPartIndex = Model.Parts[targetRenderer.Offscreen.OwnerIndex].UnmanagedParentIndex; break; default: Debug.LogWarning($"Unknown draw object type: {targetRenderer.DrawObjectType} for renderer: {targetRenderer.name}"); break; } CubismPart part = null; while (parentPartIndex > 0) { for (var partIndex = 0; partIndex < Model.Parts.Length; partIndex++) { if (Model.Parts[partIndex].UnmanagedIndex != parentPartIndex) { continue; } // Found the part. part = Model.Parts[partIndex]; break; } parentPartIndex = part?.UnmanagedParentIndex ?? -1; } return parentPartIndex == 0 && Model.Parts[parentPartIndex].OffscreenIndex > -1; } /// /// Submits drawing offscreen for all offscreen renderers. /// /// Command buffer to record draw commands. /// Pass data containing render controllers and camera data. internal void SubmitDrawOffscreen(CommandBuffer commandBuffer, CubismRenderPassFeature.CubismRenderPass.PassData passData) { if (!IsInitialized || !Model || OffscreenRenderers == null) { return; } while (CurrentFrameBuffer != (RenderTexture)passData.CommonRenderingTextureHandle) { CubismRenderer offscreenRenderer = null; if (CurrentOffscreenUnmanagedIndex == -1 && HasRootPartOffscreen) { for (var offscreenIndex = 0; offscreenIndex < OffscreenRenderers.Length; offscreenIndex++) { if (OffscreenRenderers[offscreenIndex].Offscreen.UnmanagedIndex != 0) { continue; } offscreenRenderer = OffscreenRenderers[offscreenIndex]; break; } if (!offscreenRenderer) { break; } offscreenRenderer.DrawOffscreen(commandBuffer, passData.CommonRenderingTextureHandle, offscreenRenderer, passData); offscreenRenderer.OffscreenFrameBuffer = null; CurrentFrameBuffer = passData.CommonRenderingTextureHandle; CurrentOffscreenUnmanagedIndex = -1; break; } for (var offscreenRendererIndex = 0; offscreenRendererIndex < OffscreenRenderers.Length; offscreenRendererIndex++) { if (OffscreenRenderers[offscreenRendererIndex].Offscreen.UnmanagedIndex != CurrentOffscreenUnmanagedIndex) { continue; } offscreenRenderer = OffscreenRenderers[offscreenRendererIndex]; break; } RenderTexture previousOffscreen = null; var currentOwnerIndex = offscreenRenderer?.Offscreen?.OwnerIndex ?? -1; var parentIndex = -1; if (currentOwnerIndex != -1) { parentIndex = Model.Parts[currentOwnerIndex]?.UnmanagedParentIndex ?? -1; } var previousIndex = -1; // Find the offscreen renderer with the previous offscreen unmanaged index. while (parentIndex != -1) { var part = Model.Parts[parentIndex]; if (part && part.OffscreenIndex != -1) { for (var offscreenRendererIndex = 0; offscreenRendererIndex < OffscreenRenderers.Length; offscreenRendererIndex++) { var element = OffscreenRenderers[offscreenRendererIndex]; if (!element.isActiveAndEnabled || !element.MeshRenderer.enabled) { continue; } if (element.Offscreen.UnmanagedIndex != part?.OffscreenIndex) { continue; } previousOffscreen = OffscreenRenderers[offscreenRendererIndex].OffscreenFrameBuffer; previousIndex = part.OffscreenIndex; break; } } parentIndex = Model.Parts[parentIndex]?.UnmanagedParentIndex ?? -1; if (!previousOffscreen) { continue; } break; } // Try to get the root part offscreen if there is no parent offscreen. if (!previousOffscreen && HasRootPartOffscreen) { for (var offscreenIndex = 0; offscreenIndex < OffscreenRenderers.Length; offscreenIndex++) { if (OffscreenRenderers[offscreenIndex].Offscreen.UnmanagedIndex != 0) { continue; } previousOffscreen = OffscreenRenderers[offscreenIndex].OffscreenFrameBuffer; break; } } // If there is no previous offscreen, or it is the same as the current offscreen, use the common rendering texture. if (!previousOffscreen || previousOffscreen == offscreenRenderer?.OffscreenFrameBuffer) { previousOffscreen = passData.CommonRenderingTextureHandle; } // If It can copy the parent offscreen, copy it to the current offscreen renderer. offscreenRenderer?.DrawOffscreen(commandBuffer, previousOffscreen, offscreenRenderer, passData); if (offscreenRenderer) { offscreenRenderer.OffscreenFrameBuffer = null; } CurrentFrameBuffer = previousOffscreen; CurrentOffscreenUnmanagedIndex = previousIndex; } } } }