// Copyright (C) 2019-2021 Alexander Bogarsukov. All rights reserved. // See the LICENSE.md file in the project root for more information. using System; using System.Collections.Generic; using UnityEngine; using UnityEngine.Rendering; namespace UnityFx.Outline { /// /// Renders outlines at specific camera. Should be attached to camera to function. /// /// /// /// /// [ExecuteInEditMode] [RequireComponent(typeof(Camera))] public sealed partial class OutlineEffect : MonoBehaviour { #region data [SerializeField, Tooltip(OutlineResources.OutlineResourcesTooltip)] private OutlineResources _outlineResources; [SerializeField, Tooltip(OutlineResources.OutlineLayerCollectionTooltip)] private OutlineLayerCollection _outlineLayers; [SerializeField, HideInInspector] private CameraEvent _cameraEvent = OutlineRenderer.RenderEvent; private Camera _camera; private CommandBuffer _commandBuffer; private List _renderObjects = new List(16); #endregion #region interface /// /// Gets or sets resources used by the effect implementation. /// /// Thrown if setter argument is . public OutlineResources OutlineResources { get { return _outlineResources; } set { if (value is null) { throw new ArgumentNullException(nameof(OutlineResources)); } _outlineResources = value; } } /// /// Gets collection of outline layers. /// public OutlineLayerCollection OutlineLayers { get { return _outlineLayers; } set { _outlineLayers = value; } } /// /// Gets outline layers (for internal use only). /// internal OutlineLayerCollection OutlineLayersInternal => _outlineLayers; /// /// Gets or sets used to render the outlines. /// public CameraEvent RenderEvent { get { return _cameraEvent; } set { if (_cameraEvent != value) { if (_commandBuffer != null) { var camera = GetComponent(); if (camera) { camera.RemoveCommandBuffer(_cameraEvent, _commandBuffer); camera.AddCommandBuffer(value, _commandBuffer); } } _cameraEvent = value; } } } /// /// Adds the passed to the first outline layer. Creates the layer if needed. /// /// The to add and render outline for. /// public void AddGameObject(GameObject go) { AddGameObject(go, 0); } /// /// Adds the passed to the specified outline layer. Creates the layer if needed. /// /// The to add and render outline for. /// public void AddGameObject(GameObject go, int layerIndex) { if (layerIndex < 0) { throw new ArgumentOutOfRangeException("layerIndex"); } CreateLayersIfNeeded(); while (_outlineLayers.Count <= layerIndex) { _outlineLayers.Add(new OutlineLayer()); } _outlineLayers[layerIndex].Add(go); } /// /// Removes the specified from . /// /// A to remove. public void RemoveGameObject(GameObject go) { if (_outlineLayers) { _outlineLayers.Remove(go); } } #endregion #region MonoBehaviour private void Awake() { OutlineResources.LogSrpNotSupported(this); OutlineResources.LogPpNotSupported(this); } private void OnEnable() { InitCameraAndCommandBuffer(); } private void OnDisable() { ReleaseCameraAndCommandBuffer(); } private void OnPreRender() { FillCommandBuffer(); } private void OnDestroy() { // TODO: Find a way to do this once per OutlineLayerCollection instance. if (_outlineLayers) { _outlineLayers.Reset(); } } #if UNITY_EDITOR //private void OnValidate() //{ // InitCameraAndCommandBuffer(); // FillCommandBuffer(); //} private void Reset() { _outlineLayers = null; } #endif #endregion #region implementation private void InitCameraAndCommandBuffer() { _camera = GetComponent(); if (_camera && _commandBuffer is null) { _commandBuffer = new CommandBuffer { name = string.Format("{0} - {1}", GetType().Name, name) }; _camera.depthTextureMode |= DepthTextureMode.Depth; _camera.AddCommandBuffer(_cameraEvent, _commandBuffer); } } private void ReleaseCameraAndCommandBuffer() { if (_commandBuffer != null) { if (_camera) { _camera.RemoveCommandBuffer(_cameraEvent, _commandBuffer); } _commandBuffer.Dispose(); _commandBuffer = null; } _camera = null; } private void FillCommandBuffer() { if (_camera && _outlineLayers && _commandBuffer != null) { _commandBuffer.Clear(); if (_outlineResources && _outlineResources.IsValid) { using (var renderer = new OutlineRenderer(_commandBuffer, _outlineResources, _camera.actualRenderingPath)) { _renderObjects.Clear(); _outlineLayers.GetRenderObjects(_renderObjects); renderer.Render(_renderObjects); } } } } private void CreateLayersIfNeeded() { if (_outlineLayers is null) { _outlineLayers = ScriptableObject.CreateInstance(); _outlineLayers.name = "OutlineLayers"; } } #endregion } }