讲透RenderTarget · 第一章:RenderTarget 是什么
**欢迎新朋友点赞、关注、收藏三连。第一章RenderTarget 是什么一句话概括RenderTarget 就是 GPU 的画布——不一定画在屏幕上可以画在任何一块显存里。⏱ 30 秒概览RenderTargetRT GPU 可以写入像素的任意一块显存区域。屏幕后缓冲是你最早接触的 RT但绝大多数 RT 是离屏的——不直接显示只作为渲染管线的中间产物。RT 的最小定义是格式 尺寸 可写。RT 经常与纹理共生同一块显存先当 RT 写入再当纹理采样但概念上 RT ≠ Texture。Depth/Stencil Buffer 也是 RT 的一种。多张 RT 通过 Swap Chain 轮换送达屏幕。10 行代码就能创建并使用一个 RT。 全书导读本书分四个部分按认知→原理→实践→精通的节奏递进阶段章节你将获得认知篇Ch1~4RT 是什么、为什么需要它、生命周期、三十年演进史原理篇Ch5~7GPU 硬件如何写 RT、六大 API 横向对比、格式与色彩空间实践篇Ch8~11MRT/G-Buffer、ComputeUAV、引擎层 Pool/Handle/FrameGraph、20 经典效果的 RT 拓扑精通篇Ch12~14性能优化方法论、调试诊断手册、Vis Buffer/VRS/RT/Neural Rendering/XR 前沿推荐阅读路线快速上手Ch1 → Ch2 → Ch3 → Ch6选你用的 API→ Ch11选你用的效果性能优化Ch5 → Ch7 → Ch12 → Ch8.5带宽分析引擎开发Ch10 → Ch11 → Ch12 → Ch14全书通读按 Ch1~14 顺序每章 30 秒概览先扫一遍再深读感兴趣的1.1 一个比喻画家可以画在任何面上想象你是一个画家。大多数人第一反应是画家当然是往墙上画、往画布上画。确实最终作品是要展示在墙上的。但在创作过程中画家手边可能有好几张草稿纸、调色板、甚至玻璃面板——他会在草稿纸上试构图在调色板上混颜色在玻璃板上画翻转的参考图最后再把所有成果合成到主画布上。GPU 做的事情完全一样。我们通常认为 GPU 就是把画面画到屏幕上但这只是最后一步。在到达屏幕之前GPU 可能已经在多张草稿纸上画了很多遍——一遍画深度信息一遍画法线一遍画阴影一遍画反射……这些草稿纸就是RenderTarget。所以RenderTarget 的本质很简单它就是 GPU 可以画上去的任何一块区域。屏幕是一个 RenderTarget。一张离屏的纹理也可以是 RenderTarget。只要 GPU 能往里面写入像素数据它就是一个 RenderTarget。1.2 屏幕后缓冲——你最早接触的 RenderTarget当你写下人生中第一个图形程序不管是用 OpenGL 画了一个三角形还是用 DirectX 画了一个旋转的立方体你已经在使用 RenderTarget 了——只是你可能没意识到。那个三角形/立方体最终出现在屏幕上对吧那它在出现之前GPU 是画在哪儿的答案是后缓冲Back Buffer。后缓冲就是屏幕的背面。GPU 先把画面画到后缓冲上等一帧画完了再把后缓冲的内容翻转到前缓冲——也就是屏幕实际显示的那块内存。这个翻转动作叫做Present或Swap整个机制叫做双缓冲Double Buffering。GPU 画到 → [后缓冲 Back Buffer] ↓ Present / Swap 显示器读取 ← [前缓冲 Front Buffer]后缓冲就是你最早接触的 RenderTarget。它有明确的尺寸比如 1920×1080有明确的格式比如 RGBA8GPU 可以往里面写入颜色数据。它完全符合 RenderTarget 的定义。但请注意一个关键点后缓冲是特殊的 RenderTarget——它和显示器绑定它的内容最终要被显示出来。大量其他的 RenderTarget 永远不会直接出现在屏幕上它们只是渲染过程中的中间产物。1.3 离屏渲染——GPU 可以画到看不见的地方理解了后缓冲之后下一步就是理解离屏渲染Off-screen Rendering。既然 GPU 可以画到后缓冲一块显存那它当然也可以画到另一块显存——这块显存不和任何显示器绑定画面不会直接出现在屏幕上。这就是离屏渲染。为什么要画到看不见的地方因为你需要中间结果。举个最简单的例子模糊效果。你想把整个画面变模糊。怎么做先正常画一遍场景结果不送到屏幕而是画到一张离屏 RT上然后对这张离屏 RT 做模糊处理最后把模糊后的结果画到屏幕的后缓冲上场景渲染 → [离屏 RT A清晰] ↓ 模糊 Shader [离屏 RT B模糊] ↓ 最终输出 [后缓冲屏幕]没有离屏 RT你根本做不了这件事——你总不能一边画屏幕一边读屏幕来做模糊吧事实上确实不能我们在 1.8 节会讲为什么。离屏渲染是 RenderTarget 最核心的用法。可以说学会离屏渲染就掌握了 RenderTarget 的灵魂。1.4 最小定义可写的二维 GPU 内存 格式 尺寸现在我们可以给 RenderTarget 下一个更精确的定义了。一个 RenderTarget 至少包含三个要素1. 一块 GPU 可写的二维内存这块内存位于显存GPU Memory中。它被组织成二维的行列结构——每个位置存储一个像素的数据。GPU 的光栅化管线可以向这块内存写入数据。2. 格式Format格式决定了每个像素存储什么数据、用多少位bit来存。常见格式举例格式每像素大小用途RGBA8R8G8B8A8_UNORM4 字节普通颜色每通道 8 位无符号归一化RGBA16FR16G16B16A16_FLOAT8 字节HDR 颜色每通道 16 位浮点R32FR32_FLOAT4 字节单通道浮点常用于深度、SSAOD24S8D24_UNORM_S8_UINT4 字节24 位深度 8 位模板格式的选择直接影响精度、范围和带宽消耗。第七章会深入讨论这个话题。3. 尺寸Width × Height宽度和高度以像素为单位。可以和屏幕一样大比如 1920×1080也可以更小比如半分辨率 960×540甚至可以更大比如超采样 3840×2160。把这三个要素放在一起RenderTarget 一块 GPU 可写的二维显存 像素格式 宽高尺寸就这么简单。不需要更复杂的定义。一切高级特性——MRT、MSAA、Cubemap RT、3D RT——都是在这个基础上的扩展。1.5 RT ≠ 纹理但经常共生这是初学者最容易混淆的地方之一RenderTarget 和纹理Texture是什么关系先说结论RenderTarget 是一种角色可以被 GPU 写入纹理是一种资源一块存储像素的显存。同一块显存可以同时扮演两个角色。打个比方一张纸可以是写字的纸RenderTarget——正在被写入也可以是读的纸Texture——正在被读取/采样。纸还是那张纸只是用途不同。在不同的图形 API 中这个关系的表达方式不同OpenGL 的说法OpenGL 有两种东西可以作为 FBOFramebuffer Object的 AttachmentRenderbuffer只能写、不能被采样。纯粹的 RenderTarget不能当纹理用。适合深度/模板缓冲等只写不读的场景。Texture既能写作为 FBO attachment又能读作为纹理被采样。所以 OpenGL 的世界里Texture 和 RenderTarget 的关系是纹理可以兼任 RT。DirectX 的说法DirectX 把概念拆得更清楚Resource资源比如ID3D11Texture2D这是底层的显存资源View视图描述如何看待这块资源RenderTargetViewRTV把这块资源当作写入目标来看ShaderResourceViewSRV把这块资源当作纹理来采样DepthStencilViewDSV把这块资源当作深度/模板缓冲来看同一个Texture2D可以同时创建 RTV 和 SRV——但在同一时刻它通常只能处于被写或被读的状态之一读写互斥详见 3.8 节。Vulkan / Metal 的说法类似 DirectX 的思路都是 ResourceImage / Texture ViewImageView 用途声明Usage Flags。资源在创建时就声明我可以被当作 RT和我可以被当作纹理——这些用途不是互斥的但需要通过 Barrier / 状态转换来在用途之间切换。总结概念本质能写入能采样纯 RenderTarget如 GL Renderbuffer只写的显存✅❌纯 Texture只读的显存❌✅可作为 RT 的 Texture读写兼备的显存✅✅但不能同时大多数情况下我们说RenderTarget指的都是第三种——一块既能画、又能读的显存。这也是为什么很多人把 RT 和 Texture 混为一谈——因为在实践中它们确实经常住在同一块内存里。1.6 Depth / Stencil——RenderTarget 的另一半谈 RenderTarget 时大部分人第一反应是颜色缓冲——存 RGBA 颜色的那张图。但 GPU 渲染不只需要颜色。至少还有两个关键缓冲深度缓冲Depth Buffer深度缓冲存储每个像素离摄像机的距离深度值。它的核心功能是深度测试Depth Test——决定新画的像素是否应该覆盖已有的像素。离摄像机更近的物体应该遮挡更远的物体就是靠深度缓冲实现的。深度缓冲的常见格式D16_UNORM16 位无符号归一化精度较低D24_UNORM_S8_UINT24 位深度 8 位模板最经典的格式D32_FLOAT32 位浮点深度精度最高模板缓冲Stencil Buffer模板缓冲存储一个整数值通常 8 位范围 0~255用来做模板测试Stencil Test。模板测试就像一个遮罩——你可以标记某些像素允许画或不允许画。典型用途包括镜面反射只在镜子区域内画反射内容阴影体Shadow Volume描边效果门户Portal效果Depth / Stencil 算不算 RenderTarget算GPU 每次执行 Draw Call 时不仅在写颜色缓冲同时也在写深度缓冲和模板缓冲。它们是 GPU 画的目标的一部分完全符合 RenderTarget 的定义。在 DirectX 中Depth/Stencil 对应DepthStencilViewDSV和RenderTargetViewRTV是并列的概念。在 Vulkan/Metal 中Depth/Stencil 是 Framebuffer 的 Attachment 之一和 Color Attachment 地位平等。在 OpenGL 中FBO 同时挂载 Color Attachment 和 Depth/Stencil Attachment。所以更准确地说一次渲染的目标 N 个 Color RenderTarget 0 或 1 个 Depth/Stencil RenderTarget其中 N 可以是 1最常见也可以是多个MRT详见第八章。1.7 Swap Chain——屏幕上那个特殊的 RenderTarget前面提到后缓冲是最终显示在屏幕上的 RT。那Swap Chain交换链是什么Swap Chain 是一组和显示器绑定的后缓冲。为什么是一组而不是一个因为 GPU 和显示器的工作是异步的——GPU 在画下一帧的时候显示器可能还在显示上一帧。为了不让两者冲突需要多个缓冲轮流使用。最常见的配置双缓冲Double Buffering2 个缓冲。Front Buffer 显示Back Buffer 画。画完后交换。三缓冲Triple Buffering3 个缓冲。GPU 可以继续画第三帧即使第二帧还没显示完。减少 GPU 空闲等待。Swap Chain双缓冲: ┌─────────────┐ ┌─────────────┐ │ Buffer 0 │ ←→ │ Buffer 1 │ │ (显示中) │ │ (GPU在画) │ └─────────────┘ └─────────────┘ Present 时交换Swap Chain 中的每个 Buffer 都是一个 RenderTarget但它们有一些特殊性尺寸通常等于窗口/屏幕分辨率——但也可以不等API 会帮你缩放格式受限于显示器支持——比如 SDR 显示器通常用 RGBA8_SRGBHDR 显示器可能用 RGB10A2 或 RGBA16F不能被当作纹理采样——在某些 APIVulkan中Swap Chain Image 的 Usage 可以加上 SampledImage但这不是常规做法且有性能隐患由显示系统管理——你不能随便创建和销毁 Swap Chain Buffer它由IDXGISwapChainDX/VkSwapchainKHRVulkan/CAMetalLayerMetal管理这就是为什么离屏 RT 如此重要——你几乎永远不会直接在 Swap Chain 上做复杂渲染。而是先画到离屏 RT做各种处理最后把最终结果贴到 Swap Chain 的 Back Buffer 上。1.8 动手10 行代码创建你的第一个 RenderTarget理论讲够了来看代码。以下用 OpenGL 演示创建一个最简单的离屏 RenderTarget并把一个三角形画上去。// 1. 创建纹理作为 Color AttachmentGLuint tex;glGenTextures(1,tex);glBindTexture(GL_TEXTURE_2D,tex);glTexImage2D(GL_TEXTURE_2D,0,GL_RGBA8,512,512,0,GL_RGBA,GL_UNSIGNED_BYTE,NULL);// 2. 创建 FBOFramebuffer Object把纹理挂上去GLuint fbo;glGenFramebuffers(1,fbo);glBindFramebuffer(GL_FRAMEBUFFER,fbo);glFramebufferTexture2D(GL_FRAMEBUFFER,GL_COLOR_ATTACHMENT0,GL_TEXTURE_2D,tex,0);// 3. 现在 GPU 的渲染目标就是这张 512x512 的纹理了glViewport(0,0,512,512);glClearColor(0.0f,0.0f,0.0f,1.0f);glClear(GL_COLOR_BUFFER_BIT);// 4. 画一个三角形假设 shader / VAO 已经准备好了drawTriangle();// 5. 切回默认 Framebuffer屏幕把刚才画的纹理显示出来glBindFramebuffer(GL_FRAMEBUFFER,0);glViewport(0,0,windowWidth,windowHeight);drawFullscreenQuadWithTexture(tex);// 用一个全屏四边形把 tex 画出来关键步骤拆解步骤做了什么对应概念glGenTextures glTexImage2D分配了一块 512×512 RGBA8 的显存创建 RT 资源glGenFramebuffers glFramebufferTexture2D把这块显存设为渲染目标绑定 RTglClear把 RT 清成全黑清除drawTriangle()GPU 把三角形画到 RT 上渲染glBindFramebuffer(0)切回屏幕状态转换drawFullscreenQuadWithTexture(tex)把 RT 当纹理采样采样短短几行代码就走完了 RenderTarget 的完整生命周期创建 → 绑定 → 清除 → 渲染 → 切换 → 采样。如果你用的是 DirectX 11等效代码是// 创建 Texture2DD3D11_TEXTURE2D_DESC desc{};desc.Width512;desc.Height512;desc.FormatDXGI_FORMAT_R8G8B8A8_UNORM;desc.BindFlagsD3D11_BIND_RENDER_TARGET|D3D11_BIND_SHADER_RESOURCE;desc.MipLevels1;desc.ArraySize1;desc.SampleDesc.Count1;ID3D11Texture2D*tex;device-CreateTexture2D(desc,nullptr,tex);// 创建 RenderTargetViewID3D11RenderTargetView*rtv;device-CreateRenderTargetView(tex,nullptr,rtv);// 创建 ShaderResourceView以后采样用ID3D11ShaderResourceView*srv;device-CreateShaderResourceView(tex,nullptr,srv);// 绑定 RT清除画三角形context-OMSetRenderTargets(1,rtv,nullptr);floatclearColor[]{0,0,0,1};context-ClearRenderTargetView(rtv,clearColor);drawTriangle();// 切回屏幕用 srv 作为纹理画全屏四边形context-OMSetRenderTargets(1,backBufferRTV,nullptr);drawFullscreenQuadWithSRV(srv);注意 DirectX 的代码更显式——你需要为同一块显存分别创建RenderTargetViewRTV写入视图和ShaderResourceViewSRV采样视图。这正体现了 1.5 节讲的RT 和 Texture 是同一块资源的不同视图。FAQRT 和 FrameBuffer 是一个东西吗严格来说不是。但它们经常被混用。FrameBuffer帧缓冲是一个容器或挂载点它可以挂载多个 AttachmentColor、Depth、StencilRenderTarget是被挂载的那个东西——一块实际的显存区域打个比方FrameBuffer 是画架RenderTarget 是画布。画架上可以挂多张画布MRT但画架本身不是画布。不过在日常交流中人们经常说切换 FrameBuffer来表示切换渲染目标这种用法虽然不精确但大家都能理解。在不同 API 中的术语API“画架”容器“画布”实际 RTOpenGLFramebuffer Object (FBO)Texture / RenderbufferAttachmentDX11N/A直接绑 RTVTexture2D RenderTargetViewDX12N/A直接绑 RTV DescriptorTexture2D RTV DescriptorVulkanVkFramebufferVkImage VkImageViewAttachmentMetalMTLRenderPassDescriptorMTLTextureAttachment设计哲学中间表示——计算机科学的万能钥匙 如果你仔细想想RenderTarget 的核心价值是提供一种中间表示Intermediate Representation。编译器有 IR中间代码让前端和后端解耦网络有 Buffer让发送方和接收方解耦操作系统有虚拟内存让进程和物理内存解耦。RenderTarget 做的是同一件事——让画和看在时间、空间、视角上解耦。这不是图形学独有的技巧而是计算机科学中反复出现的元模式任何足够复杂的流水线都会发展出中间产物来解耦阶段间的依赖。掌握这个视角你不仅理解了 RT还理解了为什么 GPU 管线会演变成今天的 multi-pass 架构。本章小结这一章我们建立了关于 RenderTarget 的基本认知RT 就是 GPU 的画布——任何 GPU 可以写入像素的地方屏幕后缓冲是你最早用到的 RT但大量 RT 是离屏的离屏渲染是 RT 的核心价值——提供中间结果用于加工RT 的最小定义二维 GPU 可写内存 格式 尺寸RT 和纹理经常住在同一块内存里只是角色不同写入 vs 采样Depth/Stencil 也是 RT——渲染目标不只有颜色Swap Chain 是特殊的 RT——和显示器绑定需要特殊管理创建 RT 只需要几行代码——核心流程是创建 → 绑定 → 清除 → 渲染 → 切换 → 采样 思考题如果 GPU 只能画到屏幕上没有离屏 RT哪些现代渲染效果会完全不可能实现列出至少三个并说明原因。为什么 RT 和 Texture 要设计成同一块显存、不同视图而不是写一块、拷贝到另一块再读这种设计的性能收益是什么类比操作系统中的文件系统文件是存储文件描述符是视图。这和 DX 的 Resource / View 模型有什么相似之处下一章我们来回答为什么——为什么几乎所有现代渲染效果都离不开 RenderTarget。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2468919.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!