/** * 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.Masking; using System; using UnityEngine; using UnityEngine.Rendering; namespace Live2D.Cubism.Rendering { /// /// Wrapper for drawing s. /// [ExecuteInEditMode, RequireComponent(typeof(MeshFilter), typeof(MeshRenderer))] public sealed class CubismRenderer : MonoBehaviour { /// /// backing field. /// [SerializeField, HideInInspector] private int _localSortingOrder; /// /// Local sorting order. /// public int LocalSortingOrder { get { return _localSortingOrder; } set { // Return early if same value given. if (value == _localSortingOrder) { return; } // Store value. _localSortingOrder = value; // Apply it. ApplySorting(); } } /// /// backing field. /// [SerializeField, HideInInspector] private Color _color = Color.white; /// /// Color. /// public Color Color { get { return _color; } set { // Return early if same value given. if (value == _color) { return; } // Store value. _color = value; // Apply color. ApplyVertexColors(); } } /// /// . /// public Material Material { get { #if UNITY_EDITOR if (!Application.isPlaying) { return MeshRenderer.sharedMaterial; } #endif return MeshRenderer.material; } set { #if UNITY_EDITOR if (!Application.isPlaying) { MeshRenderer.sharedMaterial = value; return; } #endif MeshRenderer.material = value; } } /// /// backing field. /// [SerializeField, HideInInspector] private Texture2D _mainTexture; /// /// 's main texture. /// public Texture2D MainTexture { get { return _mainTexture; } set { // Return early if same value given and main texture is valid. if (value == _mainTexture && _mainTexture != null) { return; } // Store value. _mainTexture = (value != null) ? value : Texture2D.whiteTexture; // Apply it. ApplyMainTexture(); } } /// /// Meshes. /// /// /// Double buffering dynamic meshes increases performance on mobile, so we double-buffer them here. /// private Mesh[] Meshes { get; set; } /// /// Index of front buffer mesh. /// private int FrontMesh { get; set; } /// /// Index of back buffer mesh.. /// private int BackMesh { get; set; } /// /// . /// public Mesh Mesh { get { return Meshes[FrontMesh]; } } /// /// backing field. /// [NonSerialized] private MeshFilter _meshFilter; /// /// . /// public MeshFilter MeshFilter { get { return _meshFilter; } } /// /// backing field. /// [NonSerialized] private MeshRenderer _meshRenderer; /// /// . /// public MeshRenderer MeshRenderer { get { return _meshRenderer; } } #region Interface For CubismRenderController /// /// backing field. /// [SerializeField, HideInInspector] private CubismSortingMode _sortingMode; /// /// Sorting mode. /// private CubismSortingMode SortingMode { get { return _sortingMode; } set { _sortingMode = value; } } /// /// backing field. /// [SerializeField, HideInInspector] private int _sortingOrder; /// /// Sorting mode. /// private int SortingOrder { get { return _sortingOrder; } set { _sortingOrder = value; } } /// /// backing field. /// [SerializeField, HideInInspector] private int _renderOrder; /// /// Sorting mode. /// private int RenderOrder { get { return _renderOrder; } set { _renderOrder = value; } } /// /// backing field. /// [SerializeField, HideInInspector] private float _depthOffset = 0.00001f; /// /// Offset to apply in case of depth sorting. /// private float DepthOffset { get { return _depthOffset; } set { _depthOffset = value; } } /// /// backing field. /// [SerializeField, HideInInspector] private float _opacity; /// /// Opacity. /// private float Opacity { get { return _opacity; } set { _opacity = value; } } /// /// Buffer for vertex colors. /// private Color[] VertexColors { get; set; } /// /// Allows tracking of what vertex data was updated last swap. /// private SwapInfo LastSwap { get; set; } /// /// Allows tracking of what vertex data will be swapped. /// private SwapInfo ThisSwap { get; set; } /// /// Swaps mesh buffers. /// /// /// Make sure to manually call this method in case you changed the . /// public void SwapMeshes() { // Perform internal swap. BackMesh = FrontMesh; FrontMesh = (FrontMesh == 0) ? 1 : 0; var mesh = Meshes[FrontMesh]; // Update colors. Meshes[BackMesh].colors = VertexColors; // Update swap info. LastSwap = ThisSwap; ResetSwapInfoFlags(); // Apply swap. #if UNITY_EDITOR if (!Application.isPlaying) { MeshFilter.mesh = mesh; return; } #endif MeshFilter.mesh = mesh; } /// /// Updates visibility. /// public void UpdateVisibility() { if (LastSwap.DidBecomeVisible) { MeshRenderer.enabled = true; } else if (LastSwap.DidBecomeInvisible) { MeshRenderer.enabled = false; } ResetVisibilityFlags(); } /// /// Updates render order. /// public void UpdateRenderOrder() { if (LastSwap.NewRenderOrder) { ApplySorting(); } ResetRenderOrderFlag(); } /// /// Updates sorting layer. /// /// New sorting layer. internal void OnControllerSortingLayerDidChange(int newSortingLayer) { MeshRenderer.sortingLayerID = newSortingLayer; } /// /// Updates sorting mode. /// /// New sorting mode. internal void OnControllerSortingModeDidChange(CubismSortingMode newSortingMode) { SortingMode = newSortingMode; ApplySorting(); } /// /// Updates sorting order. /// /// New sorting order. internal void OnControllerSortingOrderDidChange(int newSortingOrder) { SortingOrder = newSortingOrder; ApplySorting(); } /// /// Updates depth offset. /// /// internal void OnControllerDepthOffsetDidChange(float newDepthOffset) { DepthOffset = newDepthOffset; ApplySorting(); } /// /// Sets the opacity. /// /// New opacity. internal void OnDrawableOpacityDidChange(float newOpacity) { Opacity = newOpacity; ApplyVertexColors(); } /// /// Updates render order. /// /// New render order. internal void OnDrawableRenderOrderDidChange(int newRenderOrder) { RenderOrder = newRenderOrder; SetNewRenderOrder(); } /// /// Sets the . /// /// Vertex positions to set. internal void OnDrawableVertexPositionsDidChange(Vector3[] newVertexPositions) { var mesh = Mesh; // Apply positions and update bounds. mesh.vertices = newVertexPositions; mesh.RecalculateBounds(); // Set swap flag. SetNewVertexPositions(); } /// /// Sets visibility. /// /// New visibility. internal void OnDrawableVisiblityDidChange(bool newVisibility) { // Set swap flag if visible. if (newVisibility) { BecomeVisible(); } else { BecomeInvisible(); } } /// /// Sets mask properties. /// /// Value to set. internal void OnMaskPropertiesDidChange(CubismMaskProperties newMaskProperties) { MeshRenderer.GetPropertyBlock(SharedPropertyBlock); // Write properties. SharedPropertyBlock.SetTexture(CubismShaderVariables.MaskTexture, newMaskProperties.Texture); SharedPropertyBlock.SetVector(CubismShaderVariables.MaskTile, newMaskProperties.Tile); SharedPropertyBlock.SetVector(CubismShaderVariables.MaskTransform, newMaskProperties.Transform); MeshRenderer.SetPropertyBlock(SharedPropertyBlock); } /// /// Sets model opacity. /// /// Opacity to set. internal void OnModelOpacityDidChange(float newModelOpacity) { _meshRenderer.GetPropertyBlock(SharedPropertyBlock); // Write property. SharedPropertyBlock.SetFloat(CubismShaderVariables.ModelOpacity, newModelOpacity); MeshRenderer.SetPropertyBlock(SharedPropertyBlock); } #endregion /// /// backing field. /// private static MaterialPropertyBlock _sharedPropertyBlock; /// /// that can be shared on the main script thread. /// private static MaterialPropertyBlock SharedPropertyBlock { get { // Lazily initialize. if (_sharedPropertyBlock == null) { _sharedPropertyBlock = new MaterialPropertyBlock(); } return _sharedPropertyBlock; } } /// /// Applies main texture for rendering. /// private void ApplyMainTexture() { MeshRenderer.GetPropertyBlock(SharedPropertyBlock); // Write property. SharedPropertyBlock.SetTexture(CubismShaderVariables.MainTexture, MainTexture); MeshRenderer.SetPropertyBlock(SharedPropertyBlock); } /// /// Applies sorting. /// private void ApplySorting() { // Sort by order. if (SortingMode.SortByOrder()) { MeshRenderer.sortingOrder = SortingOrder + ((SortingMode == CubismSortingMode.BackToFrontOrder) ? (RenderOrder + LocalSortingOrder) : -(RenderOrder + LocalSortingOrder)); transform.localPosition = Vector3.zero; return; } // Sort by depth. var offset = (SortingMode == CubismSortingMode.BackToFrontZ) ? -DepthOffset : DepthOffset; MeshRenderer.sortingOrder = SortingOrder + LocalSortingOrder; transform.localPosition = new Vector3(0f, 0f, RenderOrder * offset); } /// /// Uploads mesh vertex colors. /// public void ApplyVertexColors() { var vertexColors = VertexColors; var color = Color; color.a *= Opacity; for (var i = 0; i < vertexColors.Length; ++i) { vertexColors[i] = color; } // Set swap flag. SetNewVertexColors(); } /// /// Initializes the mesh renderer. /// private void TryInitializeMeshRenderer() { if (_meshRenderer == null) { _meshRenderer = GetComponent(); // Lazily add component. if (_meshRenderer == null) { _meshRenderer = gameObject.AddComponent(); _meshRenderer.hideFlags = HideFlags.HideInInspector; _meshRenderer.receiveShadows = false; _meshRenderer.shadowCastingMode = ShadowCastingMode.Off; _meshRenderer.lightProbeUsage = LightProbeUsage.BlendProbes; } } } /// /// Initializes the mesh filter. /// private void TryInitializeMeshFilter() { if (_meshFilter == null) { _meshFilter = GetComponent(); // Lazily add component. if (_meshFilter == null) { _meshFilter = gameObject.AddComponent(); _meshFilter.hideFlags = HideFlags.HideInInspector; } } } /// /// Initializes the mesh if necessary. /// private void TryInitializeMesh() { // Only create mesh if necessary. // HACK 'Mesh.vertex > 0' makes sure mesh is recreated in case of runtime instantiation. if (Meshes != null && Mesh.vertexCount > 0) { return; } // Create mesh for attached drawable. var drawable = GetComponent(); if (Meshes == null) { Meshes = new Mesh[2]; } for (var i = 0; i < 2; ++i) { var mesh = new Mesh { name = drawable.name, vertices = drawable.VertexPositions, uv = drawable.VertexUvs, triangles = drawable.Indices }; mesh.MarkDynamic(); mesh.RecalculateBounds(); // Store mesh. Meshes[i] = mesh; } } /// /// Initializes vertex colors. /// private void TryInitializeVertexColor() { var mesh = Mesh; VertexColors = new Color[mesh.vertexCount]; for (var i = 0; i < VertexColors.Length; ++i) { VertexColors[i] = Color; VertexColors[i].a *= Opacity; } } /// /// Initializes the main texture if possible. /// private void TryInitializeMainTexture() { if (MainTexture == null) { MainTexture = null; } ApplyMainTexture(); } /// /// Initializes components if possible. /// public void TryInitialize() { TryInitializeMeshRenderer(); TryInitializeMeshFilter(); TryInitializeMesh(); TryInitializeVertexColor(); TryInitializeMainTexture(); ApplySorting(); } #region Swap Info /// /// Sets . /// private void SetNewVertexPositions() { var swapInfo = ThisSwap; swapInfo.NewVertexPositions = true; ThisSwap = swapInfo; } /// /// Sets . /// private void SetNewVertexColors() { var swapInfo = ThisSwap; swapInfo.NewVertexColors = true; ThisSwap = swapInfo; } /// /// Sets on visible. /// private void BecomeVisible() { var swapInfo = ThisSwap; swapInfo.DidBecomeVisible = true; ThisSwap = swapInfo; } /// /// Sets on invisible. /// private void BecomeInvisible() { var swapInfo = ThisSwap; swapInfo.DidBecomeInvisible = true; ThisSwap = swapInfo; } /// /// Sets . /// private void SetNewRenderOrder() { var swapInfo = ThisSwap; swapInfo.NewRenderOrder = true; ThisSwap = swapInfo; } /// /// Resets flags. /// private void ResetSwapInfoFlags() { var swapInfo = ThisSwap; swapInfo.NewVertexColors = false; swapInfo.NewVertexPositions = false; swapInfo.DidBecomeVisible = false; swapInfo.DidBecomeInvisible = false; ThisSwap = swapInfo; } /// /// Reset visibility flags. /// private void ResetVisibilityFlags() { var swapInfo = LastSwap; swapInfo.DidBecomeVisible = false; swapInfo.DidBecomeInvisible = false; LastSwap = swapInfo; } /// /// Reset render order flag. /// private void ResetRenderOrderFlag() { var swapInfo = LastSwap; swapInfo.NewRenderOrder = false; LastSwap = swapInfo; } /// /// Allows tracking of data changed on a swap. /// private struct SwapInfo { /// /// Vertex positions were changed. /// public bool NewVertexPositions { get; set; } /// /// Vertex colors were changed. /// public bool NewVertexColors { get; set; } /// /// Visibility were changed to visible. /// public bool DidBecomeVisible { get; set; } /// /// Visibility were changed to invisible. /// public bool DidBecomeInvisible { get; set; } /// /// Render order were changed. /// public bool NewRenderOrder { get; set; } } #endregion #region Unity Events Handling /// /// Finalizes instance. /// private void OnDestroy() { if (Meshes == null) { return; } for (var i = 0; i < Meshes.Length; i++) { DestroyImmediate(Meshes[i]); } } #endregion } }