当前位置: 首页 > news >正文

自由学习记录(98)

Shadowmask

  • 静态物体的阴影:烘焙到一张 Shadowmask 贴图,运行时直接采样。

  • 动态物体的阴影:实时计算,补充到 Shadowmask。

  • 效果:静态阴影几乎免费(性能好),动态阴影保持存在。

  • 使用场景:大场景里有很多静态物体,想减少实时阴影开销。

Shadow Distance(阴影距离)

  • Project Settings > QualityURP Asset > Shadows 中可以设置。

  • 控制摄像机前方多少米范围内使用 实时阴影

  • 超过这个距离的阴影,就直接使用 烘焙的 Shadowmask 信息(静态阴影纹理)。

举例

  • 你在场景里有一个太阳光(Directional Light,Mixed 模式,Shadowmask 子模式)。

  • Shadow Distance 设为 50 米:

    • 摄像机 50 米范围内 → 角色、动态物体会产生实时阴影。

    • 超过 50 米 → 阴影只来自于 Shadowmask(静态物体烘焙的阴影),动态物体不再投射实时阴影。

Lights in the URP are designed to be efficient but not impact the Scene performance. Even with URP’s built-in optimization settings, it’s ultimately up to you to make sure you aren’t overloading your project with lights and impeding performance.

https://learn.unity.com/tutorial/creating-urp-materials#JpI72pm885s7ezHBZknXIg

https://unity.com/resources/introduction-to-urp-advanced-creators-unity-6

stage是指 渲染管线执行过程中的阶段 (Render Stage / Render Event)。

现代的 SRP(URP/HDRP)把整个渲染过程拆成一个个可控的步骤

  • 摄像机准备 → 剔除 (Culling)

  • 渲染不透明物体

  • 渲染天空盒

  • 渲染透明物体

  • 后处理 (Post-processing)

  • UI / Overlay

每一步就是一个 stage

这样做的好处:

  • 渲染管线像流水线,可以插拔新的逻辑(Renderer Feature/Pass)。

  • 开发者可以决定“在不透明物体之前插一个 Pass”,“在后处理之后插一个特效”。

在 URP Render Graph Viewer 里的体现

你截图下面的 Pass List / Resource List,横着排的就是各个 stage,比如:

  • Depth Prepass

  • Main Opaque Pass

  • Draw Skybox

  • Draw Transparent Objects

  • Post-processing

  • UI Overlay

绿色/黄色/红色的小格子代表每个资源(比如 _CameraDepthTexture_CameraColorAttachment)在这个阶段被读/写。

所以 stage = 渲染管线里某个逻辑块

Renderer Feature 为什么能挂在“任何 stage”

Renderer Feature 本质上就是一个自定义的 Render Pass,你可以选择注入点 (Injection Point):

  • BeforeRendering

  • AfterRenderingSkybox

  • BeforeRenderingTransparents

  • AfterRenderingPostProcessing

注入点就是“stage”。这样你的 Feature 就能夹在官方的 Pass 之间执行。

前期准备

  • Initialize Frame:初始化一帧渲染的上下文、RenderGraph 资源。

  • Setup Forward Lights:把主光源、附加光源的数据准备好,打包进常量缓冲区。

  • Setup Debug Properties:调试开关(Frame Debugger 等)。

  • Setup Camera Properties:设置相机矩阵、视口参数。


阴影和深度

  • Draw Main Light Shadowmap:从主方向光的角度渲染场景,生成 Shadow Map。

  • Draw Additional Lights Shadowmap:如果有点光/聚光(支持的话),生成对应的 Shadow Map。

  • Setup Camera Properties (again):切换回主相机参数。

  • Setup Depth Normal Prepass:如果有需要深度/法线纹理的效果(SSAO、透明排序),会先跑一个 Prepass。


主体几何

  • Draw Opaque Objects:渲染不透明物体,写入颜色缓冲和深度缓冲。

  • Draw Skybox:绘制天空盒。

  • Copy Color / Copy Depth:把当前颜色/深度拷贝出来,供后处理或特效使用。

  • Draw Transparent Objects:渲染透明物体(排序在后,利用深度缓冲保证遮挡关系正确)。


特效与后处理

  • Draw Gizmos Pass:编辑器用的辅助线框、坐标轴 gizmo。

  • Setup Post FX Passes:准备后处理(颜色 LUT、Bloom、ToneMapping 等)。

  • Blit Bloom MipMaps / Blit Bloom Post Processing:做 Bloom 效果(模糊高亮部分)。

  • Final Depth Copy:拷贝最终深度缓冲。

  • Draw Wire Overlay:Scene 视图的线框覆盖层。


收尾

  • Draw Gizmos Pass (again):编辑器 Gizmo 收尾。

  • Set Editor Target:把渲染结果输出到 GameView / SceneView。

怎么理解这张图

  1. 每一条就是 管线阶段 (stage)

  2. 阶段之间的输入输出依赖,可以从左边的资源图看到,比如:

    • Shadow Map 在 光源 Pass 写入 → 在 Draw Opaque 读取。

    • Depth Texture 在 Prepass 写入 → 在 SSAO、透明物体 Pass 读取。

  3. Renderer Feature 插入点,就是在这些 stage 之间挂一个新的 Pass。

Unity 每年都会发布 ESG 报告(比如你看到的 2023、2024 报告),里面通常会写:

  • Unity 如何减少碳足迹(比如云服务能耗优化)。

  • 公司在多元化招聘和包容性上的政策。

  • 治理结构、合规和道德承诺。

这些和 渲染管线(URP/HDRP/SRP)技术无关,而是公司层面的社会责任和可持续发展汇报。

https://docs.unity3d.com/6000.0/Documentation/Manual/urp/renderer-features/create-custom-renderer-feature.html#code-renderer-feature

https://docs.unity3d.com/6000.0/Documentation/Manual/urp/renderer-features/create-custom-renderer-feature.html#code-renderer-feature

服了啊,这么强啊,我一直都在找什么啊,这么详细的官方的不看啊,,我有罪

https://docs.unity3d.com/6000.0/Documentation/Manual/urp/renderer-features/create-custom-renderer-feature.html#code-renderer-feature

为什么会有多个 Pass

在 URP 的 RenderGraph 或 Frame Debugger 里,你会看到:

  • CopyColor

  • CopyDepth

  • Draw Opaque

  • SSAO

  • Post-processing

这些本来是拆开的单独 Pass:

  • 逻辑清晰,每个阶段只做一件事。

  • 模块化,方便启用/禁用某个效果。

但缺点是:频繁切换 Render Target / Shader / GPU 状态,性能消耗大。


2. 合并 Pass 的思路

合并 Pass 的意思是:

  • 把多个原本独立的屏幕后处理步骤(例如 SSAO、Bloom、ColorGrading)融合到同一个全屏着色器里运行。

  • 避免多次 Blit 或 Render Target 切换。

  • 合并后:

    • 一个 Fullscreen Pass 同时做 SSAO + Bloom + ColorGrading,结果直接输出到 Final Color。

这样可以减少显存带宽占用和 GPU pipeline flush

Unity 官方在优化 URP 时,已经尝试过类似合并:

  • URP 14+ 引入 RenderGraph,目的就是自动检测可以合并的 Pass,比如 “CopyColor + PostProcessInput” 可以合并。

  • 部分后处理(Bloom + ColorAdjustments)在实现上已经能打包进同一个 Pass。

但注意:

  • 并不是所有 Pass 都能合并(有些需要中间结果)。

  • Renderer Feature 如果强制插入,也可能打断合并。

你能做的事情

  • Shader 层:自己写一个“大后处理” Shader,把多个效果写在一个 frag 里。

  • C# 层:写一个自定义 ScriptableRenderPass,一次性调用这个 Shader,替代多个内置 Pass。

  • RenderGraph:如果用 Unity 内置的 RenderGraph,观察哪些 Pass 会被自动合并(图里灰色条拼接的地方)。

pass

blit

read Texture Depth,Normal

为什么叫 Attachment

在现代图形 API(Vulkan、DX12、Metal)里,一个 Render Pass 会绑定一组 Attachment

  • Color Attachment:颜色缓冲,渲染结果的像素颜色写到这里。

  • Depth Attachment:深度缓冲,写入每个片元的深度值(z-buffer)。

  • Stencil Attachment:模板缓冲(通常和深度合在一起)。

Unity 的 RenderGraph 是建立在这些 API 概念上的,所以你会看到名字 _CameraTargetAttachmentA_CameraDepthAttachment

  • _CameraTargetAttachmentA
    就是 主颜色缓冲,最终场景渲染的画面会写进来。

  • _CameraDepthAttachment
    就是 主深度缓冲,存每个像素的深度(离相机的距离)。后续 Pass(比如透明物体、后处理)会用来做遮挡判断。

  • _CameraDepthTexture
    和 DepthAttachment 类似,但这是专门拷贝出来的一张贴图,供 Shader 采样使用(比如 SSAO、描边)。

  • _CameraNormalTexture
    如果开启了 DepthNormals Prepass,就会额外生成法线纹理。

为什么已经有了 DepthAttachment,还要额外生成 DepthTexture。原因在于两者的用途和访问方式完全不同。

DepthAttachment(主深度缓冲)

  • 这是 GPU 正在写入的深度缓冲,在光栅化阶段使用。

  • GPU 内部格式通常是专用的(比如 D24S8,甚至压缩格式),并且为 硬件深度测试优化

  • 在这一帧里,它主要用于:

    • ZTest(遮挡剔除)

    • Early-Z(提前丢弃被遮挡片元)

问题:

  • 它不保证能直接被 Shader 采样。

  • 硬件深度缓冲和普通纹理的访问路径不一样,如果直接暴露出来,性能会非常差甚至不可行

为什么不直接用 DepthAttachment

  • 硬件限制:深度缓冲是显卡专门优化过的格式,通常不能直接 sample。

  • 并发冲突:渲染过程中一个 Pass 在写深度,另一个 Pass 如果同时读,会出现同步问题。

  • 跨阶段需求:比如不透明物体阶段写完深度,后处理阶段才要读,这就需要拷贝到一张单独的纹理中保存。

框出来的 Screen Space Ambient Occlusion,就是 SSAO Renderer Feature:

  • 它需要 深度 (Depth)法线 (Normals) 作为输入。

  • 在 SSAO Pass 里生成一张 _ScreenSpaceOcclusionTexture

  • 后面的 Pass(比如 Draw Transparent Objects)会读取这张贴图,用来决定透明物体和环境光遮蔽的效果。

你能从这里读到的分析价值

  1. 依赖关系

    • 可以看到 SSAO 用到的输入(深度/法线),以及它写出的结果(Occlusion Texture)。

    • 确认哪些 Pass 依赖 SSAO 的输出,哪些在它之后执行。

  2. 性能分析

    • 如果你发现一个资源被反复拷贝(CopyColor、CopyDepth),就意味着有冗余 Pass,可能影响性能。

    • 有些 Feature(比如 SSAO、Bloom)会插入额外 Render Target,占显存带宽。

  3. 调试定位

    • 如果 SSAO 显示不出来,可以看 _ScreenSpaceOcclusionTexture 有没有被正确写。

    • 如果透明物体没受 SSAO 影响,可能是后续 Pass 没读到这个纹理。

https://docs.unity3d.com/6000.0/Documentation/Manual/urp/renderer-features/create-custom-renderer-feature.html#add-renderer-feature-to-asset

The example workflow on this page implements a custom renderer feature that uses custom Render Passes to add a blur effect to the camera
 output.

  • ScriptableRendererFeature instance that enqueues a ScriptableRenderPass instance every frame.

  • ScriptableRenderPass instance that performs the following steps:

    • Creates a temporary render texture
       using the RenderTextureDescriptor API.

    • Applies two passes of the custom shader to the camera output using the TextureHandle and the AddBlitPass API.

RenderTextureDescriptor 是 Unity 提供的一个 结构体,用来描述 RenderTexture 的属性

尤其在自定义渲染管线(SRP/URP/HDRP)或需要严格控制 RT 格式时常用。

  • RenderTextureDescriptor 只是一个“配置数据包”,描述你要的 RenderTexture 的宽高、格式、MSAA 等。

  • 可传递给 Graphics API
    例如 CommandBuffer.GetTemporaryRT(id, descriptor) 就是用它。

  • 减少错误:比直接传一堆参数更直观、清晰。

public struct RenderTextureDescriptor
{public int width;                      // 宽度public int height;                     // 高度public int msaaSamples;                // MSAA 抗锯齿采样数public int volumeDepth;                // 体积纹理的深度(或3D RT层数)public int mipCount;                   // mipmap 层数public GraphicsFormat graphicsFormat;  // 颜色格式 (推荐)public RenderTextureFormat colorFormat;// 旧API颜色格式 (已过时趋势)public int depthBufferBits;            // 深度缓冲精度(0,16,24,32)public TextureDimension dimension;     // 2D, Cube, 3D...public ShadowSamplingMode shadowSamplingMode;public VRTextureUsage vrUsage;public RenderTextureMemoryless memoryless;public bool useMipMap;public bool autoGenerateMips;public bool enableRandomWrite;         // 是否支持 UAV 随机写入 (ComputeShader)public bool bindMS;public bool useDynamicScale;
}

手动构建

var desc = new RenderTextureDescriptor(1920, 1080);
desc.graphicsFormat = UnityEngine.Experimental.Rendering.GraphicsFormat.R16G16B16A16_SFloat;
desc.depthBufferBits = 24;
desc.msaaSamples = 1;RenderTexture rt = new RenderTexture(desc);
rt.Create();

在 SRP / CommandBuffer 里用

RenderTextureDescriptor desc = cameraTargetDescriptor;
desc.width = Screen.width;
desc.height = Screen.height;
desc.depthBufferBits = 0;cmd.GetTemporaryRT(Shader.PropertyToID("_MyTempRT"), desc, FilterMode.Bilinear);
  • 后处理:定义一个中间 RT,用来存储 Blit 结果。

  • 多 RenderTarget (MRT):在延迟渲染或 GBuffer 设置时使用。

  • ComputeShader 输出:设置 enableRandomWrite = true

  • 跨平台兼容:通过 GraphicsFormatUtility 选择合适的格式,避免硬件不支持。

TextureHandleAddBlitPass API 的定位与用法。它们属于 RenderGraph 引入后的新 API,主要在 URP 14+ (Unity 2023.x / 6000.x) 中出现。

传统 SRP 里,我们操作 RenderTexture 或用 RenderTargetHandle 来表示目标 RT。

RenderGraph 模式下,RT 的生命周期由 RenderGraph 管理,我们不能直接操作 RenderTexture

引入 TextureHandle:它是 RenderGraph 内部资源(RT、Depth、Attachment)的一个“句柄”。

  • 只是一个 ID,不是实际的 RenderTexture

  • RenderGraph 会根据依赖关系自动决定何时创建、复用、释放

  • 确保避免临时 RT 泄漏、减少内存占用。

TextureHandle colorTex = renderGraph.CreateTexture(new TextureDesc(width, height){ colorFormat = GraphicsFormat.R8G8B8A8_UNorm,name = "_MyColorTex"});

AddBlitPass

这是 RenderGraph 提供的 内置便利方法,用来快速添加一个 Blit 操作。

  • 在旧的 CommandBuffer 里,我们写:

cmd.Blit(source, destination);

在 RenderGraph 下,直接用 AddBlitPass

  • 自动生成一个 RenderPass

  • 负责从一个 TextureHandle Blit 到另一个 TextureHandle

  • 可以指定材质、shader pass

// sourceTex / targetTex 都是 TextureHandle
UniversalRenderer renderer = ...;renderer.EnqueuePass((renderGraph, resources) =>
{var src = resources.GetTexture(sourceTex);var dst = resources.GetTexture(targetTex);renderGraph.AddBlitPass(src, dst, material: myMaterial, passIndex: 0, "My Custom Blit");
});

RenderGraph 根据 Pass 之间的依赖自动生成渲染任务 DAG(有向无环图)

MSAA 的本质

  • MSAA(Multisample Anti-Aliasing)是一种硬件抗锯齿。

  • 开启后,渲染目标会在内部存储多个采样点(比如 4×MSAA = 每像素 4 个采样)。

  • 最终颜色会在 Resolve 阶段合并。

  1. public class BlurRendererFeature : ScriptableRendererFeature    
    
  2. In the BlurRendererFeature class, implement the following methods:

    • Create: Unity calls this method on the following events:

      • When the Renderer Feature loads the first time.

      • When you enable or disable the Renderer Feature.

      • When you change a property in the inspector
         of the Renderer Feature.

    • AddRenderPasses: Unity calls this method every frame, once for each camera. This method lets you inject ScriptableRenderPass instances into the scriptable Renderer.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering.Universal;public class BlurRendererFeature : ScriptableRendererFeature
{public override void Create(){}public override void AddRenderPasses(ScriptableRenderer renderer,ref RenderingData renderingData){}
}

ref RenderingData renderingData

  • 渲染帧的上下文数据,包含了当前 Camera、灯光、渲染设置的信息。

  • 因为可能比较大,所以用 ref 引用传递,避免复制开销。

  • 常用内容:

    • renderingData.cameraData → 当前相机的信息(投影矩阵、视图矩阵、RenderTarget 等)。

    • renderingData.lightData → 主光源、额外光源数量。

    • renderingData.shadowData → 阴影设置。

    • renderingData.postProcessingData → 后处理相关。

public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
{// 获取当前相机信息var cameraData = renderingData.cameraData;if (cameraData.cameraType == CameraType.Game){myPass.Setup(cameraData.cameraTargetDescriptor);renderer.EnqueuePass(myPass);}
}

为什么用 ref

  • RenderingData 是一个结构体(struct),不是 class。

  • struct 默认是 值类型传递,传参会复制一份。

  • ref 之后传的是引用 → 不会拷贝,性能更好。

  • renderer:用来注册 Pass(告诉管线“要多跑一步”)。

  • ref renderingData:携带当前帧的渲染上下文,相机/光源/阴影信息都在这里。

  • ref:避免复制 struct,提升效率。

什么是 ScriptableRenderer

  • 它是 URP 渲染管线的核心类,负责安排并执行一帧里的所有 RenderPass

  • URP 内置的 ForwardRenderer 就是 ScriptableRenderer 的一个具体实现。

implement,虽然字面意思确实是“执行”,但在编程/文档语境里,它的含义更准确地是 “把一个抽象的设计方案,具体落实成代码或系统行为”

为什么用 implement 而不是其他词

1. 和 execute 的区别

  • execute:强调“一次性的运行”。

    e.g. The CPU executes an instruction.
    翻译成“执行指令”。

  • implement:强调“把功能落实成实际的实现方式”。

    e.g. We implement a renderer feature with custom passes.
    翻译成“通过自定义 Pass 实现渲染器功能”。

所以 implement 在这里不是“执行一次”,而是“实现某个机制,使它以后可以被执行”。

apply 的区别

  • apply:更偏向“把已有的东西作用到某对象上”。

    e.g. Apply a blur effect to the camera output.
    → 已有的模糊算法,套用到图像上。

  • implement:更偏向“你需要自己写逻辑,落地一套功能”。

    e.g. Implement a blur effect with a custom render pass.
    → 自己写一个 RenderPass 来支撑模糊效果。

realize / fulfill 的区别

  • realize/fulfill:更抽象,强调“达成、实现目标”。

  • implement:更工程化,强调“写代码/构建系统,把目标落到实处”。

这里 implement 的含义是:

  • 把“模糊效果”这个想法,具体用 RendererFeature + RenderPass + Shader 代码落地实现

  • 不只是运行一次,而是给渲染管线增加一套新的机制。

  • implement = 落实抽象 → 变成具体的实现

  • execute = 执行一次

  • apply = 套用现有的东西

  • realize = 抽象的“实现目标”
    所以 Unity 文档里选用 implement,是为了强调“你需要自己写功能模块,把效果整合进管线”,而不仅仅是“执行/应用”而已。

  • ScriptableRendererFeature instance that enqueues a ScriptableRenderPass instance every frame.

  • ScriptableRenderPass instance that performs the following steps:

    • Creates a temporary render texture
       using the RenderTextureDescriptor API.

    • Applies two passes of the custom shader to the camera output using the TextureHandle and the AddBlitPass API.

和 implement 的区别

  • implement = 设计+落地,把功能写出来(偏工程)。

  • perform = 运行时具体执行动作(偏执行)。

例子:

  • We implement a blur pass in the renderer.
    → 我们写了一个模糊 Pass(落地功能)。

  • The renderer performs a blur operation on the camera target.
    → 渲染器在相机目标上执行模糊运算(运行时的动作)。

动词偏向含义常见语境举例
implement落实、写代码、实现机制implement a feature, implement SSAO
execute按顺序执行一次任务execute a command, execute a script
apply套用、把已有的东西作用到目标上apply a filter, apply SSAO to color buffer
perform实际执行运算或过程,强调动作本身perform depth test, perform matrix calculation
realize/fulfill更抽象的“实现目标/完成需求”realize a vision, fulfill requirements

specify

  • 含义:明确指定、设定一个值。

  • 用在 “specify the base color” → 表示 在材质参数里填写或选择某个颜色

  • 替代词:set / define / choose

    • set the base color = 设置

    • define the base color = 定义

    • choose the base color = 选择

  • specify 更正式,强调你作为用户明确给出一个值,不是随便选。

respectively

  • 含义:分别地,对应顺序地。

  • “set the base colors of the Materials to blue and red respectively” → 表示 第一个材质设为蓝色,第二个材质设为红色,一一对应。

  • 替代词:in order / correspondingly

    • … to blue and red in order

    • … to blue and red correspondingly

  • respectively 是标准的技术写法,比 “in order” 更精确。

assign

  • 含义:分配、赋予,把某个资源关联到目标上。

  • “assign the Red Material to the cube” → 把材质引用赋值给 Cube 的 MeshRenderer。

  • 替代词:apply / attach / set

    • apply the Red Material to the cube = 应用

    • attach the Red Material to the cube = 附加

    • set the cube’s Material to Red = 设置

  • assign 在 Unity 里是最常用的,因为它准确表达了“把资源 slot 里的引用指定为某个对象”。

Add the Renderer Feature to the Universal Renderer asset

  1. public class BlurRenderPass : ScriptableRenderPass
    
  2. Add the RecordRenderGraph method to the class. This method adds and configures render passes in the render graph. This process includes declaring render pass inputs and outputs, but doesn’t include adding commands to command buffers. Unity calls this method every frame, once for each camera.

    public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameData)
    { }

Here’s the complete code for the BlurRenderPass.cs file from this section:

using UnityEngine.Rendering;
using UnityEngine.Rendering.RenderGraphModule;
using UnityEngine.Rendering.RenderGraphModule.Util;
using UnityEngine.Rendering.Universal;public class BlurRenderPass : ScriptableRenderPass
{public override void RecordRenderGraph(RenderGraph renderGraph,ContextContainer frameData){}
}

Implement the settings for the custom render pass

This section demonstrates how to implement the settings for the custom blur render pass.

The Renderer Feature in this example uses the shader
 that performs the blur horizontally in one pass, and vertically in another pass. To let users control the blur value for each pass, add the following BlurSettings class to the BlurRendererFeature.cs script.

[Serializable]
public class BlurSettings
{[Range(0,0.4f)] public float horizontalBlur;[Range(0,0.4f)] public float verticalBlur;
}

In the BlurRendererFeature class, declare the following fields:

[SerializeField] private BlurSettings settings;
[SerializeField] private Shader shader;
private Material material;
private BlurRenderPass blurRenderPass;

add

  • 直译:添加。

  • 在这里:表示 把一个新的 RenderPass 节点注册到 RenderGraph 里

  • 强调的是“多出来一个 Pass”,相当于列表里插入元素。

例子:

add a new render pass to the graph = 给渲染图新增一个 Pass 节点。

configure

  • 直译:配置。

  • 在这里:表示 设置 Pass 的属性,比如输入纹理、输出目标、执行条件等。

  • 重点是“如何运行”,而不是“是否存在”。

例子:

configure the render pass with a depth attachment = 给这个 Pass 配置一个深度输入。

declare

  • 直译:声明。

  • 在这里:表示 告诉 RenderGraph 这个 Pass 需要哪些输入/输出资源

  • 这是 RenderGraph 的关键,因为它是“声明式 API”:

    • 你说清楚需求(declare),

    • RenderGraph 自动负责资源分配、合并、复用。

例子:

declare render pass inputs and outputs = 声明这个 Pass 读写哪些 Attachment。

因为 RenderGraph 本质是一个“声明式图”

  • add Pass 节点(增加一个计算步骤)。

  • configure 节点(指定运行方式和参数)。

  • declare 输入/输出依赖(让系统自动管理资源)。

所以文档用这几个词,是在区分 三个不同的层次:存在 → 参数 → 依赖。

在代码层面意思就是“在类里把变量字段列出来,让编译器知道它们的存在和类型”。

declare 在代码里的含义

在 C# / Java / C++ 等语言里:

  • declare = 声明一个字段/变量/方法,让编译器知道它的名字和类型。

  • 这里并不会分配真正的运行时数据,只是“告诉系统有这么个东西”。

例子:

private int count; // 声明(declare)

count = 10; // 赋值(assign)

为什么不用 define / create

  • define:更像“定义”,比如在 C 语言里 #define 宏定义,或者“给一个概念下定义”。

  • create:更像“运行时创建对象”,比如 new Material()

  • declare:强调“把字段写在类里,注册到编译器语义里”,这正好符合这里的语境。

declare the following fields

意思就是:
BlurRendererFeature 这个类里面,先声明好需要用的字段(settings, shader, material, blurRenderPass),后面的方法里会给它们赋值或初始化。

换句话说:

  • 第一步是 declare(告诉编译器有这些变量)。

  • 后面才是 initialize / assign(实际创建对象并赋值)。

在 C#(和大多数面向对象语言)里:

  • field(字段) = 直接写在类里的变量,用来保存对象的状态或数据。

  • local variable(局部变量) 不同:

    • 局部变量只存在于方法内部。

    • 字段属于类实例,随着对象一起存在。

  • fields:类中的变量,用来存储状态。

  • properties:字段的包装器(get/set 访问器),更安全。

  • methods:类里的函数。

  • local variables:方法里的临时变量。

In the RecordRenderGraph method, create the variable for storing the UniversalResourceData instance from the frameData parameter. 

UniversalResourceData contains all the texture references used by URP, including the active color and depth textures of the camera.

UniversalResourceData resourceData = frameData.Get<UniversalResourceData>();

为什么这里用 create 而不是 declare

  • 如果写的是 declare,容易让人误会成“类的成员字段”。

  • 但这里的变量 resourceData 只在 RecordRenderGraph 方法内部用,所以文档用 create 来强调“这是运行时新建的局部变量”。

这里的 declare the variables for interacting with the shader properties 指的是:

在类里写出一组“变量”,这些变量专门用来和 Shader 的属性(Properties)建立对应关系。

为什么用 declare

  • declare = 声明。

  • 在这里表示 在类作用域中声明出几个变量(字段),让后面的代码可以用它们来读写 Shader 的属性。

  • 这些变量通常是 int(PropertyID)或者 string(属性名),它们本身不会做计算,只是保存映射关系。

declare the TextureHandle fields

  • declare:声明,在方法体或类里写出变量,让编译器知道它的存在和类型。

  • TextureHandle:RenderGraph 里的句柄(handle),不是实际的贴图,而是一个引用/占位符,用来告诉 RenderGraph “我要用到一张图”。

  • 在这里 TextureHandle srcCamColor 就是一个变量声明(declare),编译器知道它是 TextureHandle 类型。

这里的 TextureHandle srcCamColordeclare(声明引用)
blurTextureDescriptor = srcCamColor.GetDescriptor(...)initialize(初始化描述符)
renderGraph.CreateTexture(...)create(真正生成 RenderTexture)

In the RecordRenderGraph method, add the function to continuously update the blur settings in the material.

// Update the blur settings in the material
UpdateBlurSettings();// This check is to avoid an error from the material preview in the scene
if (!srcCamColor.IsValid() || !dst.IsValid())return;

UniversalCameraData cameraData = frameData.Get<UniversalCameraData>();// The following line ensures that the render pass doesn't blit
// from the back buffer.
if (resourceData.isActiveTargetBackBuffer)return;

什么是 back buffer

  • GPU 在渲染时通常会有一个 后备缓冲区(back buffer),就是屏幕最终显示之前的那块颜色缓冲。

  • 渲染顺序:先往 back buffer 画 → 最后再把 back buffer 显示到屏幕。

什么是 blit

  • blit = block image transfer,指的是把一张纹理的数据复制/绘制到另一张纹理。

  • 在 Unity 里,Blit 常见于后处理,比如把摄像机画面复制到一个临时 RT,再套上 shader 特效。

注释想表达的意思

The following line ensures that the render pass doesn't blit from the back buffer.

  • 这里的 if (resourceData.isActiveTargetBackBuffer) return;
    的作用就是:如果当前渲染目标已经是 back buffer,就直接跳过,不要再去做 Blit 操作。

原因:

  1. Blit 从 back buffer 读数据再写回去,容易导致 读写冲突(GPU 可能还没写完,就被读了)。

  2. 这样做也是没意义的,因为 back buffer 就是最后一步输出,再复制一遍等于浪费性能,还可能产生错误。

  • srcCamColordst 都是 TextureHandle,代表输入纹理和输出纹理。

  • .IsValid() 是 RenderGraph 提供的 API,用来判断这个 handle 是否指向一个真正可用的纹理资源。

为什么需要这个检查

在 Unity 的 Scene 视图Inspector 预览 里,有时候 RenderGraph 的某些资源还没准备好(比如没有摄像机渲染,或者这个 Pass 在某些模式下被跳过)。

  • 如果你不检查就直接用,会报错:

    • 材质找不到输入贴图。

    • GPU 尝试读取一个无效的 RenderTexture。

  • 通过 if (!IsValid()) return;,你可以在资源缺失时直接跳过这一帧的 Pass,而不是让游戏崩溃。

This check is to avoid an error from the material preview in the scene

翻译成更直白的话就是:
“这一步检查是为了避免在编辑器的场景预览中,由于材质使用了无效贴图而报错。”

back buffer 并不是孤立的,它只是整个 GPU **framebuffer(帧缓冲)**的一部分。你提到的颜色缓冲、深度缓冲,都可以算它的“同类”。

主要几类缓冲区

  1. Color buffer(颜色缓冲)

    • 保存每个像素的最终颜色值。

    • back buffer 就是其中一个特殊的 color buffer,最终会显示到屏幕。

  2. Depth buffer(深度缓冲,Z-buffer)

    • 保存每个像素的深度值(距离相机的远近)。

    • 用于深度测试(ZTest),决定一个像素是否被遮挡。

  3. Stencil buffer(模板缓冲)

    • 和 depth buffer 共享内存(通常叫 Depth/Stencil buffer)。

    • 用于复杂遮罩、描边、分区域渲染等。

  4. Auxiliary / Intermediate buffers(中间缓冲)

    • 后处理、延迟渲染(G-Buffer)、特效(如 SSAO)都会临时创建额外缓冲区。

时间上的先后顺序

  1. G-buffer 阶段 / ShadowMap 等预处理

    • 如果是延迟渲染,先写 G-buffer。

    • 如果是前向渲染,也可能先写 shadowmap(到单独的深度贴图里)。

  2. 主 Pass → 写 Color buffer 和 Depth buffer

    • 绘制不透明物体时,同时写入颜色缓冲和深度缓冲。

    • 深度缓冲用于后续透明物体和特效的遮挡。

  3. 透明物体 Pass

    • 读取深度缓冲(但通常不写),继续往 color buffer 混合颜色。

  4. 后处理 Pass

    • 通常在中间 RenderTexture 上进行,最后再 blit 回 back buffer。

  5. Back buffer → 屏幕显示

    • Swap chain(交换链)把 back buffer 显示出来,并把一个新的 back buffer 分配出来等待下一帧。

Implement the render passes

In the RecordRenderGraph method, using the AddBlitPass method, add the vertical and the horizontal blur render passes.

Implement the render passes

In the RecordRenderGraph method, using the AddBlitPass method, add the vertical and the horizontal blur render passes.

// The AddBlitPass method adds a vertical blur render graph pass that blits from the source texture (camera color in this case) to the destination texture using the first shader pass (the shader pass is defined in the last parameter).
RenderGraphUtils.BlitMaterialParameters paraVertical = new(srcCamColor, dst, material, 0);
renderGraph.AddBlitPass(paraVertical, k_VerticalPassName);// The AddBlitPass method adds a horizontal blur render graph pass that blits from the texture written by the vertical blur pass to the camera color texture. The method uses the second shader pass.
RenderGraphUtils.BlitMaterialParameters paraHorizontal = new(dst, srcCamColor, material, 1);
renderGraph.AddBlitPass(paraHorizontal, k_HorizontalPassName);
using System;
using UnityEditor;
using UnityEngine;
using UnityEngine.Rendering.Universal;//
// ====================== 使用者视角 ======================
//
// 这个类是一个“渲染特性插件”(ScriptableRendererFeature)。
// 使用者(游戏开发者/美术/配置者)不需要理解内部实现,
// 只需要:
//   1. 把它挂载到 RendererData 上(在 URP Renderer Inspector 界面里)。
//   2. 拖一个 Shader 到 inspector 里。
//   3. 配置 BlurSettings 中的参数。
// 就能得到额外的模糊效果。换句话说,它就是一个“开关+配置入口”。
// 
// 这种模式可以类比 Photoshop 里的“滤镜插件”:使用者只需要选择滤镜和参数,
// 而滤镜如何处理像素数据完全是插件本身的事情。
// -------------------------------------------------------
// ====================== 应用者视角 ======================
// 
// 应用者(这里是你自己,或者开发这个 Feature 的人)只关心:
//   - 如何和渲染管线交互(在什么时候插入 Pass)。
//   - 如何把 Shader 封装成一个可用的渲染过程。
//   - 如何管理资源(材质、Pass 的生命周期)。
//
// 在 URP 框架里,Feature 就是“注册点”,Pass 就是“执行单元”。
// 你写 Feature = 定义了一个插口;你写 Pass = 定义了插口插进去之后干什么事。
// 
// =======================================================
public class BlurRendererFeature : ScriptableRendererFeature
{// -------- 使用者侧可见的配置字段 --------[SerializeField] private BlurSettings settings;  // 用户可调的模糊参数(水平/垂直范围)[SerializeField] private Shader shader;          // 用户指定的 Shader// -------- 应用者侧内部维护 --------private Material material;                       // 用 shader 动态生成的材质private BlurRenderPass blurRenderPass;           // 真正执行模糊效果的 Pass// 生命周期方法:在渲染器创建时调用public override void Create(){if (shader == null){return; // 如果没传 shader,直接跳过,不让系统崩}// 用 shader 构建材质,这是 GPU 实际要用的东西material = new Material(shader);// 创建模糊 Pass,并传入材质和参数blurRenderPass = new BlurRenderPass(material, settings);// 指定 Pass 的插入点:在所有后处理(PostProcessing)之前blurRenderPass.renderPassEvent = RenderPassEvent.BeforeRenderingPostProcessing;}// 每帧调用,用于把 Pass 加入管线public override void AddRenderPasses(ScriptableRenderer renderer,ref RenderingData renderingData){if (blurRenderPass == null){ return;}// 限制只对“Game 视图”相机生效(避免 Scene 视图报错)if (renderingData.cameraData.cameraType == CameraType.Game){// 将 Pass 注册进渲染流程renderer.EnqueuePass(blurRenderPass);}}// 生命周期:释放资源,避免内存泄漏protected override void Dispose(bool disposing){if (Application.isPlaying){Destroy(material);          // 播放模式下用 Destroy}else{DestroyImmediate(material); // 编辑器模式下用 DestroyImmediate}}
}//
// 这个类是参数集合,使用者在 Inspector 里调节。
// 通过 [Serializable] + [Range],Unity 可以把它序列化并显示为滑条。
// 类比 Photoshop 滤镜的“强度滑杆”。
//
[Serializable]
public class BlurSettings
{[Range(0, 0.4f)] public float horizontalBlur;[Range(0, 0.4f)] public float verticalBlur;
}

为什么要有 Queue

  1. 顺序控制

    • GPU 渲染不是随便“插一段代码”就能执行的,它必须严格按照 RenderPassEvent 规定的先后顺序运行(比如先阴影 → 再不透明物体 → 再透明物体 → 再后处理)。

    • Queue 就是把 Pass 放到一个“任务队列”里,RenderLoop 会按照时间点依次取出执行。

  2. 依赖管理

    • Pass 之间可能依赖资源:

      • SSAO Pass 需要深度图。

      • Bloom Pass 需要前面写好的颜色图。

    • 如果不排队管理,很可能出现资源还没生成就被读取 → GPU 报错。

  3. 可扩展性

    • Unity 的 URP 是通用框架,不知道你会加什么特效。

    • 通过 Queue,你只需要告诉系统“我有一个 Pass,希望在某个阶段运行”,系统自动帮你插入,而不是你自己硬编码。

如何达到这种“管理的视角”

想站在“管理”的角度理解,等于你是“流水线调度员”,要安排工人(Pass)正确上岗:

  • 把 Pass 当作工人:每个工人负责一件事(阴影计算、模糊、后处理)。

  • Queue = 上班顺序表:工人需要排好顺序,不然有人没干完,后面的人就拿不到原料。

  • Renderer = 工厂管理者:它收集所有工人(Pass),在合适时间点叫他们干活。

你需要使用/理解的工具

要切换到管理者的思路,你要掌握以下几个关键点:

  1. RenderPassEvent

    • 指定 Pass 在管线的哪个阶段执行。

    • 常见值:BeforeRenderingShadows、AfterRenderingSkybox、BeforeRenderingPostProcessing 等。

  2. EnqueuePass

    • 把 Pass 加入队列,交给渲染器管理。

  3. ScriptableRendererFeature & ScriptableRenderPass

    • Feature = 插口(告诉管线“我要加东西”)。

    • Pass = 实际执行逻辑。

  4. RenderGraph(新版本)

    • 进一步升级的管理器,不只是排队,还自动帮你处理资源依赖(谁读写谁、谁需要先执行)。

一个 Feature vs 拆成多个 Feature

  • 单一 Feature 内部可以有多个 Pass

    • 一个 ScriptableRendererFeature 可以创建并管理多个 ScriptableRenderPass,按顺序 Enqueue 到不同阶段。

    • 好处:集中管理,代码在一个文件里,便于共享配置(比如一份设置同时驱动几个 Pass)。

  • 拆分成多个 Feature

    • 每个 Feature 单独负责一个 Pass 或一类 Pass。

    • 好处:模块化,彼此独立,方便开关。比如“BlurFeature”管模糊,“OutlineFeature”管描边。

    • 缺点:配置分散,不同 Feature 之间共享数据可能需要额外接口。

类比:

  • 一个 Feature 多 Pass → 一个“多功能工人”,既能打螺丝又能上油漆。

  • 多个 Feature 各自一个 Pass → 多个“专职工人”,一个只打螺丝,一个只上油漆。

管理的视角

要站在“流水线调度员”的位置:

  • 你能把一个插件(Feature)当作一个整体模块插进去。

  • 你也能把几个小插件(多个 Feature)分开插进去,各管一段流程。

  • 最终 Renderer 是调度核心,它会扫描所有 Feature,把每个 Feature 的 Pass 都收集好,再按阶段顺序统一排队。

所以你看到的 renderer.EnqueuePass() 其实是把工人们报到登记,最后由调度员统一派到流水线上。

同一个 Feature 里的多个 Pass

特点:

  • 共享字段:你在 Feature 类里声明的变量(材质、RT 句柄、设置参数等),可以在创建 Pass 时统一传进去。

  • 顺序可控:你在 AddRenderPasses() 里自己决定先后调用 EnqueuePass(),所以这些 Pass 的相对顺序更可预测。

  • 强耦合:Pass 之间可以直接通过引用联系,比如:

     

    var passA = new PreprocessPass(sharedMaterial);

  • var passB = new BlurPass(sharedMaterial, passA.outputTexture);

    → 这里 BlurPass 可以直接用 PreprocessPass 的输出。

类比:同一个“多功能工人”,一边打螺丝、一边上油漆,他可以把手里工具直接传给下一道工序,沟通成本低

分开到两个 Feature

特点:

  • 各自独立:每个 Feature 自己管理 Shader、材质、RT 等。

  • 顺序由 Renderer 管不同 Feature 的 Pass 全部排进统一队列,执行时顺序由 renderPassEvent 和队列顺序决定。

  • 弱耦合:如果 Feature A 想把数据给 Feature B,通常要用:

    • 全局 Shader 参数(Shader.SetGlobalTexture)。

    • 或者 RenderGraph 的全局资源。

类比:两个不同工人,一个打螺丝,一个上油漆,他们只能通过流水线传递的“物料”沟通,而不是直接交换工具。

  • 如果 Pass 强相关(例如 SSAO 前处理 → SSAO Blur → SSAO 合成),放在一个 Feature 内更合适,方便共享和管理。

  • 如果 Pass 相对独立(例如 Outline 效果和 Bloom 效果),拆成多个 Feature 更好,模块化、可选、可关。

Feature 内部多个 Pass 的常见场景

  • 多步骤后处理:比如 Bloom 效果 → 先提取高亮 → 再模糊 → 最后合成。

  • 分阶段渲染:比如 SSAO → 先生成 AO 贴图 → 再 Blur → 再把 AO 乘到颜色缓冲上。

  • 特殊几何渲染:比如 Outline → 先渲染对象边缘 → 再扩展边缘 → 再叠加回主图。

添加一个feature这里就会多一行,不管里面有几个pass

public override void Create()
{var mat = new Material(shader);prePass = new PreprocessPass(mat);blurPass = new BlurPass(mat);compositePass = new CompositePass(mat);
}public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
{renderer.EnqueuePass(prePass);       // 先做预处理renderer.EnqueuePass(blurPass);      // 再做模糊renderer.EnqueuePass(compositePass); // 最后合成
}

URP 的 Shader(尤其是 lit/unlit 这种 Universal Shader)里,往往只能写一个 Pass(比如 ForwardLit Pass),不像 Built-in Pipeline 的 ShaderLab 可以随意堆多个 Pass{}

为什么 URP Shader 往往只有一个 Pass

  • 管线设计不同
    URP 把大部分管线逻辑(阴影、深度前置、后处理、多光源累加等)搬到了 C# RenderPass 里,而不是写在 Shader 文件的多个 Pass。

  • Shader 更“单纯”
    在 URP 里 Shader 文件只定义一种材质的表面着色(BRDF、表面属性),而不是负责控制渲染流程。

  • 管线复用
    Unity 内部写好的 URP 代码,会自动决定什么时候跑 ShadowCaster、DepthOnly 等 Pass。你自己写的 Shader 通常只需实现一个 ForwardLit。

  • Loon 原本是指一种鸟,学名叫 潜鸟 (loon, diver),在北美很常见。

  • 它的叫声比较怪异、悠长,被人觉得有点“疯疯癫癫”的感觉。


2. 词义转变

  • 在口语里,loon 从“叫声怪的鸟” → 引申为“疯子、傻子”。

  • 相当于中文里说某人“呱噪的鸟、蠢鸟”,带点嘲讽。

  • 因此 a loon 就变成了 “crazy person, foolish person”。

和 lunatic 的关系

  • 有人会把 loon 看作 lunatic(疯子) 的缩略或变体。

  • 虽然严格词源上 loon 来自鸟名,但因为发音接近 loon ~ lun-,在大众用法里混在一起,进一步巩固了“傻瓜、疯子”的意思。

实际上人们甚至觉得它的叫声很漂亮,

这里用 fear of man 而不是 fear to man,原因和 fear 这个名词/动词的搭配习惯 有关。


1. fear 作为名词

  • 固定搭配是 fear of + 名词/动名词

    • fear of snakes

    • fear of death

    • fear of flying

这里的 of 表示 恐惧的对象/来源
所以:

fear of man = 对人的恐惧。


2. fear 作为动词

  • 固定用法是 fear + 名词/动名词,不需要介词:

    • I fear snakes.

    • She fears nothing.

如果一定要带介词,也会出现 fear for(担心某人安危):

  • I fear for his life.


3. 为什么不是 fear to man

  • to 一般表示方向或动作的接受者(give to sb, go to somewhere)。

  • “恐惧”不是一个可以传递的动作,所以英语里不会说 fear to man

  • 正确的是用 of 来标明“恐惧的来源/对象”。


总结

  • fear of + 名词 → 表示“对…的恐惧”。

  • fear to → 不成立,因为 to 不表示“恐惧的来源”,而表示方向。

所以句子里的 The chick initially has no fear of man 翻译为:

小鸟最初并不惧怕人类。

“思考点”        “管线逻辑”和“着色逻辑”职责划分 这个核心问题。

Built-in vs URP 的职责变化

  • Built-in
    Shader 文件本身很“重” → 它要写多个 Pass(ForwardBase、ForwardAdd、ShadowCaster、DepthOnly...),自己承担流程管理。

  • URP
    Shader 文件变“轻” → 主要描述材质本身怎么和光交互。
    管线控制(什么时候跑哪个 Pass、如何合并结果)交给了 C# RenderPass 系统。

可能在思考的几个触发点

Shader 不再是全能
→ 你突然意识到 Shader 文件不再是“既写逻辑又管顺序”的全能脚本,它只定义“怎么画材质”。
这让 Shader 更“本质化”(纯粹做 BRDF、表面函数)。

渲染管线的抽象层次
→ 你脑子可能在转:原来渲染结果不光取决于 Shader,而是 管线调度 + Shader 实现 的结合。
相当于 Shader 只是管线里的一个“插件”,而不是整个流程。

可复用 vs 可替换
→ 当你想到“Unity 内部会自动调用 ShadowCaster、DepthOnly”,可能意识到:Shader 只要遵循约定,就能被统一流程调用,不需要自己重复写。
这其实就是 解耦:Shader 专注表现,Pipeline 专注调度。

“写 Shader”转到“理解 Rendering Pipeline 设计哲学” 的一个关节点

在 Built-in 里

  • 一个 Shader 文件 = 材质定义 + 渲染流程控制

  • 你写的多个 Pass(ForwardBase、ForwardAdd、ShadowCaster、DepthOnly…)其实就是 小型的渲染管线拼接

    • ForwardBase:第一盏主光 + 环境光 + Lightmap。

    • ForwardAdd:额外的逐像素光,一个 Pass 一个。

    • ShadowCaster:深度写入阴影贴图。

    • DepthOnly:只写深度缓冲,用在深度前置或相机 DepthTexture。

换句话说,你一个 Shader 文件本身就携带了“怎么渲染它”的完整说明书
所以它很“重”,逻辑和表现都耦合在一起。

在 URP / SRP 里

  • Shader 文件只写 表现(BRDF、表面属性),几乎不管流程。

  • 渲染顺序、Pass 调度都由 C# Pipeline 代码统一调度。

  • 于是一个 Shader 通常只需要实现 ForwardLit(或 Unlit),剩下的 ShadowCaster/DepthOnly Unity 自己生成或自动调用。

所以 Shader 变得“轻”,不再背负流程管理。

“材质本身怎么和光交互”,其实就是 Shader 在 URP 里唯一保留的“核心职责”

什么叫“材质和光交互”

  • 从光源射来的光 → 照到物体表面 → 按材质属性决定如何反射、折射或吸收。

  • Shader 在这里做的就是:

    • 输入:法线、粗糙度、金属度、Albedo 颜色、Emission…

    • 光照模型(BRDF, Bidirectional Reflectance Distribution Function)

    • 输出:这个像素最终的颜色(比如漫反射多少,镜面反射多少)。

Shader 不需要再管流程,只需回答一个问题:
“当一束光打到这个材质上时,我怎么计算颜色?”

意味着什么

  • Shader 从流程控制器 → 纯粹的光照函数

  • 你的注意力可以集中在 物理正确的材质表现(比如金属、玻璃、皮肤),而不是“要写几个 Pass”。

  • 这也是 PBR(物理化渲染)的核心思路:材质描述自己,管线负责调度。

为什么说它是“迷你渲染管线”

  • 多 Pass = 多个阶段

    • ForwardBase → 处理主光 + 环境光。

    • ForwardAdd → 每个附加光源再跑一次。

    • ShadowCaster → 专门生成阴影贴图。

    • DepthOnly → 只写深度缓冲。

  • 每个 Pass 不仅描述“怎么画像素”,还决定了 渲染顺序和功能职责

  • 所以一个 Shader 文件就携带了一整套管线逻辑:谁先画、画什么、用什么数据。

与 URP 对比

  • URP

    • Shader 文件只定义 材质表现(BRDF、表面属性)。

    • 管线逻辑(何时跑 ShadowCaster、DepthOnly、后处理等)搬到 C# 的 ScriptableRenderer/RenderPass 去管理。

  • 于是 Shader 变“单纯”,不再负责流程。

换句话说:

  • Built-in Shader = “材质表现 + 渲染流程绑定” → 一份 Shader 就是一个迷你管线。

  • URP Shader = “材质表现” → 流程交给外部管线调度。


文章转载自:

http://BSuijcnY.qLkzL.cn
http://QRnFWEvE.qLkzL.cn
http://Tvu7aXlZ.qLkzL.cn
http://Tfo7dPW6.qLkzL.cn
http://AIL7MyK2.qLkzL.cn
http://lAfRohhD.qLkzL.cn
http://kdihjrFf.qLkzL.cn
http://RPsJSTfc.qLkzL.cn
http://ALj7o1M4.qLkzL.cn
http://SGbHkEtR.qLkzL.cn
http://Q5PzmipZ.qLkzL.cn
http://FWFRAYw5.qLkzL.cn
http://2ElTCNjt.qLkzL.cn
http://2mWvl022.qLkzL.cn
http://BBZRB4Q1.qLkzL.cn
http://YpqTXAlC.qLkzL.cn
http://huuzBmS7.qLkzL.cn
http://PW53aL6A.qLkzL.cn
http://MhbjPP9v.qLkzL.cn
http://KBdeUQ2W.qLkzL.cn
http://dcDcd939.qLkzL.cn
http://UzIsDhL5.qLkzL.cn
http://eHYkSjxL.qLkzL.cn
http://vWTq0fv6.qLkzL.cn
http://5HMHpYvu.qLkzL.cn
http://69tdBih9.qLkzL.cn
http://TEPm8AEW.qLkzL.cn
http://5VcOprAW.qLkzL.cn
http://92QVvje6.qLkzL.cn
http://5EOZ6sDm.qLkzL.cn
http://www.dtcms.com/a/380491.html

相关文章:

  • 【爬坑指南】亚马逊文件中心 AWS S3 预签名URL 前端直传
  • 【技术教程】如何将文档编辑器集成至用PHP编写的Web应用程序中
  • AWS RDSInstance模型优化实践:从字段长度调整到索引策略全面提升
  • ADSP-ADI sharc 内存配置笔记
  • 嵌入式C语言-关键字typedef
  • daily notes[44]
  • 手机端APP解析工具开发实战——从0到1实现漏洞检测与接口分析
  • Mysql数据库多表设计
  • open和fopen的区别
  • 排序---选择排序(Selection Sort)
  • 玩转PostMan之调试天气接口-心知天气 API
  • OpenHarmony DHCP 全栈深度剖析:从 DhcpClientStateMachine 到双栈 dhcpd 的客户端-服务器架构设计与源码实现
  • Linux 前后台作业控制及管理
  • 【设计模式】题目小练2
  • 软考中级习题与解答——第五章_面向对象方法(2)
  • 【智慧城市】2025年中国地质大学(武汉)暑期实训优秀作品(4):智矿中国
  • wslg 应用白色边框问题(Jetbrains 系列白色边框)
  • jmeter配置数据库连接步骤
  • Dest1ny安全漫谈-如何做好一个安全项目
  • qt中给QListWidget添加上下文菜单(快捷菜单)
  • Elasticsearch的理解与使用
  • android ndk编译valgrind
  • 实现调用libchdb.a静态连接库中的未公开导出函数
  • Deepoc具身智能无人机:为天空装上「自主决策大脑」
  • JX2202 直阻变比智能测试系统:重构新能源电力检测效率标准
  • 2025 年PT展前瞻:人工智能+如何走进普通人的生活?
  • 【AI论文】分享即关爱:基于集体强化学习经验共享的高效语言模型(LM)后训练方法
  • 二、网页的“化妆师”:从零学习 CSS
  • Rustdesk server docker-compose 一键搭建教程
  • 江科大《STM32入门教程》