unity在URP中实现自定义Volume

  在unity默认bulit-in管线中,后处理效果一般可以在在相机上挂一个脚本加上OnRenderImage函数,并使用Graphics.Blit用某个材质对最后相机展现的画面进行处理。
  在URP中OnRenderImage不生效了,并且有了一个专门做后处理的Volume。但由于相关代码都写在了一个叫PostProcessPass的脚本中,除非修改源码,否则无法仅通过扩展一个VolumeComponent来实现一个自定义后处理。好在URP提供了一个RendererFeature的功能,我们可以通过这个自行添加一个pass管理我们的自定义Volume。事实上URP也是这么做的。

1.创建自定义VolumeComponent

  要注意的是这里要写在面板上显示参数,不能定义int,float这些基础类型,unity做了一层封装FloatParameter,IntParameter之类。要定义限制范围的参数要使用ClampedFloatParameter等而不是FloatRangeParameter,一开始被坑了一下。

using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;

[System.Serializable, VolumeComponentMenu("XPostProcess/ColorAdjustMenstsBasedPosAndDepth")]
public class ColorAdjustMenstsBasedPosAndDepth : VolumeComponent, IPostProcessComponent
{
    public BoolParameter enableEffect = new BoolParameter(true);

    public FloatParameter radius = new FloatParameter(1000000);
    public Vector3Parameter center = new Vector3Parameter(Vector3.zero);
    public ClampedFloatParameter intensity = new ClampedFloatParameter(0.5f, 0, 1);
    public ClampedFloatParameter backgraoundIntensity = new ClampedFloatParameter(0.5f, 0, 1);
    public ColorParameter backgroundColor = new ColorParameter(Color.black);
    public Vector3Parameter colorScale = new Vector3Parameter(Vector3.one);

    public bool IsActive() => enableEffect == true;

    public bool IsTileCompatible() => false;
}

  VolumetiComponent定义完后就可以在Volume的面板上看到并添加了。unity在URP中实现自定义Volume

2.创建自定义RenderFeature

using UnityEngine.Rendering.Universal;

public class XPostProcessRenderFeature : ScriptableRendererFeature
{
    XPostProcessRenderPass m_ScriptablePass;

    public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
    {

        var src = renderer.cameraColorTarget;
        var dest = RenderTargetHandle.CameraTarget;
        m_ScriptablePass.Setup(src, dest, renderingData.cameraData.camera);
        renderer.EnqueuePass(m_ScriptablePass);
    }

    public override void Create()
    {
        m_ScriptablePass = new XPostProcessRenderPass();
    }
}

3.创建自定义RenderPass

using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;

public class XPostProcessRenderPass : ScriptableRenderPass
{
    RenderTargetHandle m_temporaryColorTexture;
    RenderTargetIdentifier source;
    RenderTargetHandle destination;
    Camera m_camera;

    private ColorAdjustMenstsBasedPosAndDepth m_CustomVolume;

    private readonly string CUSTOM_PASS_TAG = "Custom Pass";
    private readonly MaterialLibrary m_MaterialLibrary = new MaterialLibrary();


    public XPostProcessRenderPass()
    {
        this.renderPassEvent = RenderPassEvent.AfterRenderingTransparents;
        m_temporaryColorTexture.Init("temporaryColorTexture");
    }

    public void Setup(RenderTargetIdentifier src, RenderTargetHandle dest, Camera camera)
    {
        this.source = src;
        this.destination = dest;
        this.m_camera = camera;
    }

    public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
    {
        if (!renderingData.cameraData.postProcessEnabled) return;

        ConfigCamera();

        VolumeStack stack = VolumeManager.instance.stack;
        m_CustomVolume = stack.GetComponent<ColorAdjustMenstsBasedPosAndDepth>();
        // 这里可以继续添加别的后处理效果


        CommandBuffer cmd = CommandBufferPool.Get(CUSTOM_PASS_TAG);

        Render(cmd, ref renderingData);

        context.ExecuteCommandBuffer(cmd);
        CommandBufferPool.Release(cmd);
    }

    private void Render(CommandBuffer cmd, ref RenderingData renderingData)
    {
        var frustumCorners = ConfigCamera();

        if (m_CustomVolume.IsActive() && m_CustomVolume.active)
        {
            var colorAdjustmentsBasedPosAndDepth = m_MaterialLibrary.colorAdjustmentsBasedPosAndDepth;
            colorAdjustmentsBasedPosAndDepth.SetFloat(ShaderConstants.Intensity, m_CustomVolume.intensity.value);
            colorAdjustmentsBasedPosAndDepth.SetFloat(ShaderConstants.BackGroundIntensity, m_CustomVolume.backgraoundIntensity.value);
            colorAdjustmentsBasedPosAndDepth.SetVector(ShaderConstants.Center, m_CustomVolume.center.value);
            colorAdjustmentsBasedPosAndDepth.SetVector(ShaderConstants.ColorScale, m_CustomVolume.colorScale.value);
            colorAdjustmentsBasedPosAndDepth.SetFloat(ShaderConstants.Radius, m_CustomVolume.radius.value);
            colorAdjustmentsBasedPosAndDepth.SetColor(ShaderConstants.BarkGroundColor, m_CustomVolume.backgroundColor.value);
            colorAdjustmentsBasedPosAndDepth.SetMatrix("_FrustumCornersRay", frustumCorners);

            RenderTextureDescriptor opaqueDesc = renderingData.cameraData.cameraTargetDescriptor;
            opaqueDesc.depthBufferBits = 0;
            //不能读写同一个颜色target,创建一个临时的render Target去blit
            if (destination == RenderTargetHandle.CameraTarget)
            {
                cmd.GetTemporaryRT(m_temporaryColorTexture.id, opaqueDesc, FilterMode.Bilinear);
                Blit(cmd, source, m_temporaryColorTexture.Identifier(), colorAdjustmentsBasedPosAndDepth);
                Blit(cmd, m_temporaryColorTexture.Identifier(), source);
            }
            else
            {
                Blit(cmd, source, destination.Identifier(), colorAdjustmentsBasedPosAndDepth);
            }
        }
    }

    public override void FrameCleanup(CommandBuffer cmd)
    {
        if (destination == RenderTargetHandle.CameraTarget)
            cmd.ReleaseTemporaryRT(m_temporaryColorTexture.id);
    }

    private Matrix4x4 ConfigCamera()
    {
        Matrix4x4 frustumCorners = Matrix4x4.identity;

        float fov = m_camera.fieldOfView;
        float near = m_camera.nearClipPlane;
        float aspect = m_camera.aspect;
        Transform transform = m_camera.transform;

        float halfHeight = near * Mathf.Tan(fov * 0.5f * Mathf.Deg2Rad);
        Vector3 toRight = transform.right * halfHeight * aspect;
        Vector3 toTop = transform.up * halfHeight;

        Vector3 topLeft = transform.forward * near + toTop - toRight;
        float scale = topLeft.magnitude / near;

        topLeft.Normalize();
        topLeft *= scale;



        Vector3 topRight = transform.forward * near + toRight + toTop;
        topRight.Normalize();
        topRight *= scale;

        Vector3 bottomLeft = transform.forward * near - toTop - toRight;
        bottomLeft.Normalize();
        bottomLeft *= scale;

        Vector3 bottomRight = transform.forward * near + toRight - toTop;
        bottomRight.Normalize();
        bottomRight *= scale;

        frustumCorners.SetRow(0, bottomLeft);
        frustumCorners.SetRow(1, bottomRight);
        frustumCorners.SetRow(2, topRight);
        frustumCorners.SetRow(3, topLeft);

        return frustumCorners;
    }
}


class MaterialLibrary
{
    public readonly Material colorAdjustmentsBasedPosAndDepth;

    public MaterialLibrary()
    {
        colorAdjustmentsBasedPosAndDepth = Load(Shader.Find("XPostProcess/ColorAdjustmentsBasedPosAndDepth"));
    }

    Material Load(Shader shader)
    {
        if (shader == null)
        {
            Debug.LogErrorFormat($"Missing shader. {GetType().DeclaringType.Name} render pass will not execute. Check for missing reference in the renderer resources.");
            return null;
        }
        else if (!shader.isSupported)
        {
            return null;
        }

        return CoreUtils.CreateEngineMaterial(shader);
    }

    internal void Cleanup()
    {
        CoreUtils.Destroy(colorAdjustmentsBasedPosAndDepth);
    }
}

static class ShaderConstants
{
    public static readonly int Intensity = Shader.PropertyToID("_Intensity");
    public static readonly int BackGroundIntensity = Shader.PropertyToID("_BackGroundIntensity");
    public static readonly int Center = Shader.PropertyToID("_Center");
    public static readonly int Radius = Shader.PropertyToID("_Radius");
    public static readonly int BarkGroundColor = Shader.PropertyToID("_BarkGroundColor");
    public static readonly int ColorScale = Shader.PropertyToID("_ColorScale");
}

4.添加RenderFeature

  至此就可以给RenderData添加一个自定义feature了,添加完后自定义的Volume效果就可以使用了。如果有超过一个后处理效果需要添加,可以新增自定义Volume并修改RenderPass。
unity在URP中实现自定义Volume

5.小结

  目前还有一个问题没有找到原因,就是自定义的Voume添加之后再移除,效果依然在,并没有从VolumeManager.instance.stack中去掉。不知道这是个bug还是我哪儿写的有问题。不过不怎么影响,给Volume加个enable参数就行了,就随他去了。也有办法解决,VolumeComponent就是个存储后处理数据的方式,并不实际处理后处理效果,完全可以自己写个管理器管理自定义Volume的添加删除,就是还得自己再写一下编辑器扩展。

上一篇:如何查看docker容器的volume挂载情况


下一篇:external-provisioner源码分析(1)- 主体处理逻辑分析