| | 1 | | using System.Collections.Generic; |
| | 2 | | using System.Linq; |
| | 3 | | using UnityEngine; |
| | 4 | | using UnityEngine.Rendering; |
| | 5 | | using UnityEngine.Rendering.Universal; |
| | 6 | |
|
| | 7 | | public class OutlineMaskFeature : ScriptableRendererFeature |
| | 8 | | { |
| | 9 | | private class OutlinerRenderPass : ScriptableRenderPass |
| | 10 | | { |
| | 11 | | private const int DEPTH_BUFFER_BITS = 0; |
| | 12 | | private const string PROFILER_TAG = "Outliner Mask Pass"; |
| | 13 | | private const bool USE_BASE_MATERIAL = false; // Use the material in the renderer and look for an "Outliner" pas |
| | 14 | |
|
| 0 | 15 | | private static readonly int BONE_MATRICES = Shader.PropertyToID("_Matrices"); |
| 0 | 16 | | private static readonly int BIND_POSES = Shader.PropertyToID("_BindPoses"); |
| 0 | 17 | | private static readonly int RENDERER_WORLD_INVERSE = Shader.PropertyToID("_WorldInverse"); |
| 0 | 18 | | private static readonly int AVATAR_MAP1 = Shader.PropertyToID("_AvatarMap1"); |
| 0 | 19 | | private static readonly int AVATAR_MAP2 = Shader.PropertyToID("_AvatarMap2"); |
| 0 | 20 | | private static readonly int AVATAR_MAP3 = Shader.PropertyToID("_AvatarMap3"); |
| 0 | 21 | | private static readonly int AVATAR_MAP4 = Shader.PropertyToID("_AvatarMap4"); |
| 0 | 22 | | private static readonly int AVATAR_MAP5 = Shader.PropertyToID("_AvatarMap5"); |
| 0 | 23 | | private static readonly int AVATAR_MAP6 = Shader.PropertyToID("_AvatarMap6"); |
| 0 | 24 | | private static readonly int AVATAR_MAP7 = Shader.PropertyToID("_AvatarMap7"); |
| 0 | 25 | | private static readonly int AVATAR_MAP8 = Shader.PropertyToID("_AvatarMap8"); |
| 0 | 26 | | private static readonly int AVATAR_MAP9 = Shader.PropertyToID("_AvatarMap9"); |
| 0 | 27 | | private static readonly int AVATAR_MAP10 = Shader.PropertyToID("_AvatarMap10"); |
| 0 | 28 | | private static readonly int AVATAR_MAP11 = Shader.PropertyToID("_AvatarMap11"); |
| 0 | 29 | | private static readonly int AVATAR_MAP12 = Shader.PropertyToID("_AvatarMap12"); |
| | 30 | |
|
| | 31 | | private readonly OutlineRenderersSO outlineRenderersSo; |
| | 32 | | private readonly Material material; |
| | 33 | | private readonly Material gpuSkinningMaterial; |
| | 34 | |
|
| | 35 | | private RenderTargetHandle outlineTextureHandle; |
| | 36 | | private RenderTextureDescriptor descriptor; |
| | 37 | |
|
| 2 | 38 | | private readonly List<Material> toDispose = new List<Material>(); |
| | 39 | |
|
| 2 | 40 | | public OutlinerRenderPass(OutlineRenderersSO outlineRenderersSo) |
| | 41 | | { |
| 2 | 42 | | material = CoreUtils.CreateEngineMaterial("Hidden/DCL/OutlineMaskPass"); |
| 2 | 43 | | gpuSkinningMaterial = CoreUtils.CreateEngineMaterial("Hidden/DCL/OutlineGPUSkinningMaskPass"); |
| 2 | 44 | | this.outlineRenderersSo = outlineRenderersSo; |
| 2 | 45 | | } |
| | 46 | |
|
| | 47 | | public void Setup(RenderTextureDescriptor descriptor, RenderTargetHandle outlineTextureHandle) |
| | 48 | | { |
| 0 | 49 | | this.outlineTextureHandle = outlineTextureHandle; |
| 0 | 50 | | descriptor.colorFormat = RenderTextureFormat.ARGB32; |
| 0 | 51 | | descriptor.depthBufferBits = DEPTH_BUFFER_BITS; |
| 0 | 52 | | this.descriptor = descriptor; |
| 0 | 53 | | } |
| | 54 | |
|
| | 55 | | public override void Configure(CommandBuffer cmd, RenderTextureDescriptor cameraTextureDescriptor) |
| | 56 | | { |
| | 57 | | //Configure CommandBuffer to output the mask result in the provided texture |
| 0 | 58 | | cmd.GetTemporaryRT(outlineTextureHandle.id, descriptor, FilterMode.Point); |
| 0 | 59 | | ConfigureTarget(outlineTextureHandle.Identifier()); |
| 0 | 60 | | ConfigureClear(ClearFlag.All, Color.black); |
| 0 | 61 | | } |
| | 62 | |
|
| | 63 | | public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData) |
| | 64 | | { |
| 0 | 65 | | CommandBuffer cmd = CommandBufferPool.Get(PROFILER_TAG); |
| | 66 | |
|
| 0 | 67 | | using (new ProfilingScope(cmd, new ProfilingSampler(PROFILER_TAG))) |
| | 68 | | { |
| 0 | 69 | | context.ExecuteCommandBuffer(cmd); |
| 0 | 70 | | cmd.Clear(); |
| | 71 | |
|
| 0 | 72 | | if (outlineRenderersSo != null) |
| | 73 | | { |
| | 74 | | //By now only outline avatars |
| | 75 | | //DrawRenderers(outlineRenderersSo?.renderers, renderingData.cameraData.camera.cullingMask, cmd); |
| 0 | 76 | | DrawAvatar(outlineRenderersSo.avatar, renderingData.cameraData.camera.cullingMask, cmd); |
| | 77 | | } |
| | 78 | |
|
| 0 | 79 | | cmd.SetGlobalTexture("_OutlineTexture", outlineTextureHandle.id); |
| 0 | 80 | | } |
| | 81 | |
|
| 0 | 82 | | context.ExecuteCommandBuffer(cmd); |
| 0 | 83 | | CommandBufferPool.Release(cmd); |
| 0 | 84 | | } |
| | 85 | |
|
| | 86 | | private void DrawRenderers(List<(Renderer renderer, int meshCount)> renderers, int cameraCulling, CommandBuffer |
| | 87 | | { |
| 0 | 88 | | if (renderers == null) |
| 0 | 89 | | return; |
| | 90 | |
|
| 0 | 91 | | foreach ((Renderer renderer, int meshCount) in renderers) |
| | 92 | | { |
| | 93 | | //Ignore disabled renderers |
| 0 | 94 | | if (!renderer.gameObject.activeSelf || (cameraCulling & (1 << renderer.gameObject.layer)) == 0) |
| | 95 | | continue; |
| | 96 | |
|
| | 97 | | // We have to manually render all the submeshes of the selected objects. |
| 0 | 98 | | for (var i = 0; i < meshCount; i++) { cmd.DrawRenderer(renderer, material, i); } |
| | 99 | | } |
| 0 | 100 | | } |
| | 101 | |
|
| | 102 | | private void DrawAvatar((Renderer renderer, int meshCount, float avatarHeight) avatar, int cameraCulling, Comman |
| | 103 | | { |
| 0 | 104 | | if (avatar.renderer == null) |
| 0 | 105 | | return; |
| | 106 | |
|
| | 107 | | //Ignore disabled or culled by camera avatars |
| 0 | 108 | | if (!avatar.renderer.gameObject.activeSelf || (cameraCulling & (1 << avatar.renderer.gameObject.layer)) == 0 |
| 0 | 109 | | return; |
| | 110 | |
|
| 0 | 111 | | for (var i = 0; i < avatar.meshCount; i++) |
| | 112 | | { |
| 0 | 113 | | Material materialToUse = null; |
| | 114 | |
|
| | 115 | | // We use a GPU Skinning based material |
| 0 | 116 | | if (avatar.renderer.materials[i] != null) |
| | 117 | | { |
| | 118 | | //Enable it when we are capable of adding the pass into the Toon Shader |
| | 119 | | if (USE_BASE_MATERIAL) |
| | 120 | | { |
| | 121 | | int originalMaterialOutlinerPass = avatar.renderer.materials[i].FindPass("Outliner"); |
| | 122 | |
|
| | 123 | | if (originalMaterialOutlinerPass != -1) |
| | 124 | | { |
| | 125 | | //The material has a built in pass we can use |
| | 126 | | cmd.DrawRenderer(avatar.renderer, avatar.renderer.materials[i], i, originalMaterialOutlinerP |
| | 127 | | continue; |
| | 128 | | } |
| | 129 | | } |
| | 130 | |
|
| | 131 | | // We use the original material to copy the GPUSkinning values. |
| | 132 | | // We cannot use materialToUse.CopyPropertiesFromMaterial because there are non serialized uniforms |
| 0 | 133 | | materialToUse = new Material(gpuSkinningMaterial); |
| 0 | 134 | | toDispose.Add(materialToUse); |
| 0 | 135 | | CopyAvatarProperties(avatar.renderer.materials[i], materialToUse); |
| | 136 | | } |
| | 137 | | else // Fallback to the normal outliner without GPUSkinning |
| | 138 | | { |
| 0 | 139 | | materialToUse = material; |
| | 140 | | } |
| | 141 | |
|
| | 142 | | // We have to manually render all the submeshes of the selected objects. |
| 0 | 143 | | cmd.DrawRenderer(avatar.renderer, materialToUse, i); |
| | 144 | | } |
| 0 | 145 | | } |
| | 146 | |
|
| | 147 | | private void CopyAvatarProperties(Material source, Material target) |
| | 148 | | { |
| 0 | 149 | | target.SetMatrixArray(BIND_POSES, source.GetMatrixArray(BIND_POSES)); |
| 0 | 150 | | target.SetMatrix(RENDERER_WORLD_INVERSE, source.GetMatrix(RENDERER_WORLD_INVERSE)); |
| 0 | 151 | | target.SetMatrixArray(BONE_MATRICES, source.GetMatrixArray(BONE_MATRICES)); |
| | 152 | |
|
| 0 | 153 | | if (source.HasTexture(AVATAR_MAP1)) |
| 0 | 154 | | target.SetTexture(AVATAR_MAP1, source.GetTexture(AVATAR_MAP1)); |
| | 155 | |
|
| 0 | 156 | | if (source.HasTexture(AVATAR_MAP2)) |
| 0 | 157 | | target.SetTexture(AVATAR_MAP2, source.GetTexture(AVATAR_MAP2)); |
| | 158 | |
|
| 0 | 159 | | if (source.HasTexture(AVATAR_MAP3)) |
| 0 | 160 | | target.SetTexture(AVATAR_MAP3, source.GetTexture(AVATAR_MAP3)); |
| | 161 | |
|
| 0 | 162 | | if (source.HasTexture(AVATAR_MAP4)) |
| 0 | 163 | | target.SetTexture(AVATAR_MAP4, source.GetTexture(AVATAR_MAP4)); |
| | 164 | |
|
| 0 | 165 | | if (source.HasTexture(AVATAR_MAP5)) |
| 0 | 166 | | target.SetTexture(AVATAR_MAP5, source.GetTexture(AVATAR_MAP5)); |
| | 167 | |
|
| 0 | 168 | | if (source.HasTexture(AVATAR_MAP6)) |
| 0 | 169 | | target.SetTexture(AVATAR_MAP6, source.GetTexture(AVATAR_MAP6)); |
| | 170 | |
|
| 0 | 171 | | if (source.HasTexture(AVATAR_MAP7)) |
| 0 | 172 | | target.SetTexture(AVATAR_MAP7, source.GetTexture(AVATAR_MAP7)); |
| | 173 | |
|
| 0 | 174 | | if (source.HasTexture(AVATAR_MAP8)) |
| 0 | 175 | | target.SetTexture(AVATAR_MAP8, source.GetTexture(AVATAR_MAP8)); |
| | 176 | |
|
| 0 | 177 | | if (source.HasTexture(AVATAR_MAP9)) |
| 0 | 178 | | target.SetTexture(AVATAR_MAP9, source.GetTexture(AVATAR_MAP9)); |
| | 179 | |
|
| 0 | 180 | | if (source.HasTexture(AVATAR_MAP10)) |
| 0 | 181 | | target.SetTexture(AVATAR_MAP10, source.GetTexture(AVATAR_MAP10)); |
| | 182 | |
|
| 0 | 183 | | if (source.HasTexture(AVATAR_MAP11)) |
| 0 | 184 | | target.SetTexture(AVATAR_MAP11, source.GetTexture(AVATAR_MAP11)); |
| | 185 | |
|
| 0 | 186 | | if (source.HasTexture(AVATAR_MAP12)) |
| 0 | 187 | | target.SetTexture(AVATAR_MAP12, source.GetTexture(AVATAR_MAP12)); |
| 0 | 188 | | } |
| | 189 | |
|
| | 190 | | public override void FrameCleanup(CommandBuffer cmd) |
| | 191 | | { |
| 0 | 192 | | cmd.ReleaseTemporaryRT(outlineTextureHandle.id); |
| | 193 | |
|
| 0 | 194 | | for (var index = 0; index < toDispose.Count; index++) { Object.Destroy(toDispose[index]); } |
| | 195 | |
|
| 0 | 196 | | toDispose.Clear(); |
| 0 | 197 | | } |
| | 198 | | } |
| | 199 | |
|
| | 200 | | public OutlineRenderersSO renderers; |
| | 201 | | private OutlinerRenderPass scriptablePass; |
| | 202 | |
|
| | 203 | | private RenderTargetHandle outlineTexture; |
| | 204 | |
|
| | 205 | | public override void Create() |
| | 206 | | { |
| 2 | 207 | | scriptablePass = new OutlinerRenderPass(renderers) |
| | 208 | | { |
| | 209 | | renderPassEvent = RenderPassEvent.AfterRenderingTransparents, |
| | 210 | | }; |
| | 211 | |
|
| 2 | 212 | | outlineTexture.Init("_OutlineTexture"); |
| 2 | 213 | | } |
| | 214 | |
|
| | 215 | | public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData) |
| | 216 | | { |
| 0 | 217 | | scriptablePass.Setup(renderingData.cameraData.cameraTargetDescriptor, outlineTexture); |
| 0 | 218 | | renderer.EnqueuePass(scriptablePass); |
| 0 | 219 | | } |
| | 220 | | } |