/** * 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.Json; using Live2D.Cubism.Rendering.Util; using System; using UnityEngine; using UnityEngine.Rendering; using UnityEngine.Serialization; namespace Live2D.Cubism.Rendering { /// /// Wrapper for drawing s. /// [ExecuteInEditMode, RequireComponent(typeof(MeshRenderer))] public sealed partial 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(); } } /// /// backing field. /// [FormerlySerializedAs("_isOverriddenDrawableMultiplyColors")] [SerializeField, HideInInspector] private bool isOverriddenDrawObjectMultiplyColors; /// /// Whether to override with multiply color from the model. /// public bool DrawObjectMultiplyColorEnabled { get { return isOverriddenDrawObjectMultiplyColors; } set { isOverriddenDrawObjectMultiplyColors = value; } } /// /// Last . /// public bool LastIsUseUserMultiplyColor { get; set; } /// /// backing field. /// [FormerlySerializedAs("_isOverriddenDrawableScreenColors")] [SerializeField, HideInInspector] private bool _isOverriddenDrawObjectScreenColors; /// /// Whether to override with screen color from the model. /// public bool DrawObjectScreenColorEnabled { get { return _isOverriddenDrawObjectScreenColors; } set { _isOverriddenDrawObjectScreenColors = value; } } /// /// Last . /// public bool LastIsUseUserScreenColors { get; set; } /// /// backing field. /// [SerializeField, HideInInspector] private Color _multiplyColor = Color.white; /// /// Drawable Multiply Color. /// public Color MultiplyColor { get { // If it can overwrite multiply color, return it. if (RenderController.MultiplyColorEnabled || DrawObjectMultiplyColorEnabled) { return _multiplyColor; } switch (DrawObjectType) { case CubismModelTypes.DrawObjectType.Drawable: return Drawable.MultiplyColor; case CubismModelTypes.DrawObjectType.Offscreen: return Offscreen.MultiplyColor; default: // If we are not a drawable or offscreen, return the default value. return _multiplyColor; } } set { // Return early if same value given. if (value == _multiplyColor) { return; } // Store value. _multiplyColor = (value != null) ? value : Color.white; } } /// /// Last Drawable Multiply Color. /// public Color LastMultiplyColor { get; set; } /// /// backing field. /// [SerializeField, HideInInspector] private Color _screenColor = Color.clear; /// /// Drawable Screen Color. /// public Color ScreenColor { get { if (RenderController.ScreenColorEnabled || DrawObjectScreenColorEnabled) { return _screenColor; } switch (DrawObjectType) { case CubismModelTypes.DrawObjectType.Drawable: return Drawable.ScreenColor; case CubismModelTypes.DrawObjectType.Offscreen: return Offscreen.ScreenColor; default: // If we are not a drawable or offscreen, return the default value. return _screenColor; } } set { // Return early if same value given. if (value == _screenColor) { return; } // Store value. _screenColor = (value != null) ? value : Color.black; } } /// /// Last Drawable Screen Color. /// public Color LastScreenColor { get; set; } /// /// . /// public Material Material { get { #if UNITY_EDITOR if (!Application.isPlaying) { if (!MeshRenderer.sharedMaterial) { MeshRenderer.sharedMaterial = SetMaterialFromPicker(); } return MeshRenderer.sharedMaterial; } #endif if (!MeshRenderer.material) { MeshRenderer.material = SetMaterialFromPicker(); } 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 { if (_mainTexture == null && DrawObjectType != CubismModelTypes.DrawObjectType.Drawable) { _mainTexture = new Texture2D(1, 1); _mainTexture.SetPixel(1, 1, Color.clear); } return _mainTexture; } set { // Return early if same value given and main texture is valid. if (value == _mainTexture && _mainTexture != null) { return; } if (DrawObjectType != CubismModelTypes.DrawObjectType.Drawable) { _mainTexture = new Texture2D(1, 1); _mainTexture.SetPixel(1, 1, Color.clear); } else { // 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 { if (DrawObjectType == CubismModelTypes.DrawObjectType.Offscreen) { return _offscreenMesh; } return FrontMesh < Meshes?.Length ? Meshes?[FrontMesh] : null; } } /// /// backing field. /// [NonSerialized] private MeshRenderer _meshRenderer; /// /// . /// public MeshRenderer MeshRenderer { get { TryInitializeMeshRenderer(); return _meshRenderer; } } /// /// 's backing field. /// [NonSerialized] private MeshFilter _meshFilter; /// /// for Scene View picking support. /// public MeshFilter MeshFilter { get { TryInitializeMeshFilter(); return _meshFilter; } set { if (value == _meshFilter || value == null) { return; } _meshFilter = value; } } /// /// 's backing field. /// [SerializeField, HideInInspector] private Material _drawMaterial; /// /// Material used for CommandBuffer rendering (the actual rendering material). /// public Material DrawMaterial { get { // Only return _drawMaterial for Drawable type (picking is only for Drawable). if (DrawObjectType != CubismModelTypes.DrawObjectType.Drawable) { return null; } return _drawMaterial; } set { _drawMaterial = value; } } /// /// . /// public CubismDrawable Drawable { get; set; } /// /// . /// internal CubismRenderController RenderController { get; set; } #region Interface For CubismRenderController /// /// backing field. /// [SerializeField, HideInInspector] private CubismSortingMode _sortingMode; /// /// Sorting mode. /// internal 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. /// internal 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; // Update colors. Meshes[BackMesh].colors = VertexColors; // Update swap info. LastSwap = ThisSwap; ResetSwapInfoFlags(); #if UNITY_EDITOR SyncMeshFilterForPicking(); #endif } /// /// 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. /// /// New depth offset value. 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) { if (RenderOrder == newRenderOrder) return; 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 model opacity. /// /// Opacity to set. internal void OnModelOpacityDidChange(float newModelOpacity) { var property = PropertyBlock; _meshRenderer.GetPropertyBlock(property); // Write property. property.SetFloat(CubismShaderVariables.ModelOpacity, newModelOpacity); MeshRenderer.SetPropertyBlock(property); } #endregion /// /// Applies main texture for rendering. /// private void ApplyMainTexture() { var property = PropertyBlock; MeshRenderer.GetPropertyBlock(property); // Write property. property.SetTexture(CubismShaderVariables.MainTexture, MainTexture); MeshRenderer.SetPropertyBlock(property); } /// /// Applies sorting. /// private void ApplySorting() { // Return early if no controller or model. if (!RenderController || !RenderController.Model) { return; } RenderController.DidChangeSorting = true; // 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(); } /// /// Uploads diffuse colors. /// public void ApplyMultiplyColor() { if (DrawObjectType != CubismModelTypes.DrawObjectType.Drawable) { return; } var property = PropertyBlock; MeshRenderer.GetPropertyBlock(property); // Write property. property.SetColor(CubismShaderVariables.MultiplyColor, MultiplyColor); MeshRenderer.SetPropertyBlock(property); } /// /// Initializes the main texture if possible. /// private void TryInitializeMultiplyColor() { LastIsUseUserMultiplyColor = false; LastMultiplyColor = MultiplyColor; if (DrawObjectType != CubismModelTypes.DrawObjectType.Drawable) { return; } ApplyMultiplyColor(); } /// /// Uploads tint colors. /// public void ApplyScreenColor() { if (DrawObjectType != CubismModelTypes.DrawObjectType.Drawable) { return; } var property = PropertyBlock; MeshRenderer.GetPropertyBlock(property); // Write property. property.SetColor(CubismShaderVariables.ScreenColor, ScreenColor); MeshRenderer.SetPropertyBlock(property); } /// /// Initializes the main texture if possible. /// private void TryInitializeScreenColor() { LastIsUseUserScreenColors = false; LastScreenColor = ScreenColor; if (DrawObjectType != CubismModelTypes.DrawObjectType.Drawable) { return; } ApplyScreenColor(); } /// /// Sets material from picker. /// public Material SetMaterialFromPicker() { Material material = null; switch (DrawObjectType) { case CubismModelTypes.DrawObjectType.Drawable: if (!Drawable) { break; } material = CubismBuiltinPickers.DrawableMaterialPicker(null, Drawable); break; case CubismModelTypes.DrawObjectType.Offscreen: if (!Offscreen) { break; } material = CubismBuiltinPickers.OffscreenMaterialPicker(null, Offscreen); break; default: material = CubismBuiltinMaterials.GetBlendModeMaterial("UnlitBlendMode", BlendTypes.ColorBlend.Normal, BlendTypes.AlphaBlend.Over, false, false, true); Debug.LogError("Unsupported DrawObjectType."); break; } return material; } /// /// Initializes the mesh renderer. /// private void TryInitializeMeshRenderer() { if (!_meshRenderer) { _meshRenderer = GetComponent(); // Lazily add component. if (!_meshRenderer) { _meshRenderer = gameObject.AddComponent(); _meshRenderer.hideFlags = HideFlags.HideInInspector; _meshRenderer.receiveShadows = false; _meshRenderer.shadowCastingMode = ShadowCastingMode.Off; _meshRenderer.lightProbeUsage = LightProbeUsage.BlendProbes; } } #if UNITY_EDITOR if (!Application.isPlaying) { if (!_meshRenderer.sharedMaterial) { _meshRenderer.sharedMaterial = SetMaterialFromPicker(); } return; } #endif if (!_meshRenderer.material) { _meshRenderer.material = SetMaterialFromPicker(); } } /// /// Initializes the mesh filter for Scene View picking. /// private void TryInitializeMeshFilter() { #if UNITY_EDITOR if (DrawObjectType != CubismModelTypes.DrawObjectType.Drawable || Application.isPlaying) { return; } if (_meshFilter != null) { return; } _meshFilter = GetComponent(); // Lazily add component if missing. if (_meshFilter == null) { _meshFilter = gameObject.AddComponent(); _meshFilter.hideFlags = HideFlags.HideInInspector; } _meshFilter.sharedMesh = Mesh; SetupPickingMaterial(); #endif } #if UNITY_EDITOR /// /// Sets up materials for Scene View picking. /// private void SetupPickingMaterial() { if (_meshRenderer == null) { return; } var currentMaterial = _meshRenderer.sharedMaterial; if (_drawMaterial == null) { // If current material is TransparentPicking or null, get the correct material via picker. if (currentMaterial == null || currentMaterial == CubismBuiltinMaterials.TransparentPicking || (currentMaterial.shader != null && currentMaterial.shader.name == "Live2D Cubism/TransparentPicking")) { _drawMaterial = SetMaterialFromPicker(); } else { _drawMaterial = currentMaterial; } } _meshRenderer.sharedMaterial = CubismBuiltinMaterials.TransparentPicking; // Set MainTexture for alpha test in picking shader. if (MainTexture != null) { ApplyMainTexture(); } } /// /// Syncs the current mesh to the MeshFilter for Scene View picking. /// private void SyncMeshFilterForPicking() { if (DrawObjectType != CubismModelTypes.DrawObjectType.Drawable) { return; } if (_meshFilter == null || Mesh == null) { return; } _meshFilter.sharedMesh = Mesh; } #endif /// /// Initializes the mesh if necessary. /// private void TryInitializeMesh() { // Only create mesh if necessary. // HACK: 'Mesh != null' is individually implemented to avoid errors caused by the absence of a backing field. // HACK: 'Mesh.vertex > 0' makes sure mesh is recreated in case of runtime instantiation. if ((Meshes != null && Meshes.Length == 2 && Mesh != null && Mesh.vertexCount > 0 && Drawable?.VertexPositions != null && Mesh.vertexCount == Drawable?.VertexPositions.Length) || (DrawObjectType == CubismModelTypes.DrawObjectType.Offscreen && _offscreenMesh)) { return; } if (DrawObjectType == CubismModelTypes.DrawObjectType.Offscreen) { Meshes = new Mesh[1]; _offscreenMesh = new Mesh { vertices = OffscreenVertices, uv = OffscreenUVs, triangles = OffscreenTriangle }; Meshes[0] = _offscreenMesh; return; } if (Meshes != null) { for (var i = 0; i < Meshes.Length; i++) { DestroyImmediate(Meshes[i]); } } Meshes = new Mesh[2]; for (var i = 0; i < 2; ++i) { var mesh = new Mesh(); mesh.name = Drawable.name; mesh.vertices = Drawable.VertexPositions; mesh.uv = Drawable.VertexUvs; mesh.triangles = Drawable.Indices; mesh.MarkDynamic(); mesh.RecalculateBounds(); // Store mesh. Meshes[i] = mesh; } } /// /// Initializes vertex colors. /// private void TryInitializeVertexColor() { if (Mesh == null) { return; } 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) { MainTexture = Texture2D.whiteTexture; } ApplyMainTexture(); } /// /// Initializes components if possible. /// public void TryInitialize(CubismRenderController renderController) { RenderController = renderController; if (!RenderController.Model) { return; } InitializeDrawObject(); TryInitializeMeshRenderer(); TryInitializeMesh(); TryInitializeMeshFilter(); TryInitializeVertexColor(); TryInitializeMainTexture(); TryInitializeMultiplyColor(); TryInitializeScreenColor(); _previousOffscreenUnmanagedIndex = -1; ApplySorting(); #if UNITY_EDITOR SyncMeshFilterForPicking(); #endif } #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() { ThisSwap = default; } /// /// 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 } }