1 3D 项目迁移到 URP 项目后出现的问题

​ 3D 项目迁移至 URP 项目后,会出现很多渲染问题,如:材质显示异常、GL 渲染不显示、多 Pass 渲染异常、屏幕后处理异常等问题。下面将针对这些问题给出一些简单的解决方案。

​ URP 官方教程和 API 详见→Universal RP 文档Universal RP API,URP 项目环境搭建详见→Shader Graph简介

​ 本文完整资源见→Renderer Feature实验

1)内置材质渲染异常

​ 在 Assets 窗口选中异常的内置材质,依次单击【Edit→Rendering→Materials→Convert Selected Built-in Materials to URP】,异常材质就会自动转换为 URP 内置材质,转换后的材质对应的 shader 为 "Universal Render Pipeline/Lit"。

2)GL 不渲染

​ 3D 项目中,在 MonoBehaviour的生命周期 的 OnPostRender 方法中执行 GL 命令(详见→使用GL绘制线段)。但是,URP 项目中 OnPostRender 方法不会回调,我们可以将 GL 命令移至 OnRenderObject 方法中,以实现 GL 渲染。

3)多 Pass 渲染异常

​ URP 项目中,我们可以修改 Tag 里的 LightMode 属性以实现多 Pass 渲染,如下。但是,该方案最多只允许渲染 3 个 Pass 通道。

Tags{ "LightMode" = "SRPDefaultUnlit" }
Tags{ "LightMode" = "UniversalForward" }
Tags{ "LightMode" = "LightweightForward" }

4)屏幕后处理异常

​ 3D 项目中,我们在 MonoBehaviour的生命周期 的 OnRenderImage 方法中实现屏幕后处理(详见→调整屏幕亮度、饱和度、对比度)。但是,在 URP 项目中,OnRenderImage 方法不会回调。所幸,Unity 为我们提供了 Rnederer Feature(详见第 3 节),为屏幕后处理提供了一个解决方案。另外,通过 Global Volume 也可以实现屏幕后处理特效。

2 Renderer Objects

2.1 Render Objects 简介

RenderObjects 是 ScriptableRendererFeature 的一种实现,它通过 RenderObjectsPass 渲染指定图层的对象。在 Assets 窗口选中 Universal Renderer Data 文件,在 Inspector 窗口依次点击【Add Renderer Feature→Render Objects】,如下。

​ 添加 Render Objects 后,显示如下。

  • Name:Render Objects 标识,建议修改,Frame Debugger 窗口中可以看到该标识符;
  • Event:渲染的时序点,该渲染事件在什么时序上执行,详见 RenderPassEvent;
  • Queue:渲染队列;
  • Layer Mask:渲染指定图层的游戏对象;
  • LightMode Tags:渲染路径,取值有:SRPDefaultUnlit、UniversalForward、LightweightForward;
  • Material:待渲染的材质,拖入材质后,需要设置 Pass Index(Pass 通道索引);
  • Depth:深度测试配置,开启后可以配置 Write Depth (写入深度缓存)和 Depth Test(深度测试,取值有 Less、Equal 等);
  • Stencil:模板测试配置,开启后可以配置 Value(模板参考值)、Compare Function(比较函数,取值有 Less、Equal 等)、Pass(模板测试和深度测试均通过时的策略,取值有 Keep、Replace 等)、Fail(模板测试未通过时的策略,取值有 Keep、Replace 等)、ZFail(模板测试通过,但深度测试未通过时的策略,取值有 Keep、Replace 等),
  • Camera:相机配置,开启后可以配置 Field of View(视角)、Position Offset(偏移)。
public enum RenderPassEvent {
BeforeRendering = 0, // 渲染前
BeforeRenderingShadows = 50, // 渲染阴影前
AfterRenderingShadows = 100, // 渲染阴影后
BeforeRenderingPrePasses = 150, // 渲染PrePasses前
[EditorBrowsable(EditorBrowsableState.Never)]
[Obsolete("Obsolete, to match the capital from 'Prepass' to 'PrePass' (UnityUpgradable) -> BeforeRenderingPrePasses")]
BeforeRenderingPrepasses = 151, // 渲染PrePasses前
AfterRenderingPrePasses = 200, // 渲染PrePasses后
BeforeRenderingGbuffer = 210, // 渲染Gbuffer前
AfterRenderingGbuffer = 220, // 渲染Gbuffer后
BeforeRenderingDeferredLights = 230, // 渲染延时光照前
AfterRenderingDeferredLights = 240, // 渲染延时光照后
BeforeRenderingOpaques = 250, // 渲染不透明物体前
AfterRenderingOpaques = 300, // 渲染不透明物体后
BeforeRenderingSkybox = 350, // 渲染天空盒子前
AfterRenderingSkybox = 400, // 渲染天空盒子后
BeforeRenderingTransparents = 450, // 渲染透明物体前
AfterRenderingTransparents = 500, // 渲染透明物体后
BeforeRenderingPostProcessing = 550, // 屏幕后处理前
AfterRenderingPostProcessing = 600, // 屏幕后处理后
AfterRendering = 1000 // 渲染前
}

2.2 Render Objects 应用

​ 本节将通过一个简单的描边特效介绍 Render Objects 的应用,更复杂的描边详见→选中物体描边特效基于模板测试和顶点膨胀的描边方法边缘检测特效基于深度和法线纹理的边缘检测方法

1)创建 Shader

​ Outline.shader

Shader "MyShader/Outline" {
Properties {
_OutlineColor("_OutlineColor", Color) = (1, 0, 0, 1) // 描边颜色
_OutlineWidth("Outline Width", Range(0.01, 1)) = 0.1 // 描边宽度
} SubShader {
Pass {
Cull Front // 关闭剔除渲染, 取值有: Off、Front、Back, Off表示正面和背面都渲染 CGPROGRAM
#include "UnityCG.cginc" #pragma vertex vert
#pragma fragment frag fixed4 _OutlineColor; // 描边颜色
float _OutlineWidth; // 描边宽度 struct a2v {
float4 vertex : POSITION; // 模型空间顶点坐标
float3 normal : NORMAL; // 模型空间法线向量
}; struct v2f {
float4 pos : SV_POSITION; // 裁剪空间顶点坐标
}; v2f vert(a2v v) {
v2f o;
float3 viewNormal = normalize(mul((float3x3)UNITY_MATRIX_IT_MV, v.normal)); // 观察空间法线向量
float3 viewPos = UnityObjectToViewPos(v.vertex); // 观察空间顶点坐标
o.pos = UnityViewToClipPos(viewPos + viewNormal * _OutlineWidth * (-viewPos.z) * 0.1); // 裁剪空间顶点坐标
return o;
} fixed4 frag(v2f i) : SV_Target {
return _OutlineColor;
} ENDCG
}
}
}

​ 说明:创建材质,并重命名为 OutlineMat,将 Outline.shader 拖拽到 OutlineMat 中。

2)创建 Render Objects

​ 在 Assets 窗口选中 Universal Renderer Data 文件,在 Inspector 窗口点击 Add Renderer Feature 添加 Render Objects,配置 Render Objects 如下。

3)运行效果

​ 给需要描边的物体设置图层为 Outline,效果如下。

3 自定义 Renderer Feature

​ 要使用 Renderer Feature,我们需要编写两个类:继承自 ScriptableRendererFeature 的 Feature 类和继承自 ScriptableRenderPass 的 Pass 类。

3.1 Renderer Feature 简介

1)创建 Renderer Feature

​ 在 Assets 窗口右键,弹出菜单栏,依次选择【Create→Rendering→URP Renderer Feature】,生成 CustomRenderPassFeature.cs 文件。

2)CustomRenderPassFeature

​ 打开 CustomRenderPassFeature.cs 文件如下,其中 Debug 日志是笔者添加的,为方便后文查看 Feature 生命周期。

using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal; public class CustomRenderPassFeature : ScriptableRendererFeature { // 自定义的Feature
class CustomRenderPass : ScriptableRenderPass { // 自定义的Pass
public override void OnCameraSetup(CommandBuffer cmd, ref RenderingData renderingData) { // 渲染Pass前回调, 此处可以申请内存
Debug.Log("CustomRenderPass-OnCameraSetup");
} public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData) { // 渲染执行逻辑
Debug.Log("CustomRenderPass-Execute");
} public override void OnCameraCleanup(CommandBuffer cmd) { // 渲染Pass后回调, 此处可以清除内存操作
Debug.Log("CustomRenderPass-OnCameraCleanup");
}
} CustomRenderPass m_ScriptablePass; // 自定义的Pass public override void Create() { // 创建自定义的Pass
Debug.Log("CustomRenderPassFeature-Create");
m_ScriptablePass = new CustomRenderPass();
m_ScriptablePass.renderPassEvent = RenderPassEvent.AfterRenderingOpaques; // 渲染事件注入的时机
} public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData) { // 将Pass添加到渲染队列中
Debug.Log("CustomRenderPassFeature-AddRenderPasses");
renderer.EnqueuePass(m_ScriptablePass);
}
}

3)添加自定义 Renderer Feature

​ 在 Assets 窗口选中 Universal Renderer Data 文件,在 Inspector 窗口依次点击【Add Renderer Feature→Custom Render Pass Feature】,如下。

​ 添加 Custom Render Pass Feature 后,显示如下,Name 建议修改,在 Frame Debugger 中可以看到该 Name,方便调试。

4)Renderer Feature 生命周期

​ 运行程序后,打印日志如下。

​ 由日志可以得出以下结论:

  • Render Feature 的执行时序是:Create→AddRenderPasses→OnCameraSetup→Execute→OnCameraCleanup;
  • Create 方法只在程序启动时执行几次,后面就不执行了;
  • AddRenderPasses、OnCameraSetup、Execute、OnCameraCleanup 方法每帧都会执行一次。

3.2 Renderer Feature 应用

​ 本节通过一个调整屏幕亮度、饱和度、对比度 的案例,介绍 Render Feature 在屏幕后处理中的应用。

1)创建自定义 Feature

​ FullscreenFeature.cs

using UnityEngine.Rendering.Universal;
using UnityEngine; public class FullscreenFeature : ScriptableRendererFeature {
public Settings settings = new Settings(); // 设置
FullscreenPass blitPass; // 后处理的Pass public override void Create() { // 创建后处理Pass(自动回调)
blitPass = new FullscreenPass(name);
} public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData) { // 添加渲染Pass(自动回调)
if (settings.blitMaterial == null) {
return;
}
blitPass.renderPassEvent = settings.renderPassEvent;
blitPass.settings = settings;
renderer.EnqueuePass(blitPass);
} [System.Serializable]
public class Settings { // 配置项
public RenderPassEvent renderPassEvent = RenderPassEvent.AfterRenderingOpaques;
public Material blitMaterial = null;
}
}

​ 说明:FullscreenFeature 不是单例, 可以在 Universal Renderer Data 中配置多实例(可能有多个屏幕后处理特效)。

2)创建自定义 Pass

​ FullscreenPass.cs

using UnityEngine.Rendering.Universal;
using UnityEngine.Rendering;
using UnityEngine; internal class FullscreenPass : ScriptableRenderPass {
public FullscreenFeature.Settings settings; // 配置项
private string profilerTag; // 分析器标签, 在Frame Debugger中可以看到该标签
private RenderTargetIdentifier source; // 源缓存标识
private RenderTargetIdentifier destination; // 目标缓存标识
private int destinationId; // 目标缓存id
private FilterMode filterMode; // 纹理采样滤波模式, 取值有: Point、Bilinear、Trilinear public FullscreenPass(string tag) {
profilerTag = tag;
destinationId = Shader.PropertyToID("_TempRT");
} public override void OnCameraSetup(CommandBuffer cmd, ref RenderingData renderingData) { // 渲染前回调
RenderTextureDescriptor blitTargetDescriptor = renderingData.cameraData.cameraTargetDescriptor;
blitTargetDescriptor.depthBufferBits = 0;
ScriptableRenderer renderer = renderingData.cameraData.renderer;
source = renderer.cameraColorTarget;
cmd.GetTemporaryRT(destinationId, blitTargetDescriptor, filterMode);
destination = new RenderTargetIdentifier(destinationId);
} public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData) { // 执行渲染
CommandBuffer cmd = CommandBufferPool.Get(profilerTag);
Blit(cmd, source, destination, settings.blitMaterial);
Blit(cmd, destination, source);
context.ExecuteCommandBuffer(cmd);
CommandBufferPool.Release(cmd);
} public override void FrameCleanup(CommandBuffer cmd) { // 渲染后回调
if (destinationId != -1) {
cmd.ReleaseTemporaryRT(destinationId);
}
}
}

3)创建 Shader

​ BrigSatuCont.shader

Shader "MyShader/BrightnessSaturationContrast" { // 调整亮度、饱和度、对比度
Properties{
_MainTex("Base (RGB)", 2D) = "white" {} // 主纹理
_Brightness("Brightness", Range(0.5, 3)) = 1 // 亮度
_Saturation("Saturation", Range(0.1, 5)) = 1 // 饱和度
_Contrast("Contrast", Range(0.4, 3)) = 1 // 对比度
} SubShader{
Pass {
// 深度测试始终通过, 关闭深度写入
//ZTest Always ZWrite Off CGPROGRAM
#pragma vertex vert_img // 使用内置的vert_img顶点着色器
#pragma fragment frag
#include "UnityCG.cginc" sampler2D _MainTex; // 主纹理
half _Brightness; // 亮度
half _Saturation; // 饱和度
half _Contrast; // 对比度 fixed4 frag(v2f_img i) : SV_Target { // v2f_img为内置结构体, 里面只包含pos和uv
fixed4 tex = tex2D(_MainTex, i.uv); // 纹理采样
fixed3 finalColor = tex.rgb * _Brightness; // 应用亮度_Brightness
fixed luminance = 0.2125 * tex.r + 0.7154 * tex.g + 0.0721 * tex.b; // 计算亮度
fixed3 luminanceColor = fixed3(luminance, luminance, luminance); // 饱和度为0、亮度为luminance的颜色
finalColor = lerp(luminanceColor, finalColor, _Saturation); // 应用饱和度_Saturation
fixed3 avgColor = fixed3(0.5, 0.5, 0.5); // 饱和度为0、亮度为0.5的颜色
finalColor = lerp(avgColor, finalColor, _Contrast); // 应用对比度_Contrast
return fixed4(finalColor, tex.a);
} ENDCG
}
} Fallback Off
}

​ 说明:创建材质,并重命名为 BrigSatuContMat,将 BrigSatuCont.shader 拖拽到 BrigSatuContMat 中。

4)添加 Feature

​ 在 Assets 窗口选中 Universal Renderer Data 文件,在 Inspector 窗口依次点击【Add Renderer Feature→Fullscreen Feature】,如下。

​ 将 BrigSatuContMat 材质拖拽到 Renderer Feature 中,并修改 Name 属性为 BrigSatuCont,如下。

5)运行效果

​ 通过修改 BrigSatuContMat 中 Brightness、Saturation、Contrast 属性,运行效果如下。左侧是原图,右侧是调整亮度、饱和度、对比度后的渲染效果。

6)动态获取 Feature

​ 用户如果想在 MonoBehaviour 中获取 Renderer Feature,可以通过以下代码片段获取。

private FullscreenFeature GetFeature(string name) { // 获取feature
UniversalRendererData rendererData = Resources.Load<UniversalRendererData>("Full Universal Renderer Data");
if (rendererData != null && !string.IsNullOrEmpty(name)) {
List<FullscreenFeature> features = rendererData.rendererFeatures.OfType<FullscreenFeature>().ToList();
foreach (FullscreenFeature feature in features) {
if (name.Equals(feature.name)) {
return feature;
}
}
}
return null;
}

​ 也可以通过以下工具类获取 Renderer Feature。

​ URPFeatureUtils.cs

using UnityEngine.Rendering.Universal;
using UnityEngine.Rendering;
using System.Reflection; public class URPFeatureUtils {
private static URPFeatureUtils instance; // 单例
private UniversalRendererData rendererData; // 渲染数据, 存储了feature列表 private URPFeatureUtils() {
rendererData = GetRendererData();
} public static T GetFeature<T>(string name) where T : ScriptableRendererFeature { // 获取feature
if (instance == null) {
instance = new URPFeatureUtils();
}
if (instance.rendererData != null) {
return GetFeature<T>(instance.rendererData, name);
}
return null;
} private static T GetFeature<T>(UniversalRendererData rendererData, string name) where T : ScriptableRendererFeature { // 获取feature
if (rendererData != null && !string.IsNullOrEmpty(name)) {
foreach (ScriptableRendererFeature feature in rendererData.rendererFeatures) {
if (feature is T && name.Equals(feature.name)) {
return (feature as T);
}
}
}
return null;
} private UniversalRendererData GetRendererData() { // 通过反射获取渲染数据, 也可以通过Resources.Load加载, 但是需要将UniversalRendererData文件放在Resources目录下
UniversalRenderPipelineAsset urpAsset = GraphicsSettings.renderPipelineAsset as UniversalRenderPipelineAsset;
FieldInfo propertyInfo = urpAsset.GetType().GetField("m_RendererDataList", BindingFlags.Instance | BindingFlags.NonPublic);
ScriptableRendererData[] rendererDatas = (ScriptableRendererData[])propertyInfo.GetValue(urpAsset);
if (rendererDatas != null && rendererDatas.Length > 0 && (rendererDatas[0] is UniversalRendererData)) {
return rendererDatas[0] as UniversalRendererData;
}
return null;
}
}

​ 说明:之所以要使用反射,因为 UniversalRenderPipelineAsset 中的 m_RendererDataList 属性不是 public 的,并且没有给外界提供获取 m_RendererDataList 的接口。

​ 声明:本文转自【Unity3D】Renderer Feature简介

【Unity3D】Renderer Feature简介的更多相关文章

  1. 探索未知种族之osg类生物---渲染遍历之Renderer::draw()简介

    我们今天进入上一节的遗留问题Renderer::draw()的探究. 1.从_drawQueue中取出其中一个sceneView对象.SceneView是对scene和view类的封装,通过他可以方便 ...

  2. 《探索未知种族之osg类生物》目录

    精力有限,博客园不在更新<探索未知种族之osg类生物>.在这里列出所有文章目录(持续更新)有兴趣的同学可以看看. 探索未知种族之osg类生物[目录] 前序 探索未知种族之osg类生物--- ...

  3. Qt Installer Framework 使用说明(三)

    目录 6.Qt Installer Framework 示例 7.参考 Reference 配置文件 Configuration File 配置文件元素的简要说明 Summary of Configu ...

  4. [转][osg]探索未知种族之osg类生物【目录】

    作者:3wwang 原文链接:http://www.3wwang.cn/html/article_58.html 前序 探索未知种族之osg类生物---起源 ViewBase::frame函数中的Vi ...

  5. Unity3D shader简介

    Unity3D shader简介 可以肯定的说Unity3D使得很多开发者开发游戏更容易.毫无疑问,shader(着色器)编码,仍有很长的路要走.shader是一个专门运行在GPU的程序,经常被神秘包 ...

  6. Unity3D学习笔记(一):Unity3D简介

    Unity3D简介一.什么是Unity.U3D?全面整合的专业引擎 二.这个软件能做什么?1.内容:3D2D游戏,教育,建筑,网页,VRAR,家庭娱乐系统2.媒体:PC平台,主机,移动,VR 1.UE ...

  7. GJM : Unity3D HIAR -【 快速入门 】 一、简介

    感谢您的阅读.喜欢的.有用的就请大哥大嫂们高抬贵手"推荐一下"吧!你的精神支持是博主强大的写作动力以及转载收藏动力.欢迎转载! 版权声明:本文原创发表于 [请点击连接前往] ,未经 ...

  8. 第一节Unity3D简介

    Unity是由Unity Technologies开发的一个让玩家轻松创建诸如三维视频游戏.建筑可视化.实时三维动画等类型互动内容的多平台的综合型游戏开发工具,是一个全面整合的专业游戏引擎.Unity ...

  9. 探索未知种族之osg类生物---渲染遍历之Renderer简介

    我们继续renderingTraversals()的探究.我们接着上一节的”阻塞渲染线程”后就要遍历所有摄像机的渲染器(Renderer),执行 Renderer::cull 场景筛选的操作.我们在r ...

  10. unity3d简介

    一.介绍: Unity3D软件:综合开发环境,实时三维动画等类型的多媒体内容,并支持这些内容在Windows.iOS.Android等多种平台的发布. Mono:脚本编程基于Mono技术,可使用Jav ...

随机推荐

  1. org.yaml.snakeyaml.error.YAMLException: java.nio.charset.MalformedInputException: Input length = 2

    1.报错 在运行SpringBoot项目时遇到报错: 17:44:47.558 [main] ERROR org.springframework.boot.SpringApplication -- A ...

  2. HanLP — 感知机(Perceptron)

    感知机(Perceptron)是一个二类分类的线性分类模型,属于监督式学习算法.最终目的: 将不同的样本分本 感知机饮食了多个权重参数,输入的特征向量先是和对应的权重相乘,再加得到的积相加,然后将加权 ...

  3. Mongo-文档主键-ObjectId

    文档主键 文档主键时 _id,如果插入文档时,没有传入则自动生产ObjectId 作为文档主键 文档主键要求在集合中唯一 文档主键可以时另一个文档,被当作字符串对象处理 ObjectId对象 获取文档 ...

  4. [转帖]Linux命令(51)——ipcs命令

    https://cloud.tencent.com/developer/article/1380589 1.命令简介 ipcs命令用于报告Linux中进程间通信设施的状态,显示的信息包括消息列表.共享 ...

  5. [转帖]警惕Oracle数据库性能“隐形杀手”——Direct Path Read

    一. 简介 Oracle 的11g版本正式发布到今天已经10年有余,最新版本也已经到了20c,但是Direct Path Read(直接路径读)导致性能问题的案例仍时有发生,很多12c的用户还是经常遇 ...

  6. [转帖]renice和nice

    https://www.cnblogs.com/qiynet/p/17555881.html 将行程 id 为 987 及 32 的行程与行程拥有者为 daemon 及 root 的优先序号码加 1 ...

  7. [转帖]TIKV扩容之刨坑填坑​

    01 背景 某tidb集群收到告警,TIKV 节点磁盘使用率85%以上,联系业务无法快速删除数据,于是想到扩容TIKV 节点,原先TIKV 节点机器都是6TB的硬盘,目前只有3TB的机器可扩,也担心r ...

  8. [转帖]Tiup 常用运维操作命令干货

    https://zhuanlan.zhihu.com/p/356031031 **导读**> 作者:杨漆> 16年关系型数据库管理,从oracle 9i .10g.11g.12c到Mysq ...

  9. [转帖]【存储测试】vdbench存储性能测试工具

    一.前言 1.介绍  vdbench是一个I/O工作负载生成器,通常用于验证数据完整性和度量直接附加(或网络连接)存储性能.它可以运行在windows.linux环境,可用于测试文件系统或块设备基准性 ...

  10. [转帖]linux系统目录结构介绍

    linux的目录结构 Linux系统各个目录的作用 /: 根目录.有且只有一个根目录.所有的东西都是从根目录开始.举个例子:当你在终端里输入"/home",你其实是在告诉服务器,先 ...