commit d2b8ce01f80b6673ae2747064daa0cd3859f2cde Author: lrc <571244399@qq.com> Date: Sun Jul 20 21:01:09 2025 +0800 init diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..c3599af --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,205 @@ +# UnityFx.Outline changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](http://keepachangelog.com/); this project adheres to [Semantic Versioning](http://semver.org/). + +## [0.8.5] - 2021.11.18 + +Bugfixes and improvements. + +### Added +- Added support for HDR color pickers ([#42](https://github.com/Arvtesh/UnityFx.Outline/issues/42)). + +### Fixed +- Added loop unroll statement to make shaders compatible with some platforms (WebGL 1.0) ([#45](https://github.com/Arvtesh/UnityFx.Outline/issues/45)). + +## [0.8.4] - 2021.08.17 +Misc improvements. + +### Added +- Enabled ourlines for renderers with no materials attached ([#33](https://github.com/Arvtesh/UnityFx.Outline/issues/33)). + +## [0.8.3] - 2021.01.25 + +Misc improvements and bugfixes. + +### Fixed +- Fixed `OutlineBehaviour` not working in edit mode after disabling and enabling it again. + +### Changed +- `OutlineEffect` now works in edit-mode. +- `OutlineEffect` now exposes `OutlineLayerCollection` instead of `IList`. +- `OutlineEffect` now uses `OnPreRender` to update its command buffer. +- Moved `MergeLayerObjects` flag to `OutlineLayer` from `OutlineLayerCollection`. +- Multiple `OutlineEffect` component instances can now be added to a camera. + +## [0.8.2] - 2020.11.10 + +Misc improvements. + +### Added +- Added support for Single Pass Instanced XR rendering for built-in render pipeline ([#13](https://github.com/Arvtesh/UnityFx.Outline/issues/13)). + +### Changed +- Misc inspector improvements. + +## [0.8.1] - 2020.09.21 + +Alpha test support, bugfixes and misc improvements. + +### Added +- Added support for alpha-testing ([#10](https://github.com/Arvtesh/UnityFx.Outline/issues/10)). +- Added support for merging outline layer objects ([#12](https://github.com/Arvtesh/UnityFx.Outline/issues/12)). +- Added `RemoveGameObject` helper methof to `OutlineEffect` ([#15](https://github.com/Arvtesh/UnityFx.Outline/issues/15)). +- Added ability to customize render event in `OutlineBehaviour`. +- Added ability to render outlines to the specified camera only for `OutlineBehaviour`. +- Added warning for unsupported render pipelines for `OutlineBehaviour` and `OutlineEffect`. + +### Changed +- Misc inspector improvements. +- Changed default render event to `AfterSkybox`. + +### Fixed +- Fixed incorrect condition for selection of render method, which sometimes caused problems with outline rendering on mobiles ([#14](https://github.com/Arvtesh/UnityFx.Outline/issues/14)). + +## [0.8.0] - 2020.05.30 + +Major refactoring and bugfixes. + +### Added +- Use procedural geometry ([DrawProcedural](https://docs.unity3d.com/ScriptReference/Rendering.CommandBuffer.DrawProcedural.html)) on SM3.5+. +- Added support for both forward and deferred renderers. +- Added ignore layer mask settings to `OutlineLayerCollection` (previously the ignore layers were specified when adding game objects to layers). +- Added `OutlineBuilder` helper script for managinf `OutlineLayerCollection` content from editor ([#8](https://github.com/Arvtesh/UnityFx.Outline/issues/8)). + +### Changed +- Changed `OutlineSettings` to display enum mask instead of checkboxes. +- Changed inspector look and feel for `OutlineLayerCollection` assets. +- Merged shaders for the 2 outline passes into one multi-pass shader. +- `OutlineLayerCollection` doe not depend on `OutlineRenderer` now. + +### Fixed +- Fixed outline rendering on mobiles ([#7](https://github.com/Arvtesh/UnityFx.Outline/issues/7)). +- Fixed outline shader error on low-end devices. + +### Removed +- Dropped .NET 3.5 support, minimal Unity version is set to 2018.4. +- Removed `IOutlineSettingsEx` interface. + +## [0.7.2] - 2020.04.08 + +Depth testing support and performance optimizations. + +### Added +- Added support for depth testing when rendering outlines. When enabled, outlines are only rendered around the visible object parts ([#1](https://github.com/Arvtesh/UnityFx.Outline/issues/1)). +- Added a few convenience methods to `OutlineEffect`. +- Added editor tooltips for outline component fileds. + +### Fixed +- Get rid of GC allocatinos during command buffer updates. +- Fixed `IndexOutOfRangeException` when setting outline width to max value ([#4](https://github.com/Arvtesh/UnityFx.Outline/issues/4)). + +### Removed +- Removed change tracking support in package entities ([#2](https://github.com/Arvtesh/UnityFx.Outline/issues/2)). + +## [0.7.1] - 2020.01.28 + +Bugfixes and project layout changes. + +### Fixed +- Fixed `OutlineBehaviour` to allow changing its state while its `GameObject` is inactive. + +## [0.7.0] - 2019.11.26 + +`MaterialPropertyBlock`-based rendering and [Unity Post-processing Stack v2](https://github.com/Unity-Technologies/PostProcessing/tree/v2) compatibility. + +### Added +- Moved to for `MaterialPropertyBlock`-based rendering. This is in-line with Unity post-processing Stack and is more performant approach. +- Significant optimizations made to `OutlineRenderer`. + +### Changed +- `IOutlineSettings` now implements `IEquatable`. +- Changed all outline shaders to use HLSL-based macros. +- Modified all shaders to ignore MVP vertex transform to be compatible with the new rendering model. +- Exposed rendering APIs for `OutlineLayer` and `OutlineLayerCollection`. + +### Fixed +- Fixed `TiledGPUPerformanceWarning` on mobile targets. + +### Removed +- Removed `OutlineMaterialSet` class. It is not used in `MaterialPropertyBlock`-based effect rendering. + +## [0.6.0] - 2019.09.26 + +Quality of life improvements. + +### Added +- Added `OutlineLayer.Enabled`. +- Added `OutlineLayer.Name`. +- Added possibility to change render order of layers via `OutlineLayer.Priority`. +- Added possibility to edit renderers of an `OutlineLayer`. +- Added possibility to alter `CameraEvent` used to render `OutlineEffect`. +- Added more info to the `OutlineLayer` preview inspector. + +### Changed +- `IOutilneSettings` setters now throw if overriden. + +### Fixed +- Fixed `OutlineLayer.Add` not filtering renderers by the mask passed. + +## [0.5.0] - 2019.09.09 + +Editor UI improvements and unit tests. + +### Added +- Added `OutlineSettings`, that can be shared between dfferent `OutlineLayer` and `OutlineBehaviour` instances. +- Added custom inspectors for `OutlineSettings`, `OutlineLayerCollection`. +- Added undo/redo support to all custom inspectors. +- Added unit-tests. + +### Changed +- Improved inspectors for `OutlineBehaviour` and `OutlineEffect`. + +## [0.4.0] - 2019.08.31 + +Blurred outlines. + +### Added +- Added Gauss blurring to outlines. +- Added outline mode parameter (possible values are `Solid` and `Blurred`). +- Added outline intensity parameter (for blurred outlines only). +- Added `IOutlineSettings` interface to make outline settings the same for `OutlineBehaviour` and `OutlineLayer`. +- Added `OutlineMaterialSet` helper. + +### Changed +- Changed solid outline to use Gauss sampling (to achieve smoother outlines). +- Changed outline implementation to use different passed for horizontal and vertical sampling (to make algorithm complexity linear instead of quadric). + +### Fixed +- Fixed an issue with `OutlineBehaviour` not rendering outlines if attached to a `GameObject` with no renderers. + +### Removed +- Removed `OutlineResourceCache` class. + +## [0.3.0] - 2019.08.27 + +### Added +- Added support for sharing outline layers between `OutlineEffect` instances. +- Added custom editors for `OutlineEffect` and `OutlineBehaviour`. +- Added possibility to setup outline layers as `ScriptableObject` asset. + +### Fixed +- Fixed profiler error 'BeginSample and EndSample count must match'. + +## [0.2.0] - 2019.08.19 + +### Added +- Added `OutlineBehaviour` for rendering per-object outlines. +- Added `OutlineResources` to help initialize outline effects in runtime. +- Added `OutlineRenderer` as low-level helper for outline rendering. + +## [0.1.0] - 2019.08.18 + +### Added +- Initial release. + diff --git a/CHANGELOG.md.meta b/CHANGELOG.md.meta new file mode 100644 index 0000000..b191c07 --- /dev/null +++ b/CHANGELOG.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: a0afeafc656ad714fbfb0e40dc4587e1 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Documentation.meta b/Documentation.meta new file mode 100644 index 0000000..f8df6aa --- /dev/null +++ b/Documentation.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: d988d8872562b794e91c8608c8b7400d +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Documentation/UnityFx.Outline.md b/Documentation/UnityFx.Outline.md new file mode 100644 index 0000000..6f65ddd --- /dev/null +++ b/Documentation/UnityFx.Outline.md @@ -0,0 +1,169 @@ +>>> +**_Package Documentation Template_** + +Use this template to create preliminary, high-level documentation meant to introduce users to the feature and the sample files included in this package. When writing your documentation, do the following: + +1. Follow instructions in blockquotes. + +2. Replace angle brackets with the appropriate text. For example, replace "<package name>" with the official name of the package. + +3. Delete sections that do not apply to your package. For example, a package containing only sample files does not have a "Using <package_name>" section, so this section can be removed. + +4. After documentation is completed, make sure you delete all instructions and examples in blockquotes including this preamble and its title: + + ``` + >>> + Delete all of the text between pairs of blockquote markdown. + >>> + ``` +>>> + +# About <package name> + +>>> +Name the heading of the first topic after the **displayName** of the package as it appears in the package manifest. + +This first topic includes a brief, high-level explanation of the package and, if applicable, provides links to Unity Manual topics. + +There are two types of packages: + + - Packages that include features that augment the Unity Editor or Runtime. + - Packages that include sample files. + +Choose one of the following introductory paragraphs that best fits the package: +>>> + +Use the <package name> package to <list of the main uses for the package>. For example, use <package name> to create/generate/extend/capture <mention major use case, or a good example of what the package can be used for>. The <package name> package also includes <other relevant features or uses>. + +> *or* + +The <package name> package includes examples of <name of asset type, model, prefabs, and/or other GameObjects in the package>. For more information, see <xref to topic in the Unity Manual>. + +>>> +**_Examples:_** + +Here are some examples for reference only. Do not include these in the final documentation file: + +*Use the Unity Recorder package to capture and save in-game data. For example, use Unity Recorder to record an mp4 file during a game session. The Unity Recorder package also includes an interface for setting-up and triggering recording sessions.* + +*The Timeline Examples package includes examples of Timeline assets, Timeline Instances, animation, GameObjects, and scripts that illustrate how to use Unity's Timeline. For more information, see [ Unity's Timeline](https://docs.unity3d.com/Manual/TimelineSection.html) in the [Unity Manual](https://docs.unity3d.com). For licensing and usage, see Package Licensing.* +>>> + +# Installing <package name> +>>> +Begin this section with a cross-reference to the official Unity Manual topic on how to install packages. If the package requires special installation instructions, include these steps in this section. +>>> + +To install this package, follow the instructions in the [Package Manager documentation](https://docs.unity3d.com/Packages/com.unity.package-manager-ui@latest/index.html). + +>>> +For some packages, there may be additional steps to complete the setup. You can add those here. +>>> + +In addition, you need to install the following resources: + + - <name of resource>: To install, open *Window > <name of menu item>*. The resource appears <at this location>. + - <name of sample>: To install, open *Window > <name of menu item>*. The new sample folder appears <at this location>. + + + +# Using <package name> +>>> +The contents of this section depends on the type of package. + +For packages that augment the Unity Editor with additional features, this section should include workflow and/or reference documentation: + +* At a minimum, this section should include reference documentation that describes the windows, editors, and properties that the package adds to Unity. This reference documentation should include screen grabs (see how to add screens below), a list of settings, an explanation of what each setting does, and the default values of each setting. +* Ideally, this section should also include a workflow: a list of steps that the user can easily follow that demonstrates how to use the feature. This list of steps should include screen grabs (see how to add screens below) to better describe how to use the feature. + +For packages that include sample files, this section may include detailed information on how the user can use these sample files in their projects and scenes. However, workflow diagrams or illustrations could be included if deemed appropriate. + +## How to add images + +*(This section is for reference. Do not include in the final documentation file)* + +If the [Using <package name>](#UsingPackageName) section includes screen grabs or diagrams, a link to the image must be added to this MD file, before or after the paragraph with the instruction or description that references the image. In addition, a caption should be added to the image link that includes the name of the screen or diagram. All images must be PNG files with underscores for spaces. No animated GIFs. + +An example is included below: + +![A cinematic in the Timeline Editor window.](images/example.png) + +Notice that the example screen shot is included in the images folder. All screen grabs and/or diagrams must be added and referenced from the images folder. + +For more on the Unity documentation standards for creating and adding screen grabs, see this confluence page: https://confluence.hq.unity3d.com/pages/viewpage.action?pageId=13500715 +>>> + + + +# Technical details +## Requirements +>>> +This subtopic includes a bullet list with the compatible versions of Unity. This subtopic may also include additional requirements or recommendations for 3rd party software or hardware. An example includes a dependency on other packages. If you need to include references to non-Unity products, make sure you refer to these products correctly and that all references include the proper trademarks (tm or r) +>>> + +This version of <package name> is compatible with the following versions of the Unity Editor: + +* 2018.1 and later (recommended) + +To use this package, you must have the following 3rd party products: + +* <product name and version with trademark or registered trademark.> +* <product name and version with trademark or registered trademark.> +* <product name and version with trademark or registered trademark.> + +## Known limitations +>>> +This section lists the known limitations with this version of the package. If there are no known limitations, or if the limitations are trivial, exclude this section. An example is provided. +>>> + +<package name> version <package version> includes the following known limitations: + +* <brief one-line description of first limitation.> +* <brief one-line description of second limitation.> +* <and so on> + +>>> +*Example (For reference. Do not include in the final documentation file):* + +The Unity Recorder version 1.0 has the following limitations:* + +* The Unity Recorder does not support sound. +* The Recorder window and Recorder properties are not available in standalone players. +* MP4 encoding is only available on Windows. +>>> + +## Package contents +>>> +This section includes the location of important files you want the user to know about. For example, if this is a sample package containing textures, models, and materials separated by sample group, you may want to provide the folder location of each group. +>>> + +The following table indicates the <describe the breakdown you used here>: + +|Location|Description| +|---|---| +|``|Contains <describe what the folder contains>.| +|``|Contains <describe what the file represents or implements>.| + +>>> +*Example (For reference. Do not include in the final documentation file):* + +The following table indicates the root folder of each type of sample in this package. Each sample's root folder contains its own Materials, Models, or Textures folders: + +|Folder Location|Description| +|---|---| +|`WoodenCrate_Orange`|Root folder containing the assets for the orange crates.| +|`WoodenCrate_Mahogany`|Root folder containing the assets for the mahogany crates.| +|`WoodenCrate_Shared`|Root folder containing any material assets shared by all crates.| +>>> + +## Document revision history +>>> +This section includes the revision history of the document. The revision history tracks when a document is created, edited, and updated. If you create or update a document, you must add a new row describing the revision. The Documentation Team also uses this table to track when a document is edited and its editing level. An example is provided: + +|Date|Reason| +|---|---| +|Sept 12, 2017|Unedited. Published to package.| +|Sept 10, 2017|Document updated for package version 1.1.
New features:
  • audio support for capturing MP4s.
  • Instructions on saving Recorder prefabs| +|Sept 5, 2017|Limited edit by Documentation Team. Published to package.| +|Aug 25, 2017|Document created. Matches package version 1.0.| +>>> \ No newline at end of file diff --git a/Documentation/UnityFx.Outline.md.meta b/Documentation/UnityFx.Outline.md.meta new file mode 100644 index 0000000..50d92b8 --- /dev/null +++ b/Documentation/UnityFx.Outline.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: e240b6e2f432ca5488342bcaa1ddc4a8 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Documentation/images.meta b/Documentation/images.meta new file mode 100644 index 0000000..a7794e7 --- /dev/null +++ b/Documentation/images.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 193923b56cd5dd049b83804460718186 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Documentation/images/example.png b/Documentation/images/example.png new file mode 100644 index 0000000..216328d Binary files /dev/null and b/Documentation/images/example.png differ diff --git a/Documentation/images/example.png.meta b/Documentation/images/example.png.meta new file mode 100644 index 0000000..144ca3b --- /dev/null +++ b/Documentation/images/example.png.meta @@ -0,0 +1,124 @@ +fileFormatVersion: 2 +guid: 02addf231624d734aac11a99b93b93d4 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 12 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMasterTextureLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 0 + wrapV: 0 + wrapW: 0 + nPOTScale: 1 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 0 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + swizzle: 50462976 + cookieLightType: 0 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Server + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + nameFileIdTable: {} + spritePackingTag: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor.meta b/Editor.meta new file mode 100644 index 0000000..b9ca736 --- /dev/null +++ b/Editor.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: b74bf422ea69c144d9559d5a8a9de006 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/Scripts.meta b/Editor/Scripts.meta new file mode 100644 index 0000000..7da429c --- /dev/null +++ b/Editor/Scripts.meta @@ -0,0 +1,10 @@ +fileFormatVersion: 2 +guid: 15c9c665be3d06048bebd0e20ec2b173 +folderAsset: yes +timeCreated: 1566558344 +licenseType: Pro +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/Scripts/OutlineBehaviourEditor.cs b/Editor/Scripts/OutlineBehaviourEditor.cs new file mode 100644 index 0000000..825853b --- /dev/null +++ b/Editor/Scripts/OutlineBehaviourEditor.cs @@ -0,0 +1,113 @@ +// Copyright (C) 2019-2021 Alexander Bogarsukov. All rights reserved. +// See the LICENSE.md file in the project root for more information. + +using System; +using UnityEditor; +using UnityEditorInternal; +using UnityEditor.SceneManagement; +using UnityEngine; +using UnityEngine.Rendering; + +namespace UnityFx.Outline +{ + [CustomEditor(typeof(OutlineBehaviour))] + public class OutlineBehaviourEditor : Editor + { + private OutlineBehaviour _effect; + private SerializedProperty _settingsProp; + private bool _debugOpened; + private bool _renderersOpened; + private bool _camerasOpened; + + private void OnEnable() + { + _effect = (OutlineBehaviour)target; + _settingsProp = serializedObject.FindProperty("_outlineSettings"); + } + + public override void OnInspectorGUI() + { + base.OnInspectorGUI(); + + // 1) Outline settings. + EditorGUI.BeginChangeCheck(); + + var mask = EditorGUILayout.MaskField("Ignore layers", _effect.IgnoreLayerMask, InternalEditorUtility.layers); + + if (_effect.IgnoreLayerMask != mask) + { + Undo.RecordObject(_effect, "Set Ignore Layers"); + _effect.IgnoreLayerMask = mask; + } + + var e = (CameraEvent)EditorGUILayout.EnumPopup("Render Event", _effect.RenderEvent); + + if (e != _effect.RenderEvent) + { + Undo.RecordObject(_effect, "Set Render Event"); + _effect.RenderEvent = e; + } + + var c = (Camera)EditorGUILayout.ObjectField("Target Camera", _effect.Camera, typeof(Camera), true); + + if (c != _effect.Camera) + { + Undo.RecordObject(_effect, "Set Target Camera"); + _effect.Camera = c; + } + + EditorGUILayout.PropertyField(_settingsProp); + serializedObject.ApplyModifiedProperties(); + + if (EditorGUI.EndChangeCheck()) + { + EditorUtility.SetDirty(_effect.gameObject); + + if (!EditorApplication.isPlayingOrWillChangePlaymode) + { + EditorSceneManager.MarkSceneDirty(_effect.gameObject.scene); + } + } + + // 2) Renderers (read-only). + _renderersOpened = EditorGUILayout.Foldout(_renderersOpened, "Renderers", true); + + if (_renderersOpened) + { + EditorGUI.BeginDisabledGroup(true); + EditorGUI.indentLevel += 1; + + var rendererNumber = 1; + + foreach (var renderer in _effect.OutlineRenderers) + { + EditorGUILayout.ObjectField("#" + rendererNumber.ToString(), renderer, typeof(Renderer), true); + rendererNumber++; + } + + EditorGUI.indentLevel -= 1; + EditorGUI.EndDisabledGroup(); + } + + // 3) Cameras (read-only). + _camerasOpened = EditorGUILayout.Foldout(_camerasOpened, "Cameras", true); + + if (_camerasOpened) + { + EditorGUI.BeginDisabledGroup(true); + EditorGUI.indentLevel += 1; + + var cameraNumber = 1; + + foreach (var camera in _effect.Cameras) + { + EditorGUILayout.ObjectField("#" + cameraNumber.ToString(), camera, typeof(Camera), true); + cameraNumber++; + } + + EditorGUI.indentLevel -= 1; + EditorGUI.EndDisabledGroup(); + } + } + } +} diff --git a/Editor/Scripts/OutlineBehaviourEditor.cs.meta b/Editor/Scripts/OutlineBehaviourEditor.cs.meta new file mode 100644 index 0000000..1c3d375 --- /dev/null +++ b/Editor/Scripts/OutlineBehaviourEditor.cs.meta @@ -0,0 +1,13 @@ +fileFormatVersion: 2 +guid: a6e77a9d499a86e4fbdc52a2977e774f +timeCreated: 1566572433 +licenseType: Pro +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/Scripts/OutlineBuilderEditor.cs b/Editor/Scripts/OutlineBuilderEditor.cs new file mode 100644 index 0000000..ec5b4eb --- /dev/null +++ b/Editor/Scripts/OutlineBuilderEditor.cs @@ -0,0 +1,169 @@ +// 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; +using System.Collections.Generic; +using UnityEditor; +using UnityEditorInternal; +using UnityEditor.SceneManagement; +using UnityEngine; + +namespace UnityFx.Outline +{ + [CustomEditor(typeof(OutlineBuilder))] + public class OutlineBuilderEditor : Editor + { + private OutlineBuilder _builder; + private ReorderableList _content; + private List _lists; + + private void OnEnable() + { + _builder = (OutlineBuilder)target; + + if (EditorApplication.isPlaying) + { + if (_builder.OutlineLayers) + { + _lists = new List(_builder.OutlineLayers.Count); + + foreach (var layer in _builder.OutlineLayers) + { + var list0 = new ArrayList(layer.Count); + + foreach (var go in layer) + { + list0.Add(go); + } + + var editorList = new ReorderableList(list0, typeof(GameObject), false, true, true, true); + + editorList.onAddCallback += (list) => + { + list.list.Add(null); + }; + + editorList.onRemoveCallback += (list) => + { + var go = list.list[list.index]; + list.list.RemoveAt(list.index); + layer.Remove(go as GameObject); + }; + + editorList.drawElementCallback += (rect, index, isActive, isFocused) => + { + var prevGo = list0[index] as GameObject; + var go = (GameObject)EditorGUI.ObjectField(new Rect(rect.x, rect.y, rect.width, EditorGUIUtility.singleLineHeight), $"#{index}", prevGo, typeof(GameObject), true); + + if (prevGo != go) + { + list0[index] = go; + layer.Remove(prevGo); + layer.Add(go); + } + }; + + editorList.drawHeaderCallback += (rect) => + { + EditorGUI.LabelField(rect, layer.Name); + }; + + editorList.elementHeightCallback += (index) => + { + return EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing; + }; + + _lists.Add(editorList); + } + } + } + else + { + _content = new ReorderableList(_builder.Content, typeof(OutlineBuilder.ContentItem), true, true, true, true); + + _content.drawElementCallback += (rect, index, isActive, isFocused) => + { + if (_builder && _builder.Content != null && index < _builder.Content.Count) + { + var item = _builder.Content[index]; + + if (item != null) + { + item.Go = (GameObject)EditorGUI.ObjectField(new Rect(rect.x + rect.width * 0.3f + 1, rect.y, rect.width * 0.7f, EditorGUIUtility.singleLineHeight), item.Go, typeof(GameObject), true); + item.LayerIndex = EditorGUI.IntField(new Rect(rect.x, rect.y, rect.width * 0.3f - 1, EditorGUIUtility.singleLineHeight), item.LayerIndex); + } + } + }; + + _content.drawHeaderCallback += (rect) => + { + EditorGUI.LabelField(rect, "Content"); + }; + + _content.elementHeightCallback += (index) => + { + return EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing; + }; + } + } + + public override void OnInspectorGUI() + { + base.OnInspectorGUI(); + + EditorGUI.BeginChangeCheck(); + + if (_content != null) + { + EditorGUILayout.HelpBox("Game objectes listed below will be added to corresponding outline layers when application is started. Only scene references are allowed.", MessageType.Info); + _content.DoLayoutList(); + EditorGUILayout.Space(); + + if (GUILayout.Button("Clear")) + { + _builder.Content.Clear(); + } + + serializedObject.ApplyModifiedProperties(); + } + else if (_lists != null && _lists.Count > 0) + { + EditorGUILayout.HelpBox("Settings below are not serialized, they only exist in runtime.", MessageType.Info); + + for (var i = 0; i < _lists.Count; ++i) + { + _lists[i].DoLayoutList(); + EditorGUILayout.Space(); + } + + if (GUILayout.Button("Clear")) + { + foreach (var list in _lists) + { + list.list.Clear(); + } + + _builder.Clear(); + } + + serializedObject.ApplyModifiedProperties(); + } + else + { + // TODO + } + + if (EditorGUI.EndChangeCheck()) + { + Undo.RecordObject(_builder, "Builder"); + + if (!EditorApplication.isPlayingOrWillChangePlaymode) + { + EditorUtility.SetDirty(_builder.gameObject); + EditorSceneManager.MarkSceneDirty(_builder.gameObject.scene); + } + } + } + } +} diff --git a/Editor/Scripts/OutlineBuilderEditor.cs.meta b/Editor/Scripts/OutlineBuilderEditor.cs.meta new file mode 100644 index 0000000..20872eb --- /dev/null +++ b/Editor/Scripts/OutlineBuilderEditor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c2ba41e0b025a6e46abc7b69d31d1907 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/Scripts/OutlineEditorUtility.cs b/Editor/Scripts/OutlineEditorUtility.cs new file mode 100644 index 0000000..df53c31 --- /dev/null +++ b/Editor/Scripts/OutlineEditorUtility.cs @@ -0,0 +1,100 @@ +// 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; +using System.Collections.Generic; +using UnityEditor; +using UnityEngine; + +namespace UnityFx.Outline +{ + public static class OutlineEditorUtility + { + public static readonly GUIContent FilterSettingsContent = new GUIContent("Outline Filter Settings", ""); + public static readonly GUIContent LayerMaskContent = new GUIContent("Layer Mask", OutlineResources.OutlineLayerMaskTooltip); + public static readonly GUIContent RenderingLayerMaskContent = new GUIContent("Rendering Layer Mask", OutlineResources.OutlineRenderingLayerMaskTooltip); + public static readonly GUIContent ColorContent = new GUIContent("Color", "Outline color."); + public static readonly GUIContent WidthContent = new GUIContent("Width", "Outline width in pixels."); + public static readonly GUIContent RenderFlagsContent = new GUIContent("Render Flags", "Outline render flags. Multiple values can be selected at the same time."); + public static readonly GUIContent BlurIntensityContent = new GUIContent("Blur Intensity", "Outline intensity value. It is only usable for blurred outlines."); + public static readonly GUIContent AlphaCutoffContent = new GUIContent("Alpha Cutoff", "Outline alpha cutoff value. It is only usable when alpha testing is enabled and the material doesn't have _Cutoff property."); + + public static void RenderPreview(OutlineLayer layer, int layerIndex, bool showObjects) + { + if (layer != null) + { + var goIndex = 1; + + EditorGUILayout.BeginHorizontal(); + EditorGUI.indentLevel += 1; + EditorGUILayout.PrefixLabel("Layer #" + layerIndex.ToString()); + EditorGUI.indentLevel -= 1; + + if (layer.Enabled) + { + EditorGUILayout.LabelField(layer.OutlineRenderMode == OutlineRenderFlags.None ? layer.OutlineRenderMode.ToString() : string.Format("Blurred ({0})", layer.OutlineIntensity), GUILayout.MaxWidth(70)); + EditorGUILayout.IntField(layer.OutlineWidth, GUILayout.MaxWidth(100)); + EditorGUILayout.ColorField(layer.OutlineColor, GUILayout.MinWidth(100)); + } + else + { + EditorGUILayout.LabelField("Disabled."); + } + + EditorGUILayout.EndHorizontal(); + + if (showObjects) + { + if (layer.Count > 0) + { + foreach (var go in layer) + { + EditorGUI.indentLevel += 2; + EditorGUILayout.ObjectField("#" + goIndex.ToString(), go, typeof(GameObject), true); + EditorGUI.indentLevel -= 2; + + goIndex++; + } + } + else + { + EditorGUI.indentLevel += 2; + EditorGUILayout.LabelField("No objects."); + EditorGUI.indentLevel -= 2; + } + } + } + else + { + EditorGUILayout.BeginHorizontal(); + EditorGUI.indentLevel += 1; + EditorGUILayout.PrefixLabel("Layer #" + layerIndex.ToString()); + EditorGUI.indentLevel -= 1; + EditorGUILayout.LabelField("Null"); + EditorGUILayout.EndHorizontal(); + } + } + + public static void RenderPreview(IList layers, bool showObjects) + { + EditorGUI.BeginDisabledGroup(true); + + if (layers.Count > 0) + { + for (var i = 0; i < layers.Count; ++i) + { + RenderPreview(layers[i], i, showObjects); + } + } + else + { + EditorGUI.indentLevel += 1; + EditorGUILayout.LabelField("No layers."); + EditorGUI.indentLevel -= 1; + } + + EditorGUI.EndDisabledGroup(); + } + } +} diff --git a/Editor/Scripts/OutlineEditorUtility.cs.meta b/Editor/Scripts/OutlineEditorUtility.cs.meta new file mode 100644 index 0000000..28953ac --- /dev/null +++ b/Editor/Scripts/OutlineEditorUtility.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ba087138029b59d4bbdf0783db0e2606 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/Scripts/OutlineEffectEditor.cs b/Editor/Scripts/OutlineEffectEditor.cs new file mode 100644 index 0000000..75f3c3f --- /dev/null +++ b/Editor/Scripts/OutlineEffectEditor.cs @@ -0,0 +1,62 @@ +// 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; +using System.Collections.Generic; +using UnityEditor; +using UnityEditor.SceneManagement; +using UnityEngine.Rendering; + +namespace UnityFx.Outline +{ + [CustomEditor(typeof(OutlineEffect))] + public class OutlineEffectEditor : Editor + { + private OutlineEffect _effect; + private bool _debugOpened; + private bool _previewOpened; + + private void OnEnable() + { + _effect = (OutlineEffect)target; + } + + public override void OnInspectorGUI() + { + base.OnInspectorGUI(); + + EditorGUI.BeginChangeCheck(); + var e = (CameraEvent)EditorGUILayout.EnumPopup("Render Event", _effect.RenderEvent); + + if (e != _effect.RenderEvent) + { + Undo.RecordObject(_effect, "Set Render Event"); + _effect.RenderEvent = e; + } + + if (EditorGUI.EndChangeCheck()) + { + EditorUtility.SetDirty(_effect.gameObject); + + if (!EditorApplication.isPlayingOrWillChangePlaymode) + { + EditorSceneManager.MarkSceneDirty(_effect.gameObject.scene); + } + } + + if (_effect.OutlineLayers) + { + if (_effect.OutlineLayers.Count > 0) + { + _previewOpened = EditorGUILayout.Foldout(_previewOpened, "Preview", true); + + if (_previewOpened) + { + OutlineEditorUtility.RenderPreview(_effect.OutlineLayers, true); + } + } + } + } + } +} diff --git a/Editor/Scripts/OutlineEffectEditor.cs.meta b/Editor/Scripts/OutlineEffectEditor.cs.meta new file mode 100644 index 0000000..4b3f6af --- /dev/null +++ b/Editor/Scripts/OutlineEffectEditor.cs.meta @@ -0,0 +1,13 @@ +fileFormatVersion: 2 +guid: 03c2e35bb52d2ba44882b92d7cde45bf +timeCreated: 1566558360 +licenseType: Pro +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/Scripts/OutlineLayerCollectionEditor.cs b/Editor/Scripts/OutlineLayerCollectionEditor.cs new file mode 100644 index 0000000..94e799d --- /dev/null +++ b/Editor/Scripts/OutlineLayerCollectionEditor.cs @@ -0,0 +1,214 @@ +// 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; +using System.Collections.Generic; +using UnityEditor; +using UnityEditorInternal; +using UnityEngine; + +namespace UnityFx.Outline +{ + [CustomEditor(typeof(OutlineLayerCollection))] + public class OutlineLayerCollectionEditor : Editor + { + private OutlineLayerCollection _layers; + + private SerializedProperty _layersProp; + private ReorderableList _layersList; + + private void OnEnable() + { + _layers = (OutlineLayerCollection)target; + + _layersProp = serializedObject.FindProperty("_layers"); + _layersList = new ReorderableList(serializedObject, _layersProp, true, true, true, true); + _layersList.drawElementCallback += OnDrawLayer; + _layersList.drawHeaderCallback += OnDrawHeader; + _layersList.elementHeightCallback += OnGetElementHeight; + _layersList.onAddCallback += OnAddLayer; + _layersList.onRemoveCallback += OnRemoveLayer; + } + + public override void OnInspectorGUI() + { + base.OnInspectorGUI(); + + EditorGUI.BeginChangeCheck(); + + var mask = EditorGUILayout.MaskField("Ignore layers", _layers.IgnoreLayerMask, InternalEditorUtility.layers); + + if (EditorGUI.EndChangeCheck()) + { + Undo.RecordObject(_layers, "Change ignore mask"); + _layers.IgnoreLayerMask = mask; + } + + EditorGUILayout.Space(); + + _layersList.DoLayoutList(); + + if (_layers.NumberOfObjects > 0) + { + EditorGUILayout.Space(); + EditorGUILayout.HelpBox("Read-only lists below represent game objects assigned to specific outline layers. Only non-empty layers are displayed.", MessageType.Info); + + foreach (var layer in _layers) + { + if (layer.Count > 0) + { + EditorGUILayout.LabelField(layer.Name, EditorStyles.boldLabel); + EditorGUI.BeginDisabledGroup(true); + EditorGUI.indentLevel += 1; + + var index = 0; + + foreach (var go in layer) + { + EditorGUILayout.ObjectField($"#{index++}", go, typeof(GameObject), true); + } + + EditorGUI.indentLevel -= 1; + EditorGUI.EndDisabledGroup(); + } + } + } + + serializedObject.ApplyModifiedProperties(); + } + + private void OnDrawLayer(Rect rect, int index, bool isActive, bool isFocused) + { + var lineHeight = EditorGUIUtility.singleLineHeight; + var lineSpacing = EditorGUIUtility.standardVerticalSpacing; + var lineOffset = lineHeight + lineSpacing; + var y = rect.y + lineSpacing; + var layer = _layers[index]; + + var obj = layer.OutlineSettings; + var merge = layer.MergeLayerObjects; + var enabled = layer.Enabled; + var name = layer.NameTag; + var color = layer.OutlineColor; + var width = layer.OutlineWidth; + var renderMode = layer.OutlineRenderMode; + var blurIntensity = layer.OutlineIntensity; + var alphaCutoff = layer.OutlineAlphaCutoff; + + EditorGUI.BeginChangeCheck(); + + // Header + { + var rc = new Rect(rect.x, y, rect.width, lineHeight); + var bgRect = new Rect(rect.x - 2, y - 2, rect.width + 3, lineHeight + 3); + + // Header background + EditorGUI.DrawRect(rc, Color.gray); + EditorGUI.DrawRect(new Rect(bgRect.x, bgRect.y, bgRect.width, 1), Color.gray); + EditorGUI.DrawRect(new Rect(bgRect.x, bgRect.yMax, bgRect.width, 1), Color.gray); + EditorGUI.DrawRect(new Rect(bgRect.x, bgRect.y, 1, bgRect.height), Color.gray); + EditorGUI.DrawRect(new Rect(bgRect.xMax, bgRect.y, 1, bgRect.height), Color.gray); + + obj = (OutlineSettings)EditorGUI.ObjectField(rc, " ", obj, typeof(OutlineSettings), true); + enabled = EditorGUI.ToggleLeft(rc, "Layer #" + index.ToString(), enabled, EditorStyles.boldLabel); + y += lineOffset; + } + + // Layer properties + { + name = EditorGUI.TextField(new Rect(rect.x, y, rect.width, lineHeight), "Name", name); + y += lineOffset; + + merge = EditorGUI.Toggle(new Rect(rect.x, y, rect.width, lineHeight), "Merge Layer Objects", merge); + y += lineOffset; + } + + // Outline settings + { + EditorGUI.BeginDisabledGroup(obj != null); + + color = EditorGUI.ColorField(new Rect(rect.x, y, rect.width, lineHeight), OutlineEditorUtility.ColorContent, color, true, true, true); + y += lineOffset; + + width = EditorGUI.IntSlider(new Rect(rect.x, y, rect.width, lineHeight), OutlineEditorUtility.WidthContent, width, OutlineResources.MinWidth, OutlineResources.MaxWidth); + y += lineOffset; + + renderMode = (OutlineRenderFlags)EditorGUI.EnumFlagsField(new Rect(rect.x, y, rect.width, lineHeight), OutlineEditorUtility.RenderFlagsContent, renderMode); + y += lineOffset; + + if ((renderMode & OutlineRenderFlags.Blurred) != 0) + { + blurIntensity = EditorGUI.Slider(new Rect(rect.x, y, rect.width, lineHeight), OutlineEditorUtility.BlurIntensityContent, blurIntensity, OutlineResources.MinIntensity, OutlineResources.MaxIntensity); + y += lineOffset; + } + + if ((renderMode & OutlineRenderFlags.EnableAlphaTesting) != 0) + { + alphaCutoff = EditorGUI.Slider(new Rect(rect.x, y, rect.width, lineHeight), OutlineEditorUtility.AlphaCutoffContent, alphaCutoff, OutlineResources.MinAlphaCutoff, OutlineResources.MaxAlphaCutoff); + } + + EditorGUI.EndDisabledGroup(); + } + + if (EditorGUI.EndChangeCheck()) + { + Undo.RecordObject(_layers, "Layers changed"); + EditorUtility.SetDirty(_layers); + + layer.OutlineSettings = obj; + layer.Enabled = enabled; + layer.NameTag = name; + layer.MergeLayerObjects = merge; + layer.OutlineWidth = width; + layer.OutlineColor = color; + layer.OutlineRenderMode = renderMode; + layer.OutlineIntensity = blurIntensity; + layer.OutlineAlphaCutoff = alphaCutoff; + } + } + + private void OnDrawHeader(Rect rect) + { + EditorGUI.LabelField(rect, "Layer settings"); + } + + private float OnGetElementHeight(int index) + { + var numberOfLines = 6; + + if ((_layers[index].OutlineRenderMode & OutlineRenderFlags.Blurred) != 0) + { + ++numberOfLines; + } + + if ((_layers[index].OutlineRenderMode & OutlineRenderFlags.EnableAlphaTesting) != 0) + { + ++numberOfLines; + } + + return (EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing) * numberOfLines + EditorGUIUtility.standardVerticalSpacing; + } + + private void OnAddLayer(ReorderableList list) + { + var layer = new OutlineLayer(); + + Undo.RecordObject(_layers, "Add Layer"); + EditorUtility.SetDirty(_layers); + + _layers.Add(layer); + } + + private void OnRemoveLayer(ReorderableList list) + { + var index = list.index; + var layer = _layers[index]; + + Undo.RecordObject(_layers, "Remove Layer"); + EditorUtility.SetDirty(_layers); + + _layers.RemoveAt(index); + } + } +} diff --git a/Editor/Scripts/OutlineLayerCollectionEditor.cs.meta b/Editor/Scripts/OutlineLayerCollectionEditor.cs.meta new file mode 100644 index 0000000..528a02e --- /dev/null +++ b/Editor/Scripts/OutlineLayerCollectionEditor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5f4ec5de59e58794b8e34f2ca3c00199 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/Scripts/OutlineSettingsEditor.cs b/Editor/Scripts/OutlineSettingsEditor.cs new file mode 100644 index 0000000..e017287 --- /dev/null +++ b/Editor/Scripts/OutlineSettingsEditor.cs @@ -0,0 +1,240 @@ +// 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; +using System.Collections.Generic; +using UnityEditor; +using UnityEngine; + +namespace UnityFx.Outline +{ + [CustomEditor(typeof(OutlineSettings))] + public class OutlineSettingsEditor : Editor + { + private const string _filterModePropName = "_filterMode"; + private const string _layerMaskPropName = "_layerMask"; + private const string _renderingLayerMaskPropName = "_renderingLayerMask"; + private const string _settingsPropName = "_outlineSettings"; + private const string _colorPropName = "_outlineColor"; + private const string _widthPropName = "_outlineWidth"; + private const string _intensityPropName = "_outlineIntensity"; + private const string _cutoffPropName = "_outlineAlphaCutoff"; + private const string _renderModePropName = "_outlineMode"; + + private static readonly string[] _renderingLayerMaskNames = new string[] + { + "Layer1", + "Layer2", + "Layer3", + "Layer4", + "Layer5", + "Layer6", + "Layer7", + "Layer8", + "Layer9", + "Layer10", + "Layer11", + "Layer12", + "Layer13", + "Layer14", + "Layer15", + "Layer16", + "Layer17", + "Layer18", + "Layer19", + "Layer20", + "Layer21", + "Layer22", + "Layer23", + "Layer24", + "Layer25", + "Layer26", + "Layer27", + "Layer28", + "Layer29", + "Layer30", + "Layer31", + "Layer32", + }; + + public override void OnInspectorGUI() + { + base.OnInspectorGUI(); + + var colorProp = serializedObject.FindProperty(_colorPropName); + var widthProp = serializedObject.FindProperty(_widthPropName); + var intensityProp = serializedObject.FindProperty(_intensityPropName); + var cutoffProp = serializedObject.FindProperty(_cutoffPropName); + var renderModeProp = serializedObject.FindProperty(_renderModePropName); + var renderMode = (OutlineRenderFlags)renderModeProp.intValue; + + //EditorGUILayout.PropertyField(colorProp, _colorContent); + colorProp.colorValue = EditorGUILayout.ColorField(OutlineEditorUtility.ColorContent, colorProp.colorValue, true, true, true); + + EditorGUILayout.PropertyField(widthProp, OutlineEditorUtility.WidthContent); + + //EditorGUILayout.PropertyField(renderModeProp, _renderModeContent); + renderModeProp.intValue = (int)(OutlineRenderFlags)EditorGUILayout.EnumFlagsField(OutlineEditorUtility.RenderFlagsContent, renderMode); + + if ((renderMode & OutlineRenderFlags.Blurred) != 0) + { + EditorGUILayout.PropertyField(intensityProp, OutlineEditorUtility.BlurIntensityContent); + } + + if ((renderMode & OutlineRenderFlags.EnableAlphaTesting) != 0) + { + EditorGUILayout.PropertyField(cutoffProp, OutlineEditorUtility.AlphaCutoffContent); + } + + serializedObject.ApplyModifiedProperties(); + } + + internal static float GetSettingsInstanceHeight(SerializedProperty property) + { + var lineCy = EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing; + var renderModeProp = property.FindPropertyRelative(_renderModePropName); + var renderMode = (OutlineRenderFlags)renderModeProp.intValue; + var result = lineCy * 4; + + if ((renderMode & OutlineRenderFlags.Blurred) != 0) + { + result += lineCy; + } + + if ((renderMode & OutlineRenderFlags.EnableAlphaTesting) != 0) + { + result += lineCy; + } + + return result; + } + + internal static float GetSettingsWithMaskHeight(SerializedProperty property) + { + var lineCy = EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing; + var filterModeProp = property.FindPropertyRelative(_filterModePropName); + var renderOutlineSettings = false; + + if (filterModeProp.intValue == (int)OutlineFilterMode.UseLayerMask) + { + var layerMaskProp = property.FindPropertyRelative(_layerMaskPropName); + renderOutlineSettings = true; + } + else if (filterModeProp.intValue == (int)OutlineFilterMode.UseRenderingLayerMask) + { + var renderingLayerMaskProp = property.FindPropertyRelative(_renderingLayerMaskPropName); + renderOutlineSettings = true; + } + + if (renderOutlineSettings) + { + var renderModeProp = property.FindPropertyRelative(_renderModePropName); + var renderMode = (OutlineRenderFlags)renderModeProp.intValue; + var result = lineCy * 6; + + if ((renderMode & OutlineRenderFlags.Blurred) != 0) + { + result += lineCy; + } + + if ((renderMode & OutlineRenderFlags.EnableAlphaTesting) != 0) + { + result += lineCy; + } + + return result; + } + + return lineCy; + } + + internal static void DrawSettingsInstance(Rect rc, SerializedProperty property) + { + var settingsProp = property.FindPropertyRelative(_settingsPropName); + + EditorGUI.PropertyField(new Rect(rc.x, rc.y, rc.width, EditorGUIUtility.singleLineHeight), settingsProp); + EditorGUI.indentLevel += 1; + + if (settingsProp.objectReferenceValue) + { + var obj = new SerializedObject(settingsProp.objectReferenceValue); + var colorProp = obj.FindProperty(_colorPropName); + var widthProp = obj.FindProperty(_widthPropName); + var intensityProp = obj.FindProperty(_intensityPropName); + var cutoffProp = obj.FindProperty(_cutoffPropName); + var renderModeProp = obj.FindProperty(_renderModePropName); + + EditorGUI.BeginDisabledGroup(true); + DrawSettingsInternal(rc, colorProp, widthProp, intensityProp, cutoffProp, renderModeProp); + EditorGUI.EndDisabledGroup(); + } + else + { + var colorProp = property.FindPropertyRelative(_colorPropName); + var widthProp = property.FindPropertyRelative(_widthPropName); + var intensityProp = property.FindPropertyRelative(_intensityPropName); + var cutoffProp = property.FindPropertyRelative(_cutoffPropName); + var renderModeProp = property.FindPropertyRelative(_renderModePropName); + + DrawSettingsInternal(rc, colorProp, widthProp, intensityProp, cutoffProp, renderModeProp); + } + + EditorGUI.indentLevel -= 1; + } + + internal static void DrawSettingsWithMask(Rect rc, SerializedProperty property) + { + var lineCy = EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing; + var filterModeProp = property.FindPropertyRelative(_filterModePropName); + + EditorGUI.PropertyField(new Rect(rc.x, rc.y, rc.width, EditorGUIUtility.singleLineHeight), filterModeProp, OutlineEditorUtility.FilterSettingsContent); + + if (filterModeProp.intValue == (int)OutlineFilterMode.UseLayerMask) + { + var layerMaskProp = property.FindPropertyRelative(_layerMaskPropName); + + EditorGUI.indentLevel += 1; + EditorGUI.PropertyField(new Rect(rc.x, rc.y + lineCy, rc.width, EditorGUIUtility.singleLineHeight), layerMaskProp, OutlineEditorUtility.LayerMaskContent); + EditorGUI.indentLevel -= 1; + + DrawSettingsInstance(new Rect(rc.x, rc.y + lineCy * 2, rc.width, rc.height - lineCy), property); + } + else if (filterModeProp.intValue == (int)OutlineFilterMode.UseRenderingLayerMask) + { + var renderingLayerMaskProp = property.FindPropertyRelative(_renderingLayerMaskPropName); + + EditorGUI.indentLevel += 1; + renderingLayerMaskProp.intValue = EditorGUI.MaskField(new Rect(rc.x, rc.y + lineCy, rc.width, EditorGUIUtility.singleLineHeight), OutlineEditorUtility.RenderingLayerMaskContent, renderingLayerMaskProp.intValue, _renderingLayerMaskNames); + EditorGUI.indentLevel -= 1; + + DrawSettingsInstance(new Rect(rc.x, rc.y + lineCy * 2, rc.width, rc.height - lineCy), property); + } + } + + private static void DrawSettingsInternal(Rect rc, SerializedProperty colorProp, SerializedProperty widthProp, SerializedProperty intensityProp, SerializedProperty cutoffProp, SerializedProperty renderModeProp) + { + var renderMode = (OutlineRenderFlags)renderModeProp.intValue; + var lineCy = EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing; + var n = 4; + + //EditorGUI.PropertyField(new Rect(rc.x, rc.y + 1 * lineCy, rc.width, EditorGUIUtility.singleLineHeight), colorProp, _colorContent); + colorProp.colorValue = EditorGUI.ColorField(new Rect(rc.x, rc.y + 1 * lineCy, rc.width, EditorGUIUtility.singleLineHeight), OutlineEditorUtility.ColorContent, colorProp.colorValue, true, true, true); + + EditorGUI.PropertyField(new Rect(rc.x, rc.y + 2 * lineCy, rc.width, EditorGUIUtility.singleLineHeight), widthProp, OutlineEditorUtility.WidthContent); + + // NOTE: EditorGUI.PropertyField doesn't allow multi-selection, have to use EnumFlagsField explixitly. + renderModeProp.intValue = (int)(OutlineRenderFlags)EditorGUI.EnumFlagsField(new Rect(rc.x, rc.y + 3 * lineCy, rc.width, EditorGUIUtility.singleLineHeight), OutlineEditorUtility.RenderFlagsContent, renderMode); + + if ((renderMode & OutlineRenderFlags.Blurred) != 0) + { + EditorGUI.PropertyField(new Rect(rc.x, rc.y + n++ * lineCy, rc.width, EditorGUIUtility.singleLineHeight), intensityProp, OutlineEditorUtility.BlurIntensityContent); + } + + if ((renderMode & OutlineRenderFlags.EnableAlphaTesting) != 0) + { + EditorGUI.PropertyField(new Rect(rc.x, rc.y + n * lineCy, rc.width, EditorGUIUtility.singleLineHeight), cutoffProp, OutlineEditorUtility.AlphaCutoffContent); + } + } + } +} diff --git a/Editor/Scripts/OutlineSettingsEditor.cs.meta b/Editor/Scripts/OutlineSettingsEditor.cs.meta new file mode 100644 index 0000000..2569298 --- /dev/null +++ b/Editor/Scripts/OutlineSettingsEditor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 28f2a32d8600f8045a4b9a9916ff8801 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/Scripts/OutlineSettingsInstanceDrawer.cs b/Editor/Scripts/OutlineSettingsInstanceDrawer.cs new file mode 100644 index 0000000..fa470ed --- /dev/null +++ b/Editor/Scripts/OutlineSettingsInstanceDrawer.cs @@ -0,0 +1,27 @@ +// 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; +using System.Collections.Generic; +using UnityEditor; +using UnityEngine; + +namespace UnityFx.Outline +{ + [CustomPropertyDrawer(typeof(OutlineSettingsInstance))] + public class OutlineSettingsInstanceDrawer : PropertyDrawer + { + public override void OnGUI(Rect rc, SerializedProperty property, GUIContent label) + { + EditorGUI.BeginProperty(rc, label, property); + OutlineSettingsEditor.DrawSettingsInstance(rc, property); + EditorGUI.EndProperty(); + } + + public override float GetPropertyHeight(SerializedProperty property, GUIContent label) + { + return OutlineSettingsEditor.GetSettingsInstanceHeight(property); + } + } +} diff --git a/Editor/Scripts/OutlineSettingsInstanceDrawer.cs.meta b/Editor/Scripts/OutlineSettingsInstanceDrawer.cs.meta new file mode 100644 index 0000000..99ae287 --- /dev/null +++ b/Editor/Scripts/OutlineSettingsInstanceDrawer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9a7d070135166b04783f98841111c742 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/Scripts/OutlineSettingsWithLayerMaskDrawer.cs b/Editor/Scripts/OutlineSettingsWithLayerMaskDrawer.cs new file mode 100644 index 0000000..76c1971 --- /dev/null +++ b/Editor/Scripts/OutlineSettingsWithLayerMaskDrawer.cs @@ -0,0 +1,27 @@ +// 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; +using System.Collections.Generic; +using UnityEditor; +using UnityEngine; + +namespace UnityFx.Outline +{ + [CustomPropertyDrawer(typeof(OutlineSettingsWithLayerMask))] + public class OutlineSettingsWithLayerMaskDrawer : PropertyDrawer + { + public override void OnGUI(Rect rc, SerializedProperty property, GUIContent label) + { + EditorGUI.BeginProperty(rc, label, property); + OutlineSettingsEditor.DrawSettingsWithMask(rc, property); + EditorGUI.EndProperty(); + } + + public override float GetPropertyHeight(SerializedProperty property, GUIContent label) + { + return OutlineSettingsEditor.GetSettingsWithMaskHeight(property); + } + } +} diff --git a/Editor/Scripts/OutlineSettingsWithLayerMaskDrawer.cs.meta b/Editor/Scripts/OutlineSettingsWithLayerMaskDrawer.cs.meta new file mode 100644 index 0000000..358c115 --- /dev/null +++ b/Editor/Scripts/OutlineSettingsWithLayerMaskDrawer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5558cd049bcb613438115d59a4376272 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/UnityFx.Outline.Editor.asmdef b/Editor/UnityFx.Outline.Editor.asmdef new file mode 100644 index 0000000..04858cc --- /dev/null +++ b/Editor/UnityFx.Outline.Editor.asmdef @@ -0,0 +1,16 @@ +{ + "name": "UnityFx.Outline.Editor", + "references": [ + "UnityFx.Outline" + ], + "optionalUnityReferences": [], + "includePlatforms": [ + "Editor" + ], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [] +} \ No newline at end of file diff --git a/Editor/UnityFx.Outline.Editor.asmdef.meta b/Editor/UnityFx.Outline.Editor.asmdef.meta new file mode 100644 index 0000000..ec90eb8 --- /dev/null +++ b/Editor/UnityFx.Outline.Editor.asmdef.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 9ce6758f27194b64fadf06ac518b5196 +timeCreated: 1566558329 +licenseType: Pro +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/README.md b/README.md new file mode 100644 index 0000000..ab04468 --- /dev/null +++ b/README.md @@ -0,0 +1,11 @@ +# UnityFx.Outline + +## SUMMARY +Screen-space outline effect for Unity. + +## USEFUL LINKS +* [Github project](https://github.com/Arvtesh/UnityFx.Outline) +* [npm package](https://www.npmjs.com/package/com.unityfx.outline) +* [Documentation](https://github.com/Arvtesh/UnityFx.Outline/blob/master/README.md) +* [License](https://github.com/Arvtesh/UnityFx.Outline/blob/master/LICENSE.md) +* [Support](mailto:arvtesh@gmail.com) diff --git a/README.md.meta b/README.md.meta new file mode 100644 index 0000000..eb46e78 --- /dev/null +++ b/README.md.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 4b1c4eed7166ed4429494dc10c2a3d6c +timeCreated: 1566148623 +licenseType: Free +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime.meta b/Runtime.meta new file mode 100644 index 0000000..a63e99c --- /dev/null +++ b/Runtime.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: c0a74cc33268cc443a59bdce8fcf3948 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Prefabs.meta b/Runtime/Prefabs.meta new file mode 100644 index 0000000..88c5e98 --- /dev/null +++ b/Runtime/Prefabs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 82099d138ba6a5c40a5915c4ca5211fe +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Prefabs/OutlineResources.asset b/Runtime/Prefabs/OutlineResources.asset new file mode 100644 index 0000000..b00ab91 --- /dev/null +++ b/Runtime/Prefabs/OutlineResources.asset @@ -0,0 +1,17 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: b503341e0a514e3489c4851727e68257, type: 3} + m_Name: OutlineResources + m_EditorClassIdentifier: + _renderShader: {fileID: 4800000, guid: ac20fbf75bafe454aba5ef3c098349df, type: 3} + _outlineShader: {fileID: 4800000, guid: 41c9acbf41c8245498ac9beab378de12, type: 3} + _enableInstancing: 0 diff --git a/Runtime/Prefabs/OutlineResources.asset.meta b/Runtime/Prefabs/OutlineResources.asset.meta new file mode 100644 index 0000000..d66452d --- /dev/null +++ b/Runtime/Prefabs/OutlineResources.asset.meta @@ -0,0 +1,10 @@ +fileFormatVersion: 2 +guid: d28e70f030b1a634db9a6a6d5478ef19 +timeCreated: 1566149572 +licenseType: Free +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Runtime.meta b/Runtime/Runtime.meta new file mode 100644 index 0000000..d7752ff --- /dev/null +++ b/Runtime/Runtime.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 437f4cd62fcd5614e807205e9adf0cb6 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Runtime/HighLightManager.cs b/Runtime/Runtime/HighLightManager.cs new file mode 100644 index 0000000..a9c18d2 --- /dev/null +++ b/Runtime/Runtime/HighLightManager.cs @@ -0,0 +1,46 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using UnityFx.Outline; + +public class HighLightManager : MonoBehaviour +{ + static OutlineLayerCollection layerCollection; + private static HighLightManager instance = null; + + public static HighLightManager Instance + { + get + { + if (instance == null) + { + GameObject go = new GameObject("HightlightMananger"); + DontDestroyOnLoad(go); + instance = go.AddComponent(); + layerCollection = Resources.Load("OutlineLayerCollection"); + + } + return instance; + } + } + ///// + ///// 测试使用 + ///// + //[RuntimeInitializeOnLoadMethod] + //private static void Test() + //{ + // Instance.ControllerHighLight(GameObject.Find("Cube"), true); + //} + private void OnApplicationQuit() + { + layerCollection?.ClearLayerContent(); + } + public void ControllerHighLight(GameObject go, bool value) + { + if (value) + layerCollection.GetOrAddLayer(0).Add(go); + else + layerCollection.GetOrAddLayer(0).Remove(go); + } + +} diff --git a/Runtime/Runtime/HighLightManager.cs.meta b/Runtime/Runtime/HighLightManager.cs.meta new file mode 100644 index 0000000..3dc26ad --- /dev/null +++ b/Runtime/Runtime/HighLightManager.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ef887207b8bcd304c9e6ab4b272ccf0c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Runtime/Prefabs.meta b/Runtime/Runtime/Prefabs.meta new file mode 100644 index 0000000..5e39502 --- /dev/null +++ b/Runtime/Runtime/Prefabs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 66d913784d1955a4fbf8279b0a0f5d7b +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Runtime/Prefabs/OutlineResources.URP.asset b/Runtime/Runtime/Prefabs/OutlineResources.URP.asset new file mode 100644 index 0000000..7a704f1 --- /dev/null +++ b/Runtime/Runtime/Prefabs/OutlineResources.URP.asset @@ -0,0 +1,17 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: b503341e0a514e3489c4851727e68257, type: 3} + m_Name: OutlineResources.URP + m_EditorClassIdentifier: + _renderShader: {fileID: 4800000, guid: 2140fc327e711b549bc9fe301e6f4621, type: 3} + _outlineShader: {fileID: 4800000, guid: da6518c999b52e743bff80732b460ff4, type: 3} + _enableInstancing: 0 diff --git a/Runtime/Runtime/Prefabs/OutlineResources.URP.asset.meta b/Runtime/Runtime/Prefabs/OutlineResources.URP.asset.meta new file mode 100644 index 0000000..265eebe --- /dev/null +++ b/Runtime/Runtime/Prefabs/OutlineResources.URP.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 231d88937a104094b8e4e0fdb8d2e77b +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Runtime/Scripts.meta b/Runtime/Runtime/Scripts.meta new file mode 100644 index 0000000..100ae09 --- /dev/null +++ b/Runtime/Runtime/Scripts.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 85518f862b075044bbd76d57354f8f3e +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Runtime/Scripts/OutlineFeature.cs b/Runtime/Runtime/Scripts/OutlineFeature.cs new file mode 100644 index 0000000..2847198 --- /dev/null +++ b/Runtime/Runtime/Scripts/OutlineFeature.cs @@ -0,0 +1,91 @@ +// Copyright (C) 2019-2021 Alexander Bogarsukov. All rights reserved. +// See the LICENSE.md file in the project root for more information. + +using System; +using UnityEngine; +using UnityEngine.Rendering; +using UnityEngine.Rendering.Universal; + +namespace UnityFx.Outline.URP +{ + /// + /// Outline feature (URP). + /// + /// + /// Add instance of this class to . Configure + /// and assign outline resources and layers collection. Make sure + /// is set if you use . + /// + public class OutlineFeature : ScriptableRendererFeature + { + #region data + +#pragma warning disable 0649 + + [SerializeField, Tooltip(OutlineResources.OutlineResourcesTooltip)] + private OutlineResources _outlineResources; + [SerializeField, Tooltip(OutlineResources.OutlineLayerCollectionTooltip)] + private OutlineLayerCollection _outlineLayers; + [SerializeField] + private OutlineSettingsWithLayerMask _outlineSettings; + [SerializeField] + private RenderPassEvent _renderPassEvent = RenderPassEvent.AfterRenderingSkybox; + [SerializeField] + public string[] _shaderPassNames; + +#pragma warning restore 0649 + + private OutlinePass _outlinePass; + private string _featureName; + + #endregion + + #region interface + + internal OutlineResources OutlineResources => _outlineResources; + + internal OutlineLayerCollection OutlineLayers => _outlineLayers; + + internal IOutlineSettings OutlineSettings => _outlineSettings; + + internal int OutlineLayerMask => _outlineSettings.OutlineLayerMask; + + internal uint OutlineRenderingLayerMask => _outlineSettings.OutlineRenderingLayerMask; + + internal string FeatureName => _featureName; + + #endregion + + #region ScriptableRendererFeature + + /// + public override void Create() + { + if (_outlineSettings != null) + { + _featureName = OutlineResources.EffectName + '-' + _outlineSettings.OutlineLayerMask; + } + else + { + _featureName = OutlineResources.EffectName; + } + + _outlinePass = new OutlinePass(this, _shaderPassNames) + { + renderPassEvent = _renderPassEvent + }; + } + + /// + public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData) + { + if (_outlineResources && _outlineResources.IsValid) + { + _outlinePass.Setup(renderer); + renderer.EnqueuePass(_outlinePass); + } + } + + #endregion + } +} diff --git a/Runtime/Runtime/Scripts/OutlineFeature.cs.meta b/Runtime/Runtime/Scripts/OutlineFeature.cs.meta new file mode 100644 index 0000000..6c8367d --- /dev/null +++ b/Runtime/Runtime/Scripts/OutlineFeature.cs.meta @@ -0,0 +1,14 @@ +fileFormatVersion: 2 +guid: dd37d03d18ee9584d881763c34816b35 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: + - _outlineResources: {fileID: 11400000, guid: 231d88937a104094b8e4e0fdb8d2e77b, + type: 2} + - _outlineLayers: {instanceID: 0} + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Runtime/Scripts/OutlinePass.cs b/Runtime/Runtime/Scripts/OutlinePass.cs new file mode 100644 index 0000000..5cf5821 --- /dev/null +++ b/Runtime/Runtime/Scripts/OutlinePass.cs @@ -0,0 +1,106 @@ +// 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; +using UnityEngine.Rendering.Universal; +using UnityEngine.XR; + +namespace UnityFx.Outline.URP +{ + internal class OutlinePass : ScriptableRenderPass + { + private const string _profilerTag = "OutlinePass"; + + private readonly OutlineFeature _feature; + private readonly List _renderObjects = new List(); + private readonly List _shaderTagIdList = new List(); + + private ScriptableRenderer _renderer; + + public OutlinePass(OutlineFeature feature, string[] shaderTags) + { + _feature = feature; + + if (shaderTags != null && shaderTags.Length > 0) + { + foreach (var passName in shaderTags) + { + _shaderTagIdList.Add(new ShaderTagId(passName)); + } + } + else + { + _shaderTagIdList.Add(new ShaderTagId("UniversalForward")); + _shaderTagIdList.Add(new ShaderTagId("LightweightForward")); + _shaderTagIdList.Add(new ShaderTagId("SRPDefaultUnlit")); + } + } + + public void Setup(ScriptableRenderer renderer) + { + _renderer = renderer; + } + + public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData) + { + var outlineResources = _feature.OutlineResources; + var outlineSettings = _feature.OutlineSettings; + var camData = renderingData.cameraData; + var depthTexture = new RenderTargetIdentifier("_CameraDepthTexture"); + + if (_feature.OutlineLayerMask != 0) + { + var cmd = CommandBufferPool.Get(_feature.FeatureName); + var filteringSettings = new FilteringSettings(RenderQueueRange.all, _feature.OutlineLayerMask, _feature.OutlineRenderingLayerMask); + var renderStateBlock = new RenderStateBlock(RenderStateMask.Nothing); + var sortingCriteria = camData.defaultOpaqueSortFlags; + var drawingSettings = CreateDrawingSettings(_shaderTagIdList, ref renderingData, sortingCriteria); + + drawingSettings.enableDynamicBatching = true; + drawingSettings.overrideMaterial = outlineResources.RenderMaterial; + + if (outlineSettings.IsAlphaTestingEnabled()) + { + drawingSettings.overrideMaterialPassIndex = OutlineResources.RenderShaderAlphaTestPassId; + cmd.SetGlobalFloat(outlineResources.AlphaCutoffId, outlineSettings.OutlineAlphaCutoff); + } + else + { + drawingSettings.overrideMaterialPassIndex = OutlineResources.RenderShaderDefaultPassId; + } + + using (var renderer = new OutlineRenderer(cmd, outlineResources, _renderer.cameraColorTarget, depthTexture, camData.cameraTargetDescriptor)) + { + renderer.RenderObjectClear(outlineSettings.OutlineRenderMode); + context.ExecuteCommandBuffer(cmd); + + context.DrawRenderers(renderingData.cullResults, ref drawingSettings, ref filteringSettings, ref renderStateBlock); + + cmd.Clear(); + renderer.RenderOutline(outlineSettings); + } + + context.ExecuteCommandBuffer(cmd); + CommandBufferPool.Release(cmd); + } + + if (_feature.OutlineLayers) + { + var cmd = CommandBufferPool.Get(OutlineResources.EffectName); + + using (var renderer = new OutlineRenderer(cmd, outlineResources, _renderer.cameraColorTarget, depthTexture, camData.cameraTargetDescriptor)) + { + _renderObjects.Clear(); + _feature.OutlineLayers.GetRenderObjects(_renderObjects); + renderer.Render(_renderObjects); + } + + context.ExecuteCommandBuffer(cmd); + CommandBufferPool.Release(cmd); + } + } + } +} diff --git a/Runtime/Runtime/Scripts/OutlinePass.cs.meta b/Runtime/Runtime/Scripts/OutlinePass.cs.meta new file mode 100644 index 0000000..5c90ec9 --- /dev/null +++ b/Runtime/Runtime/Scripts/OutlinePass.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7fda4bd4356b87c4a93e27fbc5390d5f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Runtime/Shaders.meta b/Runtime/Runtime/Shaders.meta new file mode 100644 index 0000000..b6518ed --- /dev/null +++ b/Runtime/Runtime/Shaders.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 5e29d2be8edefda438a2865a7030dad6 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Runtime/Shaders/Outline.URP.shader b/Runtime/Runtime/Shaders/Outline.URP.shader new file mode 100644 index 0000000..bb5af30 --- /dev/null +++ b/Runtime/Runtime/Shaders/Outline.URP.shader @@ -0,0 +1,212 @@ +// Copyright (C) 2019-2021 Alexander Bogarsukov. All rights reserved. +// See the LICENSE.md file in the project root for more information. + +// Renders outline based on a texture produced with 'UnityF/OutlineColor'. +// Modified version of 'Custom/Post Outline' shader taken from https://willweissman.wordpress.com/tutorials/shaders/unity-shaderlab-object-outlines/. +Shader "Hidden/UnityFx/Outline.URP" +{ + HLSLINCLUDE + + #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" + + TEXTURE2D_X(_MaskTex); + SAMPLER(sampler_MaskTex); + + TEXTURE2D_X(_MainTex); + SAMPLER(sampler_MainTex); + float2 _MainTex_TexelSize; + + float4 _Color; + float _Intensity; + int _Width; + float _GaussSamples[32]; + + struct Varyings + { + float4 positionCS : SV_POSITION; + float2 uv : TEXCOORD0; + UNITY_VERTEX_OUTPUT_STEREO + }; + +#if SHADER_TARGET < 35 || _USE_DRAWMESH + + struct Attributes + { + float4 positionOS : POSITION; + float2 uv : TEXCOORD0; + UNITY_VERTEX_INPUT_INSTANCE_ID + }; + + Varyings VertexSimple(Attributes input) + { + Varyings output = (Varyings)0; + + UNITY_SETUP_INSTANCE_ID(input); + UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(output); + + output.positionCS = float4(input.positionOS.xy, UNITY_NEAR_CLIP_VALUE, 1); + output.uv = ComputeScreenPos(output.positionCS).xy; + + return output; + } + +#else + + struct Attributes + { + uint vertexID : SV_VertexID; + UNITY_VERTEX_INPUT_INSTANCE_ID + }; + + Varyings VertexSimple(Attributes input) + { + Varyings output = (Varyings)0; + + UNITY_SETUP_INSTANCE_ID(input); + UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(output); + + output.positionCS = GetFullScreenTriangleVertexPosition(input.vertexID); + output.uv = GetFullScreenTriangleTexCoord(input.vertexID); + + return output; + } + +#endif + + float CalcIntensityN0(float2 uv, float2 offset, int k) + { + return SAMPLE_TEXTURE2D_X(_MainTex, sampler_MainTex, uv + k * offset).r * _GaussSamples[k]; + } + + float CalcIntensityN1(float2 uv, float2 offset, int k) + { + return SAMPLE_TEXTURE2D_X(_MainTex, sampler_MainTex, uv - k * offset).r * _GaussSamples[k]; + } + + float CalcIntensity(float2 uv, float2 offset) + { + float intensity = 0; + + // Accumulates horizontal or vertical blur intensity for the specified texture position. + // Set offset = (tx, 0) for horizontal sampling and offset = (0, ty) for vertical. + // + // NOTE: Unroll directive is needed to make the method function on platforms like WebGL 1.0 where loops are not supported. + // If maximum outline width is changed here, it should be changed in OutlineResources.MaxWidth as well. + // + [unroll(32)] + for (int k = 1; k <= _Width; ++k) + { + intensity += CalcIntensityN0(uv, offset, k); + intensity += CalcIntensityN1(uv, offset, k); + } + + intensity += CalcIntensityN0(uv, offset, 0); + return intensity; + } + + float4 FragmentH(Varyings i) : SV_Target + { + UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(i); + + float2 uv = UnityStereoTransformScreenSpaceTex(i.uv); + float intensity = CalcIntensity(uv, float2(_MainTex_TexelSize.x, 0)); + return float4(intensity, intensity, intensity, 1); + } + + float4 FragmentV(Varyings i) : SV_Target + { + UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(i); + + float2 uv = UnityStereoTransformScreenSpaceTex(i.uv); + + if (SAMPLE_TEXTURE2D_X(_MaskTex, sampler_MaskTex, uv).r > 0) + { + // TODO: Avoid discard/clip to improve performance on mobiles. + discard; + } + + float intensity = CalcIntensity(uv, float2(0, _MainTex_TexelSize.y)); + intensity = _Intensity > 99 ? step(0.01, intensity) : intensity * _Intensity; + return float4(_Color.rgb, saturate(_Color.a * intensity)); + } + + ENDHLSL + + // SM3.5+ + SubShader + { + Tags{ "RenderPipeline" = "UniversalPipeline" } + + Cull Off + ZWrite Off + ZTest Always + Lighting Off + + Pass + { + Name "HPass" + + HLSLPROGRAM + + #pragma target 3.5 + #pragma multi_compile_instancing + #pragma shader_feature_local _USE_DRAWMESH + #pragma vertex VertexSimple + #pragma fragment FragmentH + + ENDHLSL + } + + Pass + { + Name "VPassBlend" + Blend SrcAlpha OneMinusSrcAlpha + + HLSLPROGRAM + + #pragma target 3.5 + #pragma multi_compile_instancing + #pragma shader_feature_local _USE_DRAWMESH + #pragma vertex VertexSimple + #pragma fragment FragmentV + + ENDHLSL + } + } + + // SM2.0 + SubShader + { + Tags { "RenderPipeline" = "UniversalPipeline" } + + Cull Off + ZWrite Off + ZTest Always + Lighting Off + + Pass + { + Name "HPass" + + HLSLPROGRAM + + #pragma vertex VertexSimple + #pragma fragment FragmentH + + ENDHLSL + } + + Pass + { + Name "VPassBlend" + Blend SrcAlpha OneMinusSrcAlpha + + HLSLPROGRAM + + #pragma vertex VertexSimple + #pragma fragment FragmentV + + ENDHLSL + } + } +} diff --git a/Runtime/Runtime/Shaders/Outline.URP.shader.meta b/Runtime/Runtime/Shaders/Outline.URP.shader.meta new file mode 100644 index 0000000..51c28b4 --- /dev/null +++ b/Runtime/Runtime/Shaders/Outline.URP.shader.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: da6518c999b52e743bff80732b460ff4 +ShaderImporter: + externalObjects: {} + defaultTextures: [] + nonModifiableTextures: [] + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Runtime/Shaders/OutlineColor.URP.shader b/Runtime/Runtime/Shaders/OutlineColor.URP.shader new file mode 100644 index 0000000..80f78ea --- /dev/null +++ b/Runtime/Runtime/Shaders/OutlineColor.URP.shader @@ -0,0 +1,67 @@ +// Copyright (C) 2019-2021 Alexander Bogarsukov. All rights reserved. +// See the LICENSE.md file in the project root for more information. + +// Renders everything with while color. +// Modified version of 'Custom/DrawSimple' shader taken from https://willweissman.wordpress.com/tutorials/shaders/unity-shaderlab-object-outlines/. +Shader "Hidden/UnityFx/OutlineColor.URP" +{ + HLSLINCLUDE + + #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" + #include "Packages/com.unity.render-pipelines.universal/Shaders/PostProcessing/Common.hlsl" + + TEXTURE2D(_MainTex); + SAMPLER(sampler_MainTex); + + half _Cutoff; + + half4 FragmentSimple(Varyings input) : SV_Target + { + return 1; + } + + half4 FragmentAlphaTest(Varyings input) : SV_Target + { + half4 c = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, input.uv); + clip(c.a - _Cutoff); + return 1; + } + + ENDHLSL + + SubShader + { + Tags { "RenderPipeline" = "UniversalPipeline" } + + Cull Off + ZWrite Off + ZTest LEqual + Lighting Off + + Pass + { + Name "Opaque" + + HLSLPROGRAM + + #pragma multi_compile_instancing + #pragma vertex Vert + #pragma fragment FragmentSimple + + ENDHLSL + } + + Pass + { + Name "Transparent" + + HLSLPROGRAM + + #pragma multi_compile_instancing + #pragma vertex Vert + #pragma fragment FragmentAlphaTest + + ENDHLSL + } + } +} diff --git a/Runtime/Runtime/Shaders/OutlineColor.URP.shader.meta b/Runtime/Runtime/Shaders/OutlineColor.URP.shader.meta new file mode 100644 index 0000000..8272b33 --- /dev/null +++ b/Runtime/Runtime/Shaders/OutlineColor.URP.shader.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 2140fc327e711b549bc9fe301e6f4621 +ShaderImporter: + externalObjects: {} + defaultTextures: [] + nonModifiableTextures: [] + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Runtime/UnityFx.Outline.URP.asmdef b/Runtime/Runtime/UnityFx.Outline.URP.asmdef new file mode 100644 index 0000000..3f9ff3f --- /dev/null +++ b/Runtime/Runtime/UnityFx.Outline.URP.asmdef @@ -0,0 +1,17 @@ +{ + "name": "UnityFx.Outline.URP", + "references": [ + "Unity.RenderPipelines.Core.Runtime", + "Unity.RenderPipelines.Universal.Runtime", + "UnityFx.Outline" + ], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/Runtime/Runtime/UnityFx.Outline.URP.asmdef.meta b/Runtime/Runtime/UnityFx.Outline.URP.asmdef.meta new file mode 100644 index 0000000..418ae3e --- /dev/null +++ b/Runtime/Runtime/UnityFx.Outline.URP.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 8130e23c3199afb43ae1c34b3e328d00 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts.meta b/Runtime/Scripts.meta new file mode 100644 index 0000000..fdf3731 --- /dev/null +++ b/Runtime/Scripts.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 5474ddc00e5e1574cba82c3dbad68ded +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/IOutlineSettings.cs b/Runtime/Scripts/IOutlineSettings.cs new file mode 100644 index 0000000..f44bbe7 --- /dev/null +++ b/Runtime/Scripts/IOutlineSettings.cs @@ -0,0 +1,51 @@ +// Copyright (C) 2019-2021 Alexander Bogarsukov. All rights reserved. +// See the LICENSE.md file in the project root for more information. + +using System; +using UnityEngine; + +namespace UnityFx.Outline +{ + /// + /// Generic outline settings. + /// + public interface IOutlineSettings : IEquatable + { + /// + /// Gets or sets outline color. + /// + /// + /// + Color OutlineColor { get; set; } + + /// + /// Gets or sets outline width in pixels. Allowed range is [, ]. + /// + /// + /// + int OutlineWidth { get; set; } + + /// + /// Gets or sets outline intensity value. Allowed range is [, ]. + /// This is used for blurred oulines only (i.e. has flag). + /// + /// + /// + /// + float OutlineIntensity { get; set; } + + /// + /// Gets or sets alpha cutoff value. Allowed range is [0, 1]. This is used only when has flag. + /// + /// + float OutlineAlphaCutoff { get; set; } + + /// + /// Gets or sets outline render mode. + /// + /// + /// + /// + OutlineRenderFlags OutlineRenderMode { get; set; } + } +} diff --git a/Runtime/Scripts/IOutlineSettings.cs.meta b/Runtime/Scripts/IOutlineSettings.cs.meta new file mode 100644 index 0000000..688fd30 --- /dev/null +++ b/Runtime/Scripts/IOutlineSettings.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: efc18f75d5206f14a80e9306650c858a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/OutlineBehaviour.cs b/Runtime/Scripts/OutlineBehaviour.cs new file mode 100644 index 0000000..12193c9 --- /dev/null +++ b/Runtime/Scripts/OutlineBehaviour.cs @@ -0,0 +1,443 @@ +// 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; +using System.Collections.Generic; +using UnityEngine; +using UnityEngine.Rendering; + +namespace UnityFx.Outline +{ + /// + /// Attach this script to a to add outline effect. It can be configured in edit-time or in runtime via scripts. + /// + /// + [ExecuteInEditMode] + [DisallowMultipleComponent] + public sealed class OutlineBehaviour : MonoBehaviour, IOutlineSettings + { + #region data + +#pragma warning disable 0649 + + [SerializeField, Tooltip(OutlineResources.OutlineResourcesTooltip)] + private OutlineResources _outlineResources; + [SerializeField, HideInInspector] + private OutlineSettingsInstance _outlineSettings; + [SerializeField, HideInInspector] + private int _ignoreLayerMask; + [SerializeField, HideInInspector] + private CameraEvent _cameraEvent = OutlineRenderer.RenderEvent; + [SerializeField, HideInInspector] + private Camera _targetCamera; + [SerializeField, Tooltip("If set, list of object renderers is updated on each frame. Enable if the object has child renderers which are enabled/disabled frequently.")] + private bool _updateRenderers; + +#pragma warning restore 0649 + + private Dictionary _cameraMap = new Dictionary(); + private List _camerasToRemove = new List(); + private OutlineRendererCollection _renderers; + + #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 or sets outline settings. Set this to non- value to share settings with other components. + /// + /// + public OutlineSettings OutlineSettings + { + get + { + if (_outlineSettings == null) + { + _outlineSettings = new OutlineSettingsInstance(); + } + + return _outlineSettings.OutlineSettings; + } + set + { + if (_outlineSettings == null) + { + _outlineSettings = new OutlineSettingsInstance(); + } + + _outlineSettings.OutlineSettings = value; + } + } + + /// + /// Gets or sets layer mask to use for ignored components in this game object. + /// + public int IgnoreLayerMask + { + get + { + return _ignoreLayerMask; + } + set + { + if (_ignoreLayerMask != value) + { + _ignoreLayerMask = value; + _renderers?.Reset(false, value); + } + } + } + + /// + /// Gets or sets used to render the outlines. + /// + public CameraEvent RenderEvent + { + get + { + return _cameraEvent; + } + set + { + if (_cameraEvent != value) + { + foreach (var kvp in _cameraMap) + { + if (kvp.Key) + { + kvp.Key.RemoveCommandBuffer(_cameraEvent, kvp.Value); + kvp.Key.AddCommandBuffer(value, kvp.Value); + } + } + + _cameraEvent = value; + } + } + } + + /// + /// Gets outline renderers. By default all child components are used for outlining. + /// + /// + public ICollection OutlineRenderers + { + get + { + CreateRenderersIfNeeded(); + return _renderers; + } + } + + /// + /// Gets or sets camera to render outlines to. If not set, outlines are rendered to all active cameras. + /// + /// + public Camera Camera + { + get + { + return _targetCamera; + } + set + { + if (_targetCamera != value) + { + if (value) + { + _camerasToRemove.Clear(); + + foreach (var kvp in _cameraMap) + { + if (kvp.Key && kvp.Key != value) + { + kvp.Key.RemoveCommandBuffer(_cameraEvent, kvp.Value); + kvp.Value.Dispose(); + + _camerasToRemove.Add(kvp.Key); + } + } + + foreach (var camera in _camerasToRemove) + { + _cameraMap.Remove(camera); + } + } + + _targetCamera = value; + } + } + } + + /// + /// Gets all cameras outline data is rendered to. + /// + /// + public ICollection Cameras => _cameraMap.Keys; + + /// + /// Updates renderer list. + /// + /// + public void UpdateRenderers() + { + _renderers?.Reset(false, _ignoreLayerMask); + } + + #endregion + + #region MonoBehaviour + + private void Awake() + { + OutlineResources.LogSrpNotSupported(this); + OutlineResources.LogPpNotSupported(this); + + CreateRenderersIfNeeded(); + CreateSettingsIfNeeded(); + } + + private void OnEnable() + { + Camera.onPreRender += OnCameraPreRender; + } + + private void OnDisable() + { + Camera.onPreRender -= OnCameraPreRender; + + foreach (var kvp in _cameraMap) + { + if (kvp.Key) + { + kvp.Key.RemoveCommandBuffer(_cameraEvent, kvp.Value); + } + + kvp.Value.Dispose(); + } + + _cameraMap.Clear(); + } + + private void Update() + { + if (_outlineResources != null && _renderers != null) + { + _camerasToRemove.Clear(); + + if (_updateRenderers) + { + _renderers.Reset(false, _ignoreLayerMask); + } + + foreach (var kvp in _cameraMap) + { + var camera = kvp.Key; + var cmdBuffer = kvp.Value; + + if (camera) + { + cmdBuffer.Clear(); + FillCommandBuffer(camera, cmdBuffer); + } + else + { + cmdBuffer.Dispose(); + _camerasToRemove.Add(camera); + } + } + + foreach (var camera in _camerasToRemove) + { + _cameraMap.Remove(camera); + } + } + } + +#if UNITY_EDITOR + + private void OnValidate() + { + CreateRenderersIfNeeded(); + CreateSettingsIfNeeded(); + } + + private void Reset() + { + if (_renderers != null) + { + _renderers.Reset(false, _ignoreLayerMask); + } + } + +#endif + + #endregion + + #region IOutlineSettings + + /// + public Color OutlineColor + { + get + { + CreateSettingsIfNeeded(); + return _outlineSettings.OutlineColor; + } + set + { + CreateSettingsIfNeeded(); + _outlineSettings.OutlineColor = value; + } + } + + /// + public int OutlineWidth + { + get + { + CreateSettingsIfNeeded(); + return _outlineSettings.OutlineWidth; + } + set + { + CreateSettingsIfNeeded(); + _outlineSettings.OutlineWidth = value; + } + } + + /// + public float OutlineIntensity + { + get + { + CreateSettingsIfNeeded(); + return _outlineSettings.OutlineIntensity; + } + set + { + CreateSettingsIfNeeded(); + _outlineSettings.OutlineIntensity = value; + } + } + + /// + public float OutlineAlphaCutoff + { + get + { + CreateSettingsIfNeeded(); + return _outlineSettings.OutlineAlphaCutoff; + } + set + { + CreateSettingsIfNeeded(); + _outlineSettings.OutlineAlphaCutoff = value; + } + } + + /// + public OutlineRenderFlags OutlineRenderMode + { + get + { + CreateSettingsIfNeeded(); + return _outlineSettings.OutlineRenderMode; + } + set + { + CreateSettingsIfNeeded(); + _outlineSettings.OutlineRenderMode = value; + } + } + + #endregion + + #region IEquatable + + /// + public bool Equals(IOutlineSettings other) + { + return OutlineSettings.Equals(_outlineSettings, other); + } + + #endregion + + #region implementation + + private void OnCameraPreRender(Camera camera) + { + if (camera && (!_targetCamera || _targetCamera == camera)) + { + if (_outlineSettings.RequiresCameraDepth) + { + camera.depthTextureMode |= DepthTextureMode.Depth; + } + + if (!_cameraMap.ContainsKey(camera)) + { + var cmdBuf = new CommandBuffer(); + cmdBuf.name = string.Format("{0} - {1}", GetType().Name, name); + camera.AddCommandBuffer(_cameraEvent, cmdBuf); + + _cameraMap.Add(camera, cmdBuf); +#if UNITY_EDITOR + FillCommandBuffer(camera, cmdBuf); +#endif + } + } + } + + private void FillCommandBuffer(Camera camera, CommandBuffer cmdBuffer) + { + if (_renderers.Count > 0) + { + using (var renderer = new OutlineRenderer(cmdBuffer, _outlineResources, camera.actualRenderingPath)) + { + renderer.Render(_renderers.GetList(), _outlineSettings, name); + } + } + } + + private void CreateSettingsIfNeeded() + { + if (_outlineSettings == null) + { + _outlineSettings = new OutlineSettingsInstance(); + } + } + + private void CreateRenderersIfNeeded() + { + if (_renderers == null) + { + _renderers = new OutlineRendererCollection(gameObject); + _renderers.Reset(false, _ignoreLayerMask); + } + } + + #endregion + } +} diff --git a/Runtime/Scripts/OutlineBehaviour.cs.meta b/Runtime/Scripts/OutlineBehaviour.cs.meta new file mode 100644 index 0000000..a9c85ed --- /dev/null +++ b/Runtime/Scripts/OutlineBehaviour.cs.meta @@ -0,0 +1,15 @@ +fileFormatVersion: 2 +guid: 271c580db5fd384429cdac899152e9e0 +timeCreated: 1566149857 +licenseType: Free +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: + - _outlineResources: {fileID: 11400000, guid: d28e70f030b1a634db9a6a6d5478ef19, + type: 2} + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/OutlineBuilder.cs b/Runtime/Scripts/OutlineBuilder.cs new file mode 100644 index 0000000..f1b7433 --- /dev/null +++ b/Runtime/Scripts/OutlineBuilder.cs @@ -0,0 +1,93 @@ +// 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; +using System.Collections.Generic; +using UnityEngine; + +namespace UnityFx.Outline +{ + /// + /// A helper behaviour for managing content of via Unity Editor. + /// + public sealed class OutlineBuilder : MonoBehaviour + { + #region data + + [Serializable] + internal class ContentItem + { + public GameObject Go; + public int LayerIndex; + } + +#pragma warning disable 0649 + + [SerializeField, Tooltip(OutlineResources.OutlineLayerCollectionTooltip)] + private OutlineLayerCollection _outlineLayers; + [SerializeField, HideInInspector] + private List _content; + +#pragma warning restore 0649 + + #endregion + + #region interface + + internal List Content { get => _content; set => _content = value; } + + /// + /// Gets or sets a collection of layers to manage. + /// + public OutlineLayerCollection OutlineLayers { get => _outlineLayers; set => _outlineLayers = value; } + + /// + /// Clears content of all layers. + /// + /// + public void Clear() + { + _outlineLayers?.ClearLayerContent(); + } + + #endregion + + #region MonoBehaviour + + private void OnEnable() + { + if (_outlineLayers && _content != null) + { + foreach (var item in _content) + { + if (item.LayerIndex >= 0 && item.LayerIndex < _outlineLayers.Count && item.Go) + { + _outlineLayers.GetOrAddLayer(item.LayerIndex).Add(item.Go); + } + } + } + } + +#if UNITY_EDITOR + + private void Reset() + { + var effect = GetComponent(); + + if (effect) + { + _outlineLayers = effect.OutlineLayersInternal; + } + } + + private void OnDestroy() + { + _outlineLayers?.ClearLayerContent(); + } + +#endif + + #endregion + } +} diff --git a/Runtime/Scripts/OutlineBuilder.cs.meta b/Runtime/Scripts/OutlineBuilder.cs.meta new file mode 100644 index 0000000..fc6b25c --- /dev/null +++ b/Runtime/Scripts/OutlineBuilder.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e746e776b0ae00d4a9d458b9430b95d7 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/OutlineEffect.cs b/Runtime/Scripts/OutlineEffect.cs new file mode 100644 index 0000000..3d5eda5 --- /dev/null +++ b/Runtime/Scripts/OutlineEffect.cs @@ -0,0 +1,267 @@ +// 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 + } +} diff --git a/Runtime/Scripts/OutlineEffect.cs.meta b/Runtime/Scripts/OutlineEffect.cs.meta new file mode 100644 index 0000000..1044087 --- /dev/null +++ b/Runtime/Scripts/OutlineEffect.cs.meta @@ -0,0 +1,15 @@ +fileFormatVersion: 2 +guid: 270d3185d159bf54fb4cddbb42235437 +timeCreated: 1566149591 +licenseType: Free +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: + - _outlineResources: {fileID: 11400000, guid: d28e70f030b1a634db9a6a6d5478ef19, + type: 2} + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/OutlineFilterMode.cs b/Runtime/Scripts/OutlineFilterMode.cs new file mode 100644 index 0000000..f1edfdc --- /dev/null +++ b/Runtime/Scripts/OutlineFilterMode.cs @@ -0,0 +1,15 @@ +// Copyright (C) 2019-2021 Alexander Bogarsukov. All rights reserved. +// See the LICENSE.md file in the project root for more information. + +using System; +using UnityEngine; + +namespace UnityFx.Outline +{ + internal enum OutlineFilterMode + { + None, + UseLayerMask, + UseRenderingLayerMask, + } +} diff --git a/Runtime/Scripts/OutlineFilterMode.cs.meta b/Runtime/Scripts/OutlineFilterMode.cs.meta new file mode 100644 index 0000000..cd45e8d --- /dev/null +++ b/Runtime/Scripts/OutlineFilterMode.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 82c9d42cc303be24d852b8db7c4b650f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/OutlineLayer.cs b/Runtime/Scripts/OutlineLayer.cs new file mode 100644 index 0000000..b558afd --- /dev/null +++ b/Runtime/Scripts/OutlineLayer.cs @@ -0,0 +1,509 @@ +// 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; +using System.Collections.Generic; +using System.Text; +using UnityEngine; + +namespace UnityFx.Outline +{ + /// + /// A collection of instances that share outline settings. An + /// can only belong to one at time. + /// + /// + /// + [Serializable] + public sealed class OutlineLayer : ICollection, IReadOnlyCollection, IOutlineSettings + { + #region data + + [SerializeField, HideInInspector] + private OutlineSettingsInstance _settings = new OutlineSettingsInstance(); + [SerializeField, HideInInspector] + private string _name; + [SerializeField, HideInInspector] + private bool _enabled = true; + [SerializeField, HideInInspector] + private bool _mergeLayerObjects; + + private OutlineLayerCollection _parentCollection; + private Dictionary _outlineObjects = new Dictionary(); + private List _mergedRenderers; + + #endregion + + #region interface + + /// + /// Gets the layer name. + /// + public string Name + { + get + { + if (string.IsNullOrEmpty(_name)) + { + return "OutlineLayer #" + Index.ToString(); + } + + return _name; + } + } + + /// + /// Gets or sets a value indicating whether the layer is enabled. + /// + /// + public bool Enabled + { + get + { + return _enabled; + } + set + { + _enabled = value; + } + } + + /// + /// Gets or sets a value indicating whether layer game objects should be trated as one. + /// + public bool MergeLayerObjects + { + get + { + return _mergeLayerObjects; + } + set + { + _mergeLayerObjects = value; + } + } + + /// + /// Gets index of the layer in parent collection. + /// + public int Index + { + get + { + if (_parentCollection != null) + { + return _parentCollection.IndexOf(this); + } + + return -1; + } + } + + /// + /// Gets or sets outline settings. Set this to non- value to share settings with other components. + /// + public OutlineSettings OutlineSettings + { + get + { + return _settings.OutlineSettings; + } + set + { + _settings.OutlineSettings = value; + } + } + + /// + /// Initializes a new instance of the class. + /// + public OutlineLayer() + { + } + + /// + /// Initializes a new instance of the class. + /// + internal OutlineLayer(OutlineLayerCollection parentCollection) + { + _parentCollection = parentCollection; + } + + /// + /// Initializes a new instance of the class. + /// + public OutlineLayer(string name) + { + _name = name; + } + + /// + /// Initializes a new instance of the class. + /// + /// Thrown if is . + public OutlineLayer(OutlineSettings settings) + { + if (settings is null) + { + throw new ArgumentNullException(nameof(settings)); + } + + _settings.OutlineSettings = settings; + } + + /// + /// Initializes a new instance of the class. + /// + /// Thrown if is . + public OutlineLayer(string name, OutlineSettings settings) + { + if (settings is null) + { + throw new ArgumentNullException(nameof(settings)); + } + + _name = name; + _settings.OutlineSettings = settings; + } + + /// + /// Attempts to get renderers assosiated with the specified . + /// + /// Thrown if is . + public bool TryGetRenderers(GameObject go, out ICollection renderers) + { + if (go is null) + { + throw new ArgumentNullException(nameof(go)); + } + + if (_outlineObjects.TryGetValue(go, out var result)) + { + renderers = result; + return true; + } + + renderers = null; + return false; + } + + /// + /// Gets the objects for rendering. + /// + public void GetRenderObjects(IList renderObjects) + { + if (_enabled) + { + if (_mergeLayerObjects) + { + renderObjects.Add(new OutlineRenderObject(GetRenderers(), this, Name)); + } + else + { + foreach (var kvp in _outlineObjects) + { + var go = kvp.Key; + + if (go && go.activeInHierarchy) + { + renderObjects.Add(new OutlineRenderObject(kvp.Value.GetList(), _settings, go.name)); + } + } + } + } + } + + /// + /// Gets all layer renderers. + /// + public IReadOnlyList GetRenderers() + { + if (_enabled) + { + if (_mergedRenderers != null) + { + _mergedRenderers.Clear(); + } + else + { + _mergedRenderers = new List(); + } + + foreach (var kvp in _outlineObjects) + { + var go = kvp.Key; + + if (go && go.activeInHierarchy) + { + var rl = kvp.Value.GetList(); + + for (var i = 0; i < rl.Count; i++) + { + _mergedRenderers.Add(rl[i]); + } + } + } + + return _mergedRenderers; + } + + return Array.Empty(); + } + + #endregion + + #region internals + + internal string NameTag + { + get + { + return _name; + } + set + { + _name = value; + } + } + + internal OutlineLayerCollection ParentCollection => _parentCollection; + + internal void UpdateRenderers(int ignoreLayers) + { + foreach (var renderers in _outlineObjects.Values) + { + renderers.Reset(false, ignoreLayers); + } + } + + internal void Reset() + { + _outlineObjects.Clear(); + } + + internal void SetCollection(OutlineLayerCollection collection) + { + if (_parentCollection == null || collection == null || _parentCollection == collection) + { + _parentCollection = collection; + } + else + { + throw new InvalidOperationException("OutlineLayer can only belong to a single OutlineLayerCollection."); + } + } + + #endregion + + #region IOutlineSettings + + /// + public Color OutlineColor + { + get + { + return _settings.OutlineColor; + } + set + { + _settings.OutlineColor = value; + } + } + + /// + public int OutlineWidth + { + get + { + return _settings.OutlineWidth; + } + set + { + _settings.OutlineWidth = value; + } + } + + /// + public float OutlineIntensity + { + get + { + return _settings.OutlineIntensity; + } + set + { + _settings.OutlineIntensity = value; + } + } + + /// + public float OutlineAlphaCutoff + { + get + { + return _settings.OutlineAlphaCutoff; + } + set + { + _settings.OutlineAlphaCutoff = value; + } + } + + /// + public OutlineRenderFlags OutlineRenderMode + { + get + { + return _settings.OutlineRenderMode; + } + set + { + _settings.OutlineRenderMode = value; + } + } + + #endregion + + #region ICollection + + /// + public int Count => _outlineObjects.Count; + + /// + public bool IsReadOnly => false; + + /// + public void Add(GameObject go) + { + if (go is null) + { + throw new ArgumentNullException(nameof(go)); + } + + if (!_outlineObjects.ContainsKey(go)) + { + var renderers = new OutlineRendererCollection(go); + renderers.Reset(false, _parentCollection.IgnoreLayerMask); + _outlineObjects.Add(go, renderers); + } + } + + /// + public bool Remove(GameObject go) + { + if (go is null) + { + return false; + } + + return _outlineObjects.Remove(go); + } + + /// + public bool Contains(GameObject go) + { + if (go is null) + { + return false; + } + + return _outlineObjects.ContainsKey(go); + } + + /// + public void Clear() + { + _outlineObjects.Clear(); + } + + /// + public void CopyTo(GameObject[] array, int arrayIndex) + { + _outlineObjects.Keys.CopyTo(array, arrayIndex); + } + + #endregion + + #region IEnumerable + + /// + public IEnumerator GetEnumerator() + { + return _outlineObjects.Keys.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return _outlineObjects.Keys.GetEnumerator(); + } + + #endregion + + #region IEquatable + + /// + public bool Equals(IOutlineSettings other) + { + return OutlineSettings.Equals(this, other); + } + + #endregion + + #region Object + + /// + public override string ToString() + { + var text = new StringBuilder(); + + if (string.IsNullOrEmpty(_name)) + { + text.Append("OutlineLayer"); + } + else + { + text.Append(_name); + } + + if (_parentCollection != null) + { + text.Append(" #"); + text.Append(_parentCollection.IndexOf(this)); + } + + if (_outlineObjects.Count > 0) + { + text.Append(" ("); + + foreach (var go in _outlineObjects.Keys) + { + text.Append(go.name); + text.Append(", "); + } + + text.Remove(text.Length - 2, 2); + text.Append(")"); + } + + return string.Format("{0}", text); + } + + /// + public override bool Equals(object other) + { + return OutlineSettings.Equals(this, other as IOutlineSettings); + } + + /// + public override int GetHashCode() + { + return base.GetHashCode(); + } + + #endregion + + #region implementation + #endregion + } +} diff --git a/Runtime/Scripts/OutlineLayer.cs.meta b/Runtime/Scripts/OutlineLayer.cs.meta new file mode 100644 index 0000000..f199256 --- /dev/null +++ b/Runtime/Scripts/OutlineLayer.cs.meta @@ -0,0 +1,13 @@ +fileFormatVersion: 2 +guid: 1360e19784ddfac45a7dcb6ba39595ed +timeCreated: 1566130871 +licenseType: Free +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/OutlineLayerCollection.cs b/Runtime/Scripts/OutlineLayerCollection.cs new file mode 100644 index 0000000..5b12e22 --- /dev/null +++ b/Runtime/Scripts/OutlineLayerCollection.cs @@ -0,0 +1,320 @@ +// 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; +using System.Collections.Generic; +using UnityEngine; + +namespace UnityFx.Outline +{ + /// + /// A serializable collection of outline layers. + /// + /// + /// + /// + [CreateAssetMenu(fileName = "OutlineLayerCollection", menuName = "UnityFx/Outline/Outline Layer Collection")] + public sealed class OutlineLayerCollection : ScriptableObject, IList, IReadOnlyList + { + #region data + + [SerializeField, HideInInspector] + private List _layers = new List(); + + [SerializeField, HideInInspector] + private int _ignoreLayerMask; + + #endregion + + #region interface + + /// + /// Gets or sets layer mask to use for ignored components in layer game objects. + /// + public int IgnoreLayerMask + { + get + { + return _ignoreLayerMask; + } + set + { + if (_ignoreLayerMask != value) + { + _ignoreLayerMask = value; + + foreach (var layer in _layers) + { + layer.UpdateRenderers(value); + } + } + } + } + + /// + /// Gets number of game objects in the layers. + /// + public int NumberOfObjects + { + get + { + var result = 0; + + foreach (var layer in _layers) + { + result += layer.Count; + } + + return result; + } + } + + /// + /// Gets a layer with the specified index. If layer at the does not exist, creates one. + /// + public OutlineLayer GetOrAddLayer(int index) + { + if (index < 0) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + + while (index >= _layers.Count) + { + _layers.Add(new OutlineLayer(this)); + } + + return _layers[index]; + } + + /// + /// Adds a new layer. + /// + public OutlineLayer AddLayer() + { + var layer = new OutlineLayer(this); + _layers.Add(layer); + return layer; + } + + /// + /// Gets the objects for rendering. + /// + public void GetRenderObjects(IList renderObjects) + { + foreach (var layer in _layers) + { + layer.GetRenderObjects(renderObjects); + } + } + + /// + /// Removes the specified from layers. + /// + /// A to remove. + public void Remove(GameObject go) + { + foreach (var layer in _layers) + { + if (layer.Remove(go)) + { + break; + } + } + } + + /// + /// Removes all game objects registered in layers. + /// + public void ClearLayerContent() + { + foreach (var layer in _layers) + { + layer.Clear(); + } + } + + #endregion + + #region internals + + internal void Reset() + { + foreach (var layer in _layers) + { + layer.Reset(); + } + } + + #endregion + + #region ScriptableObject + + private void OnEnable() + { + foreach (var layer in _layers) + { + layer.Clear(); + layer.SetCollection(this); + } + } + + #endregion + + #region IList + + /// + public OutlineLayer this[int layerIndex] + { + get + { + return _layers[layerIndex]; + } + set + { + if (value is null) + { + throw new ArgumentNullException("layer"); + } + + if (layerIndex < 0 || layerIndex >= _layers.Count) + { + throw new ArgumentOutOfRangeException(nameof(layerIndex)); + } + + if (_layers[layerIndex] != value) + { + value.SetCollection(this); + + _layers[layerIndex].SetCollection(null); + _layers[layerIndex] = value; + } + } + } + + /// + public int IndexOf(OutlineLayer layer) + { + if (layer != null) + { + return _layers.IndexOf(layer); + } + + return -1; + } + + /// + public void Insert(int index, OutlineLayer layer) + { + if (layer is null) + { + throw new ArgumentNullException(nameof(layer)); + } + + if (layer.ParentCollection != this) + { + layer.SetCollection(this); + _layers.Insert(index, layer); + } + } + + /// + public void RemoveAt(int index) + { + if (index >= 0 && index < _layers.Count) + { + _layers[index].SetCollection(null); + _layers.RemoveAt(index); + } + } + + #endregion + + #region ICollection + + /// + public int Count => _layers.Count; + + /// + public bool IsReadOnly => false; + + /// + public void Add(OutlineLayer layer) + { + if (layer is null) + { + throw new ArgumentNullException(nameof(layer)); + } + + if (layer.ParentCollection != this) + { + layer.SetCollection(this); + _layers.Add(layer); + } + } + + /// + public bool Remove(OutlineLayer layer) + { + if (_layers.Remove(layer)) + { + layer.SetCollection(null); + return true; + } + + return false; + } + + /// + public void Clear() + { + if (_layers.Count > 0) + { + foreach (var layer in _layers) + { + layer.SetCollection(null); + } + + _layers.Clear(); + } + } + + /// + public bool Contains(OutlineLayer layer) + { + if (layer is null) + { + return false; + } + + return _layers.Contains(layer); + } + + /// + public void CopyTo(OutlineLayer[] array, int arrayIndex) + { + _layers.CopyTo(array, arrayIndex); + } + + #endregion + + #region IEnumerable + + /// + public IEnumerator GetEnumerator() + { + return _layers.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return _layers.GetEnumerator(); + } + + #endregion + + #region implementation + #endregion + } +} diff --git a/Runtime/Scripts/OutlineLayerCollection.cs.meta b/Runtime/Scripts/OutlineLayerCollection.cs.meta new file mode 100644 index 0000000..3b46761 --- /dev/null +++ b/Runtime/Scripts/OutlineLayerCollection.cs.meta @@ -0,0 +1,13 @@ +fileFormatVersion: 2 +guid: 57d0c11168277cf4eb3b4b89706e6aa5 +timeCreated: 1566560091 +licenseType: Pro +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/OutlineResources.cs b/Runtime/Scripts/OutlineResources.cs new file mode 100644 index 0000000..d930930 --- /dev/null +++ b/Runtime/Scripts/OutlineResources.cs @@ -0,0 +1,532 @@ +// 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 System.Diagnostics; +using UnityEditor; +using UnityEngine; +using UnityEngine.Rendering; + +namespace UnityFx.Outline +{ + /// + /// This asset is used to store references to shaders and other resources needed at runtime without having to use a Resources folder. + /// + /// + [CreateAssetMenu(fileName = "OutlineResources", menuName = "UnityFx/Outline/Outline Resources")] + public sealed class OutlineResources : ScriptableObject + { + #region data + + [SerializeField] + private Shader _renderShader; + [SerializeField] + private Shader _outlineShader; + + private Material _renderMaterial; + private Material _outlineMaterial; + private MaterialPropertyBlock _props; + private Mesh _fullscreenTriangleMesh; + private float[][] _gaussSamples; + private bool _useDrawMesh; + + #endregion + + #region interface + + /// + /// Minimum value of outline width parameter. + /// + /// + public const int MinWidth = 1; + + /// + /// Maximum value of outline width parameter. + /// + /// + /// If the value is changed here, it should be adjusted in Outline.shader as well. + /// + /// + public const int MaxWidth = 32; + + /// + /// Minimum value of outline intensity parameter. + /// + /// + /// + public const int MinIntensity = 1; + + /// + /// Maximum value of outline intensity parameter. + /// + /// + /// + public const int MaxIntensity = 64; + + /// + /// Value of outline intensity parameter that is treated as solid fill. + /// + /// + /// + public const int SolidIntensity = 100; + + /// + /// Minimum value of outline alpha cutoff parameter. + /// + /// + public const float MinAlphaCutoff = 0; + + /// + /// Maximum value of outline alpha cutoff parameter. + /// + /// + public const float MaxAlphaCutoff = 1; + + /// + /// Name of _MainTex shader parameter. + /// + public const string MainTexName = "_MainTex"; + + /// + /// Name of _MaskTex shader parameter. + /// + public const string MaskTexName = "_MaskTex"; + + /// + /// Name of _TempTex shader parameter. + /// + public const string TempTexName = "_TempTex"; + + /// + /// Name of _Color shader parameter. + /// + public const string ColorName = "_Color"; + + /// + /// Name of _Width shader parameter. + /// + public const string WidthName = "_Width"; + + /// + /// Name of _Intensity shader parameter. + /// + public const string IntensityName = "_Intensity"; + + /// + /// Name of _Cutoff shader parameter. + /// + public const string AlphaCutoffName = "_Cutoff"; + + /// + /// Name of _GaussSamples shader parameter. + /// + public const string GaussSamplesName = "_GaussSamples"; + + /// + /// Name of the _USE_DRAWMESH shader feature. + /// + public const string UseDrawMeshFeatureName = "_USE_DRAWMESH"; + + /// + /// Name of the outline effect. + /// + public const string EffectName = "Outline"; + + /// + /// Tooltip text for field. + /// + public const string OutlineResourcesTooltip = "Outline resources to use (shaders, materials etc). Do not change defaults unless you know what you're doing."; + + /// + /// Tooltip text for field. + /// + public const string OutlineLayerCollectionTooltip = "Collection of outline layers to use. This can be used to share outline settings between multiple cameras."; + + /// + /// Tooltip text for outline field. + /// + public const string OutlineLayerMaskTooltip = "Layer mask for outined objects."; + + /// + /// Tooltip text for outline field. + /// + public const string OutlineRenderingLayerMaskTooltip = "Rendering layer mask for outined objects."; + + /// + /// Index of the default pass in . + /// + public const int RenderShaderDefaultPassId = 0; + + /// + /// Index of the alpha-test pass in . + /// + public const int RenderShaderAlphaTestPassId = 1; + + /// + /// Index of the HPass in . + /// + public const int OutlineShaderHPassId = 0; + + /// + /// Index of the VPass in . + /// + public const int OutlineShaderVPassId = 1; + + /// + /// SRP not supported message. + /// + internal const string SrpNotSupported = "{0} works with built-in render pipeline only. It does not support SRP (including URP and HDRP)."; + + /// + /// Post-processing not supported message. + /// + internal const string PpNotSupported = "{0} does not support Unity Post-processing stack v2. It might not work as expected."; + + /// + /// Hashed name of _MainTex shader parameter. + /// + public readonly int MainTexId = Shader.PropertyToID(MainTexName); + + /// + /// Texture identifier for _MainTex shader parameter. + /// + public readonly RenderTargetIdentifier MainTex = new RenderTargetIdentifier(MainTexName); + + /// + /// Hashed name of _MaskTex shader parameter. + /// + public readonly int MaskTexId = Shader.PropertyToID(MaskTexName); + + /// + /// Texture identifier for _MaskTex shader parameter. + /// + public readonly RenderTargetIdentifier MaskTex = new RenderTargetIdentifier(MaskTexName); + + /// + /// Hashed name of _TempTex shader parameter. + /// + public readonly int TempTexId = Shader.PropertyToID(TempTexName); + + /// + /// Texture identifier for _TempTex shader parameter. + /// + public readonly RenderTargetIdentifier TempTex = new RenderTargetIdentifier(TempTexName); + + /// + /// Hashed name of _Color shader parameter. + /// + public readonly int ColorId = Shader.PropertyToID(ColorName); + + /// + /// Hashed name of _Width shader parameter. + /// + public readonly int WidthId = Shader.PropertyToID(WidthName); + + /// + /// Hashed name of _Intensity shader parameter. + /// + public readonly int IntensityId = Shader.PropertyToID(IntensityName); + + /// + /// Hashed name of _Cutoff shader parameter. + /// + public readonly int AlphaCutoffId = Shader.PropertyToID(AlphaCutoffName); + + /// + /// Hashed name of _GaussSamples shader parameter. + /// + public readonly int GaussSamplesId = Shader.PropertyToID(GaussSamplesName); + + /// + /// Temp materials list. Used by to avoid GC allocations. + /// + internal readonly List TmpMaterials = new List(); + + /// + /// Gets a that renders objects outlined with a solid while color. + /// + public Shader RenderShader + { + get + { + return _renderShader; + } + } + + /// + /// Gets a that renders outline around the mask, that was generated with . + /// + public Shader OutlineShader + { + get + { + return _outlineShader; + } + } + + /// + /// Gets a -based material. + /// + public Material RenderMaterial + { + get + { + if (_renderMaterial == null) + { + UnityEngine.Debug.Assert(_renderShader != null, "No RenderShader is set in outline resources.", this); + + _renderMaterial = new Material(_renderShader) + { + name = "Outline - RenderColor", + hideFlags = HideFlags.HideAndDontSave + }; + } + + return _renderMaterial; + } + } + + /// + /// Gets a -based material. + /// + public Material OutlineMaterial + { + get + { + if (_outlineMaterial == null) + { + UnityEngine.Debug.Assert(_outlineShader != null, "No OutlineShader is set in outline resources.", this); + + _outlineMaterial = new Material(_outlineShader) + { + name = "Outline - Main", + hideFlags = HideFlags.HideAndDontSave + }; + + if (_useDrawMesh) + { + _outlineMaterial.EnableKeyword(UseDrawMeshFeatureName); + } + } + + return _outlineMaterial; + } + } + + /// + /// Gets a for . + /// + public MaterialPropertyBlock Properties + { + get + { + if (_props is null) + { + _props = new MaterialPropertyBlock(); + } + + return _props; + } + } + + /// + /// Gets or sets a fullscreen triangle mesh. The mesh is lazy-initialized on the first access. + /// + /// + /// This is used by to avoid Blit() calls and use DrawMesh() passing + /// this mesh as the first argument. When running on a device with Shader Model 3.5 support this + /// should not be used at all, as the vertices are generated in vertex shader with DrawProcedural() call. + /// + /// + public Mesh FullscreenTriangleMesh + { + get + { + if (_fullscreenTriangleMesh == null) + { + _fullscreenTriangleMesh = new Mesh() + { + name = "Outline - FullscreenTriangle", + hideFlags = HideFlags.HideAndDontSave, + vertices = new Vector3[] { new Vector3(-1, -1, 0), new Vector3(3, -1, 0), new Vector3(-1, 3, 0) }, + triangles = new int[] { 0, 1, 2 } + }; + + _fullscreenTriangleMesh.UploadMeshData(true); + } + + return _fullscreenTriangleMesh; + } + set + { + _fullscreenTriangleMesh = value; + } + } + + /// + /// Gets or sets a value indicating whether is used for image effects rendering even when procedural rendering is available. + /// + public bool UseFullscreenTriangleMesh + { + get + { + return _useDrawMesh; + } + set + { + if (_useDrawMesh != value) + { + _useDrawMesh = value; + + if (_outlineMaterial) + { + if (_useDrawMesh) + { + _outlineMaterial.EnableKeyword(UseDrawMeshFeatureName); + } + else + { + _outlineMaterial.DisableKeyword(UseDrawMeshFeatureName); + } + } + } + } + } + + /// + /// Gets a value indicating whether the instance is in valid state. + /// + public bool IsValid => RenderShader && OutlineShader; + + /// + /// Returns a instance initialized with values from . + /// + public MaterialPropertyBlock GetProperties(IOutlineSettings settings) + { + if (_props is null) + { + _props = new MaterialPropertyBlock(); + } + + _props.SetFloat(WidthId, settings.OutlineWidth); + _props.SetColor(ColorId, settings.OutlineColor); + + if ((settings.OutlineRenderMode & OutlineRenderFlags.Blurred) != 0) + { + _props.SetFloat(IntensityId, settings.OutlineIntensity); + } + else + { + _props.SetFloat(IntensityId, SolidIntensity); + } + + return _props; + } + + /// + /// Gets cached gauss samples for the specified outline . + /// + public float[] GetGaussSamples(int width) + { + var index = Mathf.Clamp(width, 1, MaxWidth) - 1; + + if (_gaussSamples is null) + { + _gaussSamples = new float[MaxWidth][]; + } + + if (_gaussSamples[index] is null) + { + _gaussSamples[index] = GetGaussSamples(width, null); + } + + return _gaussSamples[index]; + } + + /// + /// Resets the resources to defaults. + /// + public void ResetToDefaults() + { + _renderShader = Shader.Find("Hidden/UnityFx/OutlineColor"); + _outlineShader = Shader.Find("Hidden/UnityFx/Outline"); + } + + /// + /// Calculates value of Gauss function for the specified and values. + /// + /// + /// + public static float Gauss(float x, float stdDev) + { + var stdDev2 = stdDev * stdDev * 2; + var a = 1 / Mathf.Sqrt(Mathf.PI * stdDev2); + var gauss = a * Mathf.Pow((float)Math.E, -x * x / stdDev2); + + return gauss; + } + + /// + /// Samples Gauss function for the specified . + /// + /// + public static float[] GetGaussSamples(int width, float[] samples) + { + // NOTE: According to '3 sigma' rule there is no reason to have StdDev less then width / 3. + // In practice blur looks best when StdDev is within range [width / 3, width / 2]. + var stdDev = width * 0.5f; + + if (samples is null) + { + samples = new float[MaxWidth]; + } + + for (var i = 0; i < width; i++) + { + samples[i] = Gauss(i, stdDev); + } + + return samples; + } + + /// + /// Writes a console warning if SRP is detected. + /// + public static void LogSrpNotSupported(UnityEngine.Object obj) + { + if (GraphicsSettings.renderPipelineAsset) + { + UnityEngine.Debug.LogWarningFormat(obj, SrpNotSupported, obj.GetType().Name); + } + } + + /// + /// Writes a console warning if Post Processing Stack v2 is detected. + /// + [Conditional("UNITY_POST_PROCESSING_STACK_V2")] + public static void LogPpNotSupported(UnityEngine.Object obj) + { + UnityEngine.Debug.LogWarningFormat(obj, PpNotSupported, obj.GetType().Name); + } + + #endregion + + #region ScriptableObject + + private void OnValidate() + { + if (_renderMaterial) + { + _renderMaterial.shader = _renderShader; + } + + if (_outlineMaterial) + { + _outlineMaterial.shader = _outlineShader; + } + } + + #endregion + } +} diff --git a/Runtime/Scripts/OutlineResources.cs.meta b/Runtime/Scripts/OutlineResources.cs.meta new file mode 100644 index 0000000..ce5153a --- /dev/null +++ b/Runtime/Scripts/OutlineResources.cs.meta @@ -0,0 +1,13 @@ +fileFormatVersion: 2 +guid: b503341e0a514e3489c4851727e68257 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: + - _renderShader: {fileID: 4800000, guid: ac20fbf75bafe454aba5ef3c098349df, type: 3} + - _outlineShader: {fileID: 4800000, guid: 41c9acbf41c8245498ac9beab378de12, type: 3} + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/OutlineSettings.cs b/Runtime/Scripts/OutlineSettings.cs new file mode 100644 index 0000000..281c773 --- /dev/null +++ b/Runtime/Scripts/OutlineSettings.cs @@ -0,0 +1,144 @@ +// Copyright (C) 2019-2021 Alexander Bogarsukov. All rights reserved. +// See the LICENSE.md file in the project root for more information. + +using System; +using UnityEngine; + +namespace UnityFx.Outline +{ + /// + /// Outline settings. + /// + [CreateAssetMenu(fileName = "OutlineSettings", menuName = "UnityFx/Outline/Outline Settings")] + public sealed class OutlineSettings : ScriptableObject, IOutlineSettings + { + #region data + + // NOTE: There is a custom editor for OutlineSettings, so no need to show these in default inspector. + [SerializeField, HideInInspector] + private Color _outlineColor = Color.red; + [SerializeField, HideInInspector, Range(OutlineResources.MinWidth, OutlineResources.MaxWidth)] + private int _outlineWidth = 4; + [SerializeField, HideInInspector, Range(OutlineResources.MinIntensity, OutlineResources.MaxIntensity)] + private float _outlineIntensity = 2; + [SerializeField, HideInInspector, Range(OutlineResources.MinAlphaCutoff, OutlineResources.MaxAlphaCutoff)] + private float _outlineAlphaCutoff = 0.9f; + [SerializeField, HideInInspector] + private OutlineRenderFlags _outlineMode; + + #endregion + + #region interface + + public static bool Equals(IOutlineSettings lhs, IOutlineSettings rhs) + { + if (lhs == null || rhs == null) + { + return false; + } + + return lhs.OutlineColor == rhs.OutlineColor && + lhs.OutlineWidth == rhs.OutlineWidth && + lhs.OutlineRenderMode == rhs.OutlineRenderMode && + Mathf.Approximately(lhs.OutlineIntensity, rhs.OutlineIntensity) && + Mathf.Approximately(lhs.OutlineAlphaCutoff, rhs.OutlineAlphaCutoff); + } + + #endregion + + #region IOutlineSettings + + /// + public Color OutlineColor + { + get + { + return _outlineColor; + } + set + { + _outlineColor = value; + } + } + + /// + public int OutlineWidth + { + get + { + return _outlineWidth; + } + set + { + _outlineWidth = Mathf.Clamp(value, OutlineResources.MinWidth, OutlineResources.MaxWidth); + } + } + + /// + public float OutlineIntensity + { + get + { + return _outlineIntensity; + } + set + { + _outlineIntensity = Mathf.Clamp(value, OutlineResources.MinIntensity, OutlineResources.MaxIntensity); + } + } + + /// + public float OutlineAlphaCutoff + { + get + { + return _outlineAlphaCutoff; + } + set + { + _outlineAlphaCutoff = Mathf.Clamp(value, 0, 1); + } + } + + /// + public OutlineRenderFlags OutlineRenderMode + { + get + { + return _outlineMode; + } + set + { + _outlineMode = value; + } + } + + #endregion + + #region IEquatable + + /// + public bool Equals(IOutlineSettings other) + { + return Equals(this, other); + } + + #endregion + + #region Object + + /// + public override bool Equals(object other) + { + return Equals(this, other as IOutlineSettings); + } + + /// + public override int GetHashCode() + { + return base.GetHashCode(); + } + + #endregion + } +} diff --git a/Runtime/Scripts/OutlineSettings.cs.meta b/Runtime/Scripts/OutlineSettings.cs.meta new file mode 100644 index 0000000..c6de014 --- /dev/null +++ b/Runtime/Scripts/OutlineSettings.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b579424fd3338724cba3155ee4d53475 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/OutlineSettingsExtensions.cs b/Runtime/Scripts/OutlineSettingsExtensions.cs new file mode 100644 index 0000000..584658f --- /dev/null +++ b/Runtime/Scripts/OutlineSettingsExtensions.cs @@ -0,0 +1,50 @@ +// 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.ComponentModel; +using System.Runtime.CompilerServices; +using UnityEngine; + +namespace UnityFx.Outline +{ + /// + /// Extension methods for . + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public static class OutlineSettingsExtensions + { + /// + /// Gets a value indicating whether outline should use alpha testing. + /// + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsAlphaTestingEnabled(this IOutlineSettings settings) + { + return (settings.OutlineRenderMode & OutlineRenderFlags.EnableAlphaTesting) != 0; + } + + /// + /// Gets a value indicating whether outline should use depth testing. + /// + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsDepthTestingEnabled(this IOutlineSettings settings) + { + return (settings.OutlineRenderMode & OutlineRenderFlags.EnableDepthTesting) != 0; + } + + /// + /// Gets a value indicating whether outline frame should be blurred. + /// + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsBlurEnabled(this IOutlineSettings settings) + { + return (settings.OutlineRenderMode & OutlineRenderFlags.Blurred) != 0; + } + } +} diff --git a/Runtime/Scripts/OutlineSettingsExtensions.cs.meta b/Runtime/Scripts/OutlineSettingsExtensions.cs.meta new file mode 100644 index 0000000..961ea48 --- /dev/null +++ b/Runtime/Scripts/OutlineSettingsExtensions.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1faa9de2a3d5a374b84983eae45fc559 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/OutlineSettingsInstance.cs b/Runtime/Scripts/OutlineSettingsInstance.cs new file mode 100644 index 0000000..40168a7 --- /dev/null +++ b/Runtime/Scripts/OutlineSettingsInstance.cs @@ -0,0 +1,139 @@ +// Copyright (C) 2019-2021 Alexander Bogarsukov. All rights reserved. +// See the LICENSE.md file in the project root for more information. + +using System; +using UnityEngine; + +namespace UnityFx.Outline +{ + [Serializable] + internal class OutlineSettingsInstance : IOutlineSettings + { + #region data + +#pragma warning disable 0649 + + // NOTE: There are custom editors for public components, so no need to show these in default inspector. + [SerializeField, HideInInspector] + private OutlineSettings _outlineSettings; + [SerializeField, HideInInspector] + private Color _outlineColor = Color.red; + [SerializeField, HideInInspector, Range(OutlineResources.MinWidth, OutlineResources.MaxWidth)] + private int _outlineWidth = 4; + [SerializeField, HideInInspector, Range(OutlineResources.MinIntensity, OutlineResources.MaxIntensity)] + private float _outlineIntensity = 2; + [SerializeField, HideInInspector, Range(OutlineResources.MinAlphaCutoff, OutlineResources.MaxAlphaCutoff)] + private float _outlineAlphaCutoff = 0.9f; + [SerializeField, HideInInspector] + private OutlineRenderFlags _outlineMode; + +#pragma warning restore 0649 + + #endregion + + #region interface + + public bool RequiresCameraDepth + { + get + { + return (OutlineRenderMode & OutlineRenderFlags.EnableDepthTesting) != 0; + } + } + + public OutlineSettings OutlineSettings + { + get + { + return _outlineSettings; + } + set + { + _outlineSettings = value; + } + } + + #endregion + + #region IOutlineSettings + + /// + public Color OutlineColor + { + get + { + return _outlineSettings is null ? _outlineColor : _outlineSettings.OutlineColor; + } + set + { + _outlineColor = value; + } + } + + /// + public int OutlineWidth + { + get + { + return _outlineSettings is null ? _outlineWidth : _outlineSettings.OutlineWidth; + } + set + { + _outlineWidth = Mathf.Clamp(value, OutlineResources.MinWidth, OutlineResources.MaxWidth); + } + } + + /// + public float OutlineIntensity + { + get + { + return _outlineSettings is null ? _outlineIntensity : _outlineSettings.OutlineIntensity; + } + set + { + _outlineIntensity = Mathf.Clamp(value, OutlineResources.MinIntensity, OutlineResources.MaxIntensity); + } + } + + /// + public float OutlineAlphaCutoff + { + get + { + return _outlineSettings is null ? _outlineAlphaCutoff : _outlineSettings.OutlineAlphaCutoff; + } + set + { + _outlineAlphaCutoff = Mathf.Clamp(value, 0, 1); + } + } + + /// + public OutlineRenderFlags OutlineRenderMode + { + get + { + return _outlineSettings is null ? _outlineMode : _outlineSettings.OutlineRenderMode; + } + set + { + _outlineMode = value; + } + } + + #endregion + + #region IEquatable + + public bool Equals(IOutlineSettings other) + { + return OutlineSettings.Equals(this, other); + } + + #endregion + + #region implementation + #endregion + } +} diff --git a/Runtime/Scripts/OutlineSettingsInstance.cs.meta b/Runtime/Scripts/OutlineSettingsInstance.cs.meta new file mode 100644 index 0000000..177c3cb --- /dev/null +++ b/Runtime/Scripts/OutlineSettingsInstance.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8ea4c60e473b8ef4790934bb274993cc +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/OutlineSettingsWithLayerMask.cs b/Runtime/Scripts/OutlineSettingsWithLayerMask.cs new file mode 100644 index 0000000..17ab348 --- /dev/null +++ b/Runtime/Scripts/OutlineSettingsWithLayerMask.cs @@ -0,0 +1,71 @@ +// Copyright (C) 2019-2021 Alexander Bogarsukov. All rights reserved. +// See the LICENSE.md file in the project root for more information. + +using System; +using UnityEngine; + +namespace UnityFx.Outline +{ + [Serializable] + internal class OutlineSettingsWithLayerMask : OutlineSettingsInstance + { + #region data + +#pragma warning disable 0649 + + // NOTE: There are custom editors for public components, so no need to show these in default inspector. + [SerializeField, HideInInspector] + private OutlineFilterMode _filterMode; + [SerializeField, HideInInspector] + private LayerMask _layerMask; + [SerializeField, HideInInspector] + private uint _renderingLayerMask = 1; + +#pragma warning restore 0649 + + #endregion + + #region interface + + public int OutlineLayerMask + { + get + { + if (_filterMode == OutlineFilterMode.UseLayerMask) + { + return _layerMask; + } + + if (_filterMode == OutlineFilterMode.UseRenderingLayerMask) + { + return -1; + } + + return 0; + } + } + + public uint OutlineRenderingLayerMask + { + get + { + if (_filterMode == OutlineFilterMode.UseLayerMask) + { + return uint.MaxValue; + } + + if (_filterMode == OutlineFilterMode.UseRenderingLayerMask) + { + return _renderingLayerMask; + } + + return 0; + } + } + + #endregion + + #region implementation + #endregion + } +} diff --git a/Runtime/Scripts/OutlineSettingsWithLayerMask.cs.meta b/Runtime/Scripts/OutlineSettingsWithLayerMask.cs.meta new file mode 100644 index 0000000..86fb5b9 --- /dev/null +++ b/Runtime/Scripts/OutlineSettingsWithLayerMask.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9f6645ede9c6d2346b6aee185f8261d8 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Properties.meta b/Runtime/Scripts/Properties.meta new file mode 100644 index 0000000..708e1e7 --- /dev/null +++ b/Runtime/Scripts/Properties.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 7bd10545b6de6654b864faecdec920cd +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Properties/AssemblyInfo.cs b/Runtime/Scripts/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..903d3da --- /dev/null +++ b/Runtime/Scripts/Properties/AssemblyInfo.cs @@ -0,0 +1,30 @@ +// Copyright (C) 2019-2021 Alexander Bogarsukov. All rights reserved. +// See the LICENSE.md file in the project root for more information. + +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("UnityFx.Outline")] +[assembly: AssemblyProduct("UnityFx.Outline")] +[assembly: AssemblyDescription("Screen-space outlines for Unity3d.")] +#if DEBUG +[assembly: AssemblyConfiguration("Debug")] +#else +[assembly: AssemblyConfiguration("Release")] +#endif +[assembly: AssemblyCompany("")] +[assembly: AssemblyCopyright("Copyright © Alexander Bogarsukov 2019-2020")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// Make internals visible to the editor assembly. +[assembly: InternalsVisibleTo("UnityFx.Outline.Editor")] +[assembly: InternalsVisibleTo("UnityFx.Outline.URP")] diff --git a/Runtime/Scripts/Properties/AssemblyInfo.cs.meta b/Runtime/Scripts/Properties/AssemblyInfo.cs.meta new file mode 100644 index 0000000..dd9f2bd --- /dev/null +++ b/Runtime/Scripts/Properties/AssemblyInfo.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1613c034178676349be3282789167284 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Properties/Startup.cs b/Runtime/Scripts/Properties/Startup.cs new file mode 100644 index 0000000..d504f29 --- /dev/null +++ b/Runtime/Scripts/Properties/Startup.cs @@ -0,0 +1,10 @@ +// Copyright (C) 2019-2021 Alexander Bogarsukov. All rights reserved. +// See the LICENSE.md file in the project root for more information. + +#if !UNITY_2018_4_OR_NEWER +#error UnityFx.Outline requires Unity 2018.4 or newer. +#endif + +#if NET_LEGACY || NET_2_0 || NET_2_0_SUBSET +#error UnityFx.Outline does not support .NET 3.5. Please set Scripting Runtime Version to .NET 4.x Equivalent in Unity Player Settings. +#endif diff --git a/Runtime/Scripts/Properties/Startup.cs.meta b/Runtime/Scripts/Properties/Startup.cs.meta new file mode 100644 index 0000000..b4865cd --- /dev/null +++ b/Runtime/Scripts/Properties/Startup.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 955bb53eefa37054cb49969575341469 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Rendering.meta b/Runtime/Scripts/Rendering.meta new file mode 100644 index 0000000..aacee54 --- /dev/null +++ b/Runtime/Scripts/Rendering.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 263f9a02e31427d4d9d910267274bfa0 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Rendering/OutlineRenderFlags.cs b/Runtime/Scripts/Rendering/OutlineRenderFlags.cs new file mode 100644 index 0000000..0537f8c --- /dev/null +++ b/Runtime/Scripts/Rendering/OutlineRenderFlags.cs @@ -0,0 +1,34 @@ +// Copyright (C) 2019-2021 Alexander Bogarsukov. All rights reserved. +// See the LICENSE.md file in the project root for more information. + +using System; + +namespace UnityFx.Outline +{ + /// + /// Enumerates outline render modes. + /// + [Flags] + public enum OutlineRenderFlags + { + /// + /// Outline frame is a solid line. + /// + None = 0, + + /// + /// Outline frame is blurred. + /// + Blurred = 1, + + /// + /// Enables depth testing when rendering object outlines. Only visible parts of objects are outlined. + /// + EnableDepthTesting = 2, + + /// + /// Enabled alpha testing when rendering outlines. + /// + EnableAlphaTesting = 4 + } +} diff --git a/Runtime/Scripts/Rendering/OutlineRenderFlags.cs.meta b/Runtime/Scripts/Rendering/OutlineRenderFlags.cs.meta new file mode 100644 index 0000000..8c8d06c --- /dev/null +++ b/Runtime/Scripts/Rendering/OutlineRenderFlags.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 836bd13bd33c59246b1cebab92f8e62a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Rendering/OutlineRenderObject.cs b/Runtime/Scripts/Rendering/OutlineRenderObject.cs new file mode 100644 index 0000000..02f4f8a --- /dev/null +++ b/Runtime/Scripts/Rendering/OutlineRenderObject.cs @@ -0,0 +1,64 @@ +// 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; +using System.Collections.Generic; +using System.Text; +using UnityEngine; + +namespace UnityFx.Outline +{ + /// + /// A single outline object + its outline settings. + /// + public readonly struct OutlineRenderObject : IEquatable + { + #region data + + private readonly string _tag; + private readonly IReadOnlyList _renderers; + private readonly IOutlineSettings _outlineSettings; + + #endregion + + #region interface + + /// + /// Gets the object tag name. + /// + public string Tag => _tag; + + /// + /// Gets renderers for the object. + /// + public IReadOnlyList Renderers => _renderers; + + /// + /// Gets outline settings for this object. + /// + public IOutlineSettings OutlineSettings => _outlineSettings; + + /// + /// Initializes a new instance of the struct. + /// + public OutlineRenderObject(IReadOnlyList renderers, IOutlineSettings outlineSettings, string tag = null) + { + _renderers = renderers; + _outlineSettings = outlineSettings; + _tag = tag; + } + + #endregion + + #region IEquatable + + /// + public bool Equals(OutlineRenderObject other) + { + return string.CompareOrdinal(_tag, other._tag) == 0 && _renderers == other._renderers && _outlineSettings == other._outlineSettings; + } + + #endregion + } +} diff --git a/Runtime/Scripts/Rendering/OutlineRenderObject.cs.meta b/Runtime/Scripts/Rendering/OutlineRenderObject.cs.meta new file mode 100644 index 0000000..024cad2 --- /dev/null +++ b/Runtime/Scripts/Rendering/OutlineRenderObject.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b9fa0d37014ee9049afd5e65be9f288b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Rendering/OutlineRenderer.cs b/Runtime/Scripts/Rendering/OutlineRenderer.cs new file mode 100644 index 0000000..3a351ed --- /dev/null +++ b/Runtime/Scripts/Rendering/OutlineRenderer.cs @@ -0,0 +1,476 @@ +// 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 System.Runtime.CompilerServices; +using UnityEngine; +using UnityEngine.Rendering; +using UnityEngine.XR; + +namespace UnityFx.Outline +{ + /// + /// Helper class for outline rendering with . + /// + /// + /// The class can be used on its own or as part of a higher level systems. It is used + /// by higher level outline implementations ( and + /// ). It is fully compatible with Unity post processing stack as well. + /// The class implements to be used inside + /// block as shown in the code samples. Disposing does not dispose + /// the corresponding . + /// Command buffer is not cleared before rendering. It is user responsibility to do so if needed. + /// + /// + /// var commandBuffer = new CommandBuffer(); + /// + /// using (var renderer = new OutlineRenderer(commandBuffer, resources)) + /// { + /// renderer.Render(renderers, settings); + /// } + /// + /// camera.AddCommandBuffer(CameraEvent.BeforeImageEffects, commandBuffer); + /// + /// + public readonly struct OutlineRenderer : IDisposable + { + #region data + + private readonly TextureDimension _rtDimention; + private readonly RenderTargetIdentifier _rt; + private readonly RenderTargetIdentifier _depth; + private readonly CommandBuffer _commandBuffer; + private readonly OutlineResources _resources; + + #endregion + + #region interface + + /// + /// A default outline rendering should be assosiated with. + /// + public const CameraEvent RenderEvent = CameraEvent.AfterSkybox; + + /// + /// A default render texture format for the outline effect. + /// + public const RenderTextureFormat RtFormat = RenderTextureFormat.R8; + + /// + /// Initializes a new instance of the struct. + /// + /// A to render the effect to. It should be cleared manually (if needed) before passing to this method. + /// Outline resources. + /// Thrown if is . + public OutlineRenderer(CommandBuffer cmd, OutlineResources resources) + : this(cmd, resources, BuiltinRenderTextureType.CameraTarget, BuiltinRenderTextureType.Depth, Vector2Int.zero) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// A to render the effect to. It should be cleared manually (if needed) before passing to this method. + /// Outline resources. + /// The rendering path of target camera (). + /// Thrown if is . + public OutlineRenderer(CommandBuffer cmd, OutlineResources resources, RenderingPath renderingPath) + : this(cmd, resources, BuiltinRenderTextureType.CameraTarget, GetBuiltinDepth(renderingPath), Vector2Int.zero) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// A to render the effect to. It should be cleared manually (if needed) before passing to this method. + /// Outline resources. + /// Render target. + /// Thrown if is . + public OutlineRenderer(CommandBuffer cmd, OutlineResources resources, RenderTargetIdentifier dst) + : this(cmd, resources, dst, BuiltinRenderTextureType.Depth, Vector2Int.zero) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// A to render the effect to. It should be cleared manually (if needed) before passing to this method. + /// Render target. + /// The rendering path of target camera (). + /// Thrown if is . + public OutlineRenderer(CommandBuffer cmd, OutlineResources resources, RenderTargetIdentifier dst, RenderingPath renderingPath, Vector2Int rtSize) + : this(cmd, resources, dst, GetBuiltinDepth(renderingPath), rtSize) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// A to render the effect to. It should be cleared manually (if needed) before passing to this method. + /// Outline resources. + /// Render target. + /// Depth dexture to use. + /// Thrown if is . + public OutlineRenderer(CommandBuffer cmd, OutlineResources resources, RenderTargetIdentifier dst, RenderTargetIdentifier depth, Vector2Int rtSize) + { + if (cmd is null) + { + throw new ArgumentNullException(nameof(cmd)); + } + + if (resources is null) + { + throw new ArgumentNullException(nameof(resources)); + } + + if (rtSize.x <= 0) + { + rtSize.x = -1; + } + + if (rtSize.y <= 0) + { + rtSize.y = -1; + } + + if (XRSettings.enabled) + { + var rtDesc = XRSettings.eyeTextureDesc; + + rtDesc.shadowSamplingMode = ShadowSamplingMode.None; + rtDesc.depthBufferBits = 0; + rtDesc.colorFormat = RtFormat; + + cmd.GetTemporaryRT(resources.MaskTexId, rtDesc, FilterMode.Bilinear); + cmd.GetTemporaryRT(resources.TempTexId, rtDesc, FilterMode.Bilinear); + + _rtDimention = rtDesc.dimension; + } + else + { + cmd.GetTemporaryRT(resources.MaskTexId, rtSize.x, rtSize.y, 0, FilterMode.Bilinear, RtFormat); + cmd.GetTemporaryRT(resources.TempTexId, rtSize.x, rtSize.y, 0, FilterMode.Bilinear, RtFormat); + + _rtDimention = TextureDimension.Tex2D; + } + + _rt = dst; + _depth = depth; + _commandBuffer = cmd; + _resources = resources; + } + + /// + /// Initializes a new instance of the struct. + /// + /// A to render the effect to. It should be cleared manually (if needed) before passing to this method. + /// Outline resources. + /// Render target. + /// Depth dexture to use. + /// Render texture decsriptor. + /// Thrown if is . + public OutlineRenderer(CommandBuffer cmd, OutlineResources resources, RenderTargetIdentifier dst, RenderTargetIdentifier depth, RenderTextureDescriptor rtDesc) + { + if (cmd is null) + { + throw new ArgumentNullException(nameof(cmd)); + } + + if (resources is null) + { + throw new ArgumentNullException(nameof(resources)); + } + + if (rtDesc.width <= 0) + { + rtDesc.width = -1; + } + + if (rtDesc.height <= 0) + { + rtDesc.height = -1; + } + + if (rtDesc.dimension == TextureDimension.None || rtDesc.dimension == TextureDimension.Unknown) + { + rtDesc.dimension = TextureDimension.Tex2D; + } + + rtDesc.shadowSamplingMode = ShadowSamplingMode.None; + rtDesc.depthBufferBits = 0; + rtDesc.colorFormat = RtFormat; + rtDesc.msaaSamples = 1; + + cmd.GetTemporaryRT(resources.MaskTexId, rtDesc, FilterMode.Bilinear); + cmd.GetTemporaryRT(resources.TempTexId, rtDesc, FilterMode.Bilinear); + + _rtDimention = rtDesc.dimension; + _rt = dst; + _depth = depth; + _commandBuffer = cmd; + _resources = resources; + } + + /// + /// Renders outline around a single object. + /// + /// An object to be outlined. + /// + public void Render(OutlineRenderObject obj) + { + Render(obj.Renderers, obj.OutlineSettings, obj.Tag); + } + + /// + /// Renders outline around multiple . + /// + /// An object to be outlined. + /// Thrown if is . + /// + public void Render(IReadOnlyList objects) + { + if (objects is null) + { + throw new ArgumentNullException(nameof(objects)); + } + + for (var i = 0; i < objects.Count; i++) + { + Render(objects[i]); + } + } + + /// + /// Renders outline around multiple . + /// + /// One or more renderers representing a single object to be outlined. + /// Outline settings. + /// Optional name of the sample (visible in profiler). + /// Thrown if any of the arguments is . + /// + public void Render(IReadOnlyList renderers, IOutlineSettings settings, string sampleName = null) + { + if (renderers is null) + { + throw new ArgumentNullException(nameof(renderers)); + } + + if (settings is null) + { + throw new ArgumentNullException(nameof(settings)); + } + + if (renderers.Count > 0) + { + // NOTE: Remove BeginSample/EndSample for now (https://github.com/Arvtesh/UnityFx.Outline/issues/44). + //if (string.IsNullOrEmpty(sampleName)) + //{ + // sampleName = renderers[0].name; + //} + + //_commandBuffer.BeginSample(sampleName); + { + RenderObjectClear(settings.OutlineRenderMode); + + for (var i = 0; i < renderers.Count; ++i) + { + DrawRenderer(renderers[i], settings); + } + + RenderOutline(settings); + } + //_commandBuffer.EndSample(sampleName); + } + } + + /// + /// Renders outline around a single . + /// + /// A representing an object to be outlined. + /// Outline settings. + /// Optional name of the sample (visible in profiler). + /// Thrown if any of the arguments is . + /// + public void Render(Renderer renderer, IOutlineSettings settings, string sampleName = null) + { + if (renderer is null) + { + throw new ArgumentNullException(nameof(renderer)); + } + + if (settings is null) + { + throw new ArgumentNullException(nameof(settings)); + } + + // NOTE: Remove BeginSample/EndSample for now (https://github.com/Arvtesh/UnityFx.Outline/issues/44). + //if (string.IsNullOrEmpty(sampleName)) + //{ + // sampleName = renderer.name; + //} + + // NOTE: Remove this for now (https://github.com/Arvtesh/UnityFx.Outline/issues/44). + //_commandBuffer.BeginSample(sampleName); + { + RenderObjectClear(settings.OutlineRenderMode); + DrawRenderer(renderer, settings); + RenderOutline(settings); + } + //_commandBuffer.EndSample(sampleName); + } + + /// + /// Specialized render target setup. Do not use if not sure. + /// + public void RenderObjectClear(OutlineRenderFlags flags) + { + // NOTE: Use the camera depth buffer when rendering the mask. Shader only reads from the depth buffer (ZWrite Off). + if ((flags & OutlineRenderFlags.EnableDepthTesting) != 0) + { + if (_rtDimention == TextureDimension.Tex2DArray) + { + // NOTE: Need to use this SetRenderTarget overload for XR, otherwise single pass instanced rendering does not function properly. + _commandBuffer.SetRenderTarget(_resources.MaskTex, _depth, 0, CubemapFace.Unknown, -1); + } + else + { + _commandBuffer.SetRenderTarget(_resources.MaskTex, RenderBufferLoadAction.DontCare, RenderBufferStoreAction.Store, _depth, RenderBufferLoadAction.Load, RenderBufferStoreAction.DontCare); + } + } + else + { + if (_rtDimention == TextureDimension.Tex2DArray) + { + _commandBuffer.SetRenderTarget(_resources.MaskTex, 0, CubemapFace.Unknown, -1); + } + else + { + _commandBuffer.SetRenderTarget(_resources.MaskTex, RenderBufferLoadAction.DontCare, RenderBufferStoreAction.Store); + } + } + + _commandBuffer.ClearRenderTarget(false, true, Color.clear); + } + + /// + /// Renders outline. Do not use if not sure. + /// + public void RenderOutline(IOutlineSettings settings) + { + var mat = _resources.OutlineMaterial; + var props = _resources.GetProperties(settings); + + _commandBuffer.SetGlobalFloatArray(_resources.GaussSamplesId, _resources.GetGaussSamples(settings.OutlineWidth)); + + if (_rtDimention == TextureDimension.Tex2DArray) + { + // HPass + _commandBuffer.SetRenderTarget(_resources.TempTex, 0, CubemapFace.Unknown, -1); + Blit(_resources.MaskTex, OutlineResources.OutlineShaderHPassId, mat, props); + + // VPassBlend + _commandBuffer.SetRenderTarget(_rt, 0, CubemapFace.Unknown, -1); + Blit(_resources.TempTex, OutlineResources.OutlineShaderVPassId, mat, props); + } + else + { + // HPass + _commandBuffer.SetRenderTarget(_resources.TempTex, RenderBufferLoadAction.DontCare, RenderBufferStoreAction.Store); + Blit(_resources.MaskTex, OutlineResources.OutlineShaderHPassId, mat, props); + + // VPassBlend + _commandBuffer.SetRenderTarget(_rt, RenderBufferLoadAction.Load, RenderBufferStoreAction.Store); + Blit(_resources.TempTex, OutlineResources.OutlineShaderVPassId, mat, props); + } + } + + #endregion + + #region IDisposable + + /// + /// Finalizes the effect rendering and releases temporary textures used. Should only be called once. + /// + public void Dispose() + { + _commandBuffer.ReleaseTemporaryRT(_resources.TempTexId); + _commandBuffer.ReleaseTemporaryRT(_resources.MaskTexId); + } + + #endregion + + #region implementation + + private void DrawRenderer(Renderer renderer, IOutlineSettings settings) + { + if (renderer && renderer.enabled && renderer.isVisible && renderer.gameObject.activeInHierarchy) + { + // NOTE: Accessing Renderer.sharedMaterials triggers GC.Alloc. That's why we use a temporary + // list of materials, cached with the outline resources. + renderer.GetSharedMaterials(_resources.TmpMaterials); + + if (_resources.TmpMaterials.Count > 0) + { + if (settings.IsAlphaTestingEnabled()) + { + for (var i = 0; i < _resources.TmpMaterials.Count; ++i) + { + var mat = _resources.TmpMaterials[i]; + + // Use material cutoff value if available. + if (mat.HasProperty(_resources.AlphaCutoffId)) + { + _commandBuffer.SetGlobalFloat(_resources.AlphaCutoffId, mat.GetFloat(_resources.AlphaCutoffId)); + } + else + { + _commandBuffer.SetGlobalFloat(_resources.AlphaCutoffId, settings.OutlineAlphaCutoff); + } + + _commandBuffer.SetGlobalTexture(_resources.MainTexId, _resources.TmpMaterials[i].mainTexture); + _commandBuffer.DrawRenderer(renderer, _resources.RenderMaterial, i, OutlineResources.RenderShaderAlphaTestPassId); + } + } + else + { + for (var i = 0; i < _resources.TmpMaterials.Count; ++i) + { + _commandBuffer.DrawRenderer(renderer, _resources.RenderMaterial, i, OutlineResources.RenderShaderDefaultPassId); + } + } + } + else + { + // NOTE: No materials set for renderer means we should still render outline for it. + _commandBuffer.DrawRenderer(renderer, _resources.RenderMaterial, 0, OutlineResources.RenderShaderDefaultPassId); + } + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void Blit(RenderTargetIdentifier src, int shaderPass, Material mat, MaterialPropertyBlock props) + { + // Set source texture as _MainTex to match Blit behavior. + _commandBuffer.SetGlobalTexture(_resources.MainTexId, src); + + // NOTE: SystemInfo.graphicsShaderLevel check is not enough sometimes (esp. on mobiles), so there is SystemInfo.supportsInstancing + // check and a flag for forcing DrawMesh. + if (SystemInfo.graphicsShaderLevel >= 35 && SystemInfo.supportsInstancing && !_resources.UseFullscreenTriangleMesh) + { + _commandBuffer.DrawProcedural(Matrix4x4.identity, mat, shaderPass, MeshTopology.Triangles, 3, 1, props); + } + else + { + _commandBuffer.DrawMesh(_resources.FullscreenTriangleMesh, Matrix4x4.identity, mat, 0, shaderPass, props); + } + } + + private static RenderTargetIdentifier GetBuiltinDepth(RenderingPath renderingPath) + { + return (renderingPath == RenderingPath.DeferredShading || renderingPath == RenderingPath.DeferredLighting) ? BuiltinRenderTextureType.ResolvedDepth : BuiltinRenderTextureType.Depth; + } + + #endregion + } +} diff --git a/Runtime/Scripts/Rendering/OutlineRenderer.cs.meta b/Runtime/Scripts/Rendering/OutlineRenderer.cs.meta new file mode 100644 index 0000000..6bfadc4 --- /dev/null +++ b/Runtime/Scripts/Rendering/OutlineRenderer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4271470bd9f5d5041a4a8881d8457a55 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Rendering/OutlineRendererCollection.cs b/Runtime/Scripts/Rendering/OutlineRendererCollection.cs new file mode 100644 index 0000000..6660443 --- /dev/null +++ b/Runtime/Scripts/Rendering/OutlineRendererCollection.cs @@ -0,0 +1,128 @@ +// 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; +using System.Collections.Generic; +using UnityEngine; + +namespace UnityFx.Outline +{ + internal class OutlineRendererCollection : ICollection + { + #region data + + private readonly List _renderers = new List(); + private readonly GameObject _go; + + #endregion + + #region interface + + internal OutlineRendererCollection(GameObject go) + { + Debug.Assert(go); + _go = go; + } + + internal IReadOnlyList GetList() + { + return _renderers; + } + + internal void Reset(bool includeInactive) + { + _go.GetComponentsInChildren(includeInactive, _renderers); + } + + internal void Reset(bool includeInactive, int ignoreLayerMask) + { + _renderers.Clear(); + + if (ignoreLayerMask != 0) + { + var renderers = _go.GetComponentsInChildren(includeInactive); + + foreach (var renderer in renderers) + { + if (((1 << renderer.gameObject.layer) & ignoreLayerMask) == 0) + { + _renderers.Add(renderer); + } + } + } + else + { + _go.GetComponentsInChildren(includeInactive, _renderers); + } + } + + #endregion + + #region ICollection + + public int Count => _renderers.Count; + + public bool IsReadOnly => false; + + public void Add(Renderer renderer) + { + Validate(renderer); + + _renderers.Add(renderer); + } + + public bool Remove(Renderer renderer) + { + return _renderers.Remove(renderer); + } + + public void Clear() + { + _renderers.Clear(); + } + + public bool Contains(Renderer renderer) + { + return _renderers.Contains(renderer); + } + + public void CopyTo(Renderer[] array, int arrayIndex) + { + _renderers.CopyTo(array, arrayIndex); + } + + #endregion + + #region IEnumerable + + public IEnumerator GetEnumerator() + { + return _renderers.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return _renderers.GetEnumerator(); + } + + #endregion + + #region implementation + + private void Validate(Renderer renderer) + { + if (renderer is null) + { + throw new ArgumentNullException(nameof(renderer)); + } + + if (!renderer.transform.IsChildOf(_go.transform)) + { + throw new ArgumentException(string.Format("Only children of the {0} are allowed.", _go.name), nameof(renderer)); + } + } + + #endregion + } +} diff --git a/Runtime/Scripts/Rendering/OutlineRendererCollection.cs.meta b/Runtime/Scripts/Rendering/OutlineRendererCollection.cs.meta new file mode 100644 index 0000000..f582b73 --- /dev/null +++ b/Runtime/Scripts/Rendering/OutlineRendererCollection.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 89621a3cc73c4e6498a00b2d180ed462 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Shaders.meta b/Runtime/Shaders.meta new file mode 100644 index 0000000..bf1ab62 --- /dev/null +++ b/Runtime/Shaders.meta @@ -0,0 +1,10 @@ +fileFormatVersion: 2 +guid: e4ede0e617beaeb4a8781136599aa84e +folderAsset: yes +timeCreated: 1566126961 +licenseType: Free +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Shaders/Outline.shader b/Runtime/Shaders/Outline.shader new file mode 100644 index 0000000..41f6332 --- /dev/null +++ b/Runtime/Shaders/Outline.shader @@ -0,0 +1,196 @@ +// Copyright (C) 2019-2021 Alexander Bogarsukov. All rights reserved. +// See the LICENSE.md file in the project root for more information. + +// Renders outline based on a texture produced with 'UnityF/OutlineColor'. +// Modified version of 'Custom/Post Outline' shader taken from https://willweissman.wordpress.com/tutorials/shaders/unity-shaderlab-object-outlines/. +Shader "Hidden/UnityFx/Outline" +{ + HLSLINCLUDE + + #include "UnityCG.cginc" + + UNITY_DECLARE_SCREENSPACE_TEXTURE(_MaskTex); + UNITY_DECLARE_SCREENSPACE_TEXTURE(_MainTex); + float2 _MainTex_TexelSize; + + float4 _Color; + float _Intensity; + int _Width; + float _GaussSamples[32]; + +#if SHADER_TARGET < 35 || _USE_DRAWMESH + + v2f_img vert(appdata_img v) + { + v2f_img o; + UNITY_SETUP_INSTANCE_ID(v); + UNITY_INITIALIZE_OUTPUT(v2f_img, o); + UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o); + + o.pos = float4(v.vertex.xy, UNITY_NEAR_CLIP_VALUE, 1); + o.uv = ComputeScreenPos(o.pos); + + return o; + } + +#else + + struct appdata_vid + { + uint vertexID : SV_VertexID; + UNITY_VERTEX_INPUT_INSTANCE_ID + }; + + float4 GetFullScreenTriangleVertexPosition(uint vertexID, float z = UNITY_NEAR_CLIP_VALUE) + { + // Generates a triangle in homogeneous clip space, s.t. + // v0 = (-1, -1, 1), v1 = (3, -1, 1), v2 = (-1, 3, 1). + float2 uv = float2((vertexID << 1) & 2, vertexID & 2); + return float4(uv * 2 - 1, z, 1); + } + + v2f_img vert(appdata_vid v) + { + v2f_img o; + UNITY_SETUP_INSTANCE_ID(v); + UNITY_INITIALIZE_OUTPUT(v2f_img, o); + UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o); + + o.pos = GetFullScreenTriangleVertexPosition(v.vertexID); + o.uv = ComputeScreenPos(o.pos); + + return o; + } + +#endif + + float CalcIntensityN0(float2 uv, float2 offset, int k) + { + return UNITY_SAMPLE_SCREENSPACE_TEXTURE(_MainTex, uv + k * offset).r * _GaussSamples[k]; + } + + float CalcIntensityN1(float2 uv, float2 offset, int k) + { + return UNITY_SAMPLE_SCREENSPACE_TEXTURE(_MainTex, uv - k * offset).r * _GaussSamples[k]; + } + + float CalcIntensity(float2 uv, float2 offset) + { + float intensity = 0; + + // Accumulates horizontal or vertical blur intensity for the specified texture position. + // Set offset = (tx, 0) for horizontal sampling and offset = (0, ty) for vertical. + // + // NOTE: Unroll directive is needed to make the method function on platforms like WebGL 1.0 where loops are not supported. + // If maximum outline width is changed here, it should be changed in OutlineResources.MaxWidth as well. + // + [unroll(32)] + for (int k = 1; k <= _Width; ++k) + { + intensity += CalcIntensityN0(uv, offset, k); + intensity += CalcIntensityN1(uv, offset, k); + } + + intensity += CalcIntensityN0(uv, offset, 0); + return intensity; + } + + float4 frag_h(v2f_img i) : SV_Target + { + UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(i); + + float intensity = CalcIntensity(i.uv, float2(_MainTex_TexelSize.x, 0)); + return float4(intensity, intensity, intensity, 1); + } + + float4 frag_v(v2f_img i) : SV_Target + { + UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(i); + + if (UNITY_SAMPLE_SCREENSPACE_TEXTURE(_MaskTex, i.uv).r > 0) + { + // TODO: Avoid discard/clip to improve performance on mobiles. + discard; + } + + float intensity = CalcIntensity(i.uv, float2(0, _MainTex_TexelSize.y)); + intensity = _Intensity > 99 ? step(0.01, intensity) : intensity * _Intensity; + return float4(_Color.rgb, saturate(_Color.a * intensity)); + } + + ENDHLSL + + // SM3.5+ + SubShader + { + Cull Off + ZWrite Off + ZTest Always + Lighting Off + + Pass + { + Name "HPass" + + HLSLPROGRAM + + #pragma target 3.5 + #pragma multi_compile_instancing + #pragma shader_feature_local _USE_DRAWMESH + #pragma vertex vert + #pragma fragment frag_h + + ENDHLSL + } + + Pass + { + Name "VPassBlend" + Blend SrcAlpha OneMinusSrcAlpha + + HLSLPROGRAM + + #pragma target 3.5 + #pragma multi_compile_instancing + #pragma shader_feature_local _USE_DRAWMESH + #pragma vertex vert + #pragma fragment frag_v + + ENDHLSL + } + } + + // SM2.0 + SubShader + { + Cull Off + ZWrite Off + ZTest Always + Lighting Off + + Pass + { + Name "HPass" + + HLSLPROGRAM + + #pragma vertex vert + #pragma fragment frag_h + + ENDHLSL + } + + Pass + { + Name "VPassBlend" + Blend SrcAlpha OneMinusSrcAlpha + + HLSLPROGRAM + + #pragma vertex vert + #pragma fragment frag_v + + ENDHLSL + } + } +} diff --git a/Runtime/Shaders/Outline.shader.meta b/Runtime/Shaders/Outline.shader.meta new file mode 100644 index 0000000..bb55354 --- /dev/null +++ b/Runtime/Shaders/Outline.shader.meta @@ -0,0 +1,10 @@ +fileFormatVersion: 2 +guid: 41c9acbf41c8245498ac9beab378de12 +timeCreated: 1566126977 +licenseType: Free +ShaderImporter: + externalObjects: {} + defaultTextures: [] + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Shaders/OutlineColor.shader b/Runtime/Shaders/OutlineColor.shader new file mode 100644 index 0000000..9383693 --- /dev/null +++ b/Runtime/Shaders/OutlineColor.shader @@ -0,0 +1,77 @@ +// Copyright (C) 2019-2021 Alexander Bogarsukov. All rights reserved. +// See the LICENSE.md file in the project root for more information. + +// Renders everything with while color. +// Modified version of 'Custom/DrawSimple' shader taken from https://willweissman.wordpress.com/tutorials/shaders/unity-shaderlab-object-outlines/. +Shader "Hidden/UnityFx/OutlineColor" +{ + HLSLINCLUDE + + #include "UnityCG.cginc" + + UNITY_DECLARE_TEX2D(_MainTex); + float _Cutoff; + + v2f_img vert(appdata_img v) + { + v2f_img o; + UNITY_SETUP_INSTANCE_ID(v); + UNITY_INITIALIZE_OUTPUT(v2f_img, o); + UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o); + + o.pos = UnityObjectToClipPos(v.vertex); + o.uv = v.texcoord; + + return o; + } + + half4 frag() : SV_Target + { + return 1; + } + + half4 frag_clip(v2f_img i) : SV_Target + { + UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(i); + + half4 c = UNITY_SAMPLE_TEX2D(_MainTex, i.uv); + clip(c.a - _Cutoff); + return 1; + } + + ENDHLSL + + SubShader + { + Cull Off + ZWrite Off + ZTest LEqual + Lighting Off + + Pass + { + Name "Opaque" + + HLSLPROGRAM + + #pragma multi_compile_instancing + #pragma vertex vert + #pragma fragment frag + + ENDHLSL + } + + Pass + { + Name "Transparent" + + HLSLPROGRAM + + #pragma multi_compile_instancing + #pragma vertex vert + #pragma fragment frag_clip + + ENDHLSL + } + } +} diff --git a/Runtime/Shaders/OutlineColor.shader.meta b/Runtime/Shaders/OutlineColor.shader.meta new file mode 100644 index 0000000..66aa3a2 --- /dev/null +++ b/Runtime/Shaders/OutlineColor.shader.meta @@ -0,0 +1,10 @@ +fileFormatVersion: 2 +guid: ac20fbf75bafe454aba5ef3c098349df +timeCreated: 1566126977 +licenseType: Free +ShaderImporter: + externalObjects: {} + defaultTextures: [] + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/UnityFx.Outline.asmdef b/Runtime/UnityFx.Outline.asmdef new file mode 100644 index 0000000..af0702d --- /dev/null +++ b/Runtime/UnityFx.Outline.asmdef @@ -0,0 +1,3 @@ +{ + "name": "UnityFx.Outline" +} diff --git a/Runtime/UnityFx.Outline.asmdef.meta b/Runtime/UnityFx.Outline.asmdef.meta new file mode 100644 index 0000000..6a74c49 --- /dev/null +++ b/Runtime/UnityFx.Outline.asmdef.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: de7ed7f8e7092c144bd17cbabf282ba3 +timeCreated: 1566126961 +licenseType: Free +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/package.json b/package.json new file mode 100644 index 0000000..2778456 --- /dev/null +++ b/package.json @@ -0,0 +1,34 @@ +{ + "name": "com.unityfx.outline", + "version": "0.8.5", + "displayName": "Outline toolkit", + "description": "This package contains configurable per-object and per-camera outline effect implementation for built-in render pipeline. Both solid and blurred outline modes are supported (Gauss blur), as well as depth testing. Reusable and extensible API.", + "unity": "2018.4", + "keywords": [ + "UnityFx", + "UnityFx.Outline", + "outline", + "post-effect" + ], + "category": "UnityFx", + "author": { + "name": "Arvtesh", + "email": "arvtesh@gmail.com" + }, + "license": "MIT", + "homepage": "https://github.com/Arvtesh/UnityFx.Outline", + "repository": { + "type": "git", + "url": "https://github.com/Arvtesh/UnityFx.Outline.git" + }, + "bugs": { + "url": "https://github.com/Arvtesh/UnityFx.Outline/issues" + }, + "samples": [ + { + "displayName": "Example Setting", + "description": "", + "path": "~Samples/Example" + } + ] +} \ No newline at end of file diff --git a/package.json.meta b/package.json.meta new file mode 100644 index 0000000..198e6dc --- /dev/null +++ b/package.json.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: f0fa2388b44716e42afe4670ecd796b3 +PackageManifestImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/~Samples.meta b/~Samples.meta new file mode 100644 index 0000000..dd1a919 --- /dev/null +++ b/~Samples.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 7a2ba83db4065a54ea4edc22eb4efb69 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/~Samples/Example.meta b/~Samples/Example.meta new file mode 100644 index 0000000..3307acb --- /dev/null +++ b/~Samples/Example.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 122e93f02cdf07045a82cf5646b17038 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/~Samples/Example/.sample.json b/~Samples/Example/.sample.json new file mode 100644 index 0000000..3b43eaf --- /dev/null +++ b/~Samples/Example/.sample.json @@ -0,0 +1,5 @@ +{ + "displayName":"Example Sample", + "description": "Replace this string with your own description of the sample. Delete the Samples folder if not needed.", + "createSeparatePackage": false +} diff --git a/~Samples/Example/OutlineResources.asset b/~Samples/Example/OutlineResources.asset new file mode 100644 index 0000000..df54c32 --- /dev/null +++ b/~Samples/Example/OutlineResources.asset @@ -0,0 +1,16 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: b503341e0a514e3489c4851727e68257, type: 3} + m_Name: OutlineResources + m_EditorClassIdentifier: + _renderShader: {fileID: 4800000, guid: ac20fbf75bafe454aba5ef3c098349df, type: 3} + _outlineShader: {fileID: 4800000, guid: 41c9acbf41c8245498ac9beab378de12, type: 3} diff --git a/~Samples/Example/OutlineResources.asset.meta b/~Samples/Example/OutlineResources.asset.meta new file mode 100644 index 0000000..41bc6af --- /dev/null +++ b/~Samples/Example/OutlineResources.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 9030338688e432a4a883ae181554f544 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/~Samples/Example/OutlineSettings.asset b/~Samples/Example/OutlineSettings.asset new file mode 100644 index 0000000..c75681a --- /dev/null +++ b/~Samples/Example/OutlineSettings.asset @@ -0,0 +1,19 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: b579424fd3338724cba3155ee4d53475, type: 3} + m_Name: OutlineSettings + m_EditorClassIdentifier: + _outlineColor: {r: 0, g: 1, b: 1, a: 1} + _outlineWidth: 32 + _outlineIntensity: 2.3 + _outlineAlphaCutoff: 0.9 + _outlineMode: 1 diff --git a/~Samples/Example/OutlineSettings.asset.meta b/~Samples/Example/OutlineSettings.asset.meta new file mode 100644 index 0000000..288ebb7 --- /dev/null +++ b/~Samples/Example/OutlineSettings.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 8856b749fef9c674092af6d255f0a351 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/~Samples/Example/Resources.meta b/~Samples/Example/Resources.meta new file mode 100644 index 0000000..ef4241e --- /dev/null +++ b/~Samples/Example/Resources.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 73500f231d0ee644d9ffce9390f2ff91 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/~Samples/Example/Resources/OutlineLayerCollection.asset b/~Samples/Example/Resources/OutlineLayerCollection.asset new file mode 100644 index 0000000..d5a50d8 --- /dev/null +++ b/~Samples/Example/Resources/OutlineLayerCollection.asset @@ -0,0 +1,27 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 57d0c11168277cf4eb3b4b89706e6aa5, type: 3} + m_Name: OutlineLayerCollection + m_EditorClassIdentifier: + _layers: + - _settings: + _outlineSettings: {fileID: 11400000, guid: 8856b749fef9c674092af6d255f0a351, + type: 2} + _outlineColor: {r: 1, g: 0, b: 0.009791374, a: 1} + _outlineWidth: 32 + _outlineIntensity: 2.3 + _outlineAlphaCutoff: 0.9 + _outlineMode: 1 + _name: + _enabled: 1 + _mergeLayerObjects: 0 + _ignoreLayerMask: 0 diff --git a/~Samples/Example/Resources/OutlineLayerCollection.asset.meta b/~Samples/Example/Resources/OutlineLayerCollection.asset.meta new file mode 100644 index 0000000..e15903f --- /dev/null +++ b/~Samples/Example/Resources/OutlineLayerCollection.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: c8518571f6021a848a8607839531e7fd +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 0 + userData: + assetBundleName: + assetBundleVariant: