解决Unity的PostProcess奇怪报错

news2024/11/16 0:19:43

大家好,我是阿赵。
  最近在使用Unity的PostProcess后处理效果的时候,发现了一个问题,下面记录一下这个问题的出现原因和解决办法。

一、出现问题

  问题是这样出现的:
  在场景里面添加某一个后处理效果后,当这个后处理的PostProcessVolume对象被删除时,Unity会疯狂报错

MissingReferenceException: The object of type ‘PostProcessVolume’ has
been destroyed but you are still trying to access it. Your script
should either check if it is null or you should not destroy the
object.
UnityEngine.Rendering.PostProcessing.PostProcessManager.IsVolumeRenderedByCamera
(UnityEngine.Rendering.PostProcessing.PostProcessVolume volume,
UnityEngine.Camera camera) (at
Library/PackageCache/com.unity.postprocessing@3.1.1/PostProcessing/Runtime/PostProcessManager.cs:455)
UnityEngine.Rendering.PostProcessing.PostProcessManager.UpdateSettings
(UnityEngine.Rendering.PostProcessing.PostProcessLayer
postProcessLayer, UnityEngine.Camera camera) (at
Library/PackageCache/com.unity.postprocessing@3.1.1/PostProcessing/Runtime/PostProcessManager.cs:335)
UnityEngine.Rendering.PostProcessing.PostProcessLayer.UpdateVolumeSystem
(UnityEngine.Camera cam, UnityEngine.Rendering.CommandBuffer cmd) (at
Library/PackageCache/com.unity.postprocessing@3.1.1/PostProcessing/Runtime/PostProcessLayer.cs:903)
UnityEngine.Rendering.PostProcessing.PostProcessLayer.BuildCommandBuffers
() (at
Library/PackageCache/com.unity.postprocessing@3.1.1/PostProcessing/Runtime/PostProcessLayer.cs:541)
UnityEngine.Rendering.PostProcessing.PostProcessLayer.OnPreCull () (at
Library/PackageCache/com.unity.postprocessing@3.1.1/PostProcessing/Runtime/PostProcessLayer.cs:466)

  我做后处理的方式是,通过AssetBundle加载美术做好的已经添加了PostProcessVolume的物体的Object对象,然后实例化这个物体的同时修改它的layer为指定的层,给挂了PostProcessLayer的摄像机去渲染后处理效果。
  根据分析之后,其实问题是出在实例化对象同时改变layer操作导致的,下面来具体分析一下。

二、PostProcess后处理的正常运作

  在解决这个问题之前,我们要先知道PostProcess整一套过程实际上是怎样进行的。

1、几个基础概念

这套PostProcess后处理系统,它实际上有三个主要组成部分

1.PostProcessLayer

  这是挂在摄像机上面的一个脚本,他的作用是可以指定摄像机只给指定layer的对象渲染时添加后处理效果。

2.PostProcessVolume

  这个脚本并不是挂在摄像机上面的,而是应该建立一个空物体,然后挂在上面。它的作用是定义实际后处理效果的。比如它是全局的,还是范围的,它是属于哪个layer的,它包含了哪些后处理效果,参数如何。
  我看到很多美术同事在使用这个后处理的时候都是用错的了。因为旧版本的Unity后处理脚本,是直接挂在摄像机上面的,所以对于这个新版本的后处理系统,他们也是把PostProcessLayer和PostProcessVolume同时挂在摄像机上。这种做法是绝对错误的,因为volume本身还包含了指定layer和触发范围的设置。如果把volume挂在摄像机上,那么会导致各种混乱的问题。

3.PostProcessManager

  这个脚本是一个管理类,并不需要我们操作的。之前我们挂的PostProcessVolume和PostProcessLayer都是注册在PostProcessManager上,并且通过PostProcessManager来读取需要的数据渲染的。

2、渲染的过程

1.volume的注册和反注册

  当带有PostProcessVolume的对象在场景创建或者激活时,会通过OnEnable生命周期,调动PostProcessManager的Register方法,把自己注册在管理类里面
  当带有PostProcessVolume的对象被删除,或者不激活时,会通过OnDisable生命周期,调用PostProcessManager的Unregister方法,把自己从管理类里面移除
  当带有PostProcessVolume的对象的layer被改变时,会先通过Unregister方法,从PostProcessManager里面删除,再通过Register方法重新添加
  PostProcessManager里有2个很重要的变量要了解的
第一个是readonly List m_Volumes;
  这个变量是存储场景里面当前存在并能渲染的所有PostProcessVolume对象
第二个是readonly Dictionary<int, List> m_SortedVolumes;
  这个字典对象,是排序用的,通过layer作为key,后面是使用这个layer的所有PostProcessVolume对象的排序数组

2.PostProcessLayer的渲染

  当场景里面存在带有PostProcessLayer的摄像机时,在OnPreCull生命周期时,会调用渲染方法。通过自身指定的layer,从PostProcessManager的SortedVolumes里面获取有没有当前layer的volume,如果有,则取出来,逐个渲染。

三、出现问题的原因分析

  由于我的做法是在实例化的同时,改变了GameObject的layer,所以在运行的时候,实际的流程是这样的:
1、通过Instantiate实例化,调用了PostProcessVolume的OnEnable方法,然后调用到PostProcessManager的Register方法。这时候传进来的layer,是物体实例化时的layer。Register方法把volume添加到m_Volumes总列表。
  由于排序用的m_SortedVolumes还没有当前layer作为key的数组,所以并不会直接添加到m_SortedVolumes,而只是SetLayerDirty,而由于SetLayerDirty其实也是基于m_SortedVolumes本身已经存在的layer的,这时候m_SortedVolumes是新的,还没有任何layer信息,所以实际上SetDirty是什么都没做。
在这里插入图片描述
在这里插入图片描述

2、在PostProcessLayer的OnPreCull方法里面,会刷新当前的层所用到的Volume。所以会拿着PostProcessLayer指定渲染的layer作为key去m_SortedVolumes获取当前layer里面的所有Volume。
由于之前的注册方法并没有真的添加到m_SortedVolumes这个排序的集合里面,所以到了PostProcessManager的GrabVolumes方法,会根据之前添加到m_Volumes总列表里面的所有volume做一次遍历,找到和当前PostProcessLayer相同层的volume,添加到排序列表SortedVolumes对应的layer。
  值得注意的是,这时候获取的volume是它所在的GameObject的layer。由于当前是在OnPreCull方法里调用的,OnPreCull生命周期的执行,是在相机消隐之前,也就是说,他是比刚才的同步实例化和修改layer要晚的。刚才实例化volume对象的同一帧里面,我改变了layer,将会导致这里添加的是GameObject改变后的layer。
在这里插入图片描述

3、假如我们之前改变了volume的GameObject的layer,那么在PostProcessVolume的Update方法,也就是下一帧,它发现了物体的layer发生改变,会调用PostProcessManager.instance.UpdateVolumeLayer方法,去改变volume的注册。
在这里插入图片描述

  具体的操作是,先反注册旧的layer,再添加新的layer注册
在这里插入图片描述

4、这里问题就来了
  由于第2步添加的时候,是使用GameObject的实际layer的,所以旧layer没有添加到排序,所以也卸载不到,然后新layer其实已经在排序列表里面存在了,现在再次添加,PostProcessManager里面并没有判断是否已经存在,就变成了同一个volume重复添加了,这时候,排序列表里面,就有2个一样的volume对象。
在这里插入图片描述

  假如现在我们把这个volume删除,按正常的流程,会通过PostProcessVolume的OnDisable方法去反注册volume,就会导致一个问题,因为重复添加了,所以列表里面有2个对象,但反注册的时候,只把一个删掉了,另外一个残留了。当我们再次添加同一个layer的volume时,排序的列表就变成了残留了一个已经被删掉的volume
在这里插入图片描述

  当PostProcessManager逐个layer根据排序列表渲染的时候,就会发现有一个volume是空的,然后就报错了
在这里插入图片描述
  从代码看,这个报错只会出现在unity2018.3以上版本的编辑器里面。不过很明显,出现这个错误的时候,管理类里面的数据已经出现了错乱了。

四、解决问题

  知道了问题之后,要解决这个问题就变得很简单了。有很多个方案可以尝试:

1、修改源码

  这是最直接的办法了,只需要把PostProcessManager里面添加到排序队列m_SortedVolumes时,判断一下是否已经存在,如果存在就不添加,就解决了。
  不过由于PostProcess现在是以插件的形式从PackageManager下载的,如果要修改,最好是官方去修改。

2、设置原始资源的layer

  如果在实例化volume的过程不需要修改layer,这个问题自然也不存在了。所以如果项目规划得比较详细,每个volume使用的场合都很明确,那么可以在美术制作的时候就把layer设置好。
  但这样做有2个问题
1.对美术同事的要求比较高,因为美术同事对这些参数关注度很低,很可能会忘记设置或者设置错
2.如果同一个volume需要使用在不同的场合,需要不同的layer,就不能这么做了。

3、不要在同一帧实例化和改变layer

  先实例化volume对象,然后延后一帧,再修改它的layer。这样就能避免刚才的问题了。不过这个方案要开计时器之类做延迟一帧处理。

4、设置layer前先隐藏

  在要设置volume的layer之前,先把它SetActive为false,然后同一帧修改layer,再同一帧把它SetActive回true。这样,实际上PostProcessManager会在SetActive为false时先同步反注册一次,在修改layer的时候,由于不处于激活状态,所以Update不会走,也不会触发UpdateVolumeLayer方法,然后在SetActive回true的时候,OnEnable方法会把当前layer设置正确,并重新注册一次,这样就不会有问题了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/731475.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

JUC之ThreadLocal

文章目录 1 基础知识1.1 强软弱虚四种引用 2 ThreadLocal出现的好处3 ThreadLocal源码分析3.1 ThreadLocal内存泄露问题3.2 ThreadLocal为什么使用的是弱引用3.3 清扫过期的Entry 4 ThreadLocal使用建议 1 基础知识 1.1 强软弱虚四种引用 【整体结构】 【强引用】 【软引用…

(文件[夹]批量分类整理_多级匹配_交叉匹配_路径结构交叉调整)文件[夹]批量复制

​ 首先&#xff0c;需要用到的这个工具&#xff1a; 百度 密码&#xff1a;qwu2 蓝奏云 密码&#xff1a;2r1z ​如果没有使用过此工具&#xff0c;建议阅读此教程前&#xff0c;先看下这篇文章&#xff1a;https://mp.csdn.net/mp_blog/creation/editor/113605549 原理&…

【Python爬虫与数据分析】文件读写

目录 一、概述 二、txt文件读写 三、csv文件读写 四、Json文件读写 一、概述 读写模式&#xff1a; r&#xff1a;读数据w&#xff1a;覆盖写入a&#xff1a;追加写入rb&#xff1a;以二进制形式读出wb&#xff1a;以二进制形式写入ab&#xff1a;以二进制形式追加r、w、…

Java log 日志

文章目录 log4j.properties配置 log4j.properties 配置 log4j.rootLoggerINFO, stdoutlog4j.appender.stdoutorg.apache.log4j.ConsoleAppender log4j.appender.stdout.layoutorg.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern%d{yyyy-MM-dd H…

MySQL单表查询练习题

目录 第一题 第二题 第三题 第一题 1.创建数据表pet&#xff0c;并对表进行插入、更新与删除操作&#xff0c;pet表结构如表8.3所示。 (1&#xff09;首先创建数据表pet&#xff0c;使用不同的方法将表8.4中的记录插入到pet表中。 mysql> create table pet( name varchar(…

网络安全(黑客)内网渗透基础知识

0x01 内网概述 内网也指局域网&#xff08;Local Area Network&#xff0c;LAN&#xff09;是指在某一区域内由多台计算机互联成的计算机组。一般是方圆几千米以内。局域网可以实现文件管理、应用软件共享、打印机共享、工作组内的历程安排、电子邮件和传真通信服务等功能。 内…

交叉导轨的结构与特长

在交叉导轨中&#xff0c;精密滚柱互相直交地组合在一起的滚柱保持架与设置在专用轨道上的90V形沟槽滚动面组合起来使用。通过将2列滚子导轨平行地装配&#xff0c;使导轨系统能承受4个方向的负荷。而且&#xff0c;因能向交叉滚子导轨施加预压&#xff0c;从而能获得无间隙且高…

zookeeper单机安装

1 检查环境jdk 参考&#xff1a;https://blog.csdn.net/weixin_44098426/article/details/128446376 2 解压安装包 mkdir -p /opt/zookeeper mv /home/wh/software/zk/apache-zookeeper-3.5.7-bin.tar.gz /opt/zookeeper tar -xzvf apache-zookeeper-3.5.7-bin.tar.gz 3 配置…

在Windows环境下安装Elasticsearch 8.8.2

Elasticsearch是一种开源的分布式搜索和分析引擎&#xff0c;被广泛应用于构建实时搜索、日志分析、数据可视化等应用。本文将详细介绍如何在Windows环境下安装和配置Elasticsearch 8。 安装Elasticsearch 步骤1&#xff1a;准备工作 在开始安装之前&#xff0c;确保已满足以…

多元回归预测 | Matlab灰狼算法(GWO)优化极限梯度提升树XGBoost回归预测,GWO-XGBoost回归预测模型,多变量输入模型

文章目录 效果一览文章概述部分源码参考资料效果一览 文章概述 多元回归预测 | Matlab灰狼算法(GWO)优化极限梯度提升树XGBoost回归预测,GWO-XGBoost回归预测模型,多变量输入模型 评价指标包括:MAE、RMSE和R2等,代码质量极高,方便学习和替换数据。要求2018版本及以上。 部分源…

GPON MAC SFP ONU模块介绍与应用

伴随着网络通讯技术的发展&#xff0c;pon无源光网络正逐步走进人们的视野&#xff1b;在这之前你是否仅知道以太网接入&#xff1f;相比与以太网接入&#xff0c;pon作为一种点到多点网络&#xff0c;具有运维成本低、服务范围广、资源占用少等优势&#xff1b;我们最为熟知的…

CSS自学框架之漂浮提示(上方显示 底侧显示 右侧显示 左侧显示)

漂浮提示可以用较为隐藏的方式为用户提供帮助或引导信息&#xff0c;将鼠标移入文字内容后展示。 css代码&#xff1a; /* 浮漂提示框 */[myth-tag]{ position: relative }[myth-tag]:before, [myth-tag]:after{z-index: 1;opacity: 0;position: absolute;pointer-events: non…

LVS负载均衡集群之LVS-NAT集群

目录 一、什么是集群&#xff1f; 1.1、集群&#xff08;cluster&#xff09;的含义 1.2、问题 1.3解决方法 二、集群分类 2.1、负载均衡集群(Load Balance Cluster) 2.2、高可用集群(High Availability Cluster) 2.3、高性能运算集群 (High Performance Computer Clust…

企业服务器数据库被360后缀勒索病毒攻击后的正确处理方式

在数字化时代&#xff0c;企业服务器数据库的安全性至关重要。然而&#xff0c;即使采取了各种安全措施&#xff0c;仍然有可能遭受到360等后缀的勒索病毒网络攻击。近期&#xff0c;很多企业的云服务与物理服务器遭到了360后缀的勒索病毒攻击&#xff0c;导致企业内部数据库中…

能够ping通服务器的同时端口不通的排查方法

概述 当您在客户端访问目标服务器时&#xff0c;如果能ping通&#xff0c;但业务端口无法访问&#xff0c;您可以参见以下方法进行排查。 步骤一&#xff1a;实例安全组检查 1、登录ECS管理控制台&#xff0c;单击实例。 2、在顶部菜单栏左上角处&#xff0c;选择地域。 3、…

Visual Studio下2022Opencv的配置

Visual studio2022 opencv的配置 先从官网下载opencv &#xff1a; opencv releases 下载 ​ 我这里的开发环境是window版本&#xff0c;我们选择Windows版本进行下载 点开下载的文件&#xff0c;输入需要保存的路径 记住你保存的路径(我这里安装再F:) 安装需要时间静等安装…

修改Jeecg-boot context-path(附加图片+Nginx配置)

文章目录 需求描述解决方案前端后端Nginx 需求描述 前端框架采用的是ant design vue ,后端框架采用的是jeecg-boot,前端请求后端地址中包含jeecg-boot字样&#xff0c;想修改为其它的名字&#xff0c;修改方式如下 解决方案 前端 第一步 request.js文件路径&#xff1a;src…

2个好用的ftp和ssh工具推介

为什么不用xshell和xftp,是不好用吗&#xff1f;xshell和xftp虽然好用&#xff0c;而且也有免费版&#xff0c;但是&#xff0c;一个方面使用有限制&#xff0c;再就是你看见这个报错的弹窗烦不烦&#xff1f; 一、ssh工具-WindTerm WindTerm[1] 是一个基于 C 开发的开源终端模…

【Go】Go 语言教程--GO条件和循环语句(八)

往期教程&#xff1a; Go 语言教程–介绍&#xff08;一&#xff09;Go 语言教程–语言结构&#xff08;二&#xff09;Go 语言教程–语言结构&#xff08;三&#xff09;Go 语言教程–数据类型&#xff08;四&#xff09;Go 语言教程–语言变量&#xff08;五&#xff09;Go …

红队打靶,红日系列,红日靶场2

文章目录 描述&#xff1a;环境搭建:外网渗透&#xff1a;漏洞利用冰蝎连接&#xff0c;拿到shell 内网渗透转站CSCS插件提权 域渗透与横向移动 描述&#xff1a; 红队实战系列&#xff0c;主要以真实企业环境为实例搭建一系列靶场&#xff0c;通过练习、视频教程、博客三位一…