微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

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的面板上看到并添加了。

在这里插入图片描述

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。

在这里插入图片描述

5.小结

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

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 [email protected] 举报,一经查实,本站将立刻删除。

相关推荐