├── .github └── FUNDING.yml ├── .gitignore ├── CHANGELOG.md ├── CHANGELOG.md.meta ├── Editor.meta ├── Editor ├── MirrorEditor.cs ├── MirrorEditor.cs.meta ├── kTools.Mirrors.Editor.asmdef └── kTools.Mirrors.Editor.asmdef.meta ├── Gizmos.meta ├── Gizmos ├── Mirror.png └── Mirror.png.meta ├── LICENSE ├── LICENSE.meta ├── README.md ├── README.md.meta ├── Runtime.meta ├── Runtime ├── Mirror.cs ├── Mirror.cs.meta ├── kTools.Mirrors.Runtime.asmdef └── kTools.Mirrors.Runtime.asmdef.meta ├── Tests.meta ├── Tests ├── Editor.meta └── Editor │ ├── kTools.Mirrors.Editor.Tests.asmdef │ └── kTools.Mirrors.Editor.Tests.asmdef.meta ├── package.json └── package.json.meta /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: Kink3d 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.vscode/ 2 | 3 | # Visual Studio cache directory 4 | .vs/ 5 | 6 | # Autogenerated VS/MD/Consulo solution and project files 7 | ExportedObj/ 8 | .consulo/ 9 | *.csproj 10 | *.unityproj 11 | *.sln 12 | *.suo 13 | *.tmp 14 | *.user 15 | *.userprefs 16 | *.pidb 17 | *.booproj 18 | *.svd 19 | *.pdb 20 | *.mdb 21 | *.opendb 22 | *.VC.db -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this package are documented in this file. 3 | 4 | The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) 5 | and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). 6 | 7 | ## 0.1.0 - 2020-01-30 8 | ### Added 9 | - Camera management 10 | - Reflection rendering 11 | - Camera controls 12 | - Local mirrors 13 | - Support for kShading Lit and Toon Lit 14 | -------------------------------------------------------------------------------- /CHANGELOG.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e6f16d9661a4cd04bb5b4b6af1f5e9a3 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Editor.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 91b6875a43543e9459f88d979b41fec1 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Editor/MirrorEditor.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using UnityEditor; 3 | using UnityEditorInternal; 4 | 5 | namespace kTools.Mirrors.Editor 6 | { 7 | using Editor = UnityEditor.Editor; 8 | 9 | [CustomEditor(typeof(Mirror)), CanEditMultipleObjects] 10 | sealed class MirrorEditor : Editor 11 | { 12 | #region Structs 13 | struct Styles 14 | { 15 | // Foldouts 16 | public static readonly GUIContent ProjectionOptions = new GUIContent("Projection Options"); 17 | public static readonly GUIContent OutputOptions = new GUIContent("Output Options"); 18 | 19 | // Properties 20 | public static readonly GUIContent Offset = new GUIContent("Offset", 21 | "Offset value for oplique near clip plane."); 22 | 23 | public static readonly GUIContent LayerMask = new GUIContent("Layer Mask", 24 | "Which layers should the Mirror render."); 25 | 26 | public static readonly GUIContent Scope = new GUIContent("Scope", 27 | "Global output renders to the global texture. Only one Mirror can be global. Local output renders to one texture per Mirror, this is set on all elements of the Renderers list."); 28 | 29 | public static readonly GUIContent Renderers = new GUIContent("Renderers", 30 | "Renderers to set the reflection texture on."); 31 | 32 | public static readonly GUIContent TextureScale = new GUIContent("Texture Scale", 33 | "Scale value applied to the size of the source camera texture."); 34 | 35 | public static readonly GUIContent HDR = new GUIContent("HDR", 36 | "Should reflections be rendered in HDR."); 37 | 38 | public static readonly GUIContent MSAA = new GUIContent("MSAA", 39 | "Should reflections be resolved with MSAA."); 40 | } 41 | 42 | struct PropertyNames 43 | { 44 | public static readonly string Offset = "m_Offset"; 45 | public static readonly string LayerMask = "m_LayerMask"; 46 | public static readonly string Scope = "m_Scope"; 47 | public static readonly string Renderers = "m_Renderers"; 48 | public static readonly string TextureScale = "m_TextureScale"; 49 | public static readonly string AllowHDR = "m_AllowHDR"; 50 | public static readonly string AllowMSAA = "m_AllowMSAA"; 51 | } 52 | #endregion 53 | 54 | #region Fields 55 | const string kEditorPrefKey = "kMirrors:MirrorData:"; 56 | Mirror m_Target; 57 | 58 | // Foldouts 59 | bool m_ProjectionOptionsFoldout; 60 | bool m_OutputOptionsFoldout; 61 | 62 | // Properties 63 | SerializedProperty m_OffsetProp; 64 | SerializedProperty m_LayerMaskProp; 65 | SerializedProperty m_ScopeProp; 66 | SerializedProperty m_RenderersProp; 67 | SerializedProperty m_TextureScaleProp; 68 | SerializedProperty m_AllowHDR; 69 | SerializedProperty m_AllowMSAA; 70 | #endregion 71 | 72 | #region State 73 | void OnEnable() 74 | { 75 | // Set data 76 | m_Target = target as Mirror; 77 | 78 | // Get Properties 79 | m_OffsetProp = serializedObject.FindProperty(PropertyNames.Offset); 80 | m_LayerMaskProp = serializedObject.FindProperty(PropertyNames.LayerMask); 81 | m_ScopeProp = serializedObject.FindProperty(PropertyNames.Scope); 82 | m_RenderersProp = serializedObject.FindProperty(PropertyNames.Renderers); 83 | m_TextureScaleProp = serializedObject.FindProperty(PropertyNames.TextureScale); 84 | m_AllowHDR = serializedObject.FindProperty(PropertyNames.AllowHDR); 85 | m_AllowMSAA = serializedObject.FindProperty(PropertyNames.AllowMSAA); 86 | } 87 | #endregion 88 | 89 | #region GUI 90 | public override void OnInspectorGUI() 91 | { 92 | // Get foldouts from EditorPrefs 93 | m_ProjectionOptionsFoldout = GetFoldoutState("ProjectionOptions"); 94 | m_OutputOptionsFoldout = GetFoldoutState("OutputOptions"); 95 | 96 | // Setup 97 | serializedObject.Update(); 98 | 99 | // Projection Options 100 | var projectionOptions = EditorGUILayout.BeginFoldoutHeaderGroup(m_ProjectionOptionsFoldout, Styles.ProjectionOptions); 101 | if(projectionOptions) 102 | { 103 | DrawProjectionOptions(); 104 | EditorGUILayout.Space(); 105 | } 106 | SetFoldoutState("ProjectionOptions", m_ProjectionOptionsFoldout, projectionOptions); 107 | EditorGUILayout.EndFoldoutHeaderGroup(); 108 | 109 | // Output Options 110 | var outputOptions = EditorGUILayout.BeginFoldoutHeaderGroup(m_OutputOptionsFoldout, Styles.OutputOptions); 111 | if(outputOptions) 112 | { 113 | DrawOutputOptions(); 114 | EditorGUILayout.Space(); 115 | } 116 | SetFoldoutState("OutputOptions", m_OutputOptionsFoldout, outputOptions); 117 | EditorGUILayout.EndFoldoutHeaderGroup(); 118 | 119 | // Finalize 120 | serializedObject.ApplyModifiedProperties(); 121 | } 122 | 123 | void DrawProjectionOptions() 124 | { 125 | // Clip Plane Offset 126 | EditorGUILayout.PropertyField(m_OffsetProp, Styles.Offset); 127 | 128 | // Layer Mask 129 | EditorGUI.BeginChangeCheck(); 130 | LayerMask tempMask = EditorGUILayout.MaskField(Styles.LayerMask, (LayerMask)m_LayerMaskProp.intValue, InternalEditorUtility.layers); 131 | if(EditorGUI.EndChangeCheck()) 132 | { 133 | m_LayerMaskProp.intValue = (int)tempMask; 134 | } 135 | } 136 | 137 | void DrawOutputOptions() 138 | { 139 | // Scope 140 | EditorGUILayout.PropertyField(m_ScopeProp, Styles.Scope); 141 | 142 | // Renderers 143 | if(m_ScopeProp.enumValueIndex == (int)Mirror.OutputScope.Local) 144 | { 145 | EditorGUI.indentLevel++; 146 | EditorGUILayout.PropertyField(m_RenderersProp, Styles.Renderers); 147 | EditorGUI.indentLevel--; 148 | } 149 | 150 | // Texture Scale 151 | EditorGUI.BeginChangeCheck(); 152 | var textureScale = EditorGUILayout.Slider(Styles.TextureScale, m_TextureScaleProp.floatValue, 0, 1); 153 | if(EditorGUI.EndChangeCheck()) 154 | { 155 | m_TextureScaleProp.floatValue = textureScale; 156 | } 157 | 158 | // HDR 159 | EditorGUILayout.PropertyField(m_AllowHDR, Styles.HDR); 160 | 161 | // MSAA 162 | EditorGUILayout.PropertyField(m_AllowMSAA, Styles.MSAA); 163 | } 164 | #endregion 165 | 166 | #region EditorPrefs 167 | bool GetFoldoutState(string name) 168 | { 169 | // Get value from EditorPrefs 170 | return EditorPrefs.GetBool($"{kEditorPrefKey}.{name}"); 171 | } 172 | 173 | void SetFoldoutState(string name, bool field, bool value) 174 | { 175 | if(field == value) 176 | return; 177 | 178 | // Set value to EditorPrefs and field 179 | EditorPrefs.SetBool($"{kEditorPrefKey}.{name}", value); 180 | field = value; 181 | } 182 | #endregion 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /Editor/MirrorEditor.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: f989b0e08ab6cc34f9643e90fcb99cd8 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/kTools.Mirrors.Editor.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "kTools.Mirrors.Editor", 3 | "references": [ 4 | "GUID:9e705d6a72d9a354b810376671ddb3ac" 5 | ], 6 | "includePlatforms": [ 7 | "Editor" 8 | ], 9 | "excludePlatforms": [], 10 | "allowUnsafeCode": false, 11 | "overrideReferences": true, 12 | "precompiledReferences": [], 13 | "autoReferenced": false, 14 | "defineConstraints": [], 15 | "versionDefines": [], 16 | "noEngineReferences": false 17 | } -------------------------------------------------------------------------------- /Editor/kTools.Mirrors.Editor.asmdef.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: bfe6fbd0060e7e94883a1710b8ae7995 3 | AssemblyDefinitionImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Gizmos.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 2aa965d0b276c594685f9dac6f21c0d0 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Gizmos/Mirror.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kink3d/kMirrors/79b0526e5da06689f12a7f4846b2b568e997be35/Gizmos/Mirror.png -------------------------------------------------------------------------------- /Gizmos/Mirror.png.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 679c06de8e095fc4d8489115cfc43352 3 | TextureImporter: 4 | internalIDToNameTable: [] 5 | externalObjects: {} 6 | serializedVersion: 10 7 | mipmaps: 8 | mipMapMode: 0 9 | enableMipMap: 1 10 | sRGBTexture: 1 11 | linearTexture: 0 12 | fadeOut: 0 13 | borderMipMap: 0 14 | mipMapsPreserveCoverage: 0 15 | alphaTestReferenceValue: 0.5 16 | mipMapFadeDistanceStart: 1 17 | mipMapFadeDistanceEnd: 3 18 | bumpmap: 19 | convertToNormalMap: 0 20 | externalNormalMap: 0 21 | heightScale: 0.25 22 | normalMapFilter: 0 23 | isReadable: 0 24 | streamingMipmaps: 0 25 | streamingMipmapsPriority: 0 26 | grayScaleToAlpha: 0 27 | generateCubemap: 6 28 | cubemapConvolution: 0 29 | seamlessCubemap: 0 30 | textureFormat: 1 31 | maxTextureSize: 2048 32 | textureSettings: 33 | serializedVersion: 2 34 | filterMode: -1 35 | aniso: -1 36 | mipBias: -100 37 | wrapU: -1 38 | wrapV: -1 39 | wrapW: -1 40 | nPOTScale: 1 41 | lightmap: 0 42 | compressionQuality: 50 43 | spriteMode: 0 44 | spriteExtrude: 1 45 | spriteMeshType: 1 46 | alignment: 0 47 | spritePivot: {x: 0.5, y: 0.5} 48 | spritePixelsToUnits: 100 49 | spriteBorder: {x: 0, y: 0, z: 0, w: 0} 50 | spriteGenerateFallbackPhysicsShape: 1 51 | alphaUsage: 1 52 | alphaIsTransparency: 1 53 | spriteTessellationDetail: -1 54 | textureType: 0 55 | textureShape: 1 56 | singleChannelComponent: 0 57 | maxTextureSizeSet: 0 58 | compressionQualitySet: 0 59 | textureFormatSet: 0 60 | platformSettings: 61 | - serializedVersion: 3 62 | buildTarget: DefaultTexturePlatform 63 | maxTextureSize: 2048 64 | resizeAlgorithm: 0 65 | textureFormat: -1 66 | textureCompression: 1 67 | compressionQuality: 50 68 | crunchedCompression: 0 69 | allowsAlphaSplitting: 0 70 | overridden: 0 71 | androidETC2FallbackOverride: 0 72 | forceMaximumCompressionQuality_BC6H_BC7: 0 73 | - serializedVersion: 3 74 | buildTarget: Standalone 75 | maxTextureSize: 2048 76 | resizeAlgorithm: 0 77 | textureFormat: -1 78 | textureCompression: 1 79 | compressionQuality: 50 80 | crunchedCompression: 0 81 | allowsAlphaSplitting: 0 82 | overridden: 0 83 | androidETC2FallbackOverride: 0 84 | forceMaximumCompressionQuality_BC6H_BC7: 0 85 | spriteSheet: 86 | serializedVersion: 2 87 | sprites: [] 88 | outline: [] 89 | physicsShape: [] 90 | bones: [] 91 | spriteID: 92 | internalID: 0 93 | vertices: [] 94 | indices: 95 | edges: [] 96 | weights: [] 97 | secondaryTextures: [] 98 | spritePackingTag: 99 | pSDRemoveMatte: 0 100 | pSDShowRemoveMatteOption: 0 101 | userData: 102 | assetBundleName: 103 | assetBundleVariant: 104 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Matt Dean 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /LICENSE.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 0b1ef955611321d4c964399689ef1ef7 3 | DefaultImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # kMirrors 2 | ### Planar reflections for Unity’s Universal Render Pipeline. 3 | 4 | ![alt text](https://github.com/Kink3d/kMirrors/wiki/Images/Home00.png?raw=true) 5 | *An example of global and local reflections.* 6 | 7 | kMirrors is a system for defining and rendering planar reflection cameras in Unity's Universal Render Pipeline. It supports **Global** mode, where a single reflection camera can be used across an entire scene (useful for water and other large reflective surfaces) and **Local** mode, where a list of **Renderers** can be defined to receive reflections (useful for smaller surfaces like wall mirrors). 8 | 9 | Refer to the [Wiki](https://github.com/Kink3d/kMirrors/wiki/Home) for more information. 10 | 11 | ## Instructions 12 | - Open your project manifest file (`MyProject/Packages/manifest.json`). 13 | - Add `"com.kink3d.mirrors": "https://github.com/Kink3d/kMirrors.git"` to the `dependencies` list. 14 | - Open or focus on Unity Editor to resolve packages. 15 | 16 | ## Requirements 17 | - Unity 2019.3.0f3 or higher. -------------------------------------------------------------------------------- /README.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 348769316597fe54b9db2cd435d11a02 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Runtime.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 683872b7a9e58404d86a72f64355a908 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Runtime/Mirror.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using UnityEngine; 3 | using UnityEngine.Rendering; 4 | using UnityEngine.Rendering.Universal; 5 | 6 | namespace kTools.Mirrors 7 | { 8 | /// 9 | /// Mirror Object component. 10 | /// 11 | [AddComponentMenu("kTools/Mirror"), ExecuteInEditMode] 12 | [RequireComponent(typeof(Camera), typeof(UniversalAdditionalCameraData))] 13 | public class Mirror : MonoBehaviour 14 | { 15 | #region Enumerations 16 | /// 17 | /// Camera override enumeration for Mirror properties 18 | /// 19 | public enum MirrorCameraOverride 20 | { 21 | UseSourceCameraSettings, 22 | Off, 23 | } 24 | 25 | /// 26 | /// Scope enumeration for Mirror output destination 27 | /// 28 | public enum OutputScope 29 | { 30 | Global, 31 | Local, 32 | } 33 | #endregion 34 | 35 | #region Serialized Fields 36 | [SerializeField] 37 | float m_Offset; 38 | 39 | [SerializeField] 40 | int m_LayerMask; 41 | 42 | [SerializeField] 43 | OutputScope m_Scope; 44 | 45 | [SerializeField] 46 | List m_Renderers; 47 | 48 | [SerializeField] 49 | float m_TextureScale; 50 | 51 | [SerializeField] 52 | MirrorCameraOverride m_AllowHDR; 53 | 54 | [SerializeField] 55 | MirrorCameraOverride m_AllowMSAA; 56 | #endregion 57 | 58 | #region Fields 59 | const string kGizmoPath = "Packages/com.kink3d.mirrors/Gizmos/Mirror.png"; 60 | Camera m_ReflectionCamera; 61 | UniversalAdditionalCameraData m_CameraData; 62 | RenderTexture m_RenderTexture; 63 | RenderTextureDescriptor m_PreviousDescriptor; 64 | #endregion 65 | 66 | #region Constructors 67 | public Mirror() 68 | { 69 | // Set data 70 | m_Offset = 0.01f; 71 | m_LayerMask = -1; 72 | m_Scope = OutputScope.Global; 73 | m_Renderers = new List(); 74 | m_TextureScale = 1.0f; 75 | m_AllowHDR = MirrorCameraOverride.UseSourceCameraSettings; 76 | m_AllowMSAA = MirrorCameraOverride.UseSourceCameraSettings; 77 | } 78 | #endregion 79 | 80 | #region Properties 81 | /// Offset value for oplique near clip plane. 82 | public float offest 83 | { 84 | get => m_Offset; 85 | set => m_Offset = value; 86 | } 87 | 88 | /// Which layers should the Mirror render. 89 | public LayerMask layerMask 90 | { 91 | get => m_LayerMask; 92 | set => m_LayerMask = value; 93 | } 94 | 95 | /// 96 | /// Global output renders to the global texture. Only one Mirror can be global. 97 | /// Local output renders to one texture per Mirror, this is set on all elements of the Renderers list. 98 | /// 99 | public OutputScope scope 100 | { 101 | get => m_Scope; 102 | set => m_Scope = value; 103 | } 104 | 105 | /// Renderers to set the reflection texture on. 106 | public List renderers 107 | { 108 | get => m_Renderers; 109 | set => m_Renderers = value; 110 | } 111 | 112 | /// Scale value applied to the size of the source camera texture. 113 | public float textureScale 114 | { 115 | get => m_TextureScale; 116 | set => m_TextureScale = value; 117 | } 118 | 119 | /// Should reflections be rendered in HDR. 120 | public MirrorCameraOverride allowHDR 121 | { 122 | get => m_AllowHDR; 123 | set => m_AllowHDR = value; 124 | } 125 | 126 | /// Should reflections be resolved with MSAA. 127 | public MirrorCameraOverride allowMSAA 128 | { 129 | get => m_AllowMSAA; 130 | set => m_AllowMSAA = value; 131 | } 132 | 133 | Camera reflectionCamera 134 | { 135 | get 136 | { 137 | if(m_ReflectionCamera == null) 138 | m_ReflectionCamera = GetComponent(); 139 | return m_ReflectionCamera; 140 | } 141 | } 142 | 143 | UniversalAdditionalCameraData cameraData 144 | { 145 | get 146 | { 147 | if(m_CameraData == null) 148 | m_CameraData = GetComponent(); 149 | return m_CameraData; 150 | } 151 | } 152 | #endregion 153 | 154 | #region State 155 | void OnEnable() 156 | { 157 | // Callbacks 158 | RenderPipelineManager.beginCameraRendering += BeginCameraRendering; 159 | 160 | // Initialize Components 161 | InitializeCamera(); 162 | } 163 | 164 | void OnDisable() 165 | { 166 | // Callbacks 167 | RenderPipelineManager.beginCameraRendering -= BeginCameraRendering; 168 | 169 | // Dispose RenderTexture 170 | SafeDestroyObject(m_RenderTexture); 171 | } 172 | #endregion 173 | 174 | #region Initialization 175 | void InitializeCamera() 176 | { 177 | // Setup Camera 178 | reflectionCamera.cameraType = CameraType.Reflection; 179 | reflectionCamera.targetTexture = m_RenderTexture; 180 | 181 | // Setup AdditionalCameraData 182 | cameraData.renderShadows = false; 183 | cameraData.requiresColorOption = CameraOverrideOption.Off; 184 | cameraData.requiresDepthOption = CameraOverrideOption.Off; 185 | } 186 | #endregion 187 | 188 | #region RenderTexture 189 | RenderTextureDescriptor GetDescriptor(Camera camera) 190 | { 191 | // Get scaled Texture size 192 | var width = (int)Mathf.Max(camera.pixelWidth * textureScale, 4); 193 | var height = (int)Mathf.Max(camera.pixelHeight * textureScale, 4); 194 | 195 | // Get Texture format 196 | var hdr = allowHDR == MirrorCameraOverride.UseSourceCameraSettings ? camera.allowHDR : false; 197 | var renderTextureFormat = hdr ? RenderTextureFormat.DefaultHDR : RenderTextureFormat.Default; 198 | return new RenderTextureDescriptor(width, height, renderTextureFormat, 16) { autoGenerateMips = true, useMipMap = true }; 199 | } 200 | #endregion 201 | 202 | #region Rendering 203 | void BeginCameraRendering(ScriptableRenderContext context, Camera camera) 204 | { 205 | // Never render Mirrors for Preview or Reflection cameras 206 | if(camera.cameraType == CameraType.Preview || camera.cameraType == CameraType.Reflection) 207 | return; 208 | 209 | // Profiling command 210 | CommandBuffer cmd = CommandBufferPool.Get($"Mirror {gameObject.GetInstanceID()}"); 211 | using (new ProfilingSample(cmd, $"Mirror {gameObject.GetInstanceID()}")) 212 | { 213 | ExecuteCommand(context, cmd); 214 | 215 | // Test for Descriptor changes 216 | var descriptor = GetDescriptor(camera); 217 | if(!descriptor.Equals(m_PreviousDescriptor)) 218 | { 219 | // Dispose RenderTexture 220 | if(m_RenderTexture != null) 221 | { 222 | SafeDestroyObject(m_RenderTexture); 223 | } 224 | 225 | // Create new RenderTexture 226 | m_RenderTexture = new RenderTexture(descriptor); 227 | m_PreviousDescriptor = descriptor; 228 | reflectionCamera.targetTexture = m_RenderTexture; 229 | } 230 | 231 | // Execute 232 | RenderMirror(context, camera); 233 | SetShaderUniforms(context, m_RenderTexture, cmd); 234 | } 235 | ExecuteCommand(context, cmd); 236 | } 237 | 238 | void RenderMirror(ScriptableRenderContext context, Camera camera) 239 | { 240 | // Mirror the view matrix 241 | var mirrorMatrix = GetMirrorMatrix(); 242 | reflectionCamera.worldToCameraMatrix = camera.worldToCameraMatrix * mirrorMatrix; 243 | 244 | // Make oplique projection matrix where near plane is mirror plane 245 | var mirrorPlane = GetMirrorPlane(reflectionCamera); 246 | var projectionMatrix = camera.CalculateObliqueMatrix(mirrorPlane); 247 | reflectionCamera.projectionMatrix = projectionMatrix; 248 | 249 | // Miscellanious camera settings 250 | reflectionCamera.cullingMask = layerMask; 251 | reflectionCamera.allowHDR = allowHDR == MirrorCameraOverride.UseSourceCameraSettings ? camera.allowHDR : false; 252 | reflectionCamera.allowMSAA = allowMSAA == MirrorCameraOverride.UseSourceCameraSettings ? camera.allowMSAA : false; 253 | reflectionCamera.enabled = false; 254 | 255 | // Render reflection camera with inverse culling 256 | GL.invertCulling = true; 257 | UniversalRenderPipeline.RenderSingleCamera(context, reflectionCamera); 258 | GL.invertCulling = false; 259 | } 260 | #endregion 261 | 262 | #region Projection 263 | Matrix4x4 GetMirrorMatrix() 264 | { 265 | // Setup 266 | var position = transform.position; 267 | var normal = transform.forward; 268 | var depth = -Vector3.Dot(normal, position) - offest; 269 | 270 | // Create matrix 271 | var mirrorMatrix = new Matrix4x4() 272 | { 273 | m00 = (1f - 2f * normal.x * normal.x), 274 | m01 = (-2f * normal.x * normal.y), 275 | m02 = (-2f * normal.x * normal.z), 276 | m03 = (-2f * depth * normal.x), 277 | m10 = (-2f * normal.y * normal.x), 278 | m11 = (1f - 2f * normal.y * normal.y), 279 | m12 = (-2f * normal.y * normal.z), 280 | m13 = (-2f * depth * normal.y), 281 | m20 = (-2f * normal.z * normal.x), 282 | m21 = (-2f * normal.z * normal.y), 283 | m22 = (1f - 2f * normal.z * normal.z), 284 | m23 = (-2f * depth * normal.z), 285 | m30 = 0f, 286 | m31 = 0f, 287 | m32 = 0f, 288 | m33 = 1f, 289 | }; 290 | return mirrorMatrix; 291 | } 292 | 293 | Vector4 GetMirrorPlane(Camera camera) 294 | { 295 | // Calculate mirror plane in camera space. 296 | var pos = transform.position - Vector3.forward * 0.1f; 297 | var normal = transform.forward; 298 | var offsetPos = pos + normal * offest; 299 | var cpos = camera.worldToCameraMatrix.MultiplyPoint(offsetPos); 300 | var cnormal = camera.worldToCameraMatrix.MultiplyVector(normal).normalized; 301 | return new Vector4(cnormal.x, cnormal.y, cnormal.z, -Vector3.Dot(cpos, cnormal)); 302 | } 303 | #endregion 304 | 305 | #region Output 306 | void SetShaderUniforms(ScriptableRenderContext context, RenderTexture renderTexture, CommandBuffer cmd) 307 | { 308 | var block = new MaterialPropertyBlock(); 309 | switch(scope) 310 | { 311 | case OutputScope.Global: 312 | // Globals 313 | cmd.SetGlobalTexture("_ReflectionMap", renderTexture); 314 | ExecuteCommand(context, cmd); 315 | 316 | // Property Blocm 317 | block.SetFloat("_LocalMirror", 0.0f); 318 | foreach(var renderer in renderers) 319 | { 320 | renderer.SetPropertyBlock(block); 321 | } 322 | break; 323 | case OutputScope.Local: 324 | // Keywords 325 | Shader.EnableKeyword("_BLEND_MIRRORS"); 326 | 327 | // Property Block 328 | block.SetTexture("_LocalReflectionMap", renderTexture); 329 | block.SetFloat("_LocalMirror", 1.0f); 330 | foreach(var renderer in renderers) 331 | { 332 | renderer.SetPropertyBlock(block); 333 | } 334 | break; 335 | } 336 | } 337 | #endregion 338 | 339 | #region CommandBufer 340 | void ExecuteCommand(ScriptableRenderContext context, CommandBuffer cmd) 341 | { 342 | context.ExecuteCommandBuffer(cmd); 343 | cmd.Clear(); 344 | } 345 | #endregion 346 | 347 | #region Object 348 | void SafeDestroyObject(Object obj) 349 | { 350 | if(obj == null) 351 | return; 352 | 353 | #if UNITY_EDITOR 354 | DestroyImmediate(obj); 355 | #else 356 | Destroy(obj); 357 | #endif 358 | } 359 | #endregion 360 | 361 | #region AssetMenu 362 | #if UNITY_EDITOR 363 | // Add a menu item to Mirrors 364 | [UnityEditor.MenuItem("GameObject/kTools/Mirror", false, 10)] 365 | static void CreateMirrorObject(UnityEditor.MenuCommand menuCommand) 366 | { 367 | // Create Mirror 368 | GameObject go = new GameObject("New Mirror", typeof(Mirror)); 369 | 370 | // Transform 371 | UnityEditor.GameObjectUtility.SetParentAndAlign(go, menuCommand.context as GameObject); 372 | 373 | // Undo and Selection 374 | UnityEditor.Undo.RegisterCreatedObjectUndo(go, "Create " + go.name); 375 | UnityEditor.Selection.activeObject = go; 376 | } 377 | #endif 378 | #endregion 379 | 380 | #region Gizmos 381 | #if UNITY_EDITOR 382 | void OnDrawGizmos() 383 | { 384 | // Setup 385 | var bounds = new Vector3(1.0f, 1.0f, 0.0f); 386 | var color = new Color32(0, 120, 255, 255); 387 | var selectedColor = new Color32(255, 255, 255, 255); 388 | var isSelected = UnityEditor.Selection.activeObject == gameObject; 389 | 390 | // Draw Gizmos 391 | Gizmos.matrix = transform.localToWorldMatrix; 392 | Gizmos.color = isSelected ? selectedColor : color; 393 | Gizmos.DrawIcon(transform.position, kGizmoPath, true); 394 | Gizmos.DrawWireCube(Vector3.zero, bounds); 395 | } 396 | #endif 397 | #endregion 398 | } 399 | } 400 | -------------------------------------------------------------------------------- /Runtime/Mirror.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 20b9488e8346e6445b175d59f4d2cd5c 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {fileID: 2800000, guid: 679c06de8e095fc4d8489115cfc43352, type: 3} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/kTools.Mirrors.Runtime.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "kTools.Mirrors.Runtime", 3 | "references": [ 4 | "GUID:15fc0a57446b3144c949da3e2b9737a9", 5 | "GUID:df380645f10b7bc4b97d4f5eb6303d95" 6 | ], 7 | "includePlatforms": [ 8 | "Android", 9 | "Editor", 10 | "iOS", 11 | "LinuxStandalone64", 12 | "Lumin", 13 | "macOSStandalone", 14 | "PS4", 15 | "Stadia", 16 | "Switch", 17 | "tvOS", 18 | "WSA", 19 | "WebGL", 20 | "WindowsStandalone32", 21 | "WindowsStandalone64", 22 | "XboxOne" 23 | ], 24 | "excludePlatforms": [], 25 | "allowUnsafeCode": false, 26 | "overrideReferences": false, 27 | "precompiledReferences": [], 28 | "autoReferenced": true, 29 | "defineConstraints": [], 30 | "versionDefines": [], 31 | "noEngineReferences": false 32 | } -------------------------------------------------------------------------------- /Runtime/kTools.Mirrors.Runtime.asmdef.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 9e705d6a72d9a354b810376671ddb3ac 3 | AssemblyDefinitionImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Tests.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e45556970c8b54c46b3ee1eadf721cbd 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Tests/Editor.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 22f2c05c0f4e4d14b9c3ebcd98868851 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Tests/Editor/kTools.Mirrors.Editor.Tests.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "kTools.Mirrors.Editor.Tests", 3 | "references": [ 4 | "GUID:27619889b8ba8c24980f49ee34dbb44a", 5 | "GUID:0acc523941302664db1f4e527237feb3", 6 | "GUID:9e705d6a72d9a354b810376671ddb3ac" 7 | ], 8 | "includePlatforms": [ 9 | "Editor" 10 | ], 11 | "excludePlatforms": [], 12 | "allowUnsafeCode": false, 13 | "overrideReferences": true, 14 | "precompiledReferences": [ 15 | "nunit.framework.dll" 16 | ], 17 | "autoReferenced": false, 18 | "defineConstraints": [ 19 | "UNITY_INCLUDE_TESTS" 20 | ], 21 | "versionDefines": [], 22 | "noEngineReferences": false 23 | } -------------------------------------------------------------------------------- /Tests/Editor/kTools.Mirrors.Editor.Tests.asmdef.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 64aacc806d6d42c4bb2ca701deaaf9ea 3 | AssemblyDefinitionImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "com.kink3d.mirrors", 3 | "description": "Planar reflections for Unity's Universal Render Pipeline.", 4 | "version": "0.1.0", 5 | "unity": "2019.3", 6 | "displayName": "kMirrors", 7 | "dependencies": { 8 | "com.unity.render-pipelines.universal": "7.x.x" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /package.json.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 5996b974fb4948d4fb830ac288fefbd1 3 | PackageManifestImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | --------------------------------------------------------------------------------