【IMR、TBR、TBDR】【RenderTexture、Cbuffer】学习
1. 渲染架构比较
1.1 IMR (Immediate Mode Rendering)
IMR是PC和主机GPU使用的渲染方式,指每一次渲染API的调用,都会直接绘制图形对象。所以每一次物体颜色和深度的渲染都会读写Frame Buffer和Depth Buffer。IMR架构需要大量的带宽,这点可以通过L1和L2缓存优化,但对于移动端GPU的尺寸和功耗需求,不能使用IMR。
1.2 内存带宽消耗问题
在IMR中,三角形提交的顺序决定了访问顺序,但这种访问顺序是无序的,所以可能会导致访问效率低下。也可能会遇到较高的缓存未命中率,GPU缓存中的数据经常不是渲染下一个像素所需要的数据,导致频繁的内存访问。
1.3 Tiled Memory
贴图内存是一种优化内存带宽使用的技术,它将内存划分为多个二维的矩形区域,即“贴图”或“瓦片”。
工作原理:
- 瓦片化内存:每个缓存行覆盖帧缓冲和深度缓冲中的一个瓦片区域。与传统的线性缓存相比,这种布局使得渲染操作在缓存内部执行的更多,减少了对外部内存的访问
- 缓存命中率提高:由于相邻的三角形在渲染时往往连续提交,瓦片化的缓存可以更好地利用缓存行,提高缓存的命中率
- 减少内存带宽使用:由于更多的渲染操作在缓存内完成,因此与内存的传输变得更少
1.4 Rasterizing within tiles
在现实世界中,帧缓冲区相对于缓存瓦片来说可能会大得多,如果三角形特别大,屏幕上的每一行可能覆盖的瓦片数量超过了缓存能容纳的范围。所以我们可以改变三角形内像素的光栅化顺序:可以先绘制三角形覆盖的一个瓦片内的所有像素,然后再移动到下一个瓦片。
- 减少缓存失效:通过在一个瓦片内完成所有相关像素的渲染,不需要在渲染过程中频繁地加载和卸载瓦片
- 提高缓存利用率:因为一旦瓦片被加载到缓存中,就可以处理该瓦片内所有三角形的像素,而不是仅仅处理一个三角形
1.5 分箱 Binning
我们需要确定哪些三角形会影响每个瓦片。当几何体被提交渲染时,它不是立即被光栅化,而是被“分箱”到一个内存结构中,这个结构决定了它可能影响的瓦片。注意这个过程涉及到顶点着色(确定三角形在屏幕上的位置),因为这影响着三角形的位置,但不包括片段着色。
1.6 TBR (Tile-Based Rendering)
核心思想是:将帧缓冲分割为一小块一小块,然后逐块进行渲染。在整个帧缓冲区中处理每个瓦片时,只关注该瓦片区域内的内容。在这个过程中,缓存的作用被缩小到当前处理的瓦片大小。在开始处理瓦片之前,帧缓冲区中的所有像素都需要被重置或清除。
渲染分为两个阶段:分箱(写入内存)和栅格化(读取分箱内容)。中间存储的几何体相对于帧缓冲区来说通常较小,并且以有序的方式访问
- 分箱阶段
- 几何体数据被提交给渲染器。
- 顶点着色器处理每个顶点,确定三角形在屏幕上的位置。
- 渲染器将每个三角形分配到可能影响的瓦片中,这个过程称为“分箱”。
- 分箱信息被存储在一个内存结构中,记录了每个瓦片包含哪些三角形。
- 栅格化阶段
- 渲染器按瓦片顺序处理场景,只写入本地瓦片内存。
- 每个瓦片被处理一次,包括清空帧缓冲区中的对应区域。
- 栅格化阶段会读取瓦片中的几何体信息,并将其转换为像素。
- 栅格化完成后,瓦片中的像素会被更新并写回到帧缓冲区中。
由于我们按瓦片处理图像的所有几何体,这意味着每个瓦片内的所有像素都是新渲染的,而不是基于前一个帧的内容。通过避免不必要的读取,可以减少带宽的使用,从而提高渲染效率。
1.7 TBR中的多采样
工作原理
- 创建样本点:每个像素周围会创建多个样本点,这些样本点代表了周围的颜色和深度信息
- 渲染样本点:样本点也会被渲染
- 解析样本点:渲染瓦片时会解析样本点——计算每个像素的最终颜色和深度值。这个过程通常涉及到样本点的平均值或其他形式的组合,以产生更平滑的颜色和深度值。
- 写入帧缓冲区:解析后的像素值被写入瓦片内存,然后写入帧缓冲区。由于每个像素周围的样本点数量不同,瓦片内存中的像素值需要被缩放或调整以匹配帧缓冲区中的像素大小
- 深度缓冲区:深度缓冲区通常只在芯片上处理,不会写入主内存,一旦瓦片处理完成,芯片上的深度值就可以丢弃,因为它们不会影响最终的渲染结果。
1.8 传统延迟着色 Traditional deferred shading
它首先记录每个几何体的基本信息(如表面法线),然后在第二次渲染过程中使用这些信息进行像素级的着色操作。这种方法可以减少所需的昂贵状态更改次数,并增加片段着色器可用的潜在并行性。
但是高带宽成本:在第二次渲染过程中,需要读取和写入整个帧缓冲区中的所有像素值。
1.9 TBDR
渲染器首先记录每个瓦片中的几何体的基本信息,如表面法线,然后对这些信息进行第二次渲染,使用这些信息来计算每个像素的最终颜色。只有着色阶段的最终结果需要写入帧缓冲区。
结合了瓦片化和延迟着色的优势,使得基于瓦片的渲染技术能够高效地实现延迟着色,同时保持较低的内存带宽成本。
1.10 TBR的优点与缺点
优点
- 减少帧缓冲区内存带宽
- 提高移动设备的性能
- 纹理缓存性能提升:纹理可以更一致地按瓦片访问,而不是按原始几何体访问,从而提高纹理缓存性能。
- 芯片上空间优化
限制
- 基于瓦片的渲染的延迟应该通过流水线化和性能提升来隐藏,但使得某些操作的相对成本更高。
- 可能超出当前片段的帧缓冲区读取相对成本更高。
- 由于需要多次遍历几何体,这可能会增加成本。
- 如果场景主要由顶点着色器限制,基于瓦片的渲染可能会增加开销。
2. Render Texture
参考
Unity3d中渲染到RenderTexture的原理,几种方式以及一些问题
Unity 官方文档
RenderTexture是一种 Unity 在运行时创建和更新的纹理,unity定义的一种特殊的Texture类型,它连接着一个FrameBufferObject的存在于GPU端的Texture(Server-Side Texture)
2.1 什么是server-side的texture
在渲染过程中,一开始的纹理是存储在CPU的,称为client-side的texture;它最终要被送到gpu的存储里,gpu才能使用它进行渲染,送到gpu里的那一份被称为server-side的texture。CPU和GPU之间纹理的传输需要考虑带宽瓶颈。
2.2 FrameBufferObject
Frame Buffer,存储各种颜色、深度、模板信息,是渲染结果的目的地,有一个默认的FBO它直接连着我们的显示器窗口区域,就是把我们的绘制物体绘制到显示器的窗口区域。
在OpenGL中,可以将FBO渲染到一张GPU上的纹理来使用。一般是将可读的FBO的一部分拷贝到存在于gpu上的一个texture对象中,直接考到server-sider就意味着可以马上被gpu渲染使用。
(OpenGL帧缓冲笔记 OpenGL学习笔记 帧缓冲)
在Unity中,将这个fbo直接关联一个gpu上的texture对象,这样就等于在绘制时就直接绘制到这个texure上,这样也省去了拷贝时间。
2.3 渲染到Render Texture的几种方式
- Assets > Create > Render Texture,然后将其附给一个摄像机,这样这个摄像机实时渲染的结果就都在这个tex上了。
- 有的时候,我们可以用自定义的shader去渲染这个RenderTexture。可以调用Camera的RenderWithShader函数,它将使用你指定的shader去渲染场景,这时候场景物体上原有的shader都将被自动替换成这个shader,而参数会按名字传递。
- 可以在Assets文件夹下创建RenderTexture,使用Graphics.Blit(src, target, mat)这个函数来渲染到render texture上,这里的的target就是你要绘制的render texrture,src是这个Material中需要使用的_MainTex,也可以是另一个RenderTexture。这个函数的本质是,绘制一个四方块,然后用mat这个材质,用src做maintex,然后先clear为black,然后渲染到target上。
2.4 一些注意的点
- rendertexture的格式,rt的格式和普通的tex2D的格式并不是一回事,某些机子还支持ARGBFLOAT浮点格式
- rendertexture的分配和销毁,如果你频繁的要new一个rt出来,那么不要直接new,而是使用RenderTexture提供的GetTemporary和ReleaseTemporary,它将在内部维护一个池,反复重用一些大小格式一样的rt资源,因为让gpu为你分配一个新的tex其实是要耗时间的。更重要的这里还会调用DiscardContents
- DiscardContents()这个rendertex的接口非常重要,好的习惯是你应该尽量在每次往一个已经有内容的rt上绘制之前总是调用它的这个DiscardContents函数,大致得到的优化是,在一些基于tile的gpu上,rt和一些tile的内存之间要存在着各种同步, 如果你准备往一个已经有内容的rt上绘制,将触发到这种同步,而这个函数告诉gpu这块rt的内容不用管他了,我反正是要重新绘制,这样就避免了这个同步而产生的巨大开销。
3.CBuffer
一个Command Buffer 中存储很多绘制命令(Rendering Command),这些 Rendering Command 其实就是OpenGL或者DirectX 中的一些列指令,比如glDrawElement,glClear等。Command Buffer 扩展渲染管线的方法是:预定义一系列的渲染指令,然后在特定的时机去执行这些指令。这些特定的时机是由我们来指定的,下图渲染过程中的绿点说明了这些可以指定的时机:
可以使用 Graphics.ExecuteCommandBuffer 立即执行 CommandBuffers。
也可以安排它们,以便它们在渲染管线中的给定点发生
- Camera.AddCommandBuffer 与 CameraEvent枚举使用
- Light.AddCommandBuffer 与 LightEvent枚举使用
详细CameraEvent 和 LightEvent 事件的执行顺序见Unity官方文档 使用 CommandBuffers 扩展内置渲染管线
Command Buffer的特点是可以在指定的时机执行一些特殊的渲染操作,从而达到节省资源和实现特殊效果的目的。但不要为了Command Buffer 而刻意使用Command Buffer。