forked from BilalY/Rasagar
1059 lines
37 KiB
C#
1059 lines
37 KiB
C#
using System.Collections.Generic;
|
|
using System.Reflection;
|
|
using UnityEngine;
|
|
using UnityEngine.Rendering;
|
|
using UnityEngine.Rendering.HighDefinition;
|
|
using UnityEngine.Rendering.HighDefinition.Attributes;
|
|
using UnityEngine.Video;
|
|
|
|
using UnityEngine.Experimental.Rendering;
|
|
|
|
namespace UnityEngine.Rendering.HighDefinition.Compositor
|
|
{
|
|
// The main entry point for the compositing operations. Manages the list of layers, output displays, etc.
|
|
[AddComponentMenu("")] // Hide.
|
|
[ExecuteAlways]
|
|
internal class CompositionManager : MonoBehaviour
|
|
{
|
|
public enum OutputDisplay
|
|
{
|
|
Display1 = 0,
|
|
Display2,
|
|
Display3,
|
|
Display4,
|
|
Display5,
|
|
Display6,
|
|
Display7,
|
|
Display8
|
|
}
|
|
|
|
public enum AlphaChannelSupport
|
|
{
|
|
None = 0,
|
|
Rendering,
|
|
RenderingAndPostProcessing
|
|
}
|
|
|
|
[SerializeField]
|
|
bool m_Enable = true;
|
|
|
|
public bool enableInternal { get { return m_Enable; } set { m_Enable = value; } }
|
|
|
|
[SerializeField] Material m_Material;
|
|
|
|
[SerializeField] OutputDisplay m_OutputDisplay = OutputDisplay.Display1;
|
|
|
|
public List<CompositorLayer> layers => m_InputLayers;
|
|
|
|
[SerializeField] List<CompositorLayer> m_InputLayers = new List<CompositorLayer>();
|
|
|
|
public AlphaChannelSupport alphaSupport => m_AlphaSupport;
|
|
|
|
internal AlphaChannelSupport m_AlphaSupport = AlphaChannelSupport.RenderingAndPostProcessing;
|
|
|
|
internal float timeSinceLastRepaint;
|
|
|
|
public bool enableOutput
|
|
{
|
|
get
|
|
{
|
|
if (m_OutputCamera)
|
|
{
|
|
return m_OutputCamera.enabled;
|
|
}
|
|
return false;
|
|
}
|
|
set
|
|
{
|
|
if (m_OutputCamera)
|
|
{
|
|
// If the state did not change, don't do anything
|
|
if (m_OutputCamera.enabled == value)
|
|
return;
|
|
|
|
m_OutputCamera.enabled = value;
|
|
|
|
// Aside from the output compositor camera, we also have to change the cameras of the layers
|
|
foreach (var layer in m_InputLayers)
|
|
{
|
|
if (layer.camera && layer.isUsingACameraClone)
|
|
{
|
|
layer.camera.enabled = value;
|
|
}
|
|
else
|
|
{
|
|
// The target texture was managed by the compositor, reset it so the user can see the camera output
|
|
if (layer.camera && value == false)
|
|
layer.camera.targetTexture = null;
|
|
}
|
|
}
|
|
|
|
// Toggle the compositor-related custom passes
|
|
if (value)
|
|
{
|
|
RegisterCustomPasses();
|
|
}
|
|
else
|
|
{
|
|
UnRegisterCustomPasses();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public int numLayers => m_InputLayers.Count;
|
|
|
|
public Shader shader
|
|
{
|
|
get => m_Shader;
|
|
set
|
|
{
|
|
m_Shader = value;
|
|
}
|
|
}
|
|
|
|
[SerializeField] Shader m_Shader;
|
|
|
|
public CompositionProfile profile
|
|
{
|
|
get => m_CompositionProfile;
|
|
set => m_CompositionProfile = value;
|
|
}
|
|
|
|
[HideInInspector, SerializeField] CompositionProfile m_CompositionProfile;
|
|
public Camera outputCamera
|
|
{
|
|
get => m_OutputCamera;
|
|
set => m_OutputCamera = value;
|
|
}
|
|
|
|
[SerializeField] Camera m_OutputCamera;
|
|
|
|
public float aspectRatio
|
|
{
|
|
get
|
|
{
|
|
if (m_OutputCamera)
|
|
{
|
|
return (float)m_OutputCamera.pixelWidth / m_OutputCamera.pixelHeight;
|
|
}
|
|
return 1.0f;
|
|
}
|
|
}
|
|
|
|
public bool shaderPropertiesAreDirty
|
|
{
|
|
set
|
|
{
|
|
m_ShaderPropertiesAreDirty = true;
|
|
}
|
|
}
|
|
|
|
internal bool m_ShaderPropertiesAreDirty = false;
|
|
|
|
internal Matrix4x4 m_ViewProjMatrix;
|
|
internal Matrix4x4 m_ViewProjMatrixFlipped;
|
|
internal GameObject m_CompositorGameObject;
|
|
internal MaterialPropertyBlock fullscreenProperties;
|
|
|
|
ShaderVariablesGlobal m_ShaderVariablesGlobalCB = new ShaderVariablesGlobal();
|
|
|
|
int m_RecorderTempRT = Shader.PropertyToID("TempRecorder");
|
|
|
|
// Built-in Color.black has an alpha of 1, so defien here a fully transparent black
|
|
static Color s_TransparentBlack = new Color(0, 0, 0, 0);
|
|
|
|
#region Validation
|
|
public bool ValidateLayerListOrder(int oldIndex, int newIndex)
|
|
{
|
|
if (m_InputLayers.Count > 1)
|
|
{
|
|
if (m_InputLayers[0].outputTarget == CompositorLayer.OutputTarget.CameraStack)
|
|
{
|
|
var tmp = m_InputLayers[newIndex];
|
|
m_InputLayers.RemoveAt(newIndex);
|
|
m_InputLayers.Insert(oldIndex, tmp);
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
public bool RuntimeCheck()
|
|
{
|
|
for (int i = 0; i < m_InputLayers.Count; ++i)
|
|
{
|
|
if (!m_InputLayers[i].Validate())
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Validates the rendering pipeline and fixes potential issues
|
|
bool ValidatePipeline()
|
|
{
|
|
var hdPipeline = RenderPipelineManager.currentPipeline as HDRenderPipeline;
|
|
if (hdPipeline != null)
|
|
{
|
|
m_AlphaSupport = AlphaChannelSupport.RenderingAndPostProcessing;
|
|
if (hdPipeline.GetColorBufferFormat() == (GraphicsFormat)RenderPipelineSettings.ColorBufferFormat.R11G11B10)
|
|
{
|
|
m_AlphaSupport = AlphaChannelSupport.None;
|
|
}
|
|
else if (hdPipeline.GetColorBufferFormat() == (GraphicsFormat)PostProcessBufferFormat.R11G11B10)
|
|
{
|
|
m_AlphaSupport = AlphaChannelSupport.Rendering;
|
|
}
|
|
|
|
RegisterCustomPasses();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool ValidateCompositionShader()
|
|
{
|
|
if (m_Shader == null)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (m_CompositionProfile == null)
|
|
{
|
|
Debug.Log("A composition profile was not found. Set the composition graph from the Compositor window to create one.");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool ValidateProfile()
|
|
{
|
|
if (m_CompositionProfile)
|
|
{
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
Debug.LogError("No composition profile was found! Use the compositor tool to create one.");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool ValidateMainCompositorCamera()
|
|
{
|
|
if (m_OutputCamera == null)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (!m_OutputCamera.TryGetComponent<HDAdditionalCameraData>(out var cameraData))
|
|
{
|
|
m_OutputCamera.gameObject.AddComponent(typeof(HDAdditionalCameraData));
|
|
cameraData = m_OutputCamera.GetComponent<HDAdditionalCameraData>();
|
|
}
|
|
|
|
// Setup custom rendering (we don't want HDRP to compute anything in this camera)
|
|
if (cameraData)
|
|
{
|
|
cameraData.customRender += CustomRender;
|
|
}
|
|
else
|
|
{
|
|
Debug.Log("Null additional data in compositor output");
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool ValidateAndFixRuntime()
|
|
{
|
|
if (m_OutputCamera == null)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (m_Shader == null)
|
|
{
|
|
m_InputLayers.Clear();
|
|
m_CompositionProfile = null;
|
|
return false;
|
|
}
|
|
|
|
if (m_CompositionProfile == null)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (m_Material == null)
|
|
{
|
|
SetupCompositionMaterial();
|
|
}
|
|
|
|
var cameraData = m_OutputCamera.GetComponent<HDAdditionalCameraData>();
|
|
if (cameraData && !cameraData.hasCustomRender)
|
|
{
|
|
cameraData.customRender += CustomRender;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
#endregion
|
|
|
|
// This is called when we change camera, to remove the custom draw callback from the old camera before we set the new one
|
|
public void DropCompositorCamera()
|
|
{
|
|
if (m_OutputCamera)
|
|
{
|
|
var cameraData = m_OutputCamera.GetComponent<HDAdditionalCameraData>();
|
|
if (cameraData && cameraData.hasCustomRender)
|
|
{
|
|
cameraData.customRender -= CustomRender;
|
|
}
|
|
}
|
|
}
|
|
|
|
public void Init()
|
|
{
|
|
if (ValidateCompositionShader() && ValidateProfile() && ValidateMainCompositorCamera())
|
|
{
|
|
UpdateDisplayNumber();
|
|
|
|
SetupCompositionMaterial();
|
|
|
|
SetupCompositorLayers(false);
|
|
|
|
SetupGlobalCompositorVolume();
|
|
|
|
SetupCompositorConstants();
|
|
|
|
SetupLayerPriorities();
|
|
}
|
|
else
|
|
{
|
|
Debug.LogError("The compositor was disabled due to a validation error in the configuration.");
|
|
enableInternal = false;
|
|
}
|
|
}
|
|
|
|
// Start is called before the first frame update
|
|
void Start()
|
|
{
|
|
Init();
|
|
}
|
|
|
|
void OnValidate()
|
|
{
|
|
if (shader == null)
|
|
{
|
|
m_InputLayers.Clear();
|
|
m_CompositionProfile = null;
|
|
}
|
|
}
|
|
|
|
public void OnEnable()
|
|
{
|
|
enableOutput = true;
|
|
s_CompositorInstance = null;
|
|
#if UNITY_EDITOR
|
|
//This is a work-around, to make edit and continue work when editing source code
|
|
UnityEditor.AssemblyReloadEvents.afterAssemblyReload += OnAfterAssemblyReload;
|
|
#endif
|
|
RenderPipelineManager.beginContextRendering += ResizeCallback;
|
|
}
|
|
|
|
public void DeleteLayerRTs()
|
|
{
|
|
// Delete all layer cameras first, and then the Render Targets (to avoid deleting RT that are still in use)
|
|
for (int i = m_InputLayers.Count - 1; i >= 0; --i)
|
|
{
|
|
m_InputLayers[i].DestroyCameras();
|
|
}
|
|
|
|
for (int i = m_InputLayers.Count - 1; i >= 0; --i)
|
|
{
|
|
m_InputLayers[i].DestroyRT();
|
|
}
|
|
}
|
|
|
|
public bool IsOutputLayer(int layerID)
|
|
{
|
|
if (layerID >= 0 && layerID < m_InputLayers.Count)
|
|
{
|
|
if (m_InputLayers[layerID].outputTarget == CompositorLayer.OutputTarget.CameraStack)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
public void UpdateDisplayNumber()
|
|
{
|
|
if (m_OutputCamera)
|
|
{
|
|
m_OutputCamera.targetDisplay = (int)m_OutputDisplay;
|
|
}
|
|
}
|
|
|
|
void SetupCompositorLayers(bool allowUndo = true)
|
|
{
|
|
for (int i = 0; i < m_InputLayers.Count; ++i)
|
|
{
|
|
m_InputLayers[i].Init($"Layer{i}", allowUndo);
|
|
}
|
|
|
|
SetLayerRenderTargets();
|
|
}
|
|
|
|
public void SetNewCompositionShader()
|
|
{
|
|
// When we load a new shader, we need to clear the serialized material.
|
|
m_Material = null;
|
|
SetupCompositionMaterial();
|
|
}
|
|
|
|
public void SetupCompositionMaterial()
|
|
{
|
|
// Create the composition material
|
|
if (m_Shader)
|
|
{
|
|
m_Material = new Material(m_Shader);
|
|
|
|
m_CompositionProfile.AddPropertiesFromShaderAndMaterial(this, m_Shader, m_Material);
|
|
|
|
// [case 1265631] The profile asset is auto-generated by the compositor, so do not allow the users to manually edit/reset the values in the asset because it might break things
|
|
m_CompositionProfile.hideFlags = HideFlags.NotEditable;
|
|
}
|
|
else
|
|
{
|
|
m_CompositionProfile = null;
|
|
m_Material = null;
|
|
}
|
|
}
|
|
|
|
public void SetupLayerPriorities()
|
|
{
|
|
int count = 0;
|
|
foreach (var layer in m_InputLayers)
|
|
{
|
|
// Set camera priority (camera's at the beginning of the list should be rendered first)
|
|
layer.SetPriotiry(count * 1.0f);
|
|
count++;
|
|
}
|
|
}
|
|
|
|
public void OnAfterAssemblyReload()
|
|
{
|
|
// Bug? : After assembly reload, the customRender callback is dropped, so set it again
|
|
if (m_OutputCamera)
|
|
{
|
|
var cameraData = m_OutputCamera.GetComponent<HDAdditionalCameraData>();
|
|
if (cameraData && !cameraData.hasCustomRender)
|
|
{
|
|
cameraData.customRender += CustomRender;
|
|
}
|
|
}
|
|
}
|
|
|
|
public void OnDisable()
|
|
{
|
|
enableOutput = false;
|
|
}
|
|
|
|
static string s_CompositorGlobalVolumeName = "__Internal_Global_Composition_Volume";
|
|
|
|
// Setup a global volume used for chroma keying, alpha injection etc
|
|
void SetupGlobalCompositorVolume()
|
|
{
|
|
var compositorVolumes = Resources.FindObjectsOfTypeAll(typeof(CustomPassVolume));
|
|
|
|
// Instead of using one volume per layer/camera, we setup one global volume and we store the data in the camera
|
|
// This way the compositor has to use only one layer/volume for N cameras (instead of N).
|
|
m_CompositorGameObject = new GameObject(s_CompositorGlobalVolumeName) { hideFlags = HideFlags.HideAndDontSave };
|
|
Volume globalPPVolume = m_CompositorGameObject.AddComponent<Volume>();
|
|
globalPPVolume.gameObject.layer = 31;
|
|
AlphaInjection injectAlphaNode = globalPPVolume.profile.Add<AlphaInjection>();
|
|
ChromaKeying chromaKeyingPass = globalPPVolume.profile.Add<ChromaKeying>();
|
|
chromaKeyingPass.activate.Override(true);
|
|
|
|
// Custom pass for "Clear to Texture"
|
|
CustomPassVolume globalCustomPassVolume = m_CompositorGameObject.AddComponent<CustomPassVolume>();
|
|
globalCustomPassVolume.injectionPoint = CustomPassInjectionPoint.BeforeRendering;
|
|
globalCustomPassVolume.AddPassOfType(typeof(CustomClear));
|
|
}
|
|
|
|
void SetupCompositorConstants()
|
|
{
|
|
m_ViewProjMatrix = Matrix4x4.Scale(new Vector3(2.0f, 2.0f, 0.0f)) * Matrix4x4.Translate(new Vector3(-0.5f, -0.5f, 0.0f));
|
|
m_ViewProjMatrixFlipped = Matrix4x4.Scale(new Vector3(2.0f, -2.0f, 0.0f)) * Matrix4x4.Translate(new Vector3(-0.5f, -0.5f, 0.0f));
|
|
}
|
|
|
|
public void UpdateLayerSetup()
|
|
{
|
|
SetupCompositorLayers();
|
|
SetupLayerPriorities();
|
|
}
|
|
|
|
static HDRenderPipelineAsset m_CurrentAsset;
|
|
|
|
// LateUpdate is called once per frame
|
|
void LateUpdate()
|
|
{
|
|
// TODO: move all validation calls to onValidate. Before doing it, this needs some extra testing to ensure nothing breaks
|
|
if (enableOutput == false || ValidatePipeline() == false || ValidateAndFixRuntime() == false || RuntimeCheck() == false)
|
|
{
|
|
return;
|
|
}
|
|
|
|
UpdateDisplayNumber();
|
|
|
|
#if UNITY_EDITOR
|
|
if (m_ShaderPropertiesAreDirty)
|
|
{
|
|
SetNewCompositionShader();
|
|
m_ShaderPropertiesAreDirty = false;
|
|
SetupCompositorLayers();//< required to allocate RT for the new layers
|
|
}
|
|
#endif
|
|
if (m_CompositionProfile)
|
|
{
|
|
foreach (var layer in m_InputLayers)
|
|
{
|
|
layer.Update();
|
|
}
|
|
SetLayerRenderTargets();
|
|
}
|
|
}
|
|
|
|
void OnDestroy()
|
|
{
|
|
DeleteLayerRTs();
|
|
|
|
if (m_CompositorGameObject != null)
|
|
{
|
|
CoreUtils.Destroy(m_CompositorGameObject);
|
|
m_CompositorGameObject = null;
|
|
}
|
|
|
|
var compositorVolumes = Resources.FindObjectsOfTypeAll(typeof(CustomPassVolume));
|
|
foreach (CustomPassVolume volume in compositorVolumes)
|
|
{
|
|
if (volume.name == "Global Composition Volume" && volume.injectionPoint == CustomPassInjectionPoint.BeforeRendering)
|
|
{
|
|
CoreUtils.Destroy(volume);
|
|
}
|
|
}
|
|
|
|
// We don't need the custom passes anymore
|
|
UnRegisterCustomPasses();
|
|
|
|
// By now the s_CompositorManagedCameras should be empty, but clear it just to be safe
|
|
CompositorCameraRegistry.GetInstance().CleanUpCameraOrphans();
|
|
|
|
RenderPipelineManager.beginContextRendering -= ResizeCallback;
|
|
}
|
|
|
|
public void AddInputFilterAtLayer(CompositionFilter filter, int index)
|
|
{
|
|
m_InputLayers[index].AddInputFilter(filter);
|
|
}
|
|
|
|
int GetBaseLayerForSubLayerAtIndex(int index)
|
|
{
|
|
int baseIndex = 0;
|
|
index = (index > m_InputLayers.Count - 1) ? m_InputLayers.Count - 1 : index;
|
|
for (int i = index; i >= 0; --i)
|
|
{
|
|
if (m_InputLayers[i].outputTarget == CompositorLayer.OutputTarget.CompositorLayer)
|
|
{
|
|
baseIndex = i;
|
|
break;
|
|
}
|
|
}
|
|
return baseIndex;
|
|
}
|
|
|
|
static string GetSubLayerName(int count)
|
|
{
|
|
if (count == 0)
|
|
{
|
|
return "New SubLayer";
|
|
}
|
|
else
|
|
{
|
|
return $"New SubLayer ({count + 1})";
|
|
}
|
|
}
|
|
|
|
public string GetNewSubLayerName(int index, CompositorLayer.LayerType type = CompositorLayer.LayerType.Camera)
|
|
{
|
|
// First find the base layer
|
|
int baseIndex = GetBaseLayerForSubLayerAtIndex(index - 1);
|
|
|
|
// Get a candidate name and check if it already exists
|
|
int count = 0;
|
|
string candidateName = GetSubLayerName(count);
|
|
int i = baseIndex + 1;
|
|
while (i < m_InputLayers.Count && m_InputLayers[i].outputTarget != CompositorLayer.OutputTarget.CompositorLayer)
|
|
{
|
|
if (m_InputLayers[i].name == candidateName)
|
|
{
|
|
// If this candidate name exists, get the next one and start again
|
|
candidateName = GetSubLayerName(++count);
|
|
i = baseIndex + 1;
|
|
}
|
|
else
|
|
{
|
|
++i;
|
|
}
|
|
}
|
|
|
|
return candidateName;
|
|
}
|
|
|
|
public void AddNewLayer(int index, CompositorLayer.LayerType type = CompositorLayer.LayerType.Camera)
|
|
{
|
|
var newLayer = CompositorLayer.CreateStackLayer(type, GetNewSubLayerName(index, type));
|
|
|
|
if (index >= 0 && index < m_InputLayers.Count)
|
|
{
|
|
m_InputLayers.Insert(index, newLayer);
|
|
}
|
|
else
|
|
{
|
|
m_InputLayers.Add(newLayer);
|
|
}
|
|
}
|
|
|
|
int GetNumChildrenForLayerAtIndex(int indx)
|
|
{
|
|
if (m_InputLayers[indx].outputTarget == CompositorLayer.OutputTarget.CameraStack)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
int num = 0;
|
|
for (int i = indx + 1; i < m_InputLayers.Count; ++i)
|
|
{
|
|
if (m_InputLayers[i].outputTarget == CompositorLayer.OutputTarget.CameraStack)
|
|
{
|
|
num++;
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
return num;
|
|
}
|
|
|
|
public void RemoveLayerAtIndex(int indx)
|
|
{
|
|
Debug.Assert(indx >= 0 && indx < m_InputLayers.Count);
|
|
|
|
int numChildren = GetNumChildrenForLayerAtIndex(indx);
|
|
for (int i = numChildren; i >= 0; --i)
|
|
{
|
|
m_InputLayers[indx + i].Destroy();
|
|
m_InputLayers.RemoveAt(indx + i);
|
|
}
|
|
}
|
|
|
|
public void SetLayerRenderTargets()
|
|
{
|
|
int layerPositionInStack = 0;
|
|
CompositorLayer lastLayer = null;
|
|
for (int i = 0; i < m_InputLayers.Count; ++i)
|
|
{
|
|
if (m_InputLayers[i].outputTarget != CompositorLayer.OutputTarget.CameraStack)
|
|
{
|
|
lastLayer = m_InputLayers[i];
|
|
|
|
// [case 1265061] If this layer does not have any cameras that will clear/draw the background, set a flag so the compositor will clear it explicitly.
|
|
m_InputLayers[i].clearsBackGround =
|
|
(i + 1 < m_InputLayers.Count) ? (m_InputLayers[i + 1].outputTarget == CompositorLayer.OutputTarget.CompositorLayer) : true;
|
|
}
|
|
|
|
if (m_InputLayers[i].outputTarget == CompositorLayer.OutputTarget.CameraStack && i > 0)
|
|
{
|
|
m_InputLayers[i].SetupLayerCamera(lastLayer, layerPositionInStack);
|
|
|
|
// Corner case: If the first layer in a camera stack was disabled, then it should still clear the color buffer
|
|
if (!m_InputLayers[i].enabled && layerPositionInStack == 0)
|
|
{
|
|
m_InputLayers[i].SetupClearColor();
|
|
}
|
|
layerPositionInStack++;
|
|
}
|
|
else
|
|
{
|
|
layerPositionInStack = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
public void ReorderChildren(int oldIndex, int newIndex)
|
|
{
|
|
if (m_InputLayers[newIndex].outputTarget == CompositorLayer.OutputTarget.CompositorLayer)
|
|
{
|
|
if (oldIndex > newIndex)
|
|
{
|
|
for (int i = 1; oldIndex + i < m_InputLayers.Count; ++i)
|
|
{
|
|
if (m_InputLayers[oldIndex + i].outputTarget == CompositorLayer.OutputTarget.CameraStack)
|
|
{
|
|
var tmp = m_InputLayers[oldIndex + i];
|
|
m_InputLayers.RemoveAt(oldIndex + i);
|
|
m_InputLayers.Insert(newIndex + i, tmp);
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
while (m_InputLayers[oldIndex].outputTarget == CompositorLayer.OutputTarget.CameraStack)
|
|
{
|
|
var tmp = m_InputLayers[oldIndex];
|
|
m_InputLayers.RemoveAt(oldIndex);
|
|
m_InputLayers.Insert(newIndex, tmp);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public RenderTexture GetRenderTarget(int indx)
|
|
{
|
|
if (indx >= 0 && indx < m_InputLayers.Count)
|
|
{
|
|
return m_InputLayers[indx].GetRenderTarget(true, true);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
public void Repaint()
|
|
{
|
|
for (int indx = 0; indx < m_InputLayers.Count; indx++)
|
|
{
|
|
if (m_InputLayers[indx].camera)
|
|
m_InputLayers[indx].camera.Render();
|
|
}
|
|
}
|
|
|
|
void ResizeCallback(ScriptableRenderContext cntx, List<Camera> cameras)
|
|
{
|
|
if (m_OutputCamera && enableOutput)
|
|
{
|
|
// Detect runtime resolution change
|
|
foreach (var layer in m_InputLayers)
|
|
{
|
|
if (!layer.ValidateRTSize(m_OutputCamera.pixelWidth, m_OutputCamera.pixelHeight))
|
|
{
|
|
for (int i = m_InputLayers.Count - 1; i >= 0; --i)
|
|
{
|
|
if (m_InputLayers[i].camera)
|
|
m_InputLayers[i].camera.targetTexture = null;
|
|
|
|
m_InputLayers[i].DestroyRT();
|
|
}
|
|
SetupCompositorLayers();
|
|
// After a resolution change, redraw the internal layer cameras.
|
|
InternalRender(cntx);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void InternalRender(ScriptableRenderContext cntx)
|
|
{
|
|
HDRenderPipeline renderPipeline = RenderPipelineManager.currentPipeline as HDRenderPipeline;
|
|
if (enableOutput && renderPipeline != null)
|
|
{
|
|
List<Camera> cameras = new List<Camera>(1);
|
|
foreach (var layer in m_InputLayers)
|
|
{
|
|
if (layer.camera && layer.camera.enabled)
|
|
{
|
|
cameras.Clear();
|
|
// Emit geometry manually for this camera (Unity will not do it for us because we call the internal render)
|
|
ScriptableRenderContext.EmitGeometryForCamera(layer.camera);
|
|
cameras.Add(layer.camera);
|
|
#if UNITY_2021_1_OR_NEWER
|
|
renderPipeline.InternalRender(cntx, cameras);
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CustomRender(ScriptableRenderContext context, HDCamera camera)
|
|
{
|
|
if (camera == null || camera.camera == null || m_Material == null || m_Shader == null)
|
|
{
|
|
// If something is wrong, don't keep the previous image (clear to black) to avoid confusion
|
|
var cmdbuff = CommandBufferPool.Get("Compositor Blit");
|
|
cmdbuff.ClearRenderTarget(false, true, Color.black);
|
|
return;
|
|
}
|
|
|
|
|
|
timeSinceLastRepaint = 0;
|
|
|
|
// set shader uniforms
|
|
m_CompositionProfile.CopyPropertiesToMaterial(m_Material);
|
|
|
|
int layerIndex = 0;
|
|
foreach (var layer in m_InputLayers)
|
|
{
|
|
if (layer.outputTarget != CompositorLayer.OutputTarget.CameraStack) // stacked cameras are not exposed as compositor layers
|
|
{
|
|
m_Material.SetTexture(layer.name, layer.GetRenderTarget(), RenderTextureSubElement.Color);
|
|
}
|
|
layerIndex++;
|
|
}
|
|
|
|
// Blit command
|
|
var cmd = CommandBufferPool.Get("Compositor Blit");
|
|
{
|
|
// fill the camera-related entries in the global constant buffer
|
|
// (Note: we later patch the position/_ViewProjMatrix values in order to perform a full screen blit with a SG Unlit material)
|
|
camera.UpdateShaderVariablesGlobalCB(ref m_ShaderVariablesGlobalCB, 0);
|
|
|
|
m_ShaderVariablesGlobalCB._WorldSpaceCameraPos_Internal = new Vector3(0.0f, 0.0f, 0.0f);
|
|
cmd.SetViewport(new Rect(0, 0, camera.camera.pixelWidth, camera.camera.pixelHeight));
|
|
cmd.ClearRenderTarget(true, false, Color.red);
|
|
|
|
foreach (var layer in m_InputLayers)
|
|
{
|
|
if (layer.clearsBackGround)
|
|
{
|
|
cmd.SetRenderTarget(layer.GetRenderTarget());
|
|
cmd.ClearRenderTarget(false, true, s_TransparentBlack);
|
|
}
|
|
}
|
|
}
|
|
|
|
int materialPass = m_Material.FindPass("DrawProcedural");
|
|
bool isFullscreen = materialPass != -1;
|
|
|
|
if (!isFullscreen)
|
|
materialPass = m_Material.FindPass("ForwardOnly");
|
|
|
|
if (camera.camera.targetTexture)
|
|
{
|
|
if (isFullscreen)
|
|
{
|
|
CoreUtils.DrawFullScreen(cmd, m_Material, camera.camera.targetTexture, shaderPassId: materialPass);
|
|
}
|
|
else
|
|
{
|
|
// When rendering to texture (or to camera bridge) we don't need to flip the image.
|
|
// If this matrix was used for the game view, then the image would appear flipped, hense the name of the variable.
|
|
m_ShaderVariablesGlobalCB._ViewProjMatrix = m_ViewProjMatrixFlipped;
|
|
ConstantBuffer.PushGlobal(cmd, m_ShaderVariablesGlobalCB, HDShaderIDs._ShaderVariablesGlobal);
|
|
cmd.Blit(null, camera.camera.targetTexture, m_Material, materialPass);
|
|
}
|
|
|
|
var recorderCaptureActions = CameraCaptureBridge.GetCaptureActions(camera.camera);
|
|
if (recorderCaptureActions != null)
|
|
{
|
|
for (recorderCaptureActions.Reset(); recorderCaptureActions.MoveNext();)
|
|
{
|
|
recorderCaptureActions.Current(camera.camera.targetTexture, cmd);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
var recorderCaptureActions = CameraCaptureBridge.GetCaptureActions(camera.camera);
|
|
|
|
if (recorderCaptureActions != null)
|
|
{
|
|
var format = m_InputLayers[0].GetRenderTarget().format;
|
|
cmd.GetTemporaryRT(m_RecorderTempRT, camera.camera.pixelWidth, camera.camera.pixelHeight, 0, FilterMode.Point, format);
|
|
|
|
if (isFullscreen)
|
|
{
|
|
CoreUtils.DrawFullScreen(cmd, m_Material, m_RecorderTempRT, shaderPassId: materialPass);
|
|
}
|
|
else
|
|
{
|
|
m_ShaderVariablesGlobalCB._ViewProjMatrix = m_ViewProjMatrixFlipped;
|
|
ConstantBuffer.PushGlobal(cmd, m_ShaderVariablesGlobalCB, HDShaderIDs._ShaderVariablesGlobal);
|
|
cmd.Blit(null, m_RecorderTempRT, m_Material, materialPass);
|
|
for (recorderCaptureActions.Reset(); recorderCaptureActions.MoveNext();)
|
|
{
|
|
recorderCaptureActions.Current(m_RecorderTempRT, cmd);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (isFullscreen)
|
|
{
|
|
if (fullscreenProperties == null)
|
|
fullscreenProperties = new MaterialPropertyBlock();
|
|
|
|
fullscreenProperties.SetFloat("_FlipY", 1);
|
|
CoreUtils.DrawFullScreen(cmd, m_Material, BuiltinRenderTextureType.CameraTarget, fullscreenProperties, shaderPassId: materialPass);
|
|
}
|
|
else
|
|
{
|
|
// When we render directly to game view, we render the image flipped up-side-down, like other HDRP cameras
|
|
m_ShaderVariablesGlobalCB._ViewProjMatrix = m_ViewProjMatrix;
|
|
ConstantBuffer.PushGlobal(cmd, m_ShaderVariablesGlobalCB, HDShaderIDs._ShaderVariablesGlobal);
|
|
cmd.Blit(null, BuiltinRenderTextureType.CameraTarget, m_Material, materialPass);
|
|
}
|
|
}
|
|
|
|
context.ExecuteCommandBuffer(cmd);
|
|
CommandBufferPool.Release(cmd);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Helper function that indicates if a camera is shared between multiple layers
|
|
/// </summary>
|
|
/// <param name="camera">The input camera</param>
|
|
/// <returns>Returns true if this camera is used to render in more than one layer</returns>
|
|
internal bool IsThisCameraShared(Camera camera)
|
|
{
|
|
if (camera == null)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
int count = 0;
|
|
foreach (var layer in m_InputLayers)
|
|
{
|
|
if (layer.outputTarget == CompositorLayer.OutputTarget.CameraStack &&
|
|
camera.Equals(layer.sourceCamera))
|
|
{
|
|
count++;
|
|
}
|
|
}
|
|
// If we found the camera in more than one layer then it is shared between layers
|
|
return count > 1;
|
|
}
|
|
|
|
static public Camera GetSceneCamera()
|
|
{
|
|
if (Camera.main != null)
|
|
{
|
|
return Camera.main;
|
|
}
|
|
foreach (var camera in Camera.allCameras)
|
|
{
|
|
if (camera != CompositionManager.GetInstance().outputCamera)
|
|
{
|
|
return camera;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
static public Camera CreateCamera(string cameraName)
|
|
{
|
|
var newCameraGameObject = new GameObject(cameraName)
|
|
{
|
|
hideFlags = HideFlags.HideInInspector | HideFlags.HideInHierarchy | HideFlags.HideAndDontSave
|
|
};
|
|
var newCamera = newCameraGameObject.AddComponent<Camera>();
|
|
newCameraGameObject.AddComponent<HDAdditionalCameraData>();
|
|
|
|
return newCamera;
|
|
}
|
|
|
|
private static CompositionManager s_CompositorInstance;
|
|
public static CompositionManager GetInstance() => s_CompositorInstance ??= FindAnyObjectByType<CompositionManager>(FindObjectsInactive.Include);
|
|
|
|
static public Vector4 GetAlphaScaleAndBiasForCamera(HDCamera hdCamera)
|
|
{
|
|
AdditionalCompositorData compositorData = null;
|
|
hdCamera.camera.TryGetComponent<AdditionalCompositorData>(out compositorData);
|
|
|
|
if (compositorData)
|
|
{
|
|
float alphaMin = compositorData.alphaMin;
|
|
float alphaMax = compositorData.alphaMax;
|
|
|
|
if (alphaMax == alphaMin)
|
|
alphaMax += 0.0001f; // Mathf.Epsilon is too small and in this case it creates precission issues
|
|
|
|
float alphaScale = 1.0f / (alphaMax - alphaMin);
|
|
float alphaBias = -alphaMin * alphaScale;
|
|
|
|
return new Vector4(alphaScale, alphaBias, 0.0f, 0.0f);
|
|
}
|
|
|
|
// No compositor-specific data for this camera/layer, just return the default/neutral scale and bias
|
|
return new Vector4(1.0f, 0.0f, 0.0f, 0.0f);
|
|
}
|
|
|
|
/// <summary>
|
|
/// For stacked cameras, returns the color buffer that will be used to draw on top
|
|
/// </summary>
|
|
/// <param name="hdCamera">The input camera</param>
|
|
/// <returns> The color buffer that will be used to draw on top, or null if not a stacked camera </returns>
|
|
static internal Texture GetClearTextureForStackedCamera(HDCamera hdCamera)
|
|
{
|
|
if (hdCamera.camera.TryGetComponent<AdditionalCompositorData>(out var compositorData))
|
|
{
|
|
return compositorData.clearColorTexture;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// For stacked cameras, returns the depth buffer that will be used to draw on top
|
|
/// </summary>
|
|
/// <param name="hdCamera">The input camera</param>
|
|
/// <returns> The depth buffer that will be used to draw on top, or null if not a stacked camera </returns>
|
|
static internal RenderTexture GetClearDepthForStackedCamera(HDCamera hdCamera)
|
|
{
|
|
if (hdCamera.camera.TryGetComponent<AdditionalCompositorData>(out var compositorData))
|
|
{
|
|
return compositorData.clearDepthTexture;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
// Register the custom pp passes used by the compositor
|
|
void RegisterCustomPasses()
|
|
{
|
|
if (GraphicsSettings.currentRenderPipeline is not HDRenderPipelineAsset hdrpAsset)
|
|
{
|
|
UnRegisterCustomPasses();
|
|
m_CurrentAsset = null;
|
|
}
|
|
else if (m_CurrentAsset != hdrpAsset)
|
|
{
|
|
UnRegisterCustomPasses();
|
|
m_CurrentAsset = hdrpAsset;
|
|
}
|
|
|
|
if (m_CurrentAsset == null)
|
|
return;
|
|
|
|
// If custom post processes are not registered in the HDRP asset, they are never executed so we have to add them manually
|
|
m_CurrentAsset.compositorCustomVolumeComponentsList.Add<ChromaKeying>();
|
|
m_CurrentAsset.compositorCustomVolumeComponentsList.Add<AlphaInjection>();
|
|
}
|
|
|
|
// Unregister the custom pp passes used by the compositor
|
|
void UnRegisterCustomPasses()
|
|
{
|
|
if (m_CurrentAsset != null)
|
|
{
|
|
m_CurrentAsset.compositorCustomVolumeComponentsList.Remove<ChromaKeying>();
|
|
m_CurrentAsset.compositorCustomVolumeComponentsList.Remove<AlphaInjection>();
|
|
}
|
|
}
|
|
}
|
|
}
|