/**
* 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;
}
}
}
}