using System.Collections.Generic; using UnityEngine; using UnityEngine.Rendering; using UnityEngine.Rendering.Universal; using UnityEngine.XR; namespace Golems { /// /// Helper methods for ScriptableRenderPasses /// public static class ScriptableRenderPassHelper { /// /// Helper method to create new Engine material with specified Shader /// public static void CreateMaterial(string shaderPath, out Material mat) { mat = default; if (TryGetShader(out var shader, shaderPath)) { mat = new Material(shader); } else { Debug.LogError($"Failed to load shader {shaderPath}"); } } /// /// Wrapper for Shader.Find /// /// True if Shader was found and loaded private static bool TryGetShader(out Shader shader, string path) { shader = default; shader = Shader.Find(path); return shader != null; } } public class AlbertGhostMemoryPass : ScriptableRenderPass { private const string k_ProfilerIdent = "GhostPass"; #region Ident Consts private const string k_AlbertGhostMemoryMaskTex = "_AlbertGhostMemoryMaskTex"; private const string k_AlbertGhostMemoryTex = "_AlbertGhostMemoryTex"; private const string k_AlbertGhostMemoryBlurredTex = "_AlbertGhostMemoryBlurredTex"; private const string k_TempTexGhost = "_TempTexGhost"; private const string k_MainText = "_MainTex"; private const string k_PerGhostAlphaProperty = "_PerGhostAlpha"; #endregion Ident Consts #region Render Texture IDs private readonly int m_MaskRenderTexID = Shader.PropertyToID(k_AlbertGhostMemoryMaskTex); private readonly int m_MemoryRenderTexID = Shader.PropertyToID(k_AlbertGhostMemoryTex); private readonly int m_BlurPassRenderTexID = Shader.PropertyToID(k_AlbertGhostMemoryBlurredTex); private readonly int m_TempRenderTexID = Shader.PropertyToID(k_TempTexGhost); #endregion Render Texture IDs #region Shader Properties private readonly int k_PerGhostAlphaPropertyId = Shader.PropertyToID(k_PerGhostAlphaProperty); private readonly int m_LightDirectionID = Shader.PropertyToID("_LightDirection"); private readonly int m_LightColourID = Shader.PropertyToID("_LightColour"); private readonly int m_BlurResolution = Shader.PropertyToID("_BlurResolution"); private readonly int m_BlurRadius = Shader.PropertyToID("_BlurRadius"); private readonly int m_BlurDirectionX = Shader.PropertyToID("_BlurDirectionX"); private readonly int m_BlurDirectionY = Shader.PropertyToID("_BlurDirectionY"); #endregion private RenderTargetIdentifier m_CameraColorTargetIdent; /// /// All the actively registered GhostObjectBehaviours /// Passed down via GhostObject Collection /// private IList m_GhostObjectBehaviours; private MaterialPropertyBlock m_MaterialPropertyBlock = new MaterialPropertyBlock(); private AlbertGhostMemoryCompositeSettings m_AlbertGhostMemoryCompositeSettings; private Material m_MemoryMaterial; private Material m_BlurMaterial; private Camera m_Camera; private Vector3 m_LightDirection = new Vector3(1.0f, 1.0f, 0.0f); private Color m_LightColour = Color.white; /// /// The uniform Blur texture size, calculated from the current screen size /// private int m_BlurRes; /// /// Effects how far off centre each Blur sample becomes /// private float m_BlurRad = 1.5f; /// /// The number of Blur passes performed. /// This effects ALL objects rendered /// #if UNITY_SWITCH private int m_BlurPasses = 0; #else private int m_BlurPasses = 4; #endif //private GameSettings m_GameSettings; /// /// Used in DrawObjectsXYZ. We use this in conjunction with GetSharedMaterials. /// Avoids GC alloc from .sharedMaterials /// private readonly List m_SharedMaterials = new List(); #if UNITY_EDITOR private readonly HashSet m_EditorLoggedNullMaterialRenderers = new HashSet(); #endif public bool HasSettings { get; private set; } public AlbertGhostMemoryPass(RenderPassEvent whenToInsert, Material memoryMaterial, Material blurMaterial) { renderPassEvent = whenToInsert; // // NOTE: the materials are passed in instead of being loaded to avoid the shader not // being available via Shader.Find() when the shader has changed and Unity first opens. // m_MemoryMaterial = memoryMaterial; //TODO TOBY: replace with one found on the object / the test fake TestFakeAlvertGhostMemoryCmdShader m_BlurMaterial = blurMaterial; // grab the game setting for blur passes and use it here. //Disabled for the sample //GameSettings.GetSlowStartSystem(HandleGameSettings, out m_GameSettings, true); } /* private void HandleGameSettings(GameSettings system, SlowSystemState state) { m_GameSettings = system; if (system == null) return; if (!system.TryGetValue(GameSettingsKeys.k_NumGhostBlurPasses, out int value)) return; m_BlurPasses = value; m_GameSettings.AddListenOnGameSettingChange(GameSettingsKeys.k_NumGhostBlurPasses, OnSettingChanged, true); } */ void OnSettingChanged( int value) { m_BlurPasses = value; } public void SetSettings(AlbertGhostMemorySettings settings, AlbertGhostMemoryCompositeSettings albertGhostMemoryCompositeSettings) { m_LightColour = settings.LightColour; m_LightDirection = settings.LightDirection; m_BlurRad = settings.BlurRadius; m_BlurRes = settings.BlurTextureResolution; m_AlbertGhostMemoryCompositeSettings = albertGhostMemoryCompositeSettings; HasSettings = true; } #region ScriptableRenderPass public override void Configure(CommandBuffer cmd, RenderTextureDescriptor cameraTextureDescriptor) { if (!HasSettings) { return; } // // Attempt to make RTs for XR, otherwise use Default Settings // if (!ConfigureTemporaryRenderTexturesForXR(in cmd)) ConfigureTemporaryRenderTextures(in cmd); } public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData) { if (!HasSettings) { return; } // // Reset the number of objects renderer, this is a new pass // GhostObjectCollection.Instance.NumberOfObjectsRendered = 0; var cmd = CommandBufferPool.Get(k_ProfilerIdent); cmd.Clear(); SetAllShaderParameters(in cmd); DrawObjectsDefault(in cmd); // // Only bother we the rest of the process if we actually // rendered anything using default materials // if (GhostObjectCollection.Instance.NumberOfObjectsRendered > 0) { DrawObjectsMemory(in cmd); BlurMask(in cmd); // make sure to set the render target back to the main camera! CoreUtils.SetRenderTarget(cmd, m_CameraColorTargetIdent); context.ExecuteCommandBuffer(cmd); } //cmd.Clear(); CommandBufferPool.Release(cmd); } public override void FrameCleanup(CommandBuffer cmd) { if (!HasSettings) { return; } cmd.ReleaseTemporaryRT(m_MemoryRenderTexID); cmd.ReleaseTemporaryRT(m_MaskRenderTexID); cmd.ReleaseTemporaryRT(m_BlurPassRenderTexID); } #endregion public void Setup(Camera camera, RenderTargetIdentifier cameraColorTargetIdent, List ghostObjectBehaviours) { m_Camera = camera; m_CameraColorTargetIdent = cameraColorTargetIdent; m_GhostObjectBehaviours = ghostObjectBehaviours; } /// /// Sets Global and local variables used /// across various shaders in the process /// private void SetAllShaderParameters(in CommandBuffer commandBuffer) { commandBuffer.SetGlobalVector(m_LightDirectionID, m_LightDirection); commandBuffer.SetGlobalColor(m_LightColourID, m_LightColour); commandBuffer.SetGlobalFloat(m_BlurResolution, m_BlurRes); commandBuffer.SetGlobalFloat(m_BlurRadius, m_BlurRad); } /// /// Draw all GhostObjects either with /// Default materials or with the Ghost Material /// private void DrawAllObjects(in CommandBuffer commandBuffer, bool useMemoryMaterial = false) { if (m_GhostObjectBehaviours == null) return; for (int i = 0, counti = m_GhostObjectBehaviours.Count; i < counti; ++i) { var ghostObjectBehaviour = m_GhostObjectBehaviours[i]; if (ghostObjectBehaviour == null) { continue; } var ghostObject = ghostObjectBehaviour.GhostObject; if (ghostObject == null) { continue; } var renderers = ghostObject.Renderers; if (renderers == null) { continue; } var alpha = ghostObjectBehaviour.Alpha; Debug.Log("Alpha Ghost:" + alpha); if (!ghostObjectBehaviour.IgnoreGlobalAlpha) { alpha *= m_AlbertGhostMemoryCompositeSettings.Alpha; } // // No need to render anything if the Alpha for this GhostObject is 0. Although the final composite will not // show anything we're wasting Draw calls drawing the Lit and Unlit(mask) versions of the Ghost Object Behaviours // if (alpha <= 0.0f) continue; // // Let everything know we're rendering something this Ghost pass // ++GhostObjectCollection.Instance.NumberOfObjectsRendered; if (ghostObjectBehaviour.ReplacementMemoryMaterials != null) { DrawGhostRenderer(commandBuffer, renderers, alpha, useMemoryMaterial, ghostObjectBehaviour.ReplacementMemoryMaterials); } else { DrawGhostRenderer(commandBuffer, renderers, alpha, useMemoryMaterial); } } } private void DrawGhostRenderer(CommandBuffer commandBuffer, Renderer[] renderers, float alpha, bool useMemoryMaterial, IReadOnlyList replacementMemoryMaterials = null) { Debug.Log("Ghost Renderer " + renderers.Length); for (int j = 0, countj = renderers.Length; j < countj; ++j) { var renderer = renderers[j]; if (renderer == null) continue; m_MaterialPropertyBlock.SetFloat(k_PerGhostAlphaPropertyId, alpha); renderer.SetPropertyBlock(m_MaterialPropertyBlock); m_SharedMaterials.Clear(); renderer.GetSharedMaterials(m_SharedMaterials); if (m_SharedMaterials.Count > 0) { //for (int m = 0, countm = materials.Length; m < countm; ++m) for (int m = m_SharedMaterials.Count -1, countm = 0; m >= countm; --m) { if (useMemoryMaterial) { if (replacementMemoryMaterials != null && replacementMemoryMaterials.Count > 0 && replacementMemoryMaterials[m] != null) { commandBuffer.DrawRenderer(renderer, replacementMemoryMaterials[m], m, 0); } else { commandBuffer.DrawRenderer(renderer, m_MemoryMaterial, m, 0); } } else { if (m_SharedMaterials[m] == null) { #if UNITY_EDITOR if (!m_EditorLoggedNullMaterialRenderers.Contains(renderer)) { Debug.LogErrorFormat(renderer, "AlbertGhostMemory: renderer {0} with null material", renderer.name); m_EditorLoggedNullMaterialRenderers.Add(renderer); } #endif continue; } commandBuffer.DrawRenderer(renderer, m_SharedMaterials[m], m, 0); } } } else { if (replacementMemoryMaterials != null && replacementMemoryMaterials.Count > 0 && replacementMemoryMaterials[0] != null) { commandBuffer.DrawRenderer(renderer, useMemoryMaterial ? replacementMemoryMaterials[0] : renderer.sharedMaterial); } else { commandBuffer.DrawRenderer(renderer, useMemoryMaterial ? m_MemoryMaterial : renderer.sharedMaterial); } } } } /// /// Draw all GhostObjects using their default material/s /// private void DrawObjectsDefault(in CommandBuffer commandBuffer) { CoreUtils.SetRenderTarget(commandBuffer, m_MemoryRenderTexID, ClearFlag.All, Color.clear); commandBuffer.ClearRenderTarget(true, true, Color.clear); DrawAllObjects(in commandBuffer); } /// /// Draw all GhostObjects using the Ghost/Memory material /// private void DrawObjectsMemory(in CommandBuffer commandBuffer) { CoreUtils.SetRenderTarget(commandBuffer, m_MaskRenderTexID, ClearFlag.All, Color.clear); commandBuffer.ClearRenderTarget(true, true, Color.clear); DrawAllObjects(in commandBuffer, true); } /// /// Creates the Blur mask RT. This Blurs all the GhostObjects /// private void BlurMask(in CommandBuffer commandBuffer) { commandBuffer.Blit(m_MaskRenderTexID, m_BlurPassRenderTexID); for (int i = 0; i < m_BlurPasses; ++i) { commandBuffer.SetGlobalFloat(m_BlurDirectionX, 0); commandBuffer.SetGlobalFloat(m_BlurDirectionY, 1); commandBuffer.SetGlobalTexture(k_MainText, m_BlurPassRenderTexID); commandBuffer.Blit(m_BlurPassRenderTexID, m_TempRenderTexID, m_BlurMaterial, 0); commandBuffer.SetGlobalFloat(m_BlurDirectionX, 1); commandBuffer.SetGlobalFloat(m_BlurDirectionY, 0); commandBuffer.SetGlobalTexture(k_MainText, m_TempRenderTexID); commandBuffer.Blit(m_TempRenderTexID, m_BlurPassRenderTexID, m_BlurMaterial, 0); } } /// /// Configure Temporary RTs for XR /// Relies on ENABLE_VR /// /// SRF CommandBuffer /// True if RTs were setup using XRSettings, Otherwise False. private bool ConfigureTemporaryRenderTexturesForXR(in CommandBuffer cmd) { #if ENABLE_VR if (!XRSettings.enabled) return false; var renderTextureDescriptor = XRSettings.eyeTextureDesc; cmd.GetTemporaryRT(m_MemoryRenderTexID, renderTextureDescriptor, FilterMode.Bilinear); cmd.GetTemporaryRT(m_MaskRenderTexID, renderTextureDescriptor, FilterMode.Bilinear); renderTextureDescriptor.depthBufferBits = 0; renderTextureDescriptor.width = renderTextureDescriptor.height = m_BlurRes; cmd.GetTemporaryRT(m_BlurPassRenderTexID, renderTextureDescriptor, FilterMode.Bilinear); cmd.GetTemporaryRT(m_TempRenderTexID, renderTextureDescriptor, FilterMode.Bilinear); return true; #endif return false; } /// /// Configure Temporary RTs for SRF. /// /// SRF CommandBuffer private void ConfigureTemporaryRenderTextures(in CommandBuffer cmd) { #if UNITY_SWITCH var width = 1280; var height = 720; #else var width = m_Camera.scaledPixelWidth; var height = m_Camera.scaledPixelHeight; #endif var aa = Mathf.Max(1, QualitySettings.antiAliasing); cmd.GetTemporaryRT(m_MemoryRenderTexID, width, height, 24, FilterMode.Bilinear, RenderTextureFormat.ARGB32, RenderTextureReadWrite.Default, aa); cmd.GetTemporaryRT(m_MaskRenderTexID, width >> 1, height >> 1, 0, FilterMode.Bilinear, RenderTextureFormat.ARGB32, RenderTextureReadWrite.Default, aa); cmd.GetTemporaryRT(m_BlurPassRenderTexID, m_BlurRes, m_BlurRes, 0, FilterMode.Bilinear); cmd.GetTemporaryRT(m_TempRenderTexID, m_BlurRes, m_BlurRes, 0, FilterMode.Bilinear); } } }