【Unity】RenderFeature笔记

【Unity】RenderFeature笔记

RenderFeature是在urp中添加的额外渲染pass,并可以将这个pass插入到渲染列队中的任意位置。内置渲染管线中Graphics 的功能需要在RenderFeature里实现,常见的如DrawMesh和Blit

​ 可以实现的效果包括但不限于

  1. 后处理,可以编写shader对整个渲染画面进行修改。比如一些全局特效包括:场景描边,地形扫描;

  2. 创建网格,通过DrawMesh绘制定义的网格;

  3. 渲染画面,将场景渲染到纹理;

  4. ...

一、RanderFeature简介

1. 创建RanderFeature

在project中Create→Rendering→URPRenderFeature

2.RanderFeature结构
c# 复制代码
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;

public class CustomRenderPassFeatureTest : ScriptableRendererFeature
{
    class CustomRenderPass : ScriptableRenderPass
    {
        // 在执行渲染通道之前调用
        // 创建临时渲染目标纹理。
        //为Execute提前准备需要的RenderTexture或者其它变量
        public override void OnCameraSetup(CommandBuffer cmd, ref RenderingData renderingData){}
        //核心方法,实现这个renderPass的逻辑
        public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData){}
        //释放在OnCameraSetup里,声明的变量如TemporaryRenderTexture
        public override void OnCameraCleanup(CommandBuffer cmd) {}
    }
    CustomRenderPass m_ScriptablePass;

    /// <inheritdoc/>
    public override void Create()
    {
        m_ScriptablePass = new CustomRenderPass();
        //定义渲染的位置
        m_ScriptablePass.renderPassEvent = RenderPassEvent.AfterRenderingOpaques;
    }

    // 这里你可以在渲染器中注入一个或多个渲染通道。
    //每一帧都执行
    public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
    {
        //把实例化的CustomRenderPass插入渲染管线
        renderer.EnqueuePass(m_ScriptablePass);
    }
}
3.RenderFeatrue使用

在Universal Render Pipeline Asset_Renderer直接Add RenderFeatrue

二、后处理效果

关键词
1、CommandBuffer.Blit

Blit是很常用的后期效果方法,在Build_in中常常是在OnRenderImage里被调用,与urp不同的是,在build_in里是通过Graphics.Blit调用。

Graphics.Blit

常用的接口: Blit(RenderTexturesource, RenderTexture dest, Material mat);

即使用着色器将源纹理复制到目标渲染纹理。

CommandBuffer.Blit

常用的接口:public void Blit (Rendering.RenderTargetIdentifier source , Rendering.RenderTargetIdentifier dest , Material mat);

这与 Graphics.Blit相似 - 主要用于从一个(渲染)纹理复制到其他纹理,可能使用自定义着色器;

源纹理或渲染目标将作为"_MainTex"属性传递给材质。

在shader中,_MainTex对应的是source,最后retrun对应的是dest

2、RenderTargetIdentifier

渲染目标标识符,标识 CommandBuffer的 RenderTexture;

使用 CommandBuffer.GetTemporaryRT 创建的具有名称的临时渲染纹理;

ScriptableRenderPass中有colorAttachment和depthAttachment是可以直接使用;

RenderTargetHandle.CameraTarget.Identifier()可以得到当前的渲染标识符

renderingData.cameraData.renderer.cameraColorTarget 也可以得到当前的渲染标识符

简单实现

实现脚本

c# 复制代码
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;

public class CustomRenderPassFeatureTest : ScriptableRendererFeature
{
    //结构说明
    class CustomRenderPass : ScriptableRenderPass
    {
        // 在执行渲染通道之前调用
        // 创建临时渲染目标纹理。
        //为Execute提前准备需要的RenderTexture或者其它变量

        public override void OnCameraSetup(CommandBuffer cmd, ref RenderingData renderingData) {}
      
        //核心方法,实现这个renderPass的逻辑
        public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData){}

        //释放在OnCameraSetup里,声明的变量如TemporaryRenderTexture
        public override void OnCameraCleanup(CommandBuffer cmd){}
    }
    //后处理效果
    class CustomRenderShaderPostPass : ScriptableRenderPass
    {  
        //定义渲染材质,通过Create方法赋值
        public Material _Material;
        public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
        {
            //创建一个CommandBuffer
            CommandBuffer cmd = CommandBufferPool.Get("ShowShader");
            //这里有一个疑问,渲染标识符不管怎么传都不影响结果
            cmd.Blit(colorAttachment, RenderTargetHandle.CameraTarget.Identifier(), _Material);
            //执行CommandBuffer
            context.ExecuteCommandBuffer(cmd);
            //回收CommandBuffer
            CommandBufferPool.Release(cmd);
        }
    }

    CustomRenderShaderPostPass m_ScriptablePassPost;
    public Shader shader;
    /// <inheritdoc/>
    public override void Create()
    {
        m_ScriptablePassPost = new CustomRenderShaderPostPass();
        //通过创建一个渲染材质
        m_ScriptablePassPost._Material = new Material(shader);
        //定义渲染的位置
        m_ScriptablePassPost.renderPassEvent = RenderPassEvent.AfterRenderingOpaques;
    }
    // 这里你可以在渲染器中注入一个或多个渲染通道。
    //每一帧都执行
    public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
    {
        //把实例化的CustomRenderPass插入渲染管线
        renderer.EnqueuePass(m_ScriptablePassPost);
    }
}
1.实现饱和度和对比度调整

用shaderGraph实现

用shader实现

c 复制代码
Shader "Unlit/SaturationContrastShader"
{
    SubShader
    {
        Pass
        {
           ZTest Always ZWrite Off
            CGPROGRAM

            #pragma vertex vert_img  // 使用内置的vert_img顶点着色器
            #pragma fragment frag

            #include "UnityCG.cginc"

            sampler2D _CameraColorTexture; 
            //饱和度
            fixed3   Saturation_float(float3 In, float Saturation)
            {
                float luma = dot(In, float3(0.2126729, 0.7151522, 0.0721750));
                return luma.xxx + Saturation.xxx * (In - luma.xxx);
            }
            //对比度
            fixed3  Contrast_float(float3 In, float Contrast)
            {
                float midpoint = pow(0.5, 2.2);
                return (In - midpoint) * Contrast + midpoint;
            }

            fixed4 frag (v2f_img i) : SV_Target
            {
                fixed4 col = tex2D(_CameraColorTexture, i.uv);
                fixed3  Saturation =  Saturation_float(col, 5);
                fixed3  Contrast = Saturation_float(Saturation, 5);
                return fixed4(Contrast.x, Contrast.y, Contrast.z,0.1);
            }
            ENDCG
        }
    }
   FallBack Off
}

使用前

使用后

三、创建网格

关键词
1、CommandBuffer.DrawMesh

常用的接口:DrawMesh(Mesh mesh, Matrix4x4 matrix, Material material);

这个接口相对简单,传入mesh,变换矩阵,和材质

简单实现

这里接着上一个CustomRenderPassFeatureTest写,同时实现后处理和网格创建

c# 复制代码
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;

public class CustomRenderPassFeatureTest : ScriptableRendererFeature
{
    //结构说明
    class CustomRenderPass : ScriptableRenderPass
    {
        // 在执行渲染通道之前调用
        // 创建临时渲染目标纹理。
        //为Execute提前准备需要的RenderTexture或者其它变量

        public override void OnCameraSetup(CommandBuffer cmd, ref RenderingData renderingData) {}
      
        //核心方法,实现这个renderPass的逻辑
        public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData){}

        //释放在OnCameraSetup里,声明的变量如TemporaryRenderTexture
        public override void OnCameraCleanup(CommandBuffer cmd){}
    }
    //后处理效果
    class CustomRenderShaderPostPass : ScriptableRenderPass
    {  
        //定义渲染材质,通过Create方法赋值
        public Material _Material;
        public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
        {
            //创建一个CommandBuffer
            CommandBuffer cmd = CommandBufferPool.Get("ShowShader");
            //这里有一个疑问,渲染标识符不管怎么传都不影响结果
            cmd.Blit(colorAttachment, RenderTargetHandle.CameraTarget.Identifier(), _Material);
            //执行CommandBuffer
            context.ExecuteCommandBuffer(cmd);
            //回收CommandBuffer
            CommandBufferPool.Release(cmd);
        }
    }
    //绘制网格
    class CustomRenderPassCreateMesh : ScriptableRenderPass
    {
        public Material _Material;
        public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
        {
            CommandBuffer cmd = CommandBufferPool.Get("CreateMesh");
            cmd.DrawMesh(CreateMesh(), Matrix4x4.identity, _Material);
            //这里和后处理一样的操作
            context.ExecuteCommandBuffer(cmd);
            CommandBufferPool.Release(cmd);
        }
        //创建网格
        Mesh CreateMesh()
        {
            Mesh mesh = new Mesh();
            mesh.vertices = new Vector3[4] { new Vector3(1, 1, 1), new Vector3(-1, 1, 1), new Vector3(-1, 1, -1), new Vector3(1, 1, -1) };
            int[] indices = new int[8] { 0, 1, 1, 2, 2, 3, 3, 0 };
            //创建简单的线网格
            mesh.SetIndices(indices, MeshTopology.Lines, 0);
            return mesh;
        }
    }

    CustomRenderShaderPostPass m_ScriptablePassPost;
    CustomRenderPassCreateMesh m_ScriptablePassCreateMesh;
    public Shader shaderPost;
    public Shader shaderMesh;
    /// <inheritdoc/>
    public override void Create()
    {
        m_ScriptablePassPost = new CustomRenderShaderPostPass();
        //通过创建一个渲染材质
        m_ScriptablePassPost._Material = new Material(shaderPost);
        //定义渲染的位置
        m_ScriptablePassPost.renderPassEvent = RenderPassEvent.AfterRendering;

        m_ScriptablePassCreateMesh = new CustomRenderPassCreateMesh();
        m_ScriptablePassCreateMesh._Material = new Material(shaderMesh);
        m_ScriptablePassCreateMesh.renderPassEvent = RenderPassEvent.AfterRenderingOpaques;
    }

    // 这里你可以在渲染器中注入一个或多个渲染通道。
    //每一帧都执行
    public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
    {
        //把实例化的CustomRenderPass插入渲染管线
        renderer.EnqueuePass(m_ScriptablePassPost);
        renderer.EnqueuePass(m_ScriptablePassCreateMesh);
    }
}

实现效果

知识点:RenderPassEvent控制渲染的顺序,上图我们可以看到,创建的线是可以被模型遮挡和遮挡模型的。但是如果把m_ScriptablePassCreateMesh.renderPassEvent=RenderPassEvent.AfterRendering 就会发现线完全遮挡模型,即线最后渲染

四、渲染画面到图片

关键词
1、GetTemporaryRT

获取临时渲染纹理

可使用给定参数创建临时渲染纹理,并使用 nameID 将其设置为全局着色器属性。使用 Shader.PropertyToID创建整数名称。

2、Shader.PropertyToID

获取着色器属性名称的唯一标识符。

c# 复制代码
static string RtName = "_TestRT";
static int RT_ID = Shader.PropertyToID(RtName);

简单实现

同样接着上一个CustomRenderPassFeatureTest写,同时实现后处理和网格创建

c# 复制代码
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;

public class CustomRenderPassFeatureTest : ScriptableRendererFeature
{
    //结构说明
    class CustomRenderPass : ScriptableRenderPass
    {
        // 在执行渲染通道之前调用
        // 创建临时渲染目标纹理。
        //为Execute提前准备需要的RenderTexture或者其它变量

        public override void OnCameraSetup(CommandBuffer cmd, ref RenderingData renderingData) {}
      
        //核心方法,实现这个renderPass的逻辑
        public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData){}

        //释放在OnCameraSetup里,声明的变量如TemporaryRenderTexture
        public override void OnCameraCleanup(CommandBuffer cmd){}
    }
    //后处理效果
    class CustomRenderShaderPostPass : ScriptableRenderPass
    {  
        //定义渲染材质,通过Create方法赋值
        public Material _Material;
        public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
        {
            //创建一个CommandBuffer
            CommandBuffer cmd = CommandBufferPool.Get("ShowShader");
            //这里有一个疑问,渲染标识符不管怎么传都不影响结果
            cmd.Blit(colorAttachment, RenderTargetHandle.CameraTarget.Identifier(), _Material);
            //执行CommandBuffer
            context.ExecuteCommandBuffer(cmd);
            //回收CommandBuffer
            CommandBufferPool.Release(cmd);
        }
    }
    //创建网格
    class CustomRenderPassCreateMesh : ScriptableRenderPass
    {
        public Material _Material;
        public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
        {
            CommandBuffer cmd = CommandBufferPool.Get("CreateMesh");
            cmd.DrawMesh(CreateMesh(), Matrix4x4.identity, _Material);
            context.ExecuteCommandBuffer(cmd);
            CommandBufferPool.Release(cmd);
        }
        //创建网格
        Mesh CreateMesh()
        {
            Mesh mesh = new Mesh();
            mesh.vertices = new Vector3[4] { new Vector3(1, 1, 1), new Vector3(-1, 1, 1), new Vector3(-1, 1, -1), new Vector3(1, 1, -1) };
            int[] indices = new int[8] { 0, 1, 1, 2, 2, 3, 3, 0 };
            mesh.SetIndices(indices, MeshTopology.Lines, 0);
            return mesh;
        }
    }
    //渲染画面
    class CustomRenderPassRT : ScriptableRenderPass
    {
        static string RtName = "_TestRT";
        //获取着色器属性名称的唯一标识符。
        static int RT_ID = Shader.PropertyToID(RtName);
        public override void OnCameraSetup(CommandBuffer cmd, ref RenderingData renderingData)
        {
            //新建一个RenderTextureDescriptor
            RenderTextureDescriptor textureDescriptor = new RenderTextureDescriptor(1920, 1080, RenderTextureFormat.Default, 0);
            //获取临时渲染纹理
            cmd.GetTemporaryRT(RT_ID, textureDescriptor);
        }
        public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
        {
            CommandBuffer cmd = CommandBufferPool.Get("ShowRT");
            //下面三段实现效果一样
            cmd.Blit(renderingData.cameraData.renderer.cameraColorTarget, RT_ID);
           // cmd.Blit(RenderTargetHandle.CameraTarget.Identifier(), RT_ID);
           // cmd.Blit(colorAttachment, RT_ID);
            context.ExecuteCommandBuffer(cmd);
            CommandBufferPool.Release(cmd);
        }
        public override void OnCameraCleanup(CommandBuffer cmd)
        {
            cmd.ReleaseTemporaryRT(RT_ID);
        }
    }

    CustomRenderShaderPostPass m_ScriptablePassPost;
    CustomRenderPassCreateMesh m_ScriptablePassCreateMesh;
    CustomRenderPassRT m_ScriptablePassRenderTextrue;
    public Shader shaderPost;
    public Shader shaderMesh;
    /// <inheritdoc/>
    public override void Create()
    {
        #region 后处理效果
        m_ScriptablePassPost = new CustomRenderShaderPostPass();
        //通过创建一个渲染材质
        m_ScriptablePassPost._Material = new Material(shaderPost);
        //定义渲染的位置
        m_ScriptablePassPost.renderPassEvent = RenderPassEvent.AfterRendering;
        #endregion

        #region 创建网格
        m_ScriptablePassCreateMesh = new CustomRenderPassCreateMesh();
        m_ScriptablePassCreateMesh._Material = new Material(shaderMesh);
        m_ScriptablePassCreateMesh.renderPassEvent = RenderPassEvent.AfterRenderingOpaques;
        #endregion

        #region 渲染画面
        m_ScriptablePassRenderTextrue = new CustomRenderPassRT();
        m_ScriptablePassRenderTextrue.renderPassEvent = RenderPassEvent.AfterRendering;
        #endregion

    }

    // 这里你可以在渲染器中注入一个或多个渲染通道。
    //每一帧都执行
    public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
    {
        //把实例化的CustomRenderPass插入渲染管线
        renderer.EnqueuePass(m_ScriptablePassRenderTextrue);
        renderer.EnqueuePass(m_ScriptablePassPost);
        renderer.EnqueuePass(m_ScriptablePassCreateMesh);

    }
}

五、RenderFeatrue参数赋值

1、VolumeComponent

在Volume中提供Render Feature读取参数数据,通过VolumeManager单列,拿到stack,可以直接取得Volume中的数据。

以修改上文后处理的饱和度和对比度为例

1)创建一个全局的Global Volume

右击Hierarchy面板空白处选择Volume→Global Volume

2)自定义一个VolumeComponent
c# 复制代码
using UnityEngine.Rendering;

public class VolumeComponentTest : VolumeComponent
{
    public ClampedFloatParameter Saturation = new ClampedFloatParameter(1f, 0, 3);
    public ClampedFloatParameter Contrast = new ClampedFloatParameter(1f, 0, 3);
}

可以在Volume中添加VolumeComponentTest组件

3)在shader中添加控制的参数

这添加了Saturation和Contrast两个控制参数

c 复制代码
Shader "Unlit/SaturationContrastShader"
{
    Properties
    {
        _Saturation("Saturation", Range(0, 3)) = 1.0
        _Contrast("Contrast", Range(0, 3)) = 1.0
    }
    SubShader
    {
        Pass
        {
           ZTest Always ZWrite Off
            CGPROGRAM

            #pragma vertex vert_img  // 使用内置的vert_img顶点着色器
            #pragma fragment frag

            #include "UnityCG.cginc"

            float _Saturation;
            float _Contrast;
            sampler2D _CameraColorTexture; 
            //饱和度
            fixed3   Saturation_float(float3 In, float Saturation)
            {
                float luma = dot(In, float3(0.2126729, 0.7151522, 0.0721750));
                return luma.xxx + Saturation.xxx * (In - luma.xxx);
            }
            //对比度
            fixed3  Contrast_float(float3 In, float Contrast)
            {
                float midpoint = pow(0.5, 2.2);
                return (In - midpoint) * Contrast + midpoint;
            }

            fixed4 frag (v2f_img i) : SV_Target
            {
                fixed4 col = tex2D(_CameraColorTexture, i.uv);
                fixed3  Saturation =  Saturation_float(col, _Saturation);
                fixed3  Contrast = Saturation_float(Saturation, _Contrast);
                return fixed4(Contrast.x, Contrast.y, Contrast.z,0.1);
            }
            ENDCG
        }
    }
   FallBack Off
}

用shaderGraph实现的也是一样的添加两个参数

4)在RenderFeatrue中读取参数

对CustomRenderShaderPostPass修过如下

c# 复制代码
  class CustomRenderShaderPostPass : ScriptableRenderPass
    {  
        //定义渲染材质,通过Create方法赋值
        public Material _Material;
        //定义一个VolumeComponentTest
        VolumeComponentTest volumeComponentTest;
        public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
        {
            //读取VolumeManager的单列取得stack
            var stack = VolumeManager.instance.stack;
            //为volumeComponentTest赋值
            volumeComponentTest = stack.GetComponent<VolumeComponentTest>();
            if (!volumeComponentTest) return;
            //直接从volumeComponentTest中读取数据
            _Material.SetFloat("_Saturation", volumeComponentTest.Saturation.value);
            _Material.SetFloat("_Contrast", volumeComponentTest.Contrast.value);
            //创建一个CommandBuffer
            CommandBuffer cmd = CommandBufferPool.Get("ShowShader");
            //这里有一个疑问,渲染标识符不管怎么传都不影响结果
            cmd.Blit(colorAttachment, RenderTargetHandle.CameraTarget.Identifier(), _Material);
            //执行CommandBuffer
            context.ExecuteCommandBuffer(cmd);
            //回收CommandBuffer
            CommandBufferPool.Release(cmd);
        }
    }

实现效果

2、代码直接赋值

在RenderFeatrue中开放参数,用代码直接赋值

以后处理的饱和度和对比度为例

1)开放参数

在CustomRenderPassFeatureTest和CustomRenderShaderPostPass下分别定义饱和度和对比度,并赋值

c# 复制代码
public class CustomRenderPassFeatureTest : ScriptableRendererFeature
{
  
    //后处理效果
    class CustomRenderShaderPostPass : ScriptableRenderPass
    {  
        //定义渲染材质,通过Create方法赋值
        public Material _Material;
        public float _Contrast;
        public float _Saturation;
        public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
        {
            _Material.SetFloat("_Saturation", _Saturation);
            _Material.SetFloat("_Contrast", _Contrast);
            //创建一个CommandBuffer
            CommandBuffer cmd = CommandBufferPool.Get("ShowShader");
            //这里有一个疑问,渲染标识符不管怎么传都不影响结果
            cmd.Blit(colorAttachment, RenderTargetHandle.CameraTarget.Identifier(), _Material);
            //执行CommandBuffer
            context.ExecuteCommandBuffer(cmd);
            //回收CommandBuffer
            CommandBufferPool.Release(cmd);
        }
    }
    CustomRenderShaderPostPass m_ScriptablePassPost;

    public Shader shaderPost;
    public float Saturation;
    public float Contrast;
    /// <inheritdoc/>
    public override void Create()
    {
        #region 后处理效果
        m_ScriptablePassPost = new CustomRenderShaderPostPass();
        //通过创建一个渲染材质
        m_ScriptablePassPost._Material = new Material(shaderPost);
        //将饱和度和对比度赋值给CustomRenderShaderPostPass
        m_ScriptablePassPost._Contrast = Contrast;
        m_ScriptablePassPost._Saturation = Saturation;
        //定义渲染的位置
        m_ScriptablePassPost.renderPassEvent = RenderPassEvent.AfterRendering;

        #endregion
    }

    // 这里你可以在渲染器中注入一个或多个渲染通道。
    //每一帧都执行
    public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
    {
        //把实例化的CustomRenderPass插入渲染管线
        renderer.EnqueuePass(m_ScriptablePassPost);
    }
}

2)对参数进行修改

c# 复制代码
//得到RenderFeatrue    
CustomRenderPassFeatureTest custom=renderData.rendererFeatures.OfType<CustomRenderPassFeatureTest>().FirstOrDefault();
//对值进行修改
custom.Saturation =0;
custom.Contrast =0;
//设置数据
custom.SetParam();
相关推荐
ellis19707 小时前
Unity插件SafeArea Helper适配异形屏详解
unity
nnsix9 小时前
Unity Physics.Raycast的 QueryTriggerInteraction枚举作用
unity·游戏引擎
地狱为王9 小时前
Cesium for Unity叠加行政区划线
unity·gis·cesium
小贺儿开发18 小时前
Unity3D 八大菜系连连看
游戏·unity·互动·传统文化
在路上看风景18 小时前
25. 屏幕像素和纹理像素不匹配
unity
ۓ明哲ڪ19 小时前
Unity功能——创建新脚本时自动添加自定义头注释
unity·游戏引擎
熬夜敲代码的小N19 小时前
Unity大场景卡顿“急救包”:从诊断到落地的全栈优化方案
java·unity·游戏引擎
派葛穆21 小时前
Unity-realvirtual-S7通讯快速配置(未完结)
unity·游戏引擎
w-白兰地1 天前
【Addressable远端加载资源】
unity·addressable·资源加载
小张不爱写代码1 天前
[Unity 技巧] 如何自定义 Inspector 变量显示名称 (CustomLabel)
unity·游戏引擎