using CR;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
using ProfilingScope = UnityEngine.Rendering.ProfilingScope;
#if UNITY_6000_0_OR_NEWER
using UnityEngine.Rendering.RenderGraphModule;
using UnityEngine.Rendering.RenderGraphModule.Util;
#endif
namespace CR
{
class CleanOutlineRenderPass : ScriptableRenderPass
{
const string Tag = "CleanOutline Post Process";
#if UNITY_2023_1_OR_NEWER
private RTHandle m_Source;
private RTHandle m_TempDest;
#elif UNITY_2022_3_OR_NEWER
private RTHandle m_Source;
private RTHandle m_TempDest;
#else
private RenderTargetIdentifier m_Source;
private RenderTargetHandle m_TempDest;
#endif
private CleanOutline m_Volume;
private Material m_CleanOutlineMaterial;
private ProfilingSampler m_ProfilingSampler;
private int OUTLINETHICKNESS_ID = Shader.PropertyToID("_OutlineThickness");
private int OUTLINECOLOR_ID = Shader.PropertyToID("_OutlineColor");
private int ENABLECLOSENESSBOOST_ID = Shader.PropertyToID("_EnableClosenessBoost");
private int CLOSENESSBOOSTTHICKNESS_ID = Shader.PropertyToID("_ClosenessBoostThickness");
private int BOOSTNEAR_ID = Shader.PropertyToID("_BoostNear");
private int BOOSTFAR_ID = Shader.PropertyToID("_BoostFar");
private int ENABLEDISTANTFADE_ID = Shader.PropertyToID("_EnableDistantFade");
private int FADENEAR_ID = Shader.PropertyToID("_FadeNear");
private int FADEFAR_ID = Shader.PropertyToID("_FadeFar");
private int DEPTHCHECKMORESAMPLE_ID = Shader.PropertyToID("_DepthCheckMoreSample");
private int NINETILESTHRESHOLD_ID = Shader.PropertyToID("_NineTilesThreshold");
private int NINETILEBOTTOMFIX_ID = Shader.PropertyToID("_NineTileBottomFix");
private int DEPTHTHICKNESS_ID = Shader.PropertyToID("_DepthThickness");
private int OUTLINEDEPTHMULTIPLIER_ID = Shader.PropertyToID("_OutlineDepthMultiplier");
private int OUTLINEDEPTHBIAS_ID = Shader.PropertyToID("_OutlineDepthBias");
private int DEPTHTHRESHOLD_ID = Shader.PropertyToID("_DepthThreshold");
private int ENABLENORMALOUTLINE_ID = Shader.PropertyToID("_EnableNormalOutline");
private int NORMALCHECKDIRECTION_ID = Shader.PropertyToID("_NormalCheckDirection");
private int NORMALTHICKNESS_ID = Shader.PropertyToID("_NormalThickness");
private int OUTLINENORMALMULTIPLIER_ID = Shader.PropertyToID("_OutlineNormalMultiplier");
private int OUTLINENORMALBIAS_ID = Shader.PropertyToID("_OutlineNormalBias");
private int NORMALTHRESHOLD_ID = Shader.PropertyToID("_NormalThreshold");
private int DEBUGMODE_ID = Shader.PropertyToID("_DebugMode");
private int BLITTEXTURE = Shader.PropertyToID("_BlitTexture");
public CleanOutlineRenderPass(CleanOutline volume)
{
m_Volume = volume;
#if UNITY_6000_0_OR_NEWER
m_OutlineTextureDescriptor = new RenderTextureDescriptor(Screen.width, Screen.height,
RenderTextureFormat.Default, 0);
#endif
}
public void Setup(in RenderingData renderingData)
{
m_ProfilingSampler ??= new ProfilingSampler(Tag);
#if UNITY_2023_1_OR_NEWER
var colorCopyDescriptor = renderingData.cameraData.cameraTargetDescriptor;
colorCopyDescriptor.depthBufferBits = (int)DepthBits.None;
RenderingUtils.ReAllocateIfNeeded(ref m_TempDest, colorCopyDescriptor, name: "_CleanOutlineTemp");
#endif
}
public void Dispose()
{
#if UNITY_2023_1_OR_NEWER
m_TempDest?.Release();
#elif UNITY_2022_3_OR_NEWER
m_TempDest?.Release();
#endif
}
public bool Unity2022_3_EarlyVersions()
{
#if UNITY_2022_3_OR_NEWER
#if (UNITY_2022_3_1 || UNITY_2022_3_2 || UNITY_2022_3_3 || UNITY_2022_3_4 || UNITY_2022_3_5 || UNITY_2022_3_6 || UNITY_2022_3_7 || UNITY_2022_3_8 || UNITY_2022_3_9 || UNITY_2022_3_10 || UNITY_2022_3_11 || UNITY_2022_3_12 || UNITY_2022_3_13 || UNITY_2022_3_14 || UNITY_2022_3_15 || UNITY_2022_3_16 || UNITY_2022_3_17 || UNITY_2022_3_18 || UNITY_2022_3_19 || UNITY_2022_3_20 || UNITY_2022_3_21 || UNITY_2022_3_22 || UNITY_2022_3_23 || UNITY_2022_3_24 || UNITY_2022_3_25 || UNITY_2022_3_26 || UNITY_2022_3_27 || UNITY_2022_3_28 || UNITY_2022_3_29 || UNITY_2022_3_30 || UNITY_2022_3_31 || UNITY_2022_3_32 || UNITY_2022_3_33 || UNITY_2022_3_34 || UNITY_2022_3_35 || UNITY_2022_3_36 || UNITY_2022_3_37 || UNITY_2022_3_38 || UNITY_2022_3_39 || UNITY_2022_3_40 || UNITY_2022_3_41 || UNITY_2022_3_42 || UNITY_2022_3_43 || UNITY_2022_3_44 || UNITY_2022_3_45 || UNITY_2022_3_46 || UNITY_2022_3_47 || UNITY_2022_3_48)
return true;
#endif
#endif
return false;
}
public void InitMaterialIfNeeded()
{
if (m_CleanOutlineMaterial == null)
{
#if UNITY_2023_1_OR_NEWER
m_CleanOutlineMaterial = new Material(Shader.Find("CR/CleanOutline23"));
//ConfigureInput(ScriptableRenderPassInput.Normal ^ ScriptableRenderPassInput.Color ^ ScriptableRenderPassInput.Depth);
#elif UNITY_2022_3_OR_NEWER
if (Unity2022_3_EarlyVersions())
{
m_CleanOutlineMaterial = new Material(Shader.Find("CR/CleanOutline22"));
}
else
{
m_CleanOutlineMaterial = new Material(Shader.Find("CR/CleanOutline23"));
}
#else
m_CleanOutlineMaterial = new Material(Shader.Find("CR/CleanOutline"));
#endif
}
}
// This method is called before executing the render pass.
// It can be used to configure render targets and their clear state. Also to create temporary render target textures.
// When empty this render pass will render to the active camera render target.
// You should never call CommandBuffer.SetRenderTarget. Instead call ConfigureTarget and ConfigureClear.
// The render pipeline will ensure target setup and clearing happens in a performant manner.
public override void OnCameraSetup(CommandBuffer cmd, ref RenderingData renderingData)
{
if (renderingData.cameraData.camera.cameraType == CameraType.Reflection ||
renderingData.cameraData.camera.cameraType == CameraType.SceneView)
return;
#if UNITY_2023_1_OR_NEWER
m_Source = renderingData.cameraData.renderer.cameraColorTargetHandle;
#elif UNITY_2022_3_OR_NEWER
m_Source = renderingData.cameraData.renderer.cameraColorTargetHandle;
#else
m_Source = renderingData.cameraData.renderer.cameraColorTarget;
#endif
RenderTextureDescriptor descriptor = renderingData.cameraData.cameraTargetDescriptor;
// Set the number of depth bits we need for our temporary render texture.
descriptor.depthBufferBits = 0;
InitMaterialIfNeeded();
ConfigureInput(ScriptableRenderPassInput.Normal);
#if UNITY_2023_1_OR_NEWER
#elif UNITY_2022_3_OR_NEWER
RenderingUtils.ReAllocateIfNeeded(ref m_TempDest, descriptor, name: "_CleanOutlineTemp");
#else
m_TempDest.Init("_CleanOutlineTemp");
#endif
}
#if UNITY_6000_0_OR_NEWER
private RenderTextureDescriptor m_OutlineTextureDescriptor;
private const string k_TextureName = "_TempTexture";
// RecordRenderGraph is where the RenderGraph handle can be accessed, through which render passes can be added to the graph.
// FrameData is a context container through which URP resources can be accessed and managed.
public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameData)
{
InitMaterialIfNeeded();
if (m_Volume == null)
{
var volumeStack = VolumeManager.instance.stack;
m_Volume = volumeStack.GetComponent();
}
UniversalResourceData resourceData = frameData.Get();
UniversalCameraData cameraData = frameData.Get();
// The following line ensures that the render pass doesn't blit
// from the back buffer.
if (resourceData.isActiveTargetBackBuffer)
return;
// Set the blur texture size to be the same as the camera target size.
m_OutlineTextureDescriptor.width = cameraData.cameraTargetDescriptor.width;
m_OutlineTextureDescriptor.height = cameraData.cameraTargetDescriptor.height;
m_OutlineTextureDescriptor.depthBufferBits = 0;
TextureHandle srcCamColor = resourceData.activeColorTexture;
TextureHandle dst = UniversalRenderer.CreateRenderGraphTexture(renderGraph,
m_OutlineTextureDescriptor, k_TextureName, false);
UpdateVolumeSettings();
// This check is to avoid an error from the material preview in the scene
if (!srcCamColor.IsValid() || !dst.IsValid())
return;
RenderGraphUtils.BlitMaterialParameters para1 = new(srcCamColor, dst, m_CleanOutlineMaterial, 0);
renderGraph.AddBlitPass(para1, "OutlinePass1");
RenderGraphUtils.BlitMaterialParameters para2 = new(dst, srcCamColor, Blitter.GetBlitMaterial(TextureDimension.Tex2D), 0);
renderGraph.AddBlitPass(para2, "OutlinePass2");
}
#endif
public void UpdateVolumeSettings()
{
//general
m_CleanOutlineMaterial.SetFloat(OUTLINETHICKNESS_ID, m_Volume.outlineThickness.value);
m_CleanOutlineMaterial.SetColor(OUTLINECOLOR_ID, m_Volume.outlineColor.value);
m_CleanOutlineMaterial.SetFloat(ENABLECLOSENESSBOOST_ID,
m_Volume.enableClosenessBoost.value ? 1 : 0);
m_CleanOutlineMaterial.SetFloat(CLOSENESSBOOSTTHICKNESS_ID, m_Volume.closenessBoostThickness.value);
m_CleanOutlineMaterial.SetFloat(BOOSTNEAR_ID, m_Volume.closenessBoostNear.value);
m_CleanOutlineMaterial.SetFloat(BOOSTFAR_ID, m_Volume.closenessBoostFar.value);
m_CleanOutlineMaterial.SetFloat(ENABLEDISTANTFADE_ID, m_Volume.enableDistanceFade.value ? 1 : 0);
m_CleanOutlineMaterial.SetFloat(FADENEAR_ID, m_Volume.distanceFadeNear.value);
m_CleanOutlineMaterial.SetFloat(FADEFAR_ID, m_Volume.distanceFadeFar.value);
//depth
m_CleanOutlineMaterial.SetFloat(DEPTHCHECKMORESAMPLE_ID,
(m_Volume.depthSampleType.value == CleanOutlineDepthSample.NineTiles) ? 1 : 0);
m_CleanOutlineMaterial.SetFloat(DEPTHTHICKNESS_ID, m_Volume.depthThickness.value);
m_CleanOutlineMaterial.SetFloat(DEPTHTHRESHOLD_ID, m_Volume.depthThreshold.value);
m_CleanOutlineMaterial.SetFloat(OUTLINEDEPTHMULTIPLIER_ID, m_Volume.depthMultiplier.value);
m_CleanOutlineMaterial.SetFloat(OUTLINEDEPTHBIAS_ID, m_Volume.depthBias.value);
m_CleanOutlineMaterial.SetFloat(NINETILESTHRESHOLD_ID, m_Volume.depth9TilesThreshold.value);
m_CleanOutlineMaterial.SetFloat(NINETILEBOTTOMFIX_ID, m_Volume.depth9TilesBottomFix.value);
//normal
m_CleanOutlineMaterial.SetFloat(ENABLENORMALOUTLINE_ID, m_Volume.enableNormalOutline.value ? 1 : 0);
m_CleanOutlineMaterial.SetFloat(NORMALTHICKNESS_ID, m_Volume.normalThickness.value);
m_CleanOutlineMaterial.SetFloat(OUTLINENORMALMULTIPLIER_ID, m_Volume.normalMultiplier.value);
m_CleanOutlineMaterial.SetFloat(OUTLINENORMALBIAS_ID, m_Volume.normalBias.value);
m_CleanOutlineMaterial.SetFloat(NORMALTHRESHOLD_ID, m_Volume.normalThreshold.value);
m_CleanOutlineMaterial.SetFloat(NORMALCHECKDIRECTION_ID,
m_Volume.normalCheckDirection.value ? 1 : 0);
//debug
m_CleanOutlineMaterial.SetFloat(DEBUGMODE_ID, (float)m_Volume.debugMode.value);
}
// Here you can implement the rendering logic.
// Use ScriptableRenderContext to issue drawing commands or execute command buffers
// https://docs.unity3d.com/ScriptReference/Rendering.ScriptableRenderContext.html
// You don't have to call ScriptableRenderContext.submit, the render pipeline will call it at specific points in the pipeline.
public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
{
if (renderingData.cameraData.camera.cameraType != CameraType.Game)
return;
CommandBuffer cmd = CommandBufferPool.Get("CleanOutline");
using (new ProfilingScope(cmd, profilingSampler))
{
//m_Volume might be set to null after switching scene
if (m_Volume == null)
{
var volumeStack = VolumeManager.instance.stack;
m_Volume = volumeStack.GetComponent();
}
InitMaterialIfNeeded();
if (m_Volume != null && m_Volume.standaloneActive.value)
{
UpdateVolumeSettings();
#if UNITY_2023_1_OR_NEWER
Blitter.BlitCameraTexture(cmd, m_Source, m_TempDest);
m_CleanOutlineMaterial.SetTexture(BLITTEXTURE, m_TempDest);
CoreUtils.SetRenderTarget(cmd, renderingData.cameraData.renderer.cameraColorTargetHandle);
CoreUtils.DrawFullScreen(cmd, m_CleanOutlineMaterial);
#elif UNITY_2022_3_OR_NEWER
Blitter.BlitTexture(cmd, m_Source, m_TempDest, m_CleanOutlineMaterial, 0);
Blitter.BlitCameraTexture(cmd, m_TempDest, m_Source);
#else
RenderTextureDescriptor cameraTexDesc = renderingData.cameraData.cameraTargetDescriptor;
cmd.GetTemporaryRT(m_TempDest.id, cameraTexDesc, FilterMode.Point);
Blit(cmd, m_Source, m_TempDest.Identifier(), m_CleanOutlineMaterial);
Blit(cmd, m_TempDest.Identifier(), m_Source);
#endif
}
}
context.ExecuteCommandBuffer(cmd);
CommandBufferPool.Release(cmd);
}
// Cleanup any allocated resources that were created during the execution of this render pass.
public override void OnCameraCleanup(CommandBuffer cmd)
{
#if UNITY_2023_1_OR_NEWER
#elif UNITY_2022_3_OR_NEWER
#else
cmd.ReleaseTemporaryRT(m_TempDest.id);
#endif
}
}
public class CleanOutlineRenderPassFeature : ScriptableRendererFeature
{
CleanOutlineRenderPass m_ScriptablePass;
private CleanOutline m_CleanOutlineVolume;
///
public override void Create()
{
m_CleanOutlineVolume = VolumeManager.instance.stack.GetComponent();
m_ScriptablePass = new CleanOutlineRenderPass(m_CleanOutlineVolume);
// Configures where the render pass should be injected.
m_ScriptablePass.renderPassEvent = RenderPassEvent.BeforeRenderingTransparents;
}
// Here you can inject one or multiple render passes in the renderer.
// This method is called when setting up the renderer once per-camera.
public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
{
m_ScriptablePass.Setup(renderingData);
renderer.EnqueuePass(m_ScriptablePass);
}
protected override void Dispose(bool disposing)
{
m_ScriptablePass.Dispose();
}
}
}