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

自由学习记录(99)

原因往往不在语法,而在 上下文和抽象层次

重点,,override,之前的方法,写属于类自己的销毁

Application.isPlaying 的分支

  • 运行时 (isPlaying == true)

    • Destroy(material)

    • Unity 会在安全的时机销毁材质(不会立刻删掉,而是等渲染循环安全点再清理)。

    • 这是为了避免运行中的渲染过程突然丢失资源。

  • 编辑器 (isPlaying == false)

    • DestroyImmediate(material)

    • 因为编辑器下不走“运行时调度”,必须立即删除对象,否则会有“幽灵资源”留在内存里。

. 为什么要覆盖 Dispose

  • 资源创建与释放要对称

    • 你在 Create() 里 new 出了一个 Material(shader),那就必须在 Dispose() 里销毁。

  • 否则的问题

    • 堆积一堆临时材质,场景切换后内存不降。

    • 在 Editor 下,Inspector 里可能出现不可见的材质实例。

如果不经常改,删除再创建没什么大问题;但在运行时频繁这样做,会带来 性能抖动和内存压力。推荐始终在 Create() 初始化一次,在 Dispose() 统一清理,保持资源生命周期对称。

Add the using UnityEngine.Rendering; directive.

用到的是 URP 的 Volume 系统,而 ClampedFloatParameter 是它内置的一种“可调节参数类型”。

  • VolumeComponent
    继承自它的类会变成一个“体积参数容器”,可以在 Volume(体积框、全局 Volume)里调整。

  • 用途
    常见于后处理(Bloom、Color Grading、SSAO 等),在 Inspector 里出现滑动条或开关。

  • 作用机制
    渲染时,管线会查找激活的 Volume,把里面的参数融合成一个最终配置,传给 Shader 或 RenderPass。

ClampedFloatParameter

这是一个专门的 Volume 参数类型,语义是“浮点数参数 + 限制范围”:

为什么要用这种方式

  • 统一接口:URP 的 Volume 系统用这种 Parameter 封装,让参数在运行时可以被 blend(多个 Volume 混合时插值)。

  • Inspector 自动化
    只要声明 ClampedFloatParameter,Inspector 会自动生成 UI,无需写额外的编辑器脚本。

  • 数据安全:避免数值溢出,比如模糊半径 > 1 可能会导致 shader 计算异常。

可以把 ClampedFloatParameter 看作是 “带范围的配置开关”

  • 如果你写 float horizontalBlur = 0.05f;,只是个普通变量。

  • 写成 ClampedFloatParameter,它就有:

    1. 范围限制(不能超界)。

    2. 序列化(能存进场景/预设)。

    3. Inspector 控件(UI 自动生成)。

    4. Volume 混合(能和别的 Volume 插值融合)。

using System;
using UnityEditor;
using UnityEngine;
using UnityEngine.Rendering.Universal;public class BlurRendererFeature : ScriptableRendererFeature
{[SerializeField] private BlurSettings settings;[SerializeField] private Shader shader;private Material material;private BlurRenderPass blurRenderPass;public override void Create(){if (shader == null){return;}material = new Material(shader);blurRenderPass = new BlurRenderPass(material, settings);blurRenderPass.renderPassEvent = RenderPassEvent.BeforeRenderingPostProcessing;}public override void AddRenderPasses(ScriptableRenderer renderer,ref RenderingData renderingData){if (blurRenderPass == null){ return;}    if (renderingData.cameraData.cameraType == CameraType.Game){renderer.EnqueuePass(blurRenderPass);}}protected override void Dispose(bool disposing){if (Application.isPlaying){Destroy(material);}else{DestroyImmediate(material);}}
}[Serializable]
public class BlurSettings
{[Range(0, 0.4f)] public float horizontalBlur;[Range(0, 0.4f)] public float verticalBlur;
}

using UnityEngine.Rendering.RenderGraphModule;

  • RenderGraphModule:URP/HDRP 新的底层渲染调度 API,把一帧渲染拆成一个个小 Pass 并建成有向图。

  • Util:提供一些工具方法,比如创建资源、debug。

  • 在这里引用,说明这个 BlurRenderPass 用了 RenderGraph 的 API 来描述渲染任务,而不是传统的 CommandBuffer.Blit

const=编译期死值

运行时赋值后锁定,适合API返回的ID。

所以它完全不能依赖运行时 API给的值。

为什么 Shader.PropertyToID 必须用 static readonly

  • Shader.PropertyToID 是 Unity 提供的函数,它会:

    1. 查找 Shader 属性名对应的内部整数 ID,

    2. 并把它缓存起来方便后续访问。

  • 关键:这个过程 只有运行时才知道,编译器不可能预先算出来。

  • 所以这里不能用 const,只能用 static readonly

这段代码像一个 翻译官

  • Volume 系统 → 存放玩家/美术的参数输入(Inspector 里调节的 slider)。

  • UpdateBlurSettings → 翻译过程,检查是否启用 override,取值。

  • Material → Shader 的通信接口,最终数值写到这里。

  • Shader → GPU 端真正执行模糊算法。

一次输出要同时兼顾 解释细节、保持风格一致、复用标准写法,确实会增加我这边的思考负担。如果你希望我能稳定地“遵守模板”,可以提前帮我“拆分和固化逻辑”,这样我只需要填充变化的部分,不必每次重新推演。

这里有几种方法,可以让你分担逻辑,又能让我更稳定输出:

先固定一个 标准模板骨架

你可以整理一份“固定结构”,例如:

  • Feature 层ScriptableRendererFeature,包含 Create() / AddRenderPasses() / Dispose()

  • Pass 层ScriptableRenderPass,包含构造函数 / Execute() / UpdateXXXSettings()

  • Volume 层VolumeComponent + 参数(比如 ClampedFloatParameter)。

  • Shader 层:暴露需要的属性(_BlurTexture / _HorizontalBlur / _VerticalBlur)。

我在生成代码时,就按这个骨架逐块替换参数、变量名、逻辑。

“槽位” 的概念来思考

就像预留空格:

  • 效果名称(Blur → Bloom → EdgeDetection)

  • 参数类型(float / bool / color / texture)

  • 渲染阶段(BeforeRenderingPostProcessing / AfterRenderingTransparents 等)

  • 默认值(0.05f, 0.5f…)

如果你先把这些槽位告诉我,我就能快速往模板里填而不是推理“怎么写”。

你截图那几行代码之所以要写在 Create() 里,是因为材质和 Pass 不需要每帧都 new,只要初始化一次。

public override void RecordRenderGraph(RenderGraph renderGraph,ContextContainer frameData){UniversalResourceData resourceData = frameData.Get<UniversalResourceData>();UniversalCameraData cameraData = frameData.Get<UniversalCameraData>();// The following line ensures that the render pass doesn't blit// from the back buffer.if (resourceData.isActiveTargetBackBuffer)return;TextureHandle srcCamColor = resourceData.activeColorTexture;blurTextureDescriptor = resourceData.activeColorTexture.GetDescriptor(renderGraph);blurTextureDescriptor.name = k_BlurTextureName;blurTextureDescriptor.depthBufferBits = 0;var dst = renderGraph.CreateTexture(blurTextureDescriptor);// Update the blur settings in the materialUpdateBlurSettings();// This check is to avoid an error from the material preview in the sceneif (!srcCamColor.IsValid() || !dst.IsValid())return;// 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);}

所以Record-------rendergraph

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

  • resourceData → 提供渲染中已经存在的“标准资产”,比如当前的 color buffer、depth buffer。

  • cameraData → 提供相机上下文,比如视口、target 是不是 backbuffer。

类比:就像借阅图书馆的已有藏书,你必须登记“我要哪一类书”。

防止非法写入

if (resourceData.isActiveTargetBackBuffer)return;

健壮性保护:如果当前相机 target 就是 backbuffer(直接对应屏幕输出),这里就不允许乱 blit。

否则可能会破坏 Unity Editor 的 SceneView 预览,导致报错或显示错乱。

frameData(类型是 ContextContainer)其实就是 URP 的 帧级数据容器。它的作用是:在一次渲染过程中,把和这一帧相关的各种“上下文信息”打包存放,供所有的 RenderPassRendererFeature 随时取用。

里面能取到什么?

你已经见过的:

  • frameData.Get<UniversalCameraData>() → 当前相机的信息(投影矩阵、viewport、是否在渲染 SceneView、MSAA 设置等)。

  • frameData.Get<UniversalResourceData>() → 当前帧用到的 GPU 资源(颜色纹理、深度纹理、法线纹理、shadowmap 等)。

常见还有:

  • LightData → 当前帧的主光源/额外光源、阴影配置等。

  • PostProcessingData → 后处理相关的开关和参数。

  • RenderGraphContext → RenderGraph 内部的上下文,比如调度/缓存信息。

  • TimeData → 时间、deltaTime,用于时间相关的效果(但这个通常是通过 Shader/系统统一传,不常直接在 frameData 拿)。

  • XRPassData → 如果是 VR/XR 渲染,会有每个眼睛的投影矩阵等。

你可以把它理解成一个 “渲染黑板”:URP 每一帧都会把关键数据写到这个 ContextContainer,然后所有 Pass 只需要 Get<T>() 就能拿到对应的数据,而不需要自己去维护全局变量。

为什么要这样设计?

  1. 解耦
    每个 Pass 不需要互相硬编码依赖,只要通过 frameData 取数据。比如 SSAO Pass 要深度贴图,它只要 frameData.Get<UniversalResourceData>().cameraDepthTexture,不用去管是谁先生成的。

  2. 统一管理
    渲染的数据生命周期很复杂(什么时候创建、什么时候释放),如果都放在 frameData 里,URP 就能统一调度,不会内存泄露。

  3. 方便扩展
    任何新写的 RendererFeature 也能往里面塞自己的数据,别的 Pass 就能 Get<T>() 取到。

类比

想象你在做一个剧组拍戏:

  • frameData 就像剧组的 任务单/黑板

  • 摄影师会在黑板上写上“今天的光源配置”。

  • 道具师会在黑板上写上“场景里需要的模型和贴图”。

  • 后期师会在黑板上写“要做模糊+HDR”。
    每个部门的人都不用直接交流,只需要去黑板上查,保证全局一致。

RecordRenderGraph(有时叫 RecordRenderGraphRecordAndExecuteRenderGraph,取决于 Unity 版本)是在 每一帧 都会调用的。

为什么要每帧调用

RenderGraph 的核心思想是:(Directed Acyclic Graph)

  1. 每一帧都要重新构建一张 有向无环图 (DAG),描述这一帧需要的所有渲染任务。

  2. 图里的节点就是 Pass,边就是 资源依赖(比如 A Pass 输出的 RT 要给 B Pass 用)。

  3. 构建完成 → RenderGraph 调度器会做优化(合并、复用、释放临时 RT),然后顺序执行。

👉 所以 图是帧级的,不是全局一次性的。

  • 每帧都会调用,因为相机参数、光源、后处理、UI、渲染目标都可能变。

  • 但不是每帧都 new 资源:RenderGraph 会自动做资源池化,把相同规格的 RT 复用。

  • 如果没有变化,Pass 数量和结构可能相同,但依旧要重新 build 一遍图。

frameData 本质上是 上下文容器 (ContextContainer),是 RenderGraph 在执行一帧时传进来的“行李箱”。里面装着各种你这一帧需要用到的全局数据。

if (!srcCamColor.IsValid() || !dst.IsValid())
    return;
确认输入/输出纹理存在,避免 Unity 编辑器场景预览时因为材质或 RT 缺失报错。

RenderGraphUtils.BlitMaterialParameters paraVertical = new(srcCamColor, dst, material, 0);
renderGraph.AddBlitPass(paraVertical, k_VerticalPassName);

RenderGraphUtils.BlitMaterialParameters paraHorizontal = new(dst, srcCamColor, material, 1);
renderGraph.AddBlitPass(paraHorizontal, k_HorizontalPassName);

  • 第一次 Blit:

    • srcCamColordst

    • 用材质的第 0 个 pass(vertical blur)

    • 得到“竖直模糊”的结果。

  • 第二次 Blit:

    • dstsrcCamColor(写回原来的 RT)

    • 用材质的第 1 个 pass(horizontal blur)

    • 得到最终“横向+竖向模糊”的结果。

这是典型的 两次 separable blur 算法,性能比一次 2D kernel 高效得多。

URP 中普通材质 Shader

在 URP(Universal Render Pipeline)里,常规的 Lit / Unlit Shader 通常是单 Pass 的。

  • 因为渲染流程被管线(C# RenderPass / RenderGraph)严格控制。

  • 这些 Shader 不像 Built-in 里那样包含多个 ForwardBaseShadowCaster 等 Pass,而是只暴露一个「表面着色 Pass」。

  • 其余的 Shadow、DepthOnly、Meta Pass 等都是由管线在编译时注入或强制添加的。

所以你如果去写一个 URP Shader,通常自己只写一个 Pass。

后处理 / 特效 Shader

和材质 Shader 不一样,后处理 Shader(比如你的 Blur Shader)可以有多个 Pass。

  • 这些 Pass 不是用来「同时渲染到场景里」,而是给 Blit 调用时选择的。

  • 例如:

    • Pass 0 = 垂直模糊

    • Pass 1 = 水平模糊

Built-in Pipeline 里的 ShaderLab 多个 Pass 很不一样。原因是:URP/HDRP 下的 Shader 文件更多转向 HLSL 包含式(include-based)+ 函数式编程,而不是在 .shader 文件里写完整的结构。

HLSLINCLUDEENDHLSL

  • 在 Unity ShaderLab 文件里,HLSLINCLUDE ... ENDHLSL 就是告诉编译器:
    这里面写的是纯 HLSL 代码(函数、变量、宏等)。

  • 它和 ShaderLab 的 Pass { ... }SubShader { ... } 并列存在。

  • 好处是:逻辑部分(HLSL)和配置部分(ShaderLab)分开,Shader 更清晰。

为什么要 #include

#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" #include "Packages/com.unity.render-pipelines.core/Runtime/Utilities/Blit.hlsl"

  • Core.hlsl
    提供了一些通用工具函数和宏(比如 UNITY_MATRIX_MVPSAMPLE_TEXTURE2D 等)。

  • Blit.hlsl
    提供了后处理常用的:

    • 顶点着色器 Vert

    • 输入结构 Attributes

    • 输出结构 Varyings
      这样你就不用自己再去写完整的 vertex/fragment 输入输出结构了。

换句话说,这些 hlsl 文件就是 Unity 内置的“标准库”,你只要 include,就能直接用。

路径写得“很长”的原因,其实是 Unity 的包管理体系 (Package Manager) + HLSL include 的定位机制共同决定的。

为什么路径是 "Packages/com.unity.render-pipelines.universal/..."

在 Unity 里,URP/HDRP 等渲染管线不是内置在 Editor 本体里的,而是以 Package 的形式存在。

  • 你在 Project 窗口看到的 Universal RP 文件夹,其实是 Packages/com.unity.render-pipelines.universal 的映射。

  • 当你写 #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl",编译器就会沿着 包路径 去找到对应的 hlsl 文件。

换句话说,这个长路径是 Unity 官方规定的包内路径,就像你用 C# using UnityEngine.Rendering.Universal; 一样,必须写完整命名空间。

现在应该是都移到了shaderlibrary里面了

为什么不用 #include "Core.hlsl" 这种短路径?

因为 shader 编译器不会自动搜索你的项目目录,它不像 C# 那样会帮你找命名空间。

  • 如果只写 "Core.hlsl",编译器根本不知道去哪找。

  • Unity 的规范是:只要是 来自某个包 (package) 的 include,就必须带上 "Packages/...完整路径..."

  • 这样即使换电脑/换 Unity 版本,编译器依然能从对应的包里找到文件。

用 C# 封装 API 替代 Shader include

  • 从 URP 12 以后,Unity 推出了 Blitter C# 工具类,直接在 RenderGraph 或 ScriptableRenderPass 里调用 Blitter.BlitCameraTexture,不需要再 include Blit.hlsl

  • 这也是 Unity 官方逐渐推广的写法。

URP 17 里可能已经弃用 Blit.hlsl,推荐你检查 Fullscreen.hlsl 或直接用 Blitter C# API

即使项目是 URP 管线,Unity 自带的「Create → Shader → Unlit Shader」模板 还是老的 CGPROGRAM 写法,而不是新版的 HLSLPROGRAM

  • Unity 很多内置模板(比如 Standard Surface Shader、Unlit Shader)最初就是为 Built-in 管线设计的。

  • Unity 升级到 SRP(URP/HDRP)后,并没有彻底替换掉这些「右键菜单」里的模板,而是保持兼容。

  • 所以即使你在 URP 项目里点 “Create → Unlit Shader”,它生成的文件还是 内置渲染管线的 CG 写法

HLSLPROGRAM
#pragma vertex Vert
#pragma fragment Frag#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"struct Attributes {float4 positionOS   : POSITION;float2 uv           : TEXCOORD0;
};struct Varyings {float4 positionHCS  : SV_POSITION;float2 uv           : TEXCOORD0;
};Varyings Vert (Attributes input) {Varyings output;output.positionHCS = TransformObjectToHClip(input.positionOS.xyz);output.uv = input.uv;return output;
}float4 Frag (Varyings input) : SV_Target {return float4(input.uv, 0, 1); // test
}
ENDHLSL

并没有这个问题,是看错了,实际上是存在的

Varyings Vert(Attributes input)
{Varyings output;UNITY_SETUP_INSTANCE_ID(input);UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(output);float4 pos = GetFullScreenTriangleVertexPosition(input.vertexID);float2 uv  = GetFullScreenTriangleTexCoord(input.vertexID);output.positionCS = pos;output.texcoord   = DYNAMIC_SCALING_APPLY_SCALEBIAS(uv);return output;
}

  • input.vertexID

    • 这里不是常规的 POSITION 属性,而是 GPU 自带的 顶点索引 (SV_VertexID)

    • Unity 内部只生成 3 个点,GPU 自动展开成覆盖全屏的三角形。

    • 这样比用“两个三角形拼一个矩形”更高效(少顶点、无接缝)。

  • GetFullScreenTriangleVertexPosition(input.vertexID)

    • 根据 0/1/2 三个顶点 ID,返回裁剪空间下的坐标(覆盖整个屏幕)。

  • GetFullScreenTriangleTexCoord(input.vertexID)

    • 同样用 ID 算出对应的 UV(范围 0~1)。

  • DYNAMIC_SCALING_APPLY_SCALEBIAS(uv)

    • 如果项目启用了 动态分辨率缩放 (Dynamic Resolution Scaling),这里会修正 UV。

    • 否则就是原样返回 uv。

  • UNITY_SETUP_INSTANCE_ID / UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO

    • 宏,保证 GPU InstancingVR Stereo Rendering 时能正确传数据。

Varyings 只是一个常见名字,代表 从顶点着色器传到片元着色器的结构体。它不是唯一的,也不是必须叫这个名字。Unity/URP 的不同模块里会定义不同的「varying 结构」,它们本质上都是 顶点阶段输出、像素阶段输入。常见的同类有:

Attributes

  • 用在 顶点着色器输入

  • 包含 mesh 本身的属性,比如 positionOS(模型空间坐标)、normalOS(法线)、texcoord(UV)。

  • 类比:是 CPU 提供给 GPU 的原始顶点数据。

Attributes

  • 用在 顶点着色器输入

  • 包含 mesh 本身的属性,比如 positionOS(模型空间坐标)、normalOS(法线)、texcoord(UV)。

  • 类比:是 CPU 提供给 GPU 的原始顶点数据。

Varyings

  • 用在 顶点着色器输出 / 片元着色器输入

  • 一般包含裁剪空间坐标、UV、光照贴图坐标、世界坐标等。

  • 在 URP 或 HDRP 里有时叫 Varyings,有时叫 Interpolators,名字不同但概念相同。

Attributes → Varyings → FragInput → SurfaceData → 光照

URP Asset(总入口)

  • 这是整个项目的管线设置文件(图里右下角的 URPAsset)。

  • 它会决定 Unity 运行时到底用哪个 Renderer List。

👉 需要在 Project Settings → Graphics → Scriptable Render Pipeline Settings 里,把全局 SRP 设置改成你的 URPAsset_Render,否则 Unity 还是用原来的 URPAsset。

Feature 和 Pass 的关系

  • RendererFeature 只是一个“挂钩点”(Feature = 脚本容器)。

  • 真正被放进 RenderGraph 里执行的,是它里面 EnqueueRenderPass

  • 所以你在 Feature 脚本里如果 Enqueue 了两个 Pass(Vertical + Horizontal Blur),RenderGraph 里就会出现两个节点。

👉 重点:RenderGraph 只关心 Pass,不关心 Feature。Feature 本身不会显示在 Graph 里。

Graph 里的横纵轴

  • 横向(Pass List):时间顺序,URP 把这一帧要执行的 Pass 都排出来(包括 Unity 内置的和你自定义的)。

  • 纵向(Resource List):这一帧里用到的纹理/缓冲区(比如相机颜色、深度、你创建的 _BlurTexture)。

格子里的颜色含义(简化版):

  • 绿色:读资源(Read)。

  • 红色:写资源(Write)。

  • 灰色:存在但没访问。

CreateTexture(descriptor) 的本质

RenderGraph 里,CreateTexture 并不是立刻在 GPU 上 new 出一张 RT。
它只是告诉 RenderGraph:

  • 我要一张符合 descriptor 的纹理;

  • 后续哪个 Pass 会写它,哪个 Pass 会读它。

RenderGraph 会记录这个依赖关系,等到整个 Graph build 完,才真正决定

  • 在 GPU 上分配哪块内存;

  • 这张纹理什么时候创建,什么时候释放。

顺序是这样的,所以这个临时的RT,由rendergraph管理创建以及最终销毁,在这里是中转的作用

RenderType 和 RenderGraph 有关系吗?

RenderType 标签(比如 "Opaque","Transparent")最早是 内置管线 (Built-in RP) 的概念,用于分类材质以便 Unity 内置的渲染队列、替换着色器(Replacement Shader)机制使用。

在 URP/HDRP 下,它的作用已经非常有限:

URP 默认不会再用 RenderType 去决定是否渲染,而是看 Shader Pass Tag (LightMode)。

但某些工具(Frame Debugger、ShaderVariantCollection、着色器替换)仍然可能读取 RenderType。

LOD 是如何被读取的?

LOD (Level of Detail) 不是 Shader Graph 的 LOD,而是 Shader 本身的 Pass 可见性控制。

Unity 材质在渲染时,会有一个全局 Shader.globalMaximumLOD 或者单个 Material.shader.maximumLOD。

渲染时 Unity 会遍历 SubShader/Pass:

如果 LOD > 当前允许的最大 LOD → 这个 SubShader/Pass 会被跳过。

比如一个 Shader 有三种实现:LOD100(简单版)、LOD300(中等)、LOD600(高级)。

在低端设备上设置 Shader.globalMaximumLOD = 200,就会只编译/执行 LOD100 和 LOD300,LOD600 被忽略。

👉 所以 LOD 是 Unity 在 Shader Variant 选择阶段读取的,跟 RenderGraph 也没直接关系。

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

RenderGraph/URP 不依赖 RenderType,因为它们有更细致的 Pass Tag (LightMode) 来调度。

RenderType 现在更多是 遗留兼容(方便旧的替换着色器系统或某些工具)。

一个版本号 Unity 2018.1,美国人正常说法很固定,不会像电话号码那样随意拆。

“twenty eighteen point one”

→ 最常见,尤其是官方介绍、演讲、教学视频里。

“two thousand eighteen point one”

→ 也有人这样说,但更书面,不如前一个常见。

这里的 point 是固定的(小数点),不会用 dot,除非特别随口。

省略 / 口语变化

“twenty eighteen one”

→ 很口语,有人会省略 point,尤其在快速对话里,但不算标准。

“two thousand eighteen one”

→ 这种更少见。

关于 “0” 和 “o”

在版本号里如果有 0(比如 2020.0.3),通常读成 zero,

但也有人口语上读成 oh(字母 O 的音)。

两种都能听懂。

关于 “dot”

在网址、文件名里才常说 dot(example dot com)。

版本号里很少用 dot,几乎都说 point。

主版本号.次版本号.补丁号[标签] 2020.3.15f2

美国人常见读法

标准读法(最常见)“twenty twenty point three point fifteen eff two”

这里:point = “小数点”,f 读作字母 eff,2 正常读成 two

更正式一点

“two thousand twenty point three point fifteen eff two”

不太常见,一般在更书面或官方场合。

口语快速省略

“twenty twenty three fifteen eff two”

把中间的 point 省掉,直接连着说。只在很随意的对话里出现。

RenderType 在旧管线里的“实际抓手”

https://docs.unity3d.com/530/Documentation/Manual/SL-ShaderReplacement.html

Also, when the URP is set as the active render, all the game objects will be created with the correct shaders — the pipeline will override the default materials.

这里的 “the pipeline will override the default materials” 指的是:

在 Unity 里,如果你新建一个物体(例如 Cube、Sphere、UI Button),编辑器会给它自动分配一个 默认材质 (Default Material)。

在 内置渲染管线 (Built-in RP) 下,这个默认材质用的是 Standard Shader。

在 URP (Universal Render Pipeline) 下,URP 不支持旧的 Standard Shader,于是会自动把默认材质替换成 URP/Lit 或 URP/Unlit(取决于物体类型)。

这样保证了你不需要手动去把每个物体的材质改成 URP 版本,URP 会帮你“覆盖默认值”。

Unity 的 “Upgrade Materials” 本质上做的是 材质引用的 Shader 替换,而不是在源码里自动帮你重写 Shader 代码。

所以自己写的shader文件是不受这个更改造成影响的,只是把用的材质,如果引用unity自带的那些经built in经典shader,那就换成URP的经典shader(基本上都有对应的替代,只要是unity自带的,就放心用)

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

为什么3d游戏出现在

 

 What's the point? Maybe this is the wrong way.

The SRP Batcher 。。  how depth is handled,

Animation Clip Converter: This converts animation clips. It runs once the Material Upgrade converter finishes.

Read-only Material Converter: This converts the prebuilt, read-only Materials included in a Unity project. It indexes the project and creates the temporary .index file. Note that it can take significant time.

The Shaders and Shader Graph sections outline the steps for converting custom Built-In Render Pipeline shaders to URP

Universal Render Pipeline/Baked Lit:so all real-time relevant shader keywords and variants are stripped from the shader code, making it faster to calculate

evaluate lighting

在实时渲染里,“evaluate”常用来描述:

  • 给定光源信息(方向、强度、颜色)和材质信息(法线、粗糙度、反射系数),

  • 把这些代入 光照模型(Lighting Model/BRDF) 的公式,

  • 得到每个像素的最终光照贡献。

换句话说就是:
“执行一次光照计算”算光对表面的影响

Legacy/mobile shaders only partially evaluate lighting, whereas Simple Lit considers all lights as defined by the URP Asset

  • Legacy/Mobile Shader

    • 只对主方向光(main directional light)算一次光照。

    • 额外点光源/聚光灯通常不用公式算,而是用 vertex-lit、烘焙或者干脆忽略。

    • 所以它只“evaluate”了一部分光照。

  • URP Simple Lit

    • 会对 URP Asset 里配置的所有实时光源都执行光照公式(即逐光源 evaluate)。

    • 即便它比 Lit 简单(比如不用物理 PBR,只是 Blinn-Phong 近似),但还是逐光源都算一次。

所以官方文档才会写:

  • Mobile/Legacy = partial lighting evaluation(部分光照计算)。

  • Simple Lit = evaluate all lights(对所有光源都执行光照计算)。

evaluate lighting 直译成“评估光照”容易误解。更贴切的说法是:

  • “执行光照计算”

  • “算光照贡献”

  • “应用光照模型”

Unity - Manual: Convert shaders to URP with the Render Pipeline Converter

Refer to this table in our URP documentation to see how each URP shader maps to its Built-In Render Pipeline equivalent

  • 选择转换器

    • 你可以先勾选一个或多个转换器(比如“材质转换器”、“光照设置转换器”)。

  • 点击按钮(两种模式)

    • Initialize Converters(初始化)

      • Unity 会扫描你的项目,找出哪些资源需要转换。

      • 然后把它们列在每个转换器面板里(带复选框)。

      • 你可以手动取消某些资源,不让它们被转换。

      • 最后点 Convert Assets 才开始实际转换。

    • Initialize And Convert(初始化并立即转换)

      • Unity 会先扫描项目,立刻开始转换,省略手动挑选这一步。

  • 转换完成后

    • 有些情况下 Unity 会提示你重新打开当前 Scene,以便更新已经修改过的资源。

  • 高 fidelity → 纹理分辨率高、光照计算复杂、阴影清晰且柔和 → 画面更精细、接近真实。

  • 低 fidelity → 纹理模糊、光照简化、阴影锯齿多或直接关闭 → 画面细节少,但性能更好。

音频里的“高保真 (Hi-Fi)” → 声音细节丰富,还原度高。

哪些 Quality 设置还有效,哪些被 URP Asset 接管

项目在 Built-in 下有效URP 下情况
Quality level 的切换√(可以切换不同 URP Asset)
Render Pipeline Asset 指派不存在此选项(Built-in)有,用于指定 Quality level 用哪个 URP Asset Unity Documentation
MSAA, shadow distance, texture quality 等具体渲染细节Built-in Quality settings 控制这些被 URP Asset 的对应属性控制,Project Settings > Quality 里这些细节可能不生效或被忽略 Unity Documentation+2Unity Documentation+2

体素化 (Voxelization) 技术

  • 在 Unity 图形编程里,常见用法是 把三角网格体素化,生成 3D 体素数据。

  • 应用:

    • 体素全局光照 (Voxel Cone Tracing, VCT)

      • 原理:先把场景体素化,存储颜色+光照信息,再用 cone tracing 模拟间接光。

      • Unity 没有内置 VCT,但很多研究型插件和自制渲染管线会用到。

    • 体积雾 / 体积光 (Volumetric Fog/Lighting)

      • 原理:把空气体素化,存密度,光照时查询体素体积。HDRP 内置体积雾就是类似思路。

体积纹理 (3D Texture / Volume Texture)

  • Unity 支持 Texture3D,可以用它来存储体素数据。

  • 应用:

    • 体积云 (Volumetric Clouds):在 HDRP 里已经有现成实现。

    • 噪声场/体积噪声:体素化的 Perlin Noise 存在 3D 纹理里,用于火焰、烟雾。

    • 医学数据可视化:CT/MRI 数据是体素数组,直接用 Texture3D 渲染。

Unity 内置/插件相关

  • HDRP:内置的 Volumetric FogVolumetric Clouds → 本质上是基于体素体积纹理的采样。

  • Compute Shader + Texture3D:自己实现体素存储和处理(Unity ComputeShader 支持 RWTexture3D)。

  • 第三方插件

    • MagicaVoxel 导入器(体素建模工具 → Unity)

    • Ultimate Terrains、Voxel Play 等 Unity Asset Store 插件 → 实现体素世界、地形编辑。

delegate 并不是“分发”的直译用法。在 C# / Unity RenderGraph API 里,delegate 的意思是 “函数指针 / 回调方法”

  • C# 里 delegate ≈ “函数变量” → 可以把函数当数据传来传去。

  • RenderGraph 框架不可能硬编码知道你每个 Pass 要画什么,于是它说:

    • “给我一个 delegate(即函数指针)。”

It is passed as a parameter to the delegate function that executes the RenderGraph pass
意思是:
PassData 会作为参数,传给你注册的 delegate(绘制函数回调),供它使用。

  • 外部传参

    • RendererFeature 的 Create() 里可以传不同材质,Shader 可以改动、替换。

    • 不需要改 Pass 源码。

  • 延迟初始化

    • 材质可能在运行时才创建,不能在字段定义时就 new 出来。

    • Setup() 可以在合适的时机传进去。

  • 避免资源浪费

    • 如果材质由外部统一管理,Setup() 只是引用,而不是每个 Pass 都自己 new 一份。

  • 额外逻辑绑定

    • 除了材质,还可以顺便设置一些 flag(这里的 requiresIntermediateTexture = true),这些和材质紧密相关,所以放一起初始化。

知乎这算法,给看嗨了

  • PassData
    小数据包。RenderGraph 要求把参数打包进这里传进去(因为它要做依赖分析和延迟执行)。

  • ExecutePass
    真正执行绘制的地方。你在这里写 context.cmd.DrawMesh(...)CoreUtils.DrawFullScreen(...)

  • RecordRenderGraph
    这是新 API 的核心:

    1. 你向 renderGraph 注册一个 Pass。

    2. builder.SetRenderAttachment 指定渲染目标(比如屏幕颜色贴图)。

    3. builder.SetRenderFunc 绑定上面写的 ExecutePass

  • OnCameraSetup / Execute / OnCameraCleanup
    → 这是旧 API(兼容模式)。Unity 留着给你参考,但现在推荐用上面的 RenderGraph。

  • renderPassEvent = RenderPassEvent.AfterRenderingOpaques
    决定这个 Pass 插入在管线的哪个时机。这里表示“不透明物体渲染完后”。

不要一次吞下所有函数。可以分三层:

  1. 框架层(已经帮你写好,几乎不用改):ScriptableRendererFeature + Create() + AddRenderPasses()

  2. 注册层(告诉 Unity 我要加什么 Pass):RecordRenderGraph + builder.SetRenderAttachment

  3. 执行层(你真正的绘制逻辑):ExecutePass

为什么 Unity 不干脆只给两个函数?

因为它要兼容:

  • 老版本项目(Built-in → URP 的迁移)

  • RenderGraph 新架构

  • 各种不同阶段插入点(比如只想 setup,不想 execute)

所以对初学者看起来像“代码冗余”,但对 Unity 来说是“给专家更多插口”。

  • Create()

    • 只跑一次。

    • 负责 初始化 Pass 实例

    • 如果没有这个,Pass 就没法 new 出来。

  • AddRenderPasses()

    • 每帧都会调用。

    • 负责把你创建好的 Pass 挂进渲染队列

    • 如果没有这个,管线根本不知道你要加 Pass。

  • Execute()(在 Pass 里)

    • 每帧都会调用。

    • 真正执行绘制逻辑的地方。

严格来说:

  • Create() 可以不写 → 你可以在字段里直接 new CustomRenderPass()

  • AddRenderPasses() + Execute() 必须存在,否则 Pass 根本不会进管线、也不会执行。

两个层次

  • 队列 (EnqueuePass)
    → 只是把 Pass 挂进去,告诉管线:“这一帧我要插个任务”。

  • Execute
    → 当轮到你这个 Pass 时,Unity SRP 内部会自动调用 Execute
    → 你在这里写命令(清屏、Blit、DrawMesh 等)。

Unity 要支持:

  • 同一个 Pass 的 实例 可以跨不同 Renderer 循环复用

所以队列负责 顺序调度,而 Pass 里必须有一个固定的 执行入口,这样引擎才能统一调用。

Unity 支持一个项目里有多个 Renderer(不同相机用不同渲染器 Asset)

不同阶段

  • Unity 的渲染流程有几十个阶段(BeforeRendering, AfterRenderingSkybox, AfterRenderingTransparents …)。

  • 你通过 renderPassEvent = RenderPassEvent.Xxx 指定阶段。

  • 引擎内部在到达这个阶段时,会遍历队列 → 调用所有注册在这一阶段的 Pass → 自动执行它们的 Execute

如果没有 Execute 这个统一入口,就没法保证每个 Pass 在正确阶段触发。

OnCameraSetup(CommandBuffer cmd, ref RenderingData renderingData)

  • 什么时候调用:在这个 Pass 执行前。

  • 用途:配置渲染目标(RT)、清屏模式等。

  • 例子

    ConfigureTarget(colorTexture, depthTexture);
    ConfigureClear(ClearFlag.All, Color.black);
  • 不要做的事:不要直接 cmd.SetRenderTarget,Unity 会帮你处理。

Execute(ScriptableRenderContext context, ref RenderingData renderingData)

  • 什么时候调用:真正执行 Pass 的时候。

  • 用途:写你要做的绘制逻辑,提交命令缓冲。

  • 例子

    CommandBuffer cmd = CommandBufferPool.Get("MyPass");
    Blit(cmd, source, destination);
    context.ExecuteCommandBuffer(cmd);
    CommandBufferPool.Release(cmd);
    
  • 注意:不用自己 context.Submit(),Unity 管线会统一提交。

OnCameraCleanup(CommandBuffer cmd)

  • 什么时候调用:Pass 执行完之后。

  • 用途:释放或清理你在 Pass 里创建的临时资源。

  • 例子

    cmd.ReleaseTemporaryRT(tempRT);

using System;
using UnityEngine.Rendering;[Serializable]
[VolumeComponentMenu("Custom/SphereVolumeComponent")]
public class SphereVolumeComponent : VolumeComponent, IPostProcessComponent
{public ClampedFloatParameter intensity = new ClampedFloatParameter(value: 0, min: 0, max: 1, overrideState: true);// 告诉系统什么时候启用这个效果public bool IsActive() => intensity.value > 0;
}
  • intensity 会作为一个带范围 (0–1) 的参数出现在面板上。

  • IsActive() 决定这个后处理是否启用(比如 intensity > 0 时才生效)。


文章转载自:

http://WapyFF30.nqmwk.cn
http://73JcpZjH.nqmwk.cn
http://OdEG17Kv.nqmwk.cn
http://VwqKBh56.nqmwk.cn
http://XUUiiWcA.nqmwk.cn
http://Tgl4nVor.nqmwk.cn
http://b496kvm8.nqmwk.cn
http://rHH0WPJB.nqmwk.cn
http://8umwO4Si.nqmwk.cn
http://2lm0liAL.nqmwk.cn
http://ncpoOdZw.nqmwk.cn
http://VSiegMYZ.nqmwk.cn
http://uZTbTmcY.nqmwk.cn
http://zjmcnXoa.nqmwk.cn
http://xO9Q2qWs.nqmwk.cn
http://3cbFtUDW.nqmwk.cn
http://riOYQQpI.nqmwk.cn
http://dc49rCtd.nqmwk.cn
http://MP8mIYLn.nqmwk.cn
http://nqjPu7Wo.nqmwk.cn
http://2aRz9miF.nqmwk.cn
http://UtWntvbP.nqmwk.cn
http://d77QRdxB.nqmwk.cn
http://9mTBTLKH.nqmwk.cn
http://YE4N6eU1.nqmwk.cn
http://z4YiO0xS.nqmwk.cn
http://jQEqCQ7f.nqmwk.cn
http://i9z3wWdh.nqmwk.cn
http://Hipf3x3j.nqmwk.cn
http://fPqiTTLM.nqmwk.cn
http://www.dtcms.com/a/385213.html

相关文章:

  • 【开题答辩全过程】以 C语言程序设计课程网站为例,包含答辩的问题和答案
  • RocketMQ 消息幂等性实战经验分享
  • [SC]SystemC中,一个namespace中调用了其他namespace中的函数,需要显示include那个函数所在的.h文件吗?
  • Origin气泡图画相关性系数图
  • 基于SpringBoot+Uniapp的儿童疫苗接种预约小程序(qq邮箱、二维码识别)
  • 基于HugeGraph构建法律知识图谱(一)
  • C语言常用字符串函数
  • 【STM32项目开源】STM32单片机智能饮水机控制系统
  • 新质生产力背景下基于“开源链动2+1模式+AI智能名片+S2B2C商城小程序”的商业机会挖掘研究
  • html隐藏文本利用原理,实现点击隐藏功能
  • Java vs Python Web 开发深度对比:从传统同步到现代异步的全面演进
  • Redis 不只是缓存:深入解析 Redis Stack 与实时 AI 推理
  • IPv4地址类型
  • Deepin 25 系统安装 Docker:完整教程 + 常见问题解决
  • 虚拟机因网络导致域名解析出现问题
  • 群内靶机-Next
  • 【系统分析师】2025年上半年真题:论文及解题思路
  • 绿色出行新选择:圆梦交通联合卡的环保实践
  • 协程+连接池:高并发Python爬虫的底层优化逻辑
  • 深入理解 CAS:并发编程的原子操作基石
  • 矿用本安三电车变频器绝缘监测
  • 如何录制带解说的教学视频?屏幕录制工具推荐ASCOMP Screencapt Pro
  • 多模态视频理解领域 Benchmark 与 Leaderboard 整理
  • 《投资-54》元宇宙
  • OpenLayers数据源集成 -- 章节十四:WKT图层详解:标准几何文本格式的精确解析与渲染方案
  • U8g2 库驱动oled
  • 【NTC热敏电阻】NTC电阻测温电路与ADC换算
  • Gradle深度解析:从构建工具到开发生态系统
  • 本地搭建redis-cluster开发环境
  • 优化浏览体验:4个设置让Google Chrome更好用!