live2D (sdk가 6.5 미지원으로 sprite로 대응)

This commit is contained in:
2026-08-08 22:13:54 +09:00
parent 9ce6930772
commit 8a4a001fe0
4278 changed files with 287426 additions and 18 deletions

View File

@@ -0,0 +1,874 @@
/**
* 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.Rendering.URP.RenderingInterceptor;
using System;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.RenderGraphModule;
using UnityEngine.Rendering.Universal;
namespace Live2D.Cubism.Rendering.URP
{
/// <summary>
/// Provides data for rendering events `CubismRenderingInterceptorsManager.GetInstance().OnPreRendering` and `CubismRenderingInterceptorsManager.GetInstance().OnPostRendering`.
/// </summary>
public struct CubismRenderedEventArgs
{
/// <summary>
/// Render pass data.
/// </summary>
public CubismRenderPassFeature.CubismRenderPass.PassData PassData;
/// <summary>
/// Command buffer for rendering.
/// </summary>
public CommandBuffer CommandBuffer;
/// <summary>
/// Color buffer render texture.
/// </summary>
public RenderTexture ColorBuffer;
/// <summary>
/// Depth buffer texture handle.
/// </summary>
public TextureHandle DepthBuffer;
/// <summary>
/// Sorting mode of the draw object's render controller.
/// </summary>
public CubismSortingMode SortingMode;
/// <summary>
/// Group sorting order.
/// </summary>
public int GroupSortingOrder;
/// <summary>
/// Sorting order of the draw object.
/// NOTE: If SortingMode is set to sort by depth, multiple draw objects may have the same sorting order.
/// </summary>
public int SortingOrder;
/// <summary>
/// The drawable being rendered.
/// </summary>
public CubismDrawable Drawable;
/// <summary>
/// Distance to the camera.
/// NOTE: If SortingMode is set to sort by order, multiple draw objects may have the same distance.
/// </summary>
public float Distance;
/// <summary>
/// Distance to the previous draw object in the sorted order.
/// </summary>
public float? PreviousDistance;
/// <summary>
/// Distance to the next draw object in the sorted order.
/// </summary>
public float? NextDistance;
/// <summary>
/// Position of the camera.
/// </summary>
public Vector3 CameraPos;
/// <summary>
/// Forward direction of the camera.
/// </summary>
public Vector3 CameraForward;
}
/// <summary>
/// Scriptable renderer feature for rendering Cubism models in URP.
/// </summary>
public class CubismRenderPassFeature : ScriptableRendererFeature
{
/// <summary>
/// Greater than or equal to comparison value on Z Test.
/// </summary>
internal static readonly int GEqual = 7;
/// <summary>
/// Less than or equal to comparison value on Z Test.
/// </summary>
internal static readonly int LEqual = 4;
/// <summary>
/// Scriptable render pass for rendering Cubism models.
/// </summary>
public class CubismRenderPass : ScriptableRenderPass
{
/// <summary>
/// Structure that groups renderers by their sorting index.
/// </summary>
private struct RendererGroupData
{
/// <summary>
/// Sorting index for the renderer group.
/// </summary>
public int SortingIndex;
/// <summary>
/// Array of render controllers in this group.
/// </summary>
public CubismRenderController[] RenderControllers;
/// <summary>
/// Array of renderers in this group.
/// </summary>
public CubismRenderer[] Renderers;
}
/// <summary>
/// Command buffer used for rendering operations.
/// </summary>
private static CommandBuffer _commandBuffer;
/// <summary>
/// Array of renderer groups sorted by their sorting index.
/// </summary>
private static RendererGroupData[] _sortedRendererGroupDataArray;
/// <summary>
/// Mesh used for blitting render textures.
/// </summary>
private static Mesh _blitRenderTextureMesh;
/// <summary>
/// Material used for blitting render textures.
/// </summary>
private static Material _blitRenderTextureMaterial;
/// <summary>
/// This class stores the data needed by the RenderGraph pass.
/// It is passed as a parameter to the delegate function that executes the RenderGraph pass.
/// </summary>
public class PassData
{
/// <summary>
/// Array of render controllers to be rendered.
/// </summary>
public CubismRenderController[] RenderControllers;
/// <summary>
/// Array of render controller groups.
/// </summary>
public CubismRenderControllerGroup.RenderControllerGroupData[] RenderControllerGroupDaraArray;
/// <summary>
/// Camera data for the current rendering pass.
/// </summary>
public UniversalCameraData CameraData;
/// <summary>
/// Resource data for the current rendering pass.
/// </summary>
public UniversalResourceData ResourceData;
/// <summary>
/// Texture handle for mask rendering.
/// </summary>
public TextureHandle MaskTextureHandle;
/// <summary>
/// Texture handle for the camera render target.
/// </summary>
public TextureHandle CameraTextureHandle;
/// <summary>
/// Texture handle for the camera depth buffer.
/// </summary>
public TextureHandle CameraDepthTextureHandle;
/// <summary>
/// Texture handle for common rendering operations.
/// </summary>
public TextureHandle CommonRenderingTextureHandle;
/// <summary>
/// Texture handle for temporary rendering operations.
/// </summary>
public TextureHandle CommonTemporaryTextureHandle;
}
/// <summary>
/// Sets up and fills the renderer group.
/// </summary>
/// <param name="targetIndex">Target index in the sorted renderer groups array.</param>
/// <param name="target">Added target renderer group.</param>
/// <param name="source">Source render controller group.</param>
/// <param name="cameraPos">Position of the camera</param>
/// <param name="data">Pass data containing render controller groups and camera data</param>
private static void SetUpRendererGroup(int targetIndex, RendererGroupData target, CubismRenderControllerGroup.RenderControllerGroupData source, Vector3 cameraPos, PassData data)
{
var previousCount = 0;
for (var renderControllerIndex = 0; renderControllerIndex < source.Controllers.Length; renderControllerIndex++)
{
var controller = source.Controllers[renderControllerIndex];
controller.CurrentFrameBuffer = data.CommonRenderingTextureHandle;
if (!controller || controller.Renderers == null)
{
continue;
}
// Copy the renderers from the controller to the sorted array.
for (var rendererIndex = 0; rendererIndex < controller.Renderers.Length; rendererIndex++)
{
target.Renderers[previousCount + rendererIndex] = controller.Renderers[rendererIndex];
target.Renderers[previousCount + rendererIndex].IsLastDrawObjectInModel = false;
}
// Update the previous count for the next controller
previousCount += controller.Renderers.Length;
}
// Sort the renderers by their sorting order.
SortBySortingOrder(target.Renderers, cameraPos, data.CameraData.camera.transform.forward);
// Assign the filled target to the sorted renderer groups array.
_sortedRendererGroupDataArray[targetIndex] = target;
}
/// <summary>
/// Sorts the renderer groups based on their sorting order.
/// </summary>
/// <param name="data">Pass data containing render controller groups.</param>
private static void SortingRendererGroups(PassData data)
{
var didChangeSortingRenderControllerGroup = CubismRenderControllerGroup.GetInstance().DidChangeSortingRenderControllerGroup;
if (data.RenderControllerGroupDaraArray == null
|| data.RenderControllerGroupDaraArray.Length < 1)
{
return;
}
// Iterate through each render controller group.
for (var groupsIndex = 0; groupsIndex < data.RenderControllerGroupDaraArray.Length; groupsIndex++)
{
var group = data.RenderControllerGroupDaraArray[groupsIndex];
var rendererCount = 0;
var didChangeSortingOrder = false;
for (var i = 0; i < group.Controllers.Length; i++)
{
var controller = group.Controllers[i];
// Skip if controller is null or has no renderers
if (!controller || controller.Renderers == null)
{
continue;
}
// Update sorting state from camera position.
controller.UpdateDidChangeSortingFromZ(data.CameraData.worldSpaceCameraPos);
// Update offscreen render order if it has changed.
if (controller.DidChangeDrawableRenderOrder)
{
for (var offscreenIndex = 0; offscreenIndex < controller.OffscreenRenderers?.Length; offscreenIndex++)
{
var offscreenRenderer = controller.OffscreenRenderers[offscreenIndex];
offscreenRenderer.SetDrawObjectRenderOrder(controller.Model.AllDrawObjectsRenderOrder[controller.DrawableRenderers.Length + offscreenRenderer.Offscreen.UnmanagedIndex]);
}
}
// Check if any controller has changed its drawable render order.
didChangeSortingOrder |=
controller.DidChangeSorting
|| controller.DidChangeDrawableRenderOrder
|| didChangeSortingRenderControllerGroup;
rendererCount += controller.Renderers.Length;
}
// If nothing changed, no need to sort again.
if (!didChangeSortingOrder)
{
continue;
}
// Create an empty array if it doesn't exist or its size doesn't match the number of groups.
if (_sortedRendererGroupDataArray == null
|| _sortedRendererGroupDataArray.Length > data.RenderControllerGroupDaraArray.Length)
{
_sortedRendererGroupDataArray = Array.Empty<RendererGroupData>();
}
var hasGroupFound = false;
for (var targetIndex = 0; targetIndex < _sortedRendererGroupDataArray.Length; targetIndex++)
{
var target = _sortedRendererGroupDataArray[targetIndex];
if (target.Renderers == null
|| target.SortingIndex != group.SortingGroupIndex)
{
continue;
}
if (target.Renderers.Length != rendererCount)
{
// Resize the renderers array to fit all renderers in the group.
Array.Resize(ref target.Renderers, rendererCount);
}
var hasControllersNull = false;
for (var controllerIndex = 0; controllerIndex < target.RenderControllers.Length; controllerIndex++)
{
if (target.RenderControllers[controllerIndex])
{
continue;
}
hasControllersNull = true;
break;
}
if (hasControllersNull
|| didChangeSortingRenderControllerGroup
|| target.RenderControllers.Length != data.RenderControllerGroupDaraArray[groupsIndex].Controllers.Length)
{
target.RenderControllers = data.RenderControllerGroupDaraArray[groupsIndex].Controllers;
}
// Set up and fill the renderer group.
SetUpRendererGroup(targetIndex, target, group, data.CameraData.worldSpaceCameraPos, data);
hasGroupFound = true;
break;
}
// Initialize the renderer group if necessary
if (hasGroupFound)
{
continue;
}
// Resize the sorted renderer groups array to add a new group.
Array.Resize(ref _sortedRendererGroupDataArray, _sortedRendererGroupDataArray.Length + 1);
var newRendererGroup = new RendererGroupData
{
SortingIndex = group.SortingGroupIndex,
RenderControllers = data.RenderControllerGroupDaraArray[groupsIndex].Controllers,
Renderers = new CubismRenderer[rendererCount]
};
// Set up and fill the renderer group.
SetUpRendererGroup(_sortedRendererGroupDataArray.Length - 1, newRendererGroup, group, data.CameraData.worldSpaceCameraPos,data);
}
}
/// <summary>
/// Sorts the renderers by their sorting order.
/// Calculates the distance to camera for each renderer and sorts them accordingly.
/// </summary>
/// <param name="renderers">Array of renderers to sort</param>
/// <param name="cameraPosition">Position of the camera</param>
/// <param name="cameraForward">Forward direction of the camera (normalized vector)</param>
private static void SortBySortingOrder(CubismRenderer[] renderers, Vector3 cameraPosition, Vector3 cameraForward)
{
for (var index = 0; index < renderers.Length; index++)
{
renderers[index].CalculateDistanceToCamera(cameraPosition, cameraForward);
}
Array.Sort(renderers, CompareBySortingOrder);
}
/// <summary>
/// Compares two renderers by their sorting order.
/// </summary>
/// <param name="a">First renderer to compare</param>
/// <param name="b">Second renderer to compare</param>
/// <returns>Negative value if a should be rendered before b, positive if after, zero if equal.</returns>
private static int CompareBySortingOrder(CubismRenderer a, CubismRenderer b)
{
// Fail-safe check
if (!a || !b
|| !a.MeshRenderer || !b.MeshRenderer
|| !a.RenderController || !b.RenderController)
{
return 0;
}
var result = a.MeshRenderer.sortingOrder - b.MeshRenderer.sortingOrder;
if (result != 0
|| !(a.SortingMode.SortByDepth() && b.SortingMode.SortByDepth()))
{
return result;
}
// If sorting order is the same, compare by local Z position.
var sortValue = (b.DistanceToCamera / b.RenderController.DepthOffset) - (a.DistanceToCamera / a.RenderController.DepthOffset);
result = sortValue >= 0.0f
? Mathf.CeilToInt(sortValue)
: Mathf.FloorToInt(sortValue);
return result;
}
/// <summary>
/// Checks and sets the skip rendering flag for each renderer.
/// </summary>
private static void CheckRenderingSkip()
{
// Reset skip rendering flag for each renderer.
for (var groupIndex = 0; groupIndex < _sortedRendererGroupDataArray.Length; groupIndex++)
{
var rendererGroup = _sortedRendererGroupDataArray[groupIndex];
for (var rendererIndex = 0; rendererIndex < rendererGroup.Renderers.Length; rendererIndex++)
{
rendererGroup.Renderers[rendererIndex].SkipRendering = false;
}
}
// Check and set skip rendering flag for each renderer.
for (var groupIndex = 0; groupIndex < _sortedRendererGroupDataArray.Length; groupIndex++)
{
var rendererGroup = _sortedRendererGroupDataArray[groupIndex];
for (var rendererIndex = 0; rendererIndex < rendererGroup.Renderers.Length; rendererIndex++)
{
var renderer = rendererGroup.Renderers[rendererIndex];
if (!renderer
|| renderer.SkipRendering)
{
continue;
}
renderer.SkipRendering |= !renderer.RenderController
|| !renderer.RenderController.gameObject.activeSelf
|| !renderer.RenderController.enabled
|| !renderer.gameObject.activeSelf
|| !renderer.MeshRenderer
|| !renderer.MeshRenderer.enabled;
switch (renderer.DrawObjectType)
{
case CubismModelTypes.DrawObjectType.Drawable:
renderer.SkipRendering |= renderer.Opacity <= 0.0f;
break;
case CubismModelTypes.DrawObjectType.Offscreen:
renderer.SkipRendering |= renderer.Offscreen.Opacity <= 0.0f;
if (!renderer.SkipRendering)
{
break;
}
// Skip rendering for all child drawables and offscreens of the offscreen part.
var parts = renderer.RenderController?.Model?.Parts;
CubismPart part = null;
for (var partIndex = 0; partIndex < parts?.Length; partIndex++)
{
if (parts[partIndex].UnmanagedIndex != renderer.Offscreen.OwnerIndex)
{
continue;
}
part = parts[partIndex];
break;
}
if (!part)
{
renderer.SkipRendering = true;
break;
}
// Skip rendering for all child drawables and offscreens of the offscreen part.
for (var drawablesIndex = 0; drawablesIndex < part.AllChildDrawables?.Length; drawablesIndex++)
{
var childDrawable = part.AllChildDrawables[drawablesIndex];
for (var targetRendererIndex = 0; targetRendererIndex < renderer.RenderController.Renderers.Length; targetRendererIndex++)
{
var targetRenderer = renderer.RenderController.Renderers[targetRendererIndex];
if (targetRenderer.DrawObjectType != CubismModelTypes.DrawObjectType.Drawable
|| targetRenderer.Drawable.UnmanagedIndex != childDrawable.UnmanagedIndex)
{
continue;
}
targetRenderer.SkipRendering = true;
break;
}
}
for (var offscreensIndex = 0; offscreensIndex < part.AllChildOffscreens?.Length; offscreensIndex++)
{
var childOffscreen = part.AllChildOffscreens[offscreensIndex];
for (var j = 0; j < renderer.RenderController.Renderers.Length; j++)
{
var targetRenderer = renderer.RenderController.Renderers[j];
if (targetRenderer.DrawObjectType != CubismModelTypes.DrawObjectType.Offscreen
|| targetRenderer.Offscreen.UnmanagedIndex != childOffscreen.UnmanagedIndex)
{
continue;
}
targetRenderer.SkipRendering = true;
break;
}
}
break;
default:
renderer.SkipRendering = true;
break;
}
}
}
for (var i= 0; i < _sortedRendererGroupDataArray.Length; i++)
{
var target = _sortedRendererGroupDataArray[i];
// Reset the last draw object flag for each renderer.
for (var rendererIndex = 0; rendererIndex < target.Renderers.Length; rendererIndex++)
{
var targetRenderer = target.Renderers[rendererIndex];
targetRenderer.IsLastDrawObjectInModel = false;
}
for (var controllerIndex = 0; controllerIndex < target.RenderControllers.Length; controllerIndex++)
{
var controller = target.RenderControllers[controllerIndex];
if (!controller
|| !controller.enabled
|| !controller.gameObject.activeSelf)
{
continue;
}
var lastIndex = target.Renderers.Length - 1;
while (lastIndex >= 0)
{
var targetRenderer = target.Renderers[lastIndex];
// Find the last draw renderer in the sorted array.
if (!targetRenderer.SkipRendering
&& targetRenderer.RenderController == controller)
{
// Mark it as the last draw object in the model.
targetRenderer.IsLastDrawObjectInModel = true;
break;
}
lastIndex--;
}
}
}
}
/// <summary>
/// Draws the objects using the provided command buffer and pass data.
/// </summary>
/// <param name="commandBuffer">Command buffer to record draw commands.</param>
/// <param name="data">Pass data containing render controllers and camera data.</param>
private static void DrawObjects(CommandBuffer commandBuffer, PassData data)
{
// Clear offscreen render textures at the beginning of the draw call.
CubismOffscreenRenderTextureManager.GetInstance().ClearRenderTextures(_commandBuffer);
// Check and set skip rendering flags for each renderer.
CheckRenderingSkip();
for (var groupIndex = 0; groupIndex < _sortedRendererGroupDataArray?.Length; groupIndex++)
{
var rendererGroup = _sortedRendererGroupDataArray[groupIndex];
if (rendererGroup.Renderers == null)
{
continue;
}
#if UNITY_EDITOR
// Calculate distance to camera for each renderer for CubismRenderInterceptorsManager events.
// HACK: Unity may mix scene camera and game camera information, so recalculate the distance each time in the Editor.
for (var rendererIndex = 0; rendererIndex < rendererGroup.Renderers.Length; rendererIndex++)
{
var target = rendererGroup.Renderers[rendererIndex];
if (!target)
{
continue;
}
target.CalculateDistanceToCamera(data.CameraData.worldSpaceCameraPos, data.CameraData.camera.transform.forward);
}
#endif
for (var rendererIndex = 0; rendererIndex < rendererGroup.Renderers.Length; rendererIndex++)
{
var renderer = rendererGroup.Renderers[rendererIndex];
// Skip if renderer is null or inactive
if (!renderer
|| renderer.SkipRendering)
{
continue;
}
var previousRenderer = rendererIndex > 0
? rendererGroup.Renderers[rendererIndex - 1]
: null;
var nextRenderer = rendererIndex < rendererGroup.Renderers.Length - 1
? rendererGroup.Renderers[rendererIndex + 1]
: null;
var args = new CubismRenderedEventArgs()
{
PassData = data,
CommandBuffer = commandBuffer,
ColorBuffer = renderer.RenderController.CurrentFrameBuffer,
DepthBuffer = data.CameraDepthTextureHandle,
SortingOrder = renderer.MeshRenderer.sortingOrder,
Drawable = renderer.Drawable,
SortingMode = renderer.RenderController.SortingMode,
GroupSortingOrder = rendererGroup.SortingIndex,
Distance = renderer.DistanceToCamera,
NextDistance = nextRenderer != null ? nextRenderer.DistanceToCamera : null,
PreviousDistance = previousRenderer != null ? previousRenderer.DistanceToCamera : null,
CameraPos = data.CameraData.worldSpaceCameraPos,
CameraForward = data.CameraData.camera.transform.forward
};
// Pre-rendering event.
CubismRenderingInterceptorsManager.GetInstance().OnPreRendering(args);
// Draw the object.
renderer.DrawObject(commandBuffer, data);
// Post-rendering event.
CubismRenderingInterceptorsManager.GetInstance().OnPostRendering(args);
if (renderer.IsLastDrawObjectInModel)
{
renderer.RenderController.SubmitDrawOffscreen(commandBuffer, data);
}
}
// Blit the result back to the camera texture if needed.
if (CubismRenderControllerGroup.GetInstance().IsCopiedToCameraTexture)
{
_commandBuffer.SetRenderTarget(data.CameraTextureHandle, data.CameraDepthTextureHandle);
// Draw the full-screen quad to blit the common rendering texture to the camera texture.
_blitRenderTextureMaterial.SetTexture(CubismShaderVariables.MainTexture, data.CommonRenderingTextureHandle);
// Check for reversed Z buffer.
var reversedZ = SystemInfo.usesReversedZBuffer ? GEqual : LEqual;
_blitRenderTextureMaterial.SetInt(CubismShaderVariables.ReversedZ, reversedZ);
// Draw the full-screen quad.
_commandBuffer.DrawMesh(_blitRenderTextureMesh, Matrix4x4.identity, _blitRenderTextureMaterial);
// Clear the common rendering texture for the next group.
_commandBuffer.SetRenderTarget(data.CommonRenderingTextureHandle);
_commandBuffer.ClearRenderTarget(true, true, Color.clear);
}
}
// Reset the flag after processing.
for (var i = 0; i < data.RenderControllers?.Length; i++)
{
var controller = data.RenderControllers[i];
if (!controller || !controller.enabled || !controller.gameObject.activeSelf)
{
continue;
}
controller.DidChangeSorting = false;
controller.DidChangeDrawableRenderOrder = false;
}
CubismRenderControllerGroup.GetInstance().DidChangeSortingRenderControllerGroup = false;
}
/// <summary>
/// ExecutePass is the function that executes the render pass.
/// This static method is passed as the RenderFunc delegate to the RenderGraph render pass.
/// It is used to execute draw commands.
/// </summary>
/// <param name="data">Pass data containing render controllers, camera data, and texture handles.</param>
/// <param name="context">Render graph context for executing render commands.</param>
private static void ExecutePass(PassData data, UnsafeGraphContext context)
{
// Check if we have any render controllers to process
if (data.RenderControllers == null || data.RenderControllers.Length == 0)
{
return;
}
if (_commandBuffer == null)
{
_commandBuffer = CommandBufferHelpers.GetNativeCommandBuffer(context.cmd);
}
if (!_blitRenderTextureMesh)
{
_blitRenderTextureMesh = new Mesh
{
vertices = CubismRenderer.OffscreenVertices,
uv = CubismRenderer.OffscreenUVs,
triangles = CubismRenderer.OffscreenTriangle
};
_blitRenderTextureMesh.RecalculateBounds();
}
if (!_blitRenderTextureMaterial)
{
_blitRenderTextureMaterial = new Material(CubismBuiltinMaterials.UnlitBlit);
}
#if UNITY_EDITOR
// HACK: In the editor, Scene view camera may not have the latest texture data.
if (data.CameraData.isSceneViewCamera)
{
_commandBuffer.Blit(data.CameraTextureHandle, data.CommonRenderingTextureHandle);
}
#endif
// Sort the renderers by their sorting order.
SortingRendererGroups(data);
// Set render target with both color and depth buffers for proper depth testing
_commandBuffer.SetRenderTarget(data.CommonRenderingTextureHandle, data.CameraDepthTextureHandle);
_commandBuffer.ClearRenderTarget(false, true, Color.clear);
// Draw the objects.
DrawObjects(_commandBuffer, data);
// Blit the result back to the camera texture.
if (!CubismRenderControllerGroup.GetInstance().IsCopiedToCameraTexture)
{
_commandBuffer.SetRenderTarget(data.CameraTextureHandle, data.CameraDepthTextureHandle);
// Draw the full-screen quad to blit the common rendering texture to the camera texture.
_blitRenderTextureMaterial.SetTexture(CubismShaderVariables.MainTexture, data.CommonRenderingTextureHandle);
// Check for reversed Z buffer.
var reversedZ = SystemInfo.usesReversedZBuffer? GEqual : LEqual;
_blitRenderTextureMaterial.SetInt(CubismShaderVariables.ReversedZ, reversedZ);
// Draw the full-screen quad.
_commandBuffer.DrawMesh(_blitRenderTextureMesh, Matrix4x4.identity, _blitRenderTextureMaterial);
}
// Clear the common rendering texture for the next frame.
_commandBuffer.SetRenderTarget(data.CommonRenderingTextureHandle);
_commandBuffer.ClearRenderTarget(true, true, Color.clear);
}
/// <summary>
/// This method is called by the render graph to record render passes.
/// RecordRenderGraph is where the RenderGraph handle can be accessed, through which render passes can be added to the graph.
/// FrameData is a context container through which URP resources can be accessed and managed.
/// </summary>
/// <param name="renderGraph">Render graph to add render passes to.</param>
/// <param name="frameData">Context container providing access to URP resources and camera data.</param>
public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameData)
{
const string renderCustomPass = "Cubism URP Render Pass";
// Get render controllers - this should work in both play mode and edit mode
var renderControllers = CubismRenderControllerGroup.GetInstance().RenderControllers;
// Skip if no render controllers are available
if (renderControllers == null || renderControllers.Length == 0)
{
return;
}
// This adds a raster render pass to the graph, specifying the name and the data type that will be passed to the ExecutePass function.
using (var builder = renderGraph.AddUnsafePass<PassData>(renderCustomPass, out var passData))
{
// Set up pass data
passData.RenderControllers = renderControllers;
var renderControllerGroups = CubismRenderControllerGroup.GetInstance().GroupDataArray;
passData.RenderControllerGroupDaraArray = renderControllerGroups;
var cameraData = frameData.Get<UniversalCameraData>();
var resourceData = frameData.Get<UniversalResourceData>();
passData.CameraData = cameraData;
passData.ResourceData = resourceData;
var descriptor = resourceData.activeColorTexture.GetDescriptor(renderGraph);
descriptor.wrapMode = TextureWrapMode.Repeat;
descriptor.filterMode = FilterMode.Point;
descriptor.name = "CommonTexture";
passData.CommonRenderingTextureHandle = renderGraph.CreateTexture(descriptor);
builder.UseTexture(passData.CommonRenderingTextureHandle, AccessFlags.ReadWrite);
descriptor.name = "TempTexture";
passData.CommonTemporaryTextureHandle = renderGraph.CreateTexture(descriptor);
builder.UseTexture(passData.CommonTemporaryTextureHandle, AccessFlags.ReadWrite);
descriptor.name = "MaskTexture";
var maskTextureHandle = renderGraph.CreateTexture(descriptor);
builder.UseTexture(maskTextureHandle, AccessFlags.ReadWrite);
passData.MaskTextureHandle = maskTextureHandle;
// This sets the render target of the pass to the active color texture. Change it to your own render target as needed.
passData.CameraTextureHandle = resourceData.activeColorTexture;
builder.UseTexture(passData.CameraTextureHandle, AccessFlags.ReadWrite);
// Set up depth buffer for proper depth testing with other Unity objects
passData.CameraDepthTextureHandle = resourceData.activeDepthTexture;
builder.UseTexture(passData.CameraDepthTextureHandle);
// Assigns the ExecutePass function to the render pass delegate. This will be called by the render graph when executing the pass.
builder.SetRenderFunc((PassData data, UnsafeGraphContext context) => ExecutePass(data, context));
}
}
}
/// <summary>
/// Scriptable render pass that will be injected into the renderer.
/// </summary>
private CubismRenderPass _mScriptablePass;
/// <summary>
/// Creates the scriptable render pass and configures its injection point.
/// </summary>
public override void Create()
{
_mScriptablePass = new CubismRenderPass
{
// Configures where the render pass should be injected.
renderPassEvent = RenderPassEvent.BeforeRenderingTransparents
};
}
/// <summary>
/// Injects one or multiple render passes into the renderer.
/// This method is called when setting up the renderer once per-camera.
/// </summary>
/// <param name="renderer">The scriptable renderer to add passes to.</param>
/// <param name="renderingData">Rendering state information.</param>
public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
{
#if UNITY_EDITOR
// Skip adding the pass for scene view and game view cameras in the editor.
if (!(renderingData.cameraData.cameraType == CameraType.Game
|| renderingData.cameraData.cameraType == CameraType.SceneView))
{
return;
}
#endif
renderer.EnqueuePass(_mScriptablePass);
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 3927fd6f4d7d83045b64b4cc46dcc3d7

Binary file not shown.

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 1f003999a57a1da40950cb24efcbacdc
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: b7754a173d6a71648a081287a1a8d77b
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,466 @@
/**
* 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 System;
using System.Collections.Generic;
using Live2D.Cubism.Core;
using Live2D.Cubism.Framework;
using UnityEngine;
using UnityEngine.Rendering;
namespace Live2D.Cubism.Rendering.URP.RenderingInterceptor
{
/// <summary>
/// Base class for rendering interceptors.
/// Place custom rendering logic by inheriting from this class.
/// </summary>
public class CubismRenderingInterceptController : MonoBehaviour, ICubismUpdatable, ICubismRenderingInterceptor
{
/// <summary>
/// Timing for invoking.
/// </summary>
public enum InvokeTiming
{
PreRendering,
PostRendering,
}
/// <summary>
/// Timing for invoking setting.
/// </summary>
[SerializeField]
public InvokeTiming InvokeTimingSetting;
/// <summary>
/// Modes for invoking.
/// </summary>
public enum InterceptingMode
{
SortingOrder,
ZDepth,
Drawable,
}
/// <summary>
/// Mode for invoking setting.
/// </summary>
[SerializeField]
public InterceptingMode Mode;
/// <summary>
/// Group sorting order for the interceptor.
/// </summary>
[SerializeField]
public int GroupSortingOrder = 0;
/// <summary>
/// Sorting order for the interceptor.
/// </summary>
[SerializeField]
public int SortingOrder = 0;
/// <summary>
/// Target art mesh for the interceptor.
/// </summary>
[SerializeField]
protected CubismDrawable _targetArtMesh;
/// <summary>
/// If attached <see cref="CubismRenderController"/>, reference.
/// </summary>
protected CubismRenderController _renderController;
/// <summary>
/// Renderer reference.
/// </summary>
protected Renderer _renderer;
/// <summary>
/// <see cref="_rederer"/>'s material reference.
/// </summary>
protected Material _material;
/// <summary>
/// Camera draw status.
/// </summary>
private struct CameraDrawStatus
{
public Camera Camera;
public int LastRenderedPassCount;
}
/// <summary>
/// Camera draw status array.
/// </summary>
private CameraDrawStatus[] _cameraDrawStatus;
/// <summary>
/// Render pass counter.
/// </summary>
private int _renderPassCounter = 0;
/// <summary>
/// Execution order of the interceptor.
/// </summary>
public virtual int ExecutionOrder
{
get
{
return CubismUpdateExecutionOrder.CubismPhysicsController + 10;
}
}
/// <summary>
/// Whether this interceptor needs to be updated during editing.
/// </summary>
public virtual bool NeedsUpdateOnEditing
{
get
{
return false;
}
}
/// <summary>
/// Whether a <see cref="CubismUpdateController"/> is attached to the same GameObject.
/// </summary>
public bool HasUpdateController
{
get;
set;
}
/// <summary>
/// Called by Unity when the component is enabled.
/// </summary>
public void OnEnable()
{
_renderController = GetComponent<CubismRenderController>();
if (!_renderController)
{
_renderer = GetComponent<Renderer>();
_renderer.enabled = false;
_material = _renderer.material;
#if UNITY_EDITOR
if (!Application.isPlaying)
{
_material = _renderer.sharedMaterial;
}
#endif
}
_cameraDrawStatus = Array.Empty<CameraDrawStatus>();
CubismRenderingInterceptorsManager.GetInstance().AddInterceptors(this);
RenderPipelineManager.beginContextRendering += OnBeginContextRendering;
}
/// <summary>
/// Called by Unity when the component starts.
/// </summary>
public void Start()
{
HasUpdateController = TryGetComponent<CubismUpdateController>(out _);
}
/// <summary>
/// Called by Unity when the component is disabled.
/// </summary>
public void OnDisable()
{
CubismRenderingInterceptorsManager.GetInstance().RemoveInterceptors(this);
RenderPipelineManager.beginContextRendering -= OnBeginContextRendering;
}
/// <summary>
/// Called by Unity at the beginning of context rendering.
/// </summary>
private void OnBeginContextRendering(ScriptableRenderContext context, List<Camera> cameras)
{
_renderPassCounter++;
}
/// <summary>
/// Called by Unity during LateUpdate.
/// </summary>
public void LateUpdate()
{
// Skip if an update controller is present.
if (HasUpdateController)
{
return;
}
OnLateUpdate();
}
/// <summary>
/// Called during LateUpdate if no <see cref="CubismUpdateController"/> is present.
/// </summary>
public virtual void OnLateUpdate()
{
}
/// <summary>
/// Called before rendering for each pass.
/// </summary>
/// <param name="args"> Event arguments. </param>
void ICubismRenderingInterceptor.OnPreRenderingForPass(CubismRenderedEventArgs args)
{
OnPreRendering(args);
}
/// <summary>
/// Called before rendering for each pass.
/// </summary>
/// <param name="args"> Event arguments. </param>
protected virtual void OnPreRendering(CubismRenderedEventArgs args)
{
// Skip if not invoking pre-rendering.
if (InvokeTimingSetting != InvokeTiming.PreRendering)
{
return;
}
TryDraw(args);
}
/// <summary>
/// Called after rendering for each pass.
/// </summary>
/// <param name="args"> Event arguments. </param>
void ICubismRenderingInterceptor.OnPostRenderingForPass(CubismRenderedEventArgs args)
{
OnPostRendering(args);
}
/// <summary>
/// Called after rendering for each pass.
/// </summary>
/// <param name="args"> Event arguments. </param>
protected virtual void OnPostRendering(CubismRenderedEventArgs args)
{
// Skip if not invoking post-rendering.
if (InvokeTimingSetting != InvokeTiming.PostRendering)
{
return;
}
TryDraw(args);
}
/// <summary>
/// Attempts to draw the object based on the interceptor's settings.
/// </summary>
/// <param name="args"> Event arguments. </param>
private void TryDraw(CubismRenderedEventArgs args)
{
var currentCamera = args.PassData.CameraData.camera;
// Find or create camera draw status.
var statusIndex = Array.FindIndex(_cameraDrawStatus, status => status.Camera == currentCamera);
// Create a new entry if not found.
if (statusIndex < 0)
{
// Create a new camera draw status entry.
Array.Resize(ref _cameraDrawStatus, _cameraDrawStatus.Length + 1);
statusIndex = _cameraDrawStatus.Length - 1;
// Initialize the new entry.
_cameraDrawStatus[statusIndex] = new CameraDrawStatus
{
Camera = currentCamera,
LastRenderedPassCount = -1,
};
}
// Get the current rendered frame count.
if (_cameraDrawStatus[statusIndex].LastRenderedPassCount == _renderPassCounter)
{
return;
}
var doDraw = true;
switch (Mode)
{
case InterceptingMode.SortingOrder:
// Decide whether to draw based on sorting order comparisons.
if (args.GroupSortingOrder != GroupSortingOrder || args.SortingOrder < SortingOrder)
{
doDraw = false;
}
break;
case InterceptingMode.ZDepth:
// Calculate the distance from the camera to the renderer.
var directionToRenderer = transform.position - args.CameraPos;
var projection = Vector3.Project(directionToRenderer, args.CameraForward);
var distance = projection.magnitude;
// Determine whether to draw based on distance comparisons.
var canDraw = (args.Distance < distance || args.NextDistance > distance) &&
!(distance > args.Distance && distance > args.NextDistance &&
distance > args.PreviousDistance);
// Decide whether to draw.
if (args.PreviousDistance != null
&& args.NextDistance != null
&& canDraw)
{
doDraw = false;
break;
}
if (distance < args.Distance)
{
doDraw = false;
}
break;
case InterceptingMode.Drawable:
if (!_targetArtMesh || args.Drawable != _targetArtMesh)
{
doDraw = false;
}
break;
default:
// Skip drawing for unknown modes.
break;
}
if (!doDraw)
{
return;
}
if (!_renderController)
{
// Draw the object.
args.CommandBuffer.DrawRenderer(_renderer, _material);
}
else
{
for (var index = 0; index < _renderController.SortedRenderers.Length; index++)
{
var targetRenderer = _renderController.SortedRenderers[index];
// Check whether to skip rendering.
CheckSkipRendering(targetRenderer);
if (targetRenderer.SkipRendering)
{
continue;
}
// Draw the object.
targetRenderer.DrawObject(args.CommandBuffer, args.PassData);
}
// Submit offscreen draws.
_renderController.SubmitDrawOffscreen(args.CommandBuffer, args.PassData);
}
// Mark as drawn.
_cameraDrawStatus[statusIndex].LastRenderedPassCount = _renderPassCounter;
}
/// <summary>
/// Checks whether to skip rendering for the given renderer.
/// </summary>
/// <param name="targetRenderer"> The target renderer. </param>
private void CheckSkipRendering(CubismRenderer targetRenderer)
{
targetRenderer.SkipRendering |= !targetRenderer
|| !targetRenderer.gameObject.activeSelf
|| !targetRenderer.MeshRenderer
|| !targetRenderer.MeshRenderer.enabled;
switch (targetRenderer.DrawObjectType)
{
case CubismModelTypes.DrawObjectType.Drawable:
targetRenderer.SkipRendering |= targetRenderer.Opacity <= 0.0f;
break;
case CubismModelTypes.DrawObjectType.Offscreen:
targetRenderer.SkipRendering |= targetRenderer.Offscreen.Opacity <= 0.0f;
if (!targetRenderer.SkipRendering)
{
break;
}
// Skip rendering for all child drawables and offscreens of the offscreen part.
var parts = targetRenderer.RenderController.Model.Parts;
CubismPart part = null;
for (var partIndex = 0; partIndex < parts.Length; partIndex++)
{
if (parts[partIndex].UnmanagedIndex != targetRenderer.Offscreen.OwnerIndex)
{
continue;
}
part = parts[partIndex];
break;
}
if (!part)
{
break;
}
// Skip rendering for all child drawables and offscreens of the offscreen part.
for (var drawablesIndex = 0; drawablesIndex < part.AllChildDrawables?.Length; drawablesIndex++)
{
var childDrawable = part.AllChildDrawables[drawablesIndex];
for (var rendererIndex = 0; rendererIndex < targetRenderer.RenderController.Renderers.Length; rendererIndex++)
{
var checkedRenderer = targetRenderer.RenderController.Renderers[rendererIndex];
if (checkedRenderer.DrawObjectType != CubismModelTypes.DrawObjectType.Drawable
|| checkedRenderer.Drawable.UnmanagedIndex != childDrawable.UnmanagedIndex)
{
continue;
}
checkedRenderer.SkipRendering = true;
break;
}
}
for (var offscreensIndex = 0; offscreensIndex < part.AllChildOffscreens?.Length; offscreensIndex++)
{
var childOffscreen = part.AllChildOffscreens[offscreensIndex];
for (var j = 0; j < targetRenderer.RenderController.Renderers.Length; j++)
{
var checkedRenderer = targetRenderer.RenderController.Renderers[j];
if (checkedRenderer.DrawObjectType != CubismModelTypes.DrawObjectType.Offscreen
|| checkedRenderer.Offscreen.UnmanagedIndex != childOffscreen.UnmanagedIndex)
{
continue;
}
checkedRenderer.SkipRendering = true;
break;
}
}
break;
default:
targetRenderer.SkipRendering = true;
break;
}
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 42ee021d862afdb4dbc221ac3f20ced9

View File

@@ -0,0 +1,177 @@
/**
* 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 System;
using UnityEngine;
namespace Live2D.Cubism.Rendering.URP.RenderingInterceptor
{
public class CubismRenderingInterceptorsManager
{
/// <summary>
/// Singleton instance.
/// </summary>
private static CubismRenderingInterceptorsManager _instance;
/// <summary>
/// Singleton instance of the <see cref="CubismRenderingInterceptorsManager"/>.
/// </summary>
public static CubismRenderingInterceptorsManager GetInstance()
{
if (_instance == null)
{
_instance = new CubismRenderingInterceptorsManager();
}
return _instance;
}
/// <summary>
/// <see cref="Interceptors"/>'s backing field.
/// </summary>
private ICubismRenderingInterceptor[] _interceptors;
/// <summary>
/// Registered rendering interceptors.
/// </summary>
public ICubismRenderingInterceptor[] Interceptors
{
get { return _interceptors; }
set { _interceptors = value; }
}
/// <summary>
/// Private constructor to enforce singleton pattern.
/// </summary>
private CubismRenderingInterceptorsManager()
{
_interceptors = Array.Empty<ICubismRenderingInterceptor>();
}
/// <summary>
/// Adds the given interceptor to the manager.
/// </summary>
/// <param name="interceptor"> The interceptor to add. </param>
public void AddInterceptors(ICubismRenderingInterceptor interceptor)
{
RemoveInterceptors(interceptor);
for (var interceptorIndex = 0; interceptorIndex < Interceptors.Length; interceptorIndex++)
{
var current = Interceptors[interceptorIndex];
if (interceptor != current)
{
continue;
}
Debug.LogWarning($"This component is already registered.");
return;
}
Array.Resize(ref _interceptors, Interceptors.Length + 1);
Interceptors[^1] = interceptor;
}
/// <summary>
/// Removes the given interceptor from the manager.
/// </summary>
/// <param name="interceptor"> The interceptor to remove. </param>
public void RemoveInterceptors(ICubismRenderingInterceptor interceptor)
{
var targetIndex = -1;
for (var interceptorIndex = 0; interceptorIndex < Interceptors.Length; interceptorIndex++)
{
if (Interceptors[interceptorIndex] != interceptor)
{
continue;
}
targetIndex = interceptorIndex;
break;
}
if (targetIndex < 0)
{
return;
}
for (var interceptorIndex = targetIndex; interceptorIndex < Interceptors.Length - 1; interceptorIndex++)
{
Interceptors[interceptorIndex] = Interceptors[interceptorIndex + 1];
}
Array.Resize(ref _interceptors, Interceptors.Length - 1);
}
/// <summary>
/// Reorders the given interceptor to the new index.
/// </summary>
/// <param name="interceptor"> The interceptor to reorder. </param>
/// <param name="newIndex"> The new index to move the interceptor to. </param>
public void ReorderIndices(ICubismRenderingInterceptor interceptor, int newIndex)
{
// Find the current index of the interceptor.
var currentIndex = Array.IndexOf(Interceptors, interceptor);
if (currentIndex < 0
|| newIndex < 0
|| newIndex >= Interceptors.Length
|| currentIndex == newIndex)
{
// Return silently.
return;
}
var temp = Interceptors[currentIndex];
if (currentIndex < newIndex)
{
// Shift elements to the left.
for (var i = currentIndex; i < newIndex; i++)
{
Interceptors[i] = Interceptors[i + 1];
}
}
else
{
// Shift elements to the right.
for (var i = currentIndex; i > newIndex; i--)
{
Interceptors[i] = Interceptors[i - 1];
}
}
// Place the interceptor at the new index.
Interceptors[newIndex] = temp;
}
/// <summary>
/// Called before rendering.
/// </summary>
/// <param name="args"> Rendering event arguments. </param>
public void OnPreRendering(CubismRenderedEventArgs args)
{
for (var index = 0; index < Interceptors.Length; index++)
{
Interceptors[index].OnPreRenderingForPass(args);
}
}
/// <summary>
/// Called after rendering.
/// </summary>
/// <param name="args"> Rendering event arguments. </param>
public void OnPostRendering(CubismRenderedEventArgs args)
{
for (var index = 0; index < Interceptors.Length; index++)
{
Interceptors[index].OnPostRenderingForPass(args);
}
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 89f91d617d8871840a61c1510182383a

View File

@@ -0,0 +1,28 @@
/**
* 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.
*/
namespace Live2D.Cubism.Rendering.URP.RenderingInterceptor
{
/// <summary>
/// Rendering interceptor interface.
/// </summary>
public interface ICubismRenderingInterceptor
{
/// <summary>
/// Called by CubismRenderingInterceptorsManager before rendering for Drawables and Offscreens.
/// </summary>
/// <param name="args"> Rendering event arguments. </param>
internal void OnPreRenderingForPass(CubismRenderedEventArgs args);
/// <summary>
/// Called by CubismRenderingInterceptorsManager after rendering for Drawables and Offscreens.
/// </summary>
/// <param name="args"> Rendering event arguments. </param>
internal void OnPostRenderingForPass(CubismRenderedEventArgs args);
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: e62e03fd9d651f14b9028f4e5b8d4009