【Unity】RenderFeature笔记
RenderFeature是在urp中添加的额外渲染pass,并可以将这个pass插入到渲染列队中的任意位置。内置渲染管线中Graphics 的功能需要在RenderFeature里实现,常见的如DrawMesh和Blit
可以实现的效果包括但不限于
-
后处理,可以编写shader对整个渲染画面进行修改。比如一些全局特效包括:场景描边,地形扫描;
-
创建网格,通过DrawMesh绘制定义的网格;
-
渲染画面,将场景渲染到纹理;
-
...
一、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();