1.理解FrameGraph实现(一)
2.SRP合批问题
3.Catlike Coding Custom SRP学习之旅——11Post Processing
4.ShaderToy系列:零
5.关于静态纹理(UTexture2D)的遍历与读写
理解FrameGraph实现(一)
本文将深入探讨Granite中FrameGraph的实现细节,对于FrameGraph的基本概念和用途,建议先阅读作者前文的相关介绍,特别是关于Vulkan的部分,因为Granite的实现主要围绕Vulkan展开。
FrameGraph的水鱼源码核心代码在render_graph.hpp和render_graph.cpp中,通过源码研究是理解其工作原理的关键。首先,我们来看Resource的封装,它由RenderResource派生,如RenderTextureResource和RenderBufferResource,它们代表Texture和Buffer,带有特定的属性设置。
RenderResource的设计遵循确定资源用途和区分不同使用场景的原则,如区分Async Computer中的资源生命周期。RenderBufferResource和RenderTextureResource则分别处理它们特有的信息。ResourceDimensions用于统一表示资源尺寸,为内存管理提供便利,但image_usage和queues不在alias判断范围内。
RenderPass负责处理Resource的状态设置和回调函数,它在Compile阶段起到关键作用。其操作主要分为添加输入和输出资源,以及深度/ stencil设置。RenderPass的设计使得资源状态清晰,便于后续操作。
进一步,RenderGraph封装了RenderResource和RenderPass,为RenderResource分配物理索引和维度信息,以支持内存优化。执行流程在不同引擎中可能存在差异,但Granite的bake过程涉及多个库和步骤,如Barrier的抽象和PhysicalPass的管理。
在执行流程中,数据结构如Barrier和ResourceState起着关键作用,类似知识星球源码它们处理内存依赖和状态检查。通过一系列的处理,如setup_dependencies、validate_passes和traverse_dependencies,确保资源的正确使用和依赖关系的处理。
文章没有详述的其他内容,如多线程执行和更复杂的内存考虑,可能会在后续的文章中继续探讨。总的来说,理解FrameGraph的实现需要深入源码和框架原理,同时对比其他引擎的实现可以拓展知识面。
SRP合批问题
在项目中,使用SRP进行合批时,发现除了Cull和Keywords外,其他因素也可能导致合批失败。例如,使用不同材质的物体之间位置穿插也会造成合批失败。一个场景中如果有三个Shader,三个物体分别使用这三种Shader,无论它们的穿插顺序如何,理论上只需要三个SRP Batch就能完成渲染,但实际上可能会被拆分成4-6个Batch。这需要开发者找出合适的渲染顺序或使用其他方法解决。
场景中常驻的场景相机和UI相机可能导致动态加载的Prefab自带的渲染相机与之前的渲染结果叠加出现问题,尤其是在移动平台上。尽管Overlay相机可以实现正确的叠加,但Base相机却出现花屏现象。这是因为动态加载的Base相机在设置渲染目标时,colorBuffer的Load Action没有正确调整。目前解决方案是将动态相机设置为Overlay,并通过代码将它放入常驻场景相机的CameraStack中。如果在URP下查看Blit操作时发现问题,可以考虑使用类似RenderTexture.DiscardContents的ios 群控源码方法来解决。
关于粒子系统是否支持GPU Instancing,答案是肯定的,但在Unity 版本中,粒子系统必须以Mesh模式使用GPU Instancing。粒子系统的实现与GUI的实现类似,数据放在VBO或UBO上效率提升不大,且限制了通用性。对于URP下场景和UI分辨率分离的需求,可以通过将3D场景渲染到RT中,再将RT作为RawImage的Texture渲染到UI中来实现,或者在URP源码中给每个Camera添加修改RenderScale的组件。
Catlike Coding Custom SRP学习之旅——Post Processing
来到了后处理环节,这是渲染管线中关键的一环。后处理技术能够显著提升画面效果,比如色调映射、Bloom、抗锯齿等,都能在后处理中实现。除了改善整体画面效果,后处理还能用于实现描边等美术效果。本文将主要介绍后处理堆栈和Bloom效果等内容。
考虑到篇幅和工作量,本文将从第4章节后半部分开始,以及未来的章节,主要提炼原教程的内容,尽量减少篇幅和实际代码。在我的Github工程中,包含了对源代码的详细注释,需要深入了解代码细节的读者可以查看我的Github工程。对于文章中的错误,欢迎读者批评指正。
以下是原教程链接和我的Github工程:
CatlikeCoding-SRP-Tutorial
我的Github工程
1. 后处理堆栈(Post-FX Stack)
FX,全称是触动精灵 滴滴源码Special Effects,即特殊效果,也称为VFX(Visual Special Effects),即视觉特效。参考维基百科,视觉效果(Visual effects,简称VFX)是在**制作中,在真人动作镜头之外创造或操纵图像的过程。游戏很多技术都会沿用影视技术上的一些技术,比如在色调映射时,可以采用ACES(**色调映射)等。关于Special Effects为什么叫FX,而不是SE,网上似乎只是因为FX谐音Effects,让人不知道从哪吐槽。
通常来说,因为后处理会包含很多不同的效果,如色调映射、Bloom、抗锯齿等等,因此后处理在渲染管线中的结构往往是一个堆栈式的结构(URP中也是如此,使用了Post Process Volume)。因此,在本篇中,我们将搭建这样一个堆栈结构,并实现Bloom效果。
1.1 配置资源(Settings Asset)
首先,我们定义PostFXSettings资源,即Scriptable Object,将其作为渲染管线的一项可配置属性,这样便于我们配置不同的后处理堆栈,并可以方便地切换。
1.2 栈对象(Stack Object)
类似于Light和Shadows,我们同样使用一个类来存储包括Camera、java字符画源码ScriptableRenderContext、PostFXSettings,并在其中执行后处理堆栈。
1.3 使用堆栈(Using the Stack)
在进行后处理前,我们首先需要获取当前摄像机画面的标识RenderTargetIdentifier,RenderTargetIdentifier用于标识CommandBuffer的RenderTexture。在这里,我们使用一个简单的int来标识sourceRT。
对于一个后处理效果而言,其实现过程说来很简单,传入一个矩形Mesh(其纹理即当前画面),使用一个Shader渲染该矩形Mesh,将其覆盖回Camera的RT上,我们通过Blit函数来实现该功能。
1.4 强制清除(Forced Clearing)
因为我们将摄像机渲染到了中间RT上,我们虽然会在每帧结束时释放该RT空间,但是基于Unity自身对RT的管理策略,其并不会真正地清除该RT,因此我们在下一帧时,该RT中会留存上一帧的渲染结果,导致了每一帧画面都是在前一帧的结果之上绘制的。
1.5 Gizmos
我们还需要在后处理前后绘制不同的Gizmos部分,这部分略~
1.6 自定义绘制(Custom Drawing)
使用Blit方法绘制后处理,实际上会绘制一个矩形,也就是2个三角面,即6个顶点。但我们完全可以只用一个三角面来绘制整个画面,因此我们使用自定义的绘制函数代替Blit。
1.7 屏蔽部分FX(Don't Always Apply FX)
目前,我们对于所有摄像机都执行了后处理。但是,我们希望只对Game视图和Scene视图摄像机进行后处理,并对不同Scene视图提供单独的开关控制。很简单,通过判断摄像机类型来屏蔽。
1.8 复制(Copying)
接下来,完善下Copy Pass。我们在片元着色器中,对原画面进行采样,并且由于其不存在Mip,我们可以指定mip等级0进行采样,避免一部分性能消耗。
2. 辉光(Bloom)
目前,我们已经实现了后处理堆栈的框架,接下来实现一个Bloom效果。Bloom效果应该非常常见,也是经常被用于美化画面,其主要作用就是让画面亮的区域更亮。
2.1 Bloom金字塔(Bloom Pyramid)
为了实现Bloom效果,我们需要提取画面中亮的像素,并让这些亮的像素影响周围暗的像素。因此,需要首先实现RT的降采样。通过降采样,我们可以很轻易地实现模糊功能。
2.2 配置辉光(Configurable Bloom)
通常来说,我们并不需要降采样到很小的尺寸,因此我们将最大降采样迭代次数和最小尺寸作为可配置选项。
2.3 高斯滤波(Gaussian Filtering)
目前,我们使用双线性滤波来实现降采样,这样的结果会有很多颗粒感,因此我们可以使用高斯滤波,并且使用更大的高斯核函数,通过9x9的高斯滤波加上双线性采样,实现x的模糊效果。
2.4 叠加模糊(Additive Blurring)
对于Bloom的增亮,我们直接将每次降采样后的Pyramid一步步叠加到原RT上,即直接让两张不同尺寸的以相同尺寸采样,叠加颜色,这一步也叫上采样。
2.5 双三次上采样(Bicubic Upsampling)
在上采样过程中,我们使用了双线性采样,这样可能依然会导致块状的模糊效果,因此我们可以增加双三次采样Bicubic Sampling的可选项,以此提供更高质量的上采样。
2.6 半分辨率(Half Resolution)
由于Bloom会渲染多张Pyramid,因此其消耗是比较大的,其实我们完全没必要从初始分辨率开始降采样,从一半的分辨率开始采样的效果也很好。
2.7 阈值(Threshold)
目前,我们对整个RT的每个像素都进行了增亮,这让这个画面看起来过曝了一般,但其实Bloom只需要对亮的区域增亮,本身暗的地方就不需要增亮了。
2.8 强度(Intensity)
最后,提供一个Intensity选项,控制Bloom的整体强度。
结束语
大功告成,我们在渲染管线中增加了后处理堆栈,以及实现了一个Bloom效果,其实在做完这篇之后,我觉得这个渲染管线才算基本上达成了大部分需要的功能,也算是一个里程碑吧。
ShaderToy系列:零
ShaderToy是一个展示创意和效果的平台,其中的作品通常基于片断着色器,形式丰富、风格独特。这些作品的源代码公开,为学习者提供了直接研究和借鉴的机会。本系列文章旨在将ShaderToy上的效果移植到Unity环境中,通过这一过程深入理解Shader思维与代码编写技巧。
为了在Unity中展示ShaderToy效果,我们将采用后处理技术,利用`OnRenderImage`方法。这个内置方法在所有渲染步骤完成后执行,常用于实现屏幕后处理特效。`OnRenderImage`方法接收两个`RenderTexture`参数,分别表示当前帧渲染的图像和最终渲染处理后的图像。通过`Graphs.Blit`函数处理当前画面,结合特定的Shader进行渲染,最终将结果呈现于屏幕上。
在本系列中,我们不关心当前帧的原始渲染面片,仅关注将Shader效果展示到屏幕上。这意味着在`Graphs.Blit`中,源可以指定为`null`。移植过程涉及编写Shader代码,以实现特定效果。以ShaderToy中的默认背景效果为例,这是一个基础的顶点片断着色器,通过简单的计算实现目标效果。
片断着色器中的核心语句返回的是一个四维向量,对应为`finalCol`,其值最终设定为`float4(col,1.0)`,表示颜色RGB的值为`col`,Alpha值为1。这表明片段最终返回的是`col`的值。进一步分析`col`的构成,可以发现其是一个三维向量,通过将其分解为R通道进行观察,便于理解其表现效果。最终,三个通道的组合实现了初始效果。
在解读他人Shader代码时,建议从下向上进行,这样有助于理解实现方法。关注代码逻辑和结构,理解其如何协同工作实现特定效果。通过详细分析,我们可以学习并掌握Shader中的各种操作和技巧,如颜色操作、纹理映射、光照计算等。
本系列文章侧重于通过ShaderToy实例分析学习Shader语法和技巧,对于GLSL编程细节不做过多讨论,而是关注于其在Unity环境中的操作表现和应用。对于在编写过程中遇到的疑问,初学者可以参考提供的源文件或加入QQ交流群获取帮助。欢迎关注公众号“Unity技术美术”获取更多技术干货。
关于静态纹理(UTexture2D)的遍历与读写
关于静态纹理(UTexture2D)的遍历与读写
静态纹理在Unity(UE)中,尤其是Texture2D类型,其在蓝图中的操作相对有限,没有直接的读写节点,除非通过C++实现。对于RenderTexture(UE中的RenderTarget)则提供了读取像素的节点,但这些方法可能不适合实时调用,涉及GPU和CPU的数据传输。 在C++层面,Unity引擎API设计较为底层,需要配合多个方法来实现一些功能,这可能增加了理解和使用难度,但同时也提供了更大的优化空间。遇到问题,查阅源码、社区或科学上网搜索是常用解决途径。 关于读写纹理,主要分为以下步骤:创建纹理,可以使用CreateTransient,它会初始化一些平台数据和Mip等信息。
获取纹理数据结构,如FTexturePlatformData,包括尺寸、颜色类型和Mip级别。
选择特定Mip级别,比如原始分辨率或Mipmap缩放版本。
获取对应Mip级别的数据集,即FByteBulkData。
使用Lock方法锁定图像并获得头指针,注意区分读写锁。
根据像素位置进行地址偏移,利用像素颜色格式计算正确的内存地址。
解锁Tex,确保操作完成后释放资源。
动态修改MipGenSettings,如从外部导入的图可能需要调整设置以确保有效指针。
在实践中,可以封装一个读取像素的方法,写入则需要相应地修改Lock关键字和偏移计算。而在Unity中,遍历纹理的顺序、原点位置以及API选择与UE有所不同。