unity urp源码学习一(渲染流程)
sprt的一些基础:
绘制出物体的关键代码涉及设置shader标签(例如"LightMode" = "CustomLit"),以确保管线能够获取正确的shader并绘制物体。排序设置(sortingSettings)管理渲染顺序,如不透明物体从前至后排序,透明物体从后至前,以减少过绘制。币圈源码2019逐物体数据的启用、动态合批和gpuinstance支持,以及主光源索引等配置均在此进行调整。
过滤规则(filteringSettings)允许选择性绘制cullingResults中的几何体,依据RenderQueue和LayerMask等条件进行过滤。
提交渲染命令是关键步骤,无论使用context还是commandbuffer,调用完毕后必须执行提交操作。例如,context.DrawRenderers()用于绘制场景中的网格体,本质上是执行commandbuffer以渲染网格体。
sprt管线的基本流程涉及context的命令贯穿整个渲染流程。例如,首次调用渲染不透明物体,随后可能调用渲染半透明物体、天空盒、特定层渲染等。流程大致如下:
多相机情况也通过单个context实现渲染。
urp渲染流程概览:
渲染流程始于遍历相机,如果是游戏相机,则调用RenderCameraStack函数。此函数区分base相机和Overlay相机:base相机遍历渲染自身及其挂载的Overlay相机,并将Overlay内容覆盖到base相机上;Overlay相机仅返回,不进行渲染操作。
RenderCameraStack函数接受CameraData参数,其中包含各种pass信息。添加pass到m_ActiveRenderPassQueue队列是关键步骤,各种pass类实例由此添加至队列。
以DrawObjectsPass为例,其渲染流程在UniversialRenderer.cs中实现。首先在Setup函数中将pass添加到队列,执行时,执行队列内的pass,并按顺序提交渲染操作。
研究快速修改Unity构建包内的资源文件
本文将探讨如何实现快速修改Unity构建包内资源文件的方法,目标是使整个操作耗时控制在分钟级别,且无需依赖Unity引擎。nodejs源码以一个Resource目录下的空节点Prefab为例,首先,找到其构建后对应的资源文件。预制体构建后,文件以prefab的guid命名,存放在名为Data的二进制文件中。要对这个Data文件进行修改,需了解其格式,但Unity官方文档并未提供详细的二进制文件格式定义,也没有提供将Prefab资源转化为Data二进制文件的API。因此,我们借助开源工具AssetStudio查看Unity Data二进制文件格式,并从源码中找到解析二进制文件的逻辑。
我们得知,Prefab的二进制Data资源是SerializedFile格式,其解析逻辑位于SerializedFile.cs代码文件中。SerializedFile包含文件头和数据部分,文件头格式在Unity版本中如下所示。
文件头格式包含了多个字段,部分字段含义可以通过测试推断。例如,当给Prefab挂载自定义脚本时,发现localSerializedFileIndex和localIdentifierInFile字段表示引用文件1中的第个对象,即全局游戏管理器文件中的第个MonoScript对象。通过对全局游戏管理器文件的分析,我们发现第个对象正是创建的脚本。
了解SerializedFile的结构后,接下来需要解决如何根据Editor中编辑的Prefab替换构建好的包内对应Prefab的二进制内容。Prefab文件在Editor中以文本格式存储,格式为Unity修改后的YAML格式。我们发现,YAML格式与SerializedFile相似,都将树形结构的对象平铺化,并使用文件内的唯一索引进行引用。
然而,YAML中的信息与SerializedFile格式并非完全对应,比如自定义脚本的信息,YAML中使用脚本的guid,而SerializedFile中使用的是全局游戏管理器文件中的脚本对象索引。为了解决guid到全局游戏管理器文件中脚本对象的映射关系,我们采取了以下思路:通过类型信息作为关联键,建立guid与文件内对象索引的影视解析源码映射关系。首先,编写脚本从Editor中获取所有类型信息,建立guid到类型信息的映射。然后,解析全局游戏管理器文件获取类型信息到文件内索引的映射。这样一来,就可以将YAML中的脚本引用guid序列化为localFileID。
在替换构建好的包内Prefab的二进制内容时,头信息描述了每个对象数据的起始位置和大小。我们接下来关注对象是如何序列为二进制数据的。不同的对象类型具有不同的序列化格式,我们以GameObject类型为例进行介绍。
每个Prefab节点包含一个GameObject对象,Inspector面板显示的名字、Tag等信息来源于GameObject对象。GameObject的序列化格式相对简单。在从YAML到SerializedFile的对应关系中,我们看到一些Editor环境下使用的数据在构建中不需要序列化。在SerializedFile中,挂载的组件对象使用Component指针数组表示,包含PathID和FileID信息。PathID表示对象的索引,FileID为0表示是内部文件对象。
Transform对象也包含在每个节点中,其中包含Vector3和Quaternion数据类型,这些数据在YAML中以多维浮点数保存,在SerializedFile中同样以这种方式保存。
在介绍更多的Unity内置组件类型之前,我们先关注最常用的自定义组件类型,即MonoBehaviour类的子类。MonoBehaviour对象在YAML中结构清晰,首先引用自定义脚本类文件,然后是所有可序列化字段的数据。在SerializedFile格式中,首先是指向自定义组件类的引用数据,接着是所有可序列化字段数据。组件类引用数据结构包括PathID和FileID。
在测试序列化简单字段时,我们发现序列化的字段都是4字节对齐的。要实现YAML中MonoBehaviour字段的序列化写入,我们采取了一种方法,即通过反射从Unity工程中找到对应的论坛网站源码类型,然后使用Mono Cecil库静态分析Assembly-CSharp.dll以提取类型信息。有了类型信息后,我们就需要考虑如何序列化。
序列化过程有两种方向,其中一种需要了解Unity支持的字段序列化规则,筛选出类型的可序列化字段。然而,考虑到实现目标是脱离Unity工程的独立工具,我们选择另一种方案,即从构建好的包体中寻找类型信息。通过Il2CppDumper从globalmetadata中静态提取类型信息,我们查看Assembly-CSharp.dll以确认包含类型的字段信息。
在处理其他Native Component时,除了GameObject和Transform之外,Unity还提供了丰富的原生组件。一些常见组件的YAML和SerializedFile映射关系如下所示。对于其他几十种原生组件的映射关系,需要通过耐心测试来摸索清楚。
此外,Prefab可能引用贴图、字体、材质以及其他Prefab等外部依赖资源。构建包的测试表明,如果一个Prefab通过脚本引用了其他Prefab,如TestPrefab和TestPrefab,那么在构建的序列化文件中,头信息的externals会包含TestPrefab和TestPrefab的guid。序列化的脚本引用字段分别指向这两个资源。
对于引用的内置资源,如字体文件,构建包内的资源文件为unity default resources。序列化的脚本资源值为特定格式,如{ fileID:2, pathID:},对应的YAML引用信息为。
对于内置资源,其type为0,且fileID为序列化后的文件内对象索引。因此,需要解决guid到内置资源路径的映射问题。由于内置资源数量有限且固定,我们选择先人工维护映射关系。
在处理图集资源时,偷源码遇到困难,因为它没有将图集Prefab作为externals引用,而是引用了sharedassets0.assets文件。项目中存在大量sharedassetsXXX.assets文件,不知道如何从图集Prefab的guid关联到sharedassets0.assets文件。我们推测,Unity在构建过程中会擦除一些项目资源文件的guid信息,转而使用externals依赖形式。
至此,研究暂时陷入困境。我们希望后续能有其他方法解决这个问题。本文使用Zhihu On VSCode创作并发布。
Unity加密Assembly-CSharp.dll
项目中的cs代码被打包进Assembly-CSharp.dll中,并通过Mono调用。项目的加密主要针对Assembly-CSharp.dll,加载时进行解密。采用xz库对Assembly-CSharp.dll进行加密和压缩后移位,解密过程则相反。加解密算法已定义好。
在Unity-Technologies/mono中实现解密。首先,从对应当前项目版本的mono源码中获取。然后,重新生成针对特定架构(如armeabi-v7a、x)的libmono.so。解密算法需应用在mono/metadata/image.c中的mono_image_open_from_data_with_name函数。生成libmono.so后,每次打包替换工程中的libmono.so。
生成libmono.so的步骤包括安装工具、修改源码、执行脚本生成so文件。注意优化选项,如使用-g去掉debug符号、--gc-sections去掉无用代码。优化后,so文件尺寸减小,性能提升。编译选项影响so文件是否可启动和性能。
加密过程为:每次打包后,都需要重新加密Assembly-CSharp.dll。编写加密工具,每次Unity导出Android工程或反编译apk包后,对Assembly-CSharp.dll进行加密。使用xz库进行压缩,确保文件体积减小。
Unity的屏幕后处理效果(1)
Unity中,屏幕后处理效果是通过在渲染场景后对图像进行后期处理,以实现诸如景深和运动模糊等视觉特效。这通常在OnRenderImage函数中实现,Unity通过Graphics.Blit函数处理渲染纹理,根据需要进行各种操作后再显示到屏幕上。
首先,基础的屏幕后处理脚本系统建立在OnRenderImage函数上,它获取渲染图像并允许我们在其中进行自定义处理。Unity提供了Grapbics.Blit的不同版本,以适应不同的操作需求。在实现时,需要确保平台支持、Shader兼容性等条件。
PostEffectsBase.cs是一个基础类,用于简化屏幕后处理的实现,其核心功能包括检查资源、绑定到摄像机、创建材质等。子类需覆盖Start、CheckResources或CheckSupport函数,设置所需的Shader并创建材质。Shader的选择和使用至关重要,它决定了特效的最终效果。
以调整亮度、饱和度和对比度为例,通过编写BrightnessSaturationAndContrast.cs脚本,继承PostEffectsBase,并声明所需的Shader和材质。在OnRenderImage函数中,我们检查材质是否可用,然后调用Graphics.Blit进行处理,否则显示原始图像。
Shader部分包含属性声明,如纹理坐标和亮度、饱和度、对比度调整属性。顶点着色器负责传递正确的纹理坐标给片元着色器,片元着色器则使用lerp和tex2D函数实现效果调整。Shader的Fallback部分确保了在不支持的情况下的渲染方式。
最后,通过设置Shader参数的默认值,可以简化脚本与摄像机的关联,使得拖放至摄像机时自动使用相应的Shader。《Unity Shader入门精要》提供了详细的指导,帮助开发者理解和实践屏幕后处理效果。
UnityShaderBlur 模糊(5)
本节介绍模糊算法。模糊、边沿查找本质是一个滤波器,使用低通滤波/高通滤波来完成图像处理的目的。这些似乎涉及CV(计算机视觉)的研究范围,本文旨在展示如何在Unity中实现模糊效果,而非深入研究“信号与系统”的理论知识。做工程讲究实用性,能用即可。 后续更新:在Unity中实现模糊算法的步骤。一、在Unity中的前期准备
在上一节中,我们了解到如何在Unity URP中实现后处理效果。为了扩展PostProcessing相关组件(现在称为Volume),我们需要编写一个cs脚本,继承于VolumeComponent。Volume组件包含了常用的后处理特效,如Bloom(辉光)与ToneMapping(色调映射),可以显著提升画面效果。尽管Volume组件本身没有提供模糊功能,我们可以通过自定义实现。二、模糊算法实现
虽然高斯模糊是模糊算法的代表,但还有其他方式,如方框模糊、径向模糊、双重模糊、Kawase模糊等。本节将实现其中几种自认为实用的模糊效果。1. 模糊的本质
当我们感觉图像“模糊”时,实际上是因为像素点的颜色受到周围区域颜色的影响,使得像素点的“对比度”下降,颜色特征不鲜明。这种现象可以类比于人眼在远视状态下的视觉体验,物体反射的光线无法聚焦在视网膜上,形成多个“重影”,增加了模糊感。在图像处理中,模糊算法通过“污染”像素点的颜色,达到“模糊”效果。2. Kawase Blur
Kawase模糊采用动态卷积核来提升效率,不同于高斯模糊或方框模糊中的恒定卷积核。其核心思路是对距离当前像素较远的位置进行采样,并在两个大小相等的纹理之间进行乒乓式的blit操作。通过随迭代次数移动的blur kernel,实现更加灵活且高效的模糊效果。3. Dual Blur
双重模糊通过降采样和升采样过程实现,即在blit过程中进行图像大小的调整。相比于Kawase模糊,双重模糊在实现上更加复杂,但能提供不同场景下的模糊效果。4. Radial Blur
为了在图像中产生“速度感”和“打击感”,径向模糊是最常用的技术。通过在指定方向上进行模糊处理,可以模拟出物体移动或光线照射的效果。实现径向模糊时,需要调整纹理和采样方向,以达到预期的视觉效果。小结
通过上述方法,我们能够在Unity URP中实现常见的模糊算法。在实际应用中,可以根据需求选择最合适的模糊类型,例如,Kawase模糊在性能和效果上通常被认为是一个不错的选择。模糊算法在后处理中有着广泛的应用,如体积光处理,能够有效提升场景的视觉效果。Unity3D 封装自己的帧动画组件播放动画详解
Unity3D 是一款强大的游戏开发引擎,提供丰富功能和工具,方便开发者创建各类游戏。帧动画是游戏中的常见动画效果,通过连续播放静态模拟动态效果。本文介绍封装自定义帧动画组件及播放动画的方法。
首先,创建"FrameAnimation.cs"脚本文件。此脚本是组件的核心,包含序列化字段:frames(Sprite数组,存储帧)和 frameRate(每秒帧数,控制播放速度)。Awake方法获取SpriteRenderer组件用于渲染动画。Start方法计算每帧时间间隔,设置当前帧为0,启动PlayAnimation方法播放动画。
PlayAnimation方法检查frames数组是否为空。非空时,设置当前帧为数组的第一个元素,通过Invoke方法延迟调用NextFrame方法切换下一帧。NextFrame方法判断当前帧是否为最后一帧,是则重置索引为0,否则增加索引,再次调用PlayAnimation播放下一帧。
创建新游戏对象,挂载FrameAnimation脚本,拖拽帧至frames字段,调整frameRate字段值。运行游戏,观察动画以指定帧率连续播放。
通过封装自定义帧动画组件,实现简单播放器,组件具备调节播放速度功能。根据需求扩展组件,如添加循环、逆向播放等。本文旨在帮助理解Unity3D中的帧动画播放,如有疑问或建议,随时提问。
unity按钮切换场景代码?
在Unity中实现按钮切换场景的功能,可以通过以下步骤完成:添加场景到Build Settings:
打开菜单栏中的 File -> Build Settings。
将需要切换的场景拖动到窗口中,确保它们已经被添加。
创建按钮:
在场景中创建一个UI按钮(Button),并确保它的 Button 组件已正确设置。
编写脚本:
创建一个新的C#脚本,例如 SceneSwitcher.cs,并将其附加到一个游戏对象上(可以是按钮本身或其他对象)。
在脚本中引入必要的命名空间,并实现场景切换的逻辑。以下是一个简单的示例代码:
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
public class SceneSwitcher : MonoBehaviour
{
public Button switchButton; // 在Inspector中拖拽按钮
void Start()
{
switchButton.onClick.AddListener(SwitchScene); // 添加点击事件监听
}
void SwitchScene()
{
// 使用场景名称切换
SceneManager.LoadScene("Scene2"); // 替换为你的目标场景名称
// 或者使用索引值切换
// SceneManager.LoadScene(1); // 替换为你的目标场景索引
}
}
设置按钮:在Unity编辑器中,将创建的按钮拖拽到 switchButton 字段上,以便脚本能够引用到该按钮。
运行测试:运行游戏,点击按钮时应该能够成功切换到指定的场景。
使用Rider编写Unity代码相关设置
重装系统后,对VSCode的环境配置感到困扰,尤其F等重要功能缺失。同事推荐了Rider,体验良好,决定尝试。以下是Rider的配置步骤:
首先,确保Unity内的cs文件双击可打开Rider工程,启动时会有文件解析过程,对于文件较多的情况,解析时间可能较长。
在Rider中,点击"Structure"可查看当前文件的变量和函数列表,类似Eclipse或VSCode的Outline功能。
个性化设置方面,通过Setting中的Keymap调整快捷键,如将Eclipse的Ctrl+D删除行功能迁移到Rider中。在File Types中,添加忽略meta文件,可避免在Explorer中显示和全局搜索时包含这些文件。
虽然Rider预装了多种插件,但可以根据需求取消不必要的勾选。在代码调试方面,设置断点后,通过Unity Editor的Play按钮启动调试,点击小加号查看变量值。
然而,Rider在打开Unity工程时会引入一些默认的文件夹,如Library、ProjectSettings和Plugins,这可能会影响编辑。目前尚未找到针对此问题的配置选项,需要继续探索。
2024-11-30 20:13
2024-11-30 19:43
2024-11-30 18:50
2024-11-30 18:41
2024-11-30 18:09