自由学习记录(84)
同一个 URP Shader 可以同时兼容 Forward 和 Deferred。
在同一份 Shader 里写两个 Pass,分别标 LightMode="UniversalForward"
和 LightMode="UniversalGBuffer"
。URP 会按渲染器当前的渲染路径与物体属性,挑对应的 Pass 来跑;需要同时兼容,就把两种 Pass 都写上。
为什么“写法不一样还能共存?”
-
Forward Pass(UniversalForward):在像素着色器里就把光照算完,输出一张颜色(通常是
SV_Target0
)。 -
Deferred 的 GBuffer Pass(UniversalGBuffer):不在这里算光,而是把“材质属性”写进多张 GBuffer(MRT),例如 Albedo/Specular/Smoothness/Normal/Emissive 等,光照在后面的 Deferred lighting pass 才统一计算。URP 的 GBuffer 布局和执行顺序文档都明确了这一点。
两种 Pass 的 fragment“返回值/语义”确实不同,但它们属于两个独立的 Pass,可以在同一 Shader 文件里并存:Forward 的片元函数只写一个颜色;Deferred 的片元函数写多个 SV_TargetN
(或用 URP 的打包函数)。URP 内置的 Lit.shader 就同时包含了 UniversalForward
和 UniversalGBuffer
两个 Pass,可作参考
xxx
class PassData
{internal TextureHandle copySourceTexture;
}
PassData
要被同一个程序集里别的方法读写(比如 RecordRenderGraph
里填值、ExecutePass
回调里用值),而把字段设成 private
在 C# 里外部方法就访问不到,所以示例把它写成 internal
(或 public
也行)。**Unity 官方的 Render Graph 示例与 URP 源码都这样写。
internal
只在同一程序集可见,API 面更收敛 ,自定义 Feature/Pass 通常只在自己项目的程序集内使用,没必要对所有程序集公开
internal
或 public
都可以,官方文档示例用了 internal
,URP 源码也大量使用 internal
;你保持这一习惯即可
ContextContainer frameData
:URP 把本帧可用的资源(相机颜色/深度、阴影贴图、GBuffer 等)装进这个容器,只有在 RecordRenderGraph
里能取。
典型用法就是:
var ur = frameData.Get<UniversalResourceData>();
data.screenColor = ur.activeColorTexture;
这拿到“当前活动的相机颜色纹理”的 TextureHandle(注意是句柄,不是立即可用的贴图),且必须在 Record 阶段取,不要在执行函数里取。
public override void RecordRenderGraph (RenderGraph renderGraph, ContextContainer frameData)
base. RecordRenderGraph (renderGraph, frameData) ;
把要用到的资源句柄塞进 data
using (var builder = renderGraph. AddRasterRenderPass<CustomPassData>(passName, out CustomPassData data))
SetRenderFunc
指定真正的执行函数(例如 ExecutePass
)。RenderGraph 会把 data
传入执行函数
builder. SetRenderFunc<CustomPassData>((CustomPassData data, RasterGraphContext context) => ExecutePass(data, context)) ;
xxx
取帧资源句柄
var ur = frameData.Get<UniversalResourceData>();
data.screenColor = ur.activeColorTexture;
(当前活动的颜色缓冲,可能是前/后缓冲之一)
声明读/写
-
只读源:
builder.UseTexture(data.screenColor);
-
目标 RT:自己
CreateRenderGraphTexture
或拿帧里现成的句柄,再builder.SetRenderAttachment(...)
。
登记执行函数
builder.SetRenderFunc((CustomPassData d, RasterGraphContext ctx) => ExecutePass(d, ctx));
在 ExecutePass
里用 ctx.cmd
做 Blit/绘制等。
只在 Record 阶段读 frameData
;执行函数里别再 Get<UniversalResourceData>()
。activeColorTexture
也说明了“只在 Record 时引用”
一定要用 UseTexture/SetRenderAttachment
等 显式声明访问,否则图上没有依赖,可能被裁剪或执行顺序/资源寿命不对。
current active color target texture(当前活动的颜色目标贴图)
RecordRenderGraph(登记图)这一刻、在该注入点之前所有已登记的 pass 写完之后,URP 认为最新有效的“相机颜色”是哪一张。它不是“上一帧”,也不一定是固定的一张 RT,而是本帧到此时为止处于“活动”状态的那张颜色缓冲。
-
camera color 做乒乓(A/B):某个 pass 把结果写到 A,并把 A 设为 active;下一个 pass 可能写到 B,再把 B 设为 active。
-
UniversalResourceData.activeColorTexture
返回的就是此刻被标记为 active 的那一张(可能是 A,也可能是 B)。 -
你只能在
RecordRenderGraph
阶段取这个句柄(TextureHandle
),然后用builder.UseTexture(...)
/builder.SetRenderAttachment(...)
声明读写依赖;不要在执行函数里再去取。文档里那句 “to be referenced at pass recording time” 就是这个意思。
-
activeColorTexture:当前帧、当前注入点的“活动相机颜色”句柄(A 或 B 之一)。
-
_CameraOpaqueTexture:在“不透明物体之后”抓的一张快照,后续不会自动变;用来做折射/软粒子等。
-
上一帧的颜色:不是这里;要跨帧得自己保留/交换 RT。
在 RecordRenderGraph
里:
var ur = frameData.Get<UniversalResourceData>(); data.src = ur.activeColorTexture; // 只在这里取 builder.UseTexture(data.src); // 声明读取 builder.SetRenderAttachment(data.dst, 0); // 声明写入目标 builder.SetRenderFunc(...); // 执行函数里用 ctx.cmd 做 Blit/绘制
RenderGraph 才能建立依赖关系(谁先写谁后读、何时创建/释放),也就不会出现顺序/资源寿命的问题。
Record(登记/记录)在 URP 的 RenderGraph 里就是:
“本帧、这台相机要做哪些 Pass、各 Pass 读写哪些纹理/缓冲”——先把计划写进一张图。此时不真正渲染。
-
Record(建图/登记)
-
在
RecordRenderGraph(RenderGraph rg, ContextContainer frameData)
里进行。 -
取帧资源句柄(如
activeColorTexture
/activeDepthTexture
),填进 PassData。 -
用
builder.UseTexture/UseRendererList/SetRenderAttachment
声明读写关系。 -
用
builder.SetRenderFunc(...)
绑定真正干活的函数(稍后执行)。
→ 结果:得到一张“谁读谁写”的依赖图。
-
-
Compile(编译/优化)
-
RenderGraph 根据依赖排序、合并、裁剪 Pass;计算每个纹理的生命周期(何时创建/释放/乒乓)。
-
这一步决定你看到的“activeColorTexture”到底是 A 还是 B。
-
-
Execute(执行)
-
引擎按编译结果依次调用你在
SetRenderFunc
里提供的函数(给你RasterGraphContext ctx
/ctx.cmd
)。 -
这里不能再去拿
frameData
或“临时新建 RT”;只能用 Record 阶段登记过的句柄。
-
心智模型:Record=写蓝图 → Compile=排工序 → Execute=施工。
只有在“写蓝图”阶段拿activeColorTexture
才对;执行时再取会错时机。
data.screenColor = frameData.Get<UniversalResourceData>().activeColorTexture;
这是在 Record 阶段取“到目前为止的活动相机颜色纹理”的句柄,存进你的 PassData。
-
这是在 Record 阶段取“到目前为止的活动相机颜色纹理”的句柄,存进你的
PassData
。 -
后面你要配合:
builder.UseTexture(data.screenColor); // 声明‘读’
builder.SetRenderAttachment(dst, 0); // 声明‘写到哪’
builder.SetRenderFunc((CustomPassData d, RasterGraphContext ctx) => { /* Blit/Draw... */ });
RenderGraph 的 Compile 阶段≠内置管线的 Render Queue。
RenderGraph · Compile(管线层,跨 Pass)
-
根据你在 Record 阶段声明的“谁读/谁写”关系,拓扑排序 Pass 的先后。
-
合并/裁剪 Pass,安排纹理的创建/复用/别名(A/B 乒乓、释放时机),插入必要的同步/障碍。
-
关注的是“Pass 与资源”的依赖调度和内存/带宽优化。
→ 不关心“同一 Pass 里对象以什么顺序画”。
Render Queue(对象层,单个 Pass 内)
-
决定一批要画的 Renderer(物件)在这个 Pass 里的绘制顺序:
-
不透明:
RenderQueueRange.opaque
+SortingCriteria.CommonOpaque
(前到后,减过绘) -
透明:
RenderQueueRange.transparent
+SortingCriteria.CommonTransparent
(后到前,正确混合)
-
-
由 Material 的 Queue 值、FilteringSettings、SortingSettings 决定;在 SRP 里通过
DrawRenderers/RendererList
生效。
→ 这是对象级排序,仍然存在于 URP/RenderGraph 里。
-
Record:你创建一个渲染节点(AddRasterRenderPass),如果要画一批物体,会构造 RendererList(里面就用到了
RenderQueueRange/SortingCriteria
)。并声明本 Pass 读写哪些纹理。 -
Compile:RenderGraph 按资源依赖排 Pass A→Pass B 的顺序、合并/裁剪并规划纹理寿命。此处不改你 RendererList 内部的对象排序。
-
Execute:真正执行各 Pass;当执行到“画物体”的 Pass 时,才按 RenderQueue/SortingCriteria 去绘制每个对象。
RenderGraph 的 Compile 并没有“取代” Render Queue;两者是并行存在、层级不同。
var desc = new RendererListDesc(shaderTags, cullResults, camera)
{
sortingSettings = new SortingSettings(camera){ criteria = SortingCriteria.CommonOpaque },
renderQueueRange = RenderQueueRange.opaque,
layerMask = myMask
};
挑“用哪个 Shader Pass”
通过 ShaderTagId[] shaderTags
(如 "UniversalForward"
, "ShadowCaster"
, "DepthOnly"
, "UniversalGBuffer"
, "Universal2D"
…)。管线会在材质里按顺序找第一个匹配的 Pass 来画。
renderQueueRange / layerMask / sortingSettings
控制这一批对象是否被画、以及在这个 Pass 里按什么规则排序(不透明前到后、透明后到前等)。
整批改材质/Pass
DrawingSettings.overrideMaterial
+ overrideMaterialPassIndex
:强行用另一份材质/另一个 Pass 去画同一批网格(描边、XRay、遮罩等都这么干)。
传参/开关变体
-
全局:
cmd.SetGlobalTexture/Buffer/Vector
、CoreUtils.SetKeyword(cmd, keyword, bool)
单对象:MaterialPropertyBlock
(每次 draw 的参数,比如颜色、阈值等)
目标与载体
你可把目标切到任意 RT(RenderGraph/RTHandle)去“先写后合成”,等同于在“中间结果”上二次处理。
需要 shader 预备好的,才能控制的
不能从外部“强改”的
-
着色逻辑本身:像素/顶点里怎么算(BRDF、法线扰动等)你不能“在管线里改几行”就生效——那要改 shader 代码或改用 override 材质。
-
顶点输入/插值结构:VS/PS 的结构体、插值通道布局不是运行时能改的。
-
没有的 Pass/变体:如果 shader 没写
UniversalGBuffer
,你让它参加 Deferred 也不行;透明也不会走 Deferred。
透视高亮(X-Ray / 被遮挡也能看到轮廓)
用到:ZTest Greater
、DepthWrite Off
、overrideMaterial
(自发光/加法)。
怎么做:
-
注入在 AfterRenderingOpaques。
-
只筛选目标 Layer 的对象,
ZTest Greater
让只在被遮挡处显示。 -
用一张 Unlit/Emissive +
Blend One One
的材质叠加颜色
描边 Outline(两种做法)
A. 外扩再渲(不用模板)
用到:overrideMaterial
(在 VS 沿法线外扩)、Cull Front
、DepthWrite Off
。
怎么做:AfterOpaques 再画一遍物体,外扩 0.01–0.03,填充描边色。
B. 模板描边
用到:Stencil
写/测、两次绘制或全屏合成。
怎么做:Pass1 写模板;Pass2 只对模板区域画“膨胀色”或做全屏描边合成。
选择性后处理(只让某些物体吃后效)
用到:Stencil
+ 全屏 Blit(你的 Renderer Feature)。
怎么做:先把目标物体写 StencilRef=1
;全屏 shader 里加模板测试 Comp Equal
,这样后效只在这些对象上生效(例如只给主角上 Bloom/色调)。
折射/热扰动(Heat Haze/Refraction)
用到:_CameraOpaqueTexture
、overrideMaterial
(折射 shader),可配合 Stencil
限定区域。
怎么做:开启 OpaqueTexture;在 AfterOpaques 用折射材质重画目标网格,片元里采样 _CameraOpaqueTexture
偏移 UV。
【TA进阶】头发渲染:原理与实现_哔哩哔哩_bilibili
处理多层,要使用从后到前的顺序进行渲染
面片穿插的问题,依然是只能美工去解决,不要让这种模型互相穿插的情况发生
RendererList(以及它的句柄 RendererListHandle)把“可见物体的一个子集 + 绘制规则”打包起来,供该 Pass 一次性绘制。
Universal Data(Rendering/Camera/Light):来自管线本帧的上下文(相机、灯光、渲染设置)。这些数据决定了剔除结果、排序依据里的相机信息等。
CullingResults:视锥/遮挡剔除后的“可见物体集合”。它是构造 RendererList 的输入之一。
DrawingSettings:定义“怎么画”——
-
SortingCriteria
:这一批物体在此 Pass 里的绘制顺序(不透明常用 Front-to-Back,透明 Back-to-Front 等)。
ShaderTagId
:选**哪个 Shader Pass(LightMode)**来画,比如 UniversalForward
、ShadowCaster
、UniversalGBuffer
等。Unity Documentation+1
-
FilteringSettings:定义“画谁”——
-
RenderQueueRange
(不透明/透明/自定义区间)、LayerMask
等,用于从可见物体里再筛一遍。Unity Documentation+1
-
-
以上三者组合成 RendererListParams,再生成 RendererListHandle。在执行阶段你用
CommandBuffer.DrawRendererList(handle)
把这批物体一次性画出来(旧的context.DrawRenderers
已被替换为 RendererList API)。Unity Documentation+1
最小骨架(伪代码)
var tags = new[] { new ShaderTagId("UniversalForward"), new ShaderTagId("SRPDefaultUnlit") };
var desc = new RendererListDesc(tags, cullResults, camera) {
renderQueueRange = RenderQueueRange.opaque,
layerMask = myMask,
sortingCriteria = SortingCriteria.CommonOpaque
// 可选:stateBlock 覆盖深度/模板等
};
var list = context.CreateRendererList(ref desc);
cmd.DrawRendererList(list);
(DrawingSettings
决定排序与使用的 Shader Pass;FilteringSettings
决定对象筛选;这三者打包进 RendererListDesc/Params
。)Unity Documentation+2Unity Documentation+2
Early-Z 只能在“像素着色器不会改变覆盖与深度写入结果”的前提下才能放心提前做
而一旦你用了 alpha test/discard
/alpha-to-coverage/修改 SV_Depth 等,像素是否会“被丢弃、是否写深度、会写到哪些 MSAA 样本”都要等进了frag着色器才知道,GPU就不敢把深度测试放在frag着色器之前(否则会出错),于是退回到 Late-Z(像素着色器执行后再测深度/写深度)。这就是“看起来应该能跳过,实际却变复杂”的原因。
-
Early-Z(早期深度/模板测试):在执行像素着色器之前做深度/模板测试,可直接剔除被遮挡的片元,极大减少像素着色器调用。只在像素着色器不写深度、不改变覆盖等条件下才能安全启用;HLSL 里还有
[earlydepthstencil]
强制提前测的属性(同样受限制)。Microsoft Learn -
Late-Z(后期测试):像素着色器执行之后再做深度/模板、再写 RT/做混合。这样才能正确处理
discard
/ alpha-test / 写 SV_Depth / alpha-to-coverage 等会改变覆盖或深度写入的情况。therealmjp.github.io
-
Z-prepass(显式深度预通道):用**单独的一道“只写深度”的 pass”**先把深度写好,再画真正的颜色 pass;对复杂像素着色器很有用,URP/HDRP 都能配。Unity Documentation
-
Hi-Z/层级 Z:更粗粒度的块级剔除(比如按瓦片/块维护最远深度),有时即使 Early-Z 退化了,也能起到一部分“提前杀”的作用。Mike TuritzinGame Development Stack Exchange
xxxx
首先雪地留痕迹,只用了shadergraph和粒子系统实现,,
camera和light都没有变,都是基础的urp设置
去掉粒子系统,,留痕效果消失,去掉mesh无所谓
去掉了一个rendercamera,痕迹消失,
camera组件导致效果?
写了一张图到path rt上,,这张rt会被利用到
-
读取当前顶点的原始位置。
-
用 PathMap 结合参数计算某个区域内的高度偏移量。
-
修改顶点在高度方向的坐标,让它在贴图标记的地方“凹陷”或“隆起”。
-
输出新的顶点位置,从而在模型表面形成路径、压痕、雪地脚印等效果。
float mean_height;
for(int i=-1;i<=1;i++)
{
for(int j=-1;j<=1;j++){
mean_height +=SAMPLE_TEXTURE2D_LOD(PathMap,SS,float2(TexCoord.x+i*Blur,TexCoord.y+j*Blur),1).r;
}
}
HeightPos=saturate(mean_height/9.0)*HeightMultiplier;
URP/HDRP(SRP 系列) 用的是 Volume 框架。你在菜单里建的是 GameObject > Volume > Global Volume,再往 Profile 里加 Bloom/Color Adjustments 等 Override。
Built-in 管线 没有这个 SRP 的 Volume
组件,但可以用 Post-processing Stack v2 的 Post-process Volume,把 “Is Global” 打勾就得到“全局体积”的等价效果(同样是优先级/权重+体积混合,只是组件与工作流不同)
Volume 不是“后处理的 shader”,而是一个用来装和混合参数的框架/容器:它根据相机位置在一组 Volume Profile → Volume Overrides 之间插值,驱动渲染管线里的各种效果;这些效果里有不少是后处理(确实由 shader 和渲染通道实现),但也包含很多非纯后处理的场景/环境设置。
URP implements dedicated GameObjects for Volumes: Global Volume, Box Volume, Sphere Volume, Convex Mesh Volume.
URP implements dedicated GameObjects for Volumes: Global Volume, Box Volume, Sphere Volume, Convex Mesh Volume.
The Volume component contains the Mode property that defines whether the Volume is Global or Local.
With Mode set to Global, Volumes affect the Camera everywhere in the Scene. With Mode set to Local, Volumes affect the Camera if the Camera is within the bounds of the Collider. For more information, see How to use Local Volumes.
You can add a Volume component to any GameObject. A Scene can contain multiple GameObjects with Volume components. You can add multiple Volume components to a GameObject.
At runtime, URP goes through all of the enabled Volume components attached to active GameObjects in the Scene, and determines each Volume's contribution to the final Scene settings.
URP uses the Camera position and the Volume component properties to calculate the contribution.
URP interpolates values from all Volumes with a non-zero contribution to calculate the final property values.
Profile 里勾选/调这些后处理的 Override。它确实会触发底层的后处理 pass,但 Volume 本身只是参数与混合系统
通过 Blend Distance / Weight / Priority 进行体积混合;影响的是进入该体积的相机,而不是体积里单个物体。
use Local Volumes
location-based post-processing effect.
URP applies a post-processing effect when the Camera is within a certain Box Collider.
https://docs.unity3d.com/Packages/com.unity.render-pipelines.universal%4010.2/manual/Volumes.html
粒子系统里用的shader会和一般的oldschool物体的材质shader有什么区别,
粒子常用的是“面向相机的半透明四边形 + 专用顶点数据 + 特殊混合/深度技巧”,而传统物体材质多为“网格顶点 + 法线 + 不透明/PBR 光照”
顶点阶段:输入与几何形态不同
粒子顶点流(Vertex Streams):粒子系统会把“生命周期、速度、大小、旋转、随机数、中心位置、UV2、AnimBlend、Custom1/2”等打进 TEXCOORD 等通道传给着色器,用来做尺寸/旋转/帧混合等逻辑。你可以在 Renderer→Custom Vertex Streams 里勾选和自定义
大多数粒子不是固定网格,而是相机朝向的四边形(Billboard),或按速度方向拉伸(Stretched Billboard);这些取向由粒子渲染器控制。
Flipbook(序列帧)/平滑过帧:粒子常用贴图表(Texture Sheet Animation)做序列帧,并可通过 UV2+AnimBlend 实现两帧之间的线性混合,去掉卡顿。
能否给粒子用“普通物体 shader”?
-
可以,但要补流:比如想给粒子用 Standard/PBR,需要在 Custom Vertex Streams 里添加 Tangent、UV2、AnimBlend 等,才能正确法线贴图与序列帧过渡。官方手册有明确指引。Unity Documentation+1
-
实务上更常用“专用粒子着色器”(URP 的 Particles Unlit/Lit,或内置 Standard Particle Shaders),因为它们已经内建软粒子/过帧/相机淡出/折射等特性开关。Unity Documentation+2Unity Documentation+2
-
粒子 Shader = 乘着粒子顶点流在顶点/片元里做相机对齐、序列帧、混合、深度淡化与折射等“屏幕空间向”的特效;
-
物体 Shader = 面向实体网格/PBR的常规光照与遮挡。
用Unity Shader入门有向距离场 SDF_哔哩哔哩_bilibili
“Soft” 指软粒子(depth fade)——在片元阶段采样相机深度,按与场景表面的深度差做淡出,避免硬交线。无需 GS/CS。
能轻松画出成千上万粒子,不是因为这个 shader 里有 GS/CS:Shuriken 粒子系统的模拟在 CPU,渲染在 GPU;四边形 billboard 由粒子渲染器/顶点阶段处理,并非靠几何着色器生成
要走 GPU 计算/几何扩展的是 VFX Graph(GPU/compute 路线),那是另一套系统。
This shader comes with functionality similar to the Standard Shader, but works especially well with particles. Like the Standard Shader, it supports Physically Based Shading
. It does not include features that are unsuitable for dynamic particles, such as lightmapping
This simple shader is faster than the Surface Shader
. It supports all of the generic particle controls, such as flipbook blending and distortion, but does not perform any lighting calculations.
软粒子 = 采样深度缓冲,让粒子接近被深度写入的表面时淡出;在内置管线里需开启相应选项(质量设置里的 Soft Particles),材质用带 Soft 的粒子 Shader
Legacy 源码含有 _InvFade ("Soft Particles Factor", …)
这类属性;实现上就是以此系数对深度差做衰减,再配合 Additive 混合(Blend SrcAlpha One
一类)叠加到场景