| | 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 | |
|
| | 67 | | //using (new ProfilingScope(cmd, new ProfilingSampler(PROFILER_TAG))) |
| | 68 | | { |
| 0 | 69 | | if (outlineRenderersSo != null) |
| | 70 | | { |
| | 71 | | //By now only outline avatars |
| | 72 | | //DrawRenderers(outlineRenderersSo?.renderers, renderingData.cameraData.camera.cullingMask, cmd); |
| 0 | 73 | | DrawAvatar(outlineRenderersSo.avatar, renderingData.cameraData.camera.cullingMask, cmd); |
| | 74 | | } |
| | 75 | |
|
| 0 | 76 | | cmd.SetGlobalTexture("_OutlineTexture", outlineTextureHandle.id); |
| | 77 | | } |
| | 78 | |
|
| 0 | 79 | | context.ExecuteCommandBuffer(cmd); |
| 0 | 80 | | CommandBufferPool.Release(cmd); |
| 0 | 81 | | } |
| | 82 | |
|
| | 83 | | private void DrawRenderers(List<(Renderer renderer, int meshCount)> renderers, int cameraCulling, CommandBuffer |
| | 84 | | { |
| 0 | 85 | | if (renderers == null) |
| 0 | 86 | | return; |
| | 87 | |
|
| 0 | 88 | | foreach ((Renderer renderer, int meshCount) in renderers) |
| | 89 | | { |
| | 90 | | //Ignore disabled renderers |
| 0 | 91 | | if (!renderer.gameObject.activeSelf || (cameraCulling & (1 << renderer.gameObject.layer)) == 0) |
| | 92 | | continue; |
| | 93 | |
|
| | 94 | | // We have to manually render all the submeshes of the selected objects. |
| 0 | 95 | | for (var i = 0; i < meshCount; i++) { cmd.DrawRenderer(renderer, material, i); } |
| | 96 | | } |
| 0 | 97 | | } |
| | 98 | |
|
| | 99 | | private void DrawAvatar((Renderer renderer, int meshCount, float avatarHeight) avatar, int cameraCulling, Comman |
| | 100 | | { |
| 0 | 101 | | if (avatar.renderer == null) |
| 0 | 102 | | return; |
| | 103 | |
|
| | 104 | | //Ignore disabled or culled by camera avatars |
| 0 | 105 | | if (!avatar.renderer.gameObject.activeSelf || (cameraCulling & (1 << avatar.renderer.gameObject.layer)) == 0 |
| 0 | 106 | | return; |
| | 107 | |
|
| 0 | 108 | | for (var i = 0; i < avatar.meshCount; i++) |
| | 109 | | { |
| 0 | 110 | | Material materialToUse = null; |
| | 111 | |
|
| | 112 | | // We use a GPU Skinning based material |
| 0 | 113 | | if (avatar.renderer.materials[i] != null) |
| | 114 | | { |
| | 115 | | //Enable it when we are capable of adding the pass into the Toon Shader |
| | 116 | | if (USE_BASE_MATERIAL) |
| | 117 | | { |
| | 118 | | int originalMaterialOutlinerPass = avatar.renderer.materials[i].FindPass("Outliner"); |
| | 119 | |
|
| | 120 | | if (originalMaterialOutlinerPass != -1) |
| | 121 | | { |
| | 122 | | //The material has a built in pass we can use |
| | 123 | | cmd.DrawRenderer(avatar.renderer, avatar.renderer.materials[i], i, originalMaterialOutlinerP |
| | 124 | | continue; |
| | 125 | | } |
| | 126 | | } |
| | 127 | |
|
| | 128 | | // We use the original material to copy the GPUSkinning values. |
| | 129 | | // We cannot use materialToUse.CopyPropertiesFromMaterial because there are non serialized uniforms |
| 0 | 130 | | materialToUse = new Material(gpuSkinningMaterial); |
| 0 | 131 | | toDispose.Add(materialToUse); |
| 0 | 132 | | CopyAvatarProperties(avatar.renderer.materials[i], materialToUse); |
| | 133 | | } |
| | 134 | | else // Fallback to the normal outliner without GPUSkinning |
| | 135 | | { |
| 0 | 136 | | materialToUse = material; |
| | 137 | | } |
| | 138 | |
|
| | 139 | | // We have to manually render all the submeshes of the selected objects. |
| 0 | 140 | | cmd.DrawRenderer(avatar.renderer, materialToUse, i); |
| | 141 | | } |
| 0 | 142 | | } |
| | 143 | |
|
| | 144 | | private void CopyAvatarProperties(Material source, Material target) |
| | 145 | | { |
| 0 | 146 | | target.SetMatrixArray(BIND_POSES, source.GetMatrixArray(BIND_POSES)); |
| 0 | 147 | | target.SetMatrix(RENDERER_WORLD_INVERSE, source.GetMatrix(RENDERER_WORLD_INVERSE)); |
| 0 | 148 | | target.SetMatrixArray(BONE_MATRICES, source.GetMatrixArray(BONE_MATRICES)); |
| | 149 | |
|
| 0 | 150 | | if (source.HasTexture(AVATAR_MAP1)) |
| 0 | 151 | | target.SetTexture(AVATAR_MAP1, source.GetTexture(AVATAR_MAP1)); |
| | 152 | |
|
| 0 | 153 | | if (source.HasTexture(AVATAR_MAP2)) |
| 0 | 154 | | target.SetTexture(AVATAR_MAP2, source.GetTexture(AVATAR_MAP2)); |
| | 155 | |
|
| 0 | 156 | | if (source.HasTexture(AVATAR_MAP3)) |
| 0 | 157 | | target.SetTexture(AVATAR_MAP3, source.GetTexture(AVATAR_MAP3)); |
| | 158 | |
|
| 0 | 159 | | if (source.HasTexture(AVATAR_MAP4)) |
| 0 | 160 | | target.SetTexture(AVATAR_MAP4, source.GetTexture(AVATAR_MAP4)); |
| | 161 | |
|
| 0 | 162 | | if (source.HasTexture(AVATAR_MAP5)) |
| 0 | 163 | | target.SetTexture(AVATAR_MAP5, source.GetTexture(AVATAR_MAP5)); |
| | 164 | |
|
| 0 | 165 | | if (source.HasTexture(AVATAR_MAP6)) |
| 0 | 166 | | target.SetTexture(AVATAR_MAP6, source.GetTexture(AVATAR_MAP6)); |
| | 167 | |
|
| 0 | 168 | | if (source.HasTexture(AVATAR_MAP7)) |
| 0 | 169 | | target.SetTexture(AVATAR_MAP7, source.GetTexture(AVATAR_MAP7)); |
| | 170 | |
|
| 0 | 171 | | if (source.HasTexture(AVATAR_MAP8)) |
| 0 | 172 | | target.SetTexture(AVATAR_MAP8, source.GetTexture(AVATAR_MAP8)); |
| | 173 | |
|
| 0 | 174 | | if (source.HasTexture(AVATAR_MAP9)) |
| 0 | 175 | | target.SetTexture(AVATAR_MAP9, source.GetTexture(AVATAR_MAP9)); |
| | 176 | |
|
| 0 | 177 | | if (source.HasTexture(AVATAR_MAP10)) |
| 0 | 178 | | target.SetTexture(AVATAR_MAP10, source.GetTexture(AVATAR_MAP10)); |
| | 179 | |
|
| 0 | 180 | | if (source.HasTexture(AVATAR_MAP11)) |
| 0 | 181 | | target.SetTexture(AVATAR_MAP11, source.GetTexture(AVATAR_MAP11)); |
| | 182 | |
|
| 0 | 183 | | if (source.HasTexture(AVATAR_MAP12)) |
| 0 | 184 | | target.SetTexture(AVATAR_MAP12, source.GetTexture(AVATAR_MAP12)); |
| 0 | 185 | | } |
| | 186 | |
|
| | 187 | | public override void FrameCleanup(CommandBuffer cmd) |
| | 188 | | { |
| 0 | 189 | | cmd.ReleaseTemporaryRT(outlineTextureHandle.id); |
| | 190 | |
|
| 0 | 191 | | for (var index = 0; index < toDispose.Count; index++) { Object.Destroy(toDispose[index]); } |
| | 192 | |
|
| 0 | 193 | | toDispose.Clear(); |
| 0 | 194 | | } |
| | 195 | | } |
| | 196 | |
|
| | 197 | | public OutlineRenderersSO renderers; |
| | 198 | | private OutlinerRenderPass scriptablePass; |
| | 199 | |
|
| | 200 | | private RenderTargetHandle outlineTexture; |
| | 201 | |
|
| | 202 | | public override void Create() |
| | 203 | | { |
| 2 | 204 | | scriptablePass = new OutlinerRenderPass(renderers) |
| | 205 | | { |
| | 206 | | renderPassEvent = RenderPassEvent.AfterRenderingTransparents, |
| | 207 | | }; |
| | 208 | |
|
| 2 | 209 | | outlineTexture.Init("_OutlineTexture"); |
| 2 | 210 | | } |
| | 211 | |
|
| | 212 | | public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData) |
| | 213 | | { |
| 0 | 214 | | scriptablePass.Setup(renderingData.cameraData.cameraTargetDescriptor, outlineTexture); |
| 0 | 215 | | renderer.EnqueuePass(scriptablePass); |
| 0 | 216 | | } |
| | 217 | | } |