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

自由学习记录(72)

你的问题已经不是:

“我该怎么写一个 shader 实现 RimLight”

而变成:

🧠 “URP 的 SRP 框架里,这一块由谁管?我还用自己写吗?”

所以你在学习 URP 时真正要解决的几个核心问题是:

模块问题
🧩 光照获取GetMainLight() 怎么拿的? MainLightAdditionalLight 结构怎么查?
🔧 变换函数TransformObjectToHClip() 等 SRP 内建函数在哪?是否等价于 Built-in 中的 MVP 乘法?
🧱 光照模型替换我怎么写自定义光照?能不能复用之前写的 Lambert?怎么让它插入到 pipeline 的正确位置?
🧵 Pass 编排为什么 Unlit 只写一个 Pass 就够?为什么 Lit 是靠 Include 搭起来的?我要重写整个还是只替换?
📁 include 系统#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/*.hlsl" 到底每个文件负责什么?
🕳️ Feature vs Shader有些效果(如 Outline、后处理)在 URP 里居然是放到 Renderer Feature 而不是 Shader 本体里?那该放哪?

理解 Render Feature 插入的时间点

  • URP 把很多原本“Shader 本体内部”负责的事情,交给了“Frame Pass Queue”来调度。

  • 你得熟悉 ScriptableRendererFeature, RenderObjects, Blit, DrawObjects 等接口。

  • 一些效果甚至不该写在 Shader,而该写成 Feature。

Unity 官方的教学逻辑(教学设计理念)

Unity 并不希望初学者“从裸写 URP Shader 起步”,所以它:

  • 提供的是一层层包裹的功能解构(先用,再改,再深入);

  • 鼓励你通过:
    ✅ 改官方模板 + 🔍 查看 Shader 源码 + 🧩 插入 Renderer Feature
    来渐进式掌握 SRP 渲染管线;

不像传统 OpenGL 或书本教材从“画三角形”讲起,而是让你:

从工程角度理解模块分工,再逐步控制各层逻辑。

如果你想“从官方角度完整学 URP Shader”,推荐顺序如下:

阶段资源学习重点
Step 1Unity Docs:URP Shader Overview理解 URP Shader 结构组成(HLSLPROGRAM、Pass、Include)
Step 2官方 GitHub 示例工程运行 + 阅读实际 Shader,理解光照、Shadow、Rim 等功能怎么实现
Step 3打开 ShaderLibrary跟踪 GetMainLight() 等函数底层原理(Light struct, LightData)
Step 4创建 Renderer Feature学习怎么挂自定义 Pass 到 Frame 中(后处理、深度描边)
Step 5自己写一个基于 URP 的效果(比如 Rim + ShadowRamp)全流程闭环一遍

https://github.com/Unity-Technologies/UniversalRenderingExamples

  • Assets/Shaders/ 文件夹:

    • UnlitMinimal 是最小的 URP Shader 示例;

    • CustomLighting 里是完整光照结构;

    • RimLighting, ShadowRamp, BlinnPhongLighting 这些你一定要看;

  • 场景中点击对应模型,看 Inspector 中材质绑定的是哪个 Shader;

  • 点进去 Shader 文件对照源码 + 实际效果读。

打开本地 URP ShaderLibrary 分析函数来源

推荐文件:

  • Lighting.hlsl → 核心光照结构体 Light, GetMainLight(), AdditionalLights

  • Core.hlsl → MVP 变换、半兰伯特、正交空间函数;

  • Shadows.hlsl → ShadowCaster、shadow bias 设置。

学习并创建 Renderer Feature

Renderer Feature 是 URP 中插入自定义 Pass(例如后处理、轮廓描边、深度图处理)的机制。

下载的示例项目中:

Assets/RendererFeatures/OutlineFeature.cs
Assets/RendererFeatures/CustomRenderPassFeature.cs
💡 跟踪这些文件,看它们怎么注册自定义 Pass、怎么在帧执行流程中注入、怎么调通 Shader。

Lighting.hlsl 中重构自己的光照模型

写一个 Renderer Feature 给它后处理一个描边或者辉光

“Unity 领域的 StackOverflow + 论坛” 混合体
有技术深度,也有教程型的讨论串。

  • 收藏一些你常用的关键词(比如“Shader Graph dissolve”、“URP lightprobe setup”)

  • 📑 点进别人提问看看有哪些回答,从回答里学写法与工具流程

  • 🗂 如果你准备做作品集,里面也能搜到别人对特效/动画等项目型问题的处理方式

【PC High】包含高成本渲染特性(需要独立显卡 Dedicated GPU

可能包括:

  • 实时阴影(Real-time Shadows)

  • 高分辨率光照贴图(Lightmap)

  • 多 Pass Shader(多个 Pass 同时运行)

  • 后处理特效(Bloom、Motion Blur、Depth of Field)

  • 高质量抗锯齿(MSAA、TAA)

  • 高材质分辨率(如 4K PBR 贴图)

🎯 这些特性对 显卡频宽、GPU 核心数、VRAM 有较高要求,集显常会卡顿、掉帧甚至崩溃。

【PC Low】配置相对简化,适合集显或低端机型

一般会:

  • 关闭后处理(或只留最基础的)

  • 使用低分辨率光照贴图或完全禁用

  • 使用 LOD 较低的模型

  • 简化 Shader 中的流程,比如关闭法线贴图或镜面反射

  • 渲染路径选用 Forward 而非 Deferred(减少 Pass)

📉 用于节省 GPU 运算负担,提高流畅度,哪怕牺牲一点视觉质量。

🎓 为什么教程要这样提示你?

因为 Unity 的 URP Sample 项目中很多场景(如 The Garden、The Oasis)用到了:

  • 多光源照明

  • 复杂 Shader 和动态特效

  • 高质量后处理管线

这些在集显上跑会掉帧,甚至卡死,而作者希望你能流畅体验教程内容,避免误以为“Unity 卡”。

https://docs.unity3d.com/2022.3/Documentation/Manual/class-QualitySettings.html

每个项目的渲染需求不同,无法靠一个固定渲染管线满足全部平台、风格、性能目标,因此需要可定制的 SRP。

【直播回放】从0写一个SRP_哔哩哔哩_bilibili

可以尝试在这里复制名字,然后在package Manager里找到url导入的,粘贴上去

RenderPipeline 是 SRP 的基类

你必须继承它,并重写唯一关键方法:

protected override void Render(ScriptableRenderContext context, Camera[] cameras)
foreach (var camera in cameras)
{// 1. 设置相机属性(matrix、clear flags 等)context.SetupCameraProperties(camera);// 2. 创建渲染命令缓冲(CommandBuffer)// 3. 处理光照、Culling(剔除不可见物体)// 4. 绘制场景对象(DrawRenderers)// 5. 后处理(如果你实现了)// 6. 提交绘制指令context.Submit();
}

不仅立方体没有,连scene里面的gizmo也没有,只draw skybox

此时旋转相机都不会有用,连相机的位置都需要自己设置

和这种写法一致(在最新的一些版本里加上去的,就是可以把类型直接写在参数里面了)

(新的c#语法里有)

裁剪的是cull,这个是unity依然帮忙直接做了,一般也没有更好的方法了,

out 的关键特性:你不需要在调用前 new 它,因为:

👉 方法内部负责 new 并赋值,调用者只需要声明变量即可。

你只是在“声明变量”,不需要初始化(new),因为 out 保证:

🔸 Unity 的这个方法会在内部执行 parameters = new ScriptableCullingParameters(...);
🔸 并填好其中的字段(如视锥体、遮挡剔除选项、LOD 层级等)

ref ds, ref fs

  • 在调用前就必须创建好 ds 和 fs 实例

  • Unity 会通过引用去读取它们的内容,甚至可能修改你传进去的对象(尽管通常不修改)

如果 ScriptableCullingParameters 是引用类型,是不是就不需要 out 了?

答案是:它是值类型(struct),所以你必须用 out

Unity 的很多核心渲染结构(包括 ScriptableCullingParameters, CullingResults, DrawingSettings, FilteringSettings 等)都是 struct


xxxx

写完之后,还是没有看到效果

而原因是---场景里的这些物体用的依然是standard shader,

srp里不支持内置管线的shader ,但是有一种除外,就是unlit shader

因为unlit shader不受光照影响

-----

还不够,还需要别的

这样才最后可以看见了

类型作用
DrawingSettings💡 定义如何绘制物体(用哪个 Shader Pass、排序、批处理方式)
FilteringSettings🧹 定义要绘制哪些物体(基于 LayerMask、Queue、Shader tag 等)

index=0主 Pass,最优先尝试匹配的 Shader Pass 名
index=1/2/...备用 Pass,如果前面的找不到,再尝试它们
它们组成了一个 “Shader Pass 匹配表” 

为什么是 "SRPDefaultUnlit"
  • 这是 Unity 内置 SRP(包括 URP)使用的 Unlit Shader Tag

  • 如果你写自定义 Shader 时没有设置 Name,可能不会被匹配到

ds.SetShaderPassName(0, new ShaderTagId("MyForwardLit"));
ds.SetShaderPassName(1, new ShaderTagId("SRPDefaultUnlit"));


Unity 在执行 context.DrawRenderers() 时,会做如下查找:

对每个渲染器的材质所使用的 Shader,去里面找:

有没有 Pass { Tags {... "LightMode" = "MyForwardLit" ...} ...} → ✅ 用这个

没有 → 有没有 Pass { Tags {... "LightMode" = "SRPDefaultUnlit" ...} ...} → ✅ 用这个

都没有 → ❌ 不渲染

FilteringSettings核心作用:

你告诉 SRP:

“我这次 DrawRenderers() 调用,不限制任何 Render Queue 范围,什么都可以画。

RenderQueueRange 是对材质 RenderQueue 的一个区间过滤器。

参数描述
RenderQueueRange.opaque画不透明物体(默认 queue 0–2500)
RenderQueueRange.transparent画透明物体(queue 2501–5000)
RenderQueueRange.all全都包含,不做筛选
用途ShaderTagIdFilteringSettings
画不透明物体"SRPDefaultUnlit"RenderQueueRange.opaque
画透明物体"SRPDefaultUnlit"RenderQueueRange.transparent
画阴影"ShadowCaster"任意 Queue
自定义特殊 Pass"MyCustomPass"自定义 Layer/Queue 范围

DrawingSettings, FilteringSettings, ScriptableRenderContext, ShaderTagId 等,全都是 Unity 官方 Scriptable Render Pipeline (SRP) 的核心底层类,统称为:

🔧 UnityEngine.Rendering 命名空间下的 SRP API
是 Unity 提供的底层可编程渲染接口集合,你可以完全重写 Unity 的渲染行为。

https://docs.unity3d.com/ScriptReference/Rendering.ScriptableRenderContext.html

都可以在官方文档的一个一个查

https://docs.unity3d.com/Packages/com.unity.render-pipelines.core@17.3/manual/index.html

Unity 官方自定义 SRP 的核心文档入口

xxxx

这里的文字绑定到相机上面,和相机一起移动,然后在后面自己额外增加关键帧移动,到中间再,,,,

不行,他这里一开始是和背景同一位置的,后面才冲回了视野的中心位置,不适合设置父对象

这里轻微的调整了一下,导致了和父对象的一点轻微差距

两个节点(漫射 BSDF、透明 BSDF)在渲染模型中可以被视为同级的 BRDF 模型

在 Forward 渲染中,一个 Pass 通常只负责处理一种光源影响(比如主光 / 额外点光),
多光源的支持依赖于多个 Pass 的叠加处理,或者特殊的聚合方法。

在 Forward 渲染路径中:

类型描述
ForwardBase Pass处理主方向光(Directional Light)+ 环境光(Ambient)
ForwardAdd Pass每个处理一个额外的光源(Point / Spot Light)
ShadowCaster Pass处理阴影生成
Meta / Deferred / DepthOnly用于 GI / 探针 / 深度图等特定目的

Pass
{
    Tags { "LightMode" = "ForwardBase" } // 处理主光 + ambient
}

Pass
{
    Tags { "LightMode" = "ForwardAdd" }  // 每个 point/spot light 分别加一个 pass
    Blend One One    // 累加混合(Additive)
}
Unity 会自动为每个额外光源调用一次 ForwardAdd pass,并将光源数据注入 Shader 中(如 _LightColor0, _WorldSpaceLightPos0)

延迟渲染的 Lighting Pass 并不是在材质 Shader 中处理的,而是在后期一个全屏 Shader 中,你可以循环多个光源、逐像素叠加它们的贡献,不需要为每个光源重绘物体!

在延迟渲染中,你的材质 Shader(GBuffer Pass)只需写一个 Pass,且完全不负责光照逻辑。
所有光源的计算都发生在 Unity 内部的一个 Lighting Pass 中不是你写的 Shader),
✔ 所以:你不需要在材质 Shader 中访问光源数据,也不需要写多个 Pass 来支持多个光源。

如果你想打破这套结构,你可以做的高级玩法是:

✅ 自己重写 Lighting Pass(SRP 中的 DeferredLights.hlsl + C# Pass 组织逻辑)

这样你就可以:

  • 自定义光源类型(如区块状灯、颜色噪声灯)

  • 加入多 BRDF 组合模型(如 anisotropic、clearcoat)

  • 用其他曲面响应模型替代标准 PBR(如 cartoon 光照)

一个能在 Unity 默认 Deferred 渲染路径下生效的最基本 Shader,它:

  • ✅ 只写一次 Pass

  • ✅ 输出 GBuffer 所需的基础材质信息(Albedo、Normal、Metallic、Smoothness、Emission)

  • ❌ 不参与光照计算(Deferred Lighting Pass 会处理)

Shader "Custom/DeferredMinimal"
{Properties{_MainTex ("Base Color", 2D) = "white" {}_Color ("Tint", Color) = (1,1,1,1)_Metallic ("Metallic", Range(0,1)) = 0.0_Smoothness ("Smoothness", Range(0,1)) = 0.5_EmissionColor ("Emission", Color) = (0,0,0)}SubShader{Tags { "RenderType" = "Opaque" "RenderPipeline" = "UniversalRenderPipeline" }LOD 100Pass{Name "GBuffer"Tags { "LightMode" = "Deferred" }HLSLPROGRAM#pragma vertex vert#pragma fragment frag#include "UnityCG.cginc"struct appdata{float4 vertex : POSITION;float3 normal : NORMAL;float2 uv : TEXCOORD0;};struct v2f{float2 uv : TEXCOORD0;float3 normalWS : TEXCOORD1;float4 pos : SV_POSITION;};sampler2D _MainTex;float4 _MainTex_ST;float4 _Color;float _Metallic;float _Smoothness;float4 _EmissionColor;v2f vert (appdata v){v2f o;o.pos = UnityObjectToClipPos(v.vertex);o.uv = TRANSFORM_TEX(v.uv, _MainTex);o.normalWS = UnityObjectToWorldNormal(v.normal);return o;}struct GBufferOutput{float4 gbuffer0 : SV_Target0; // Albedo.rgb, Metallicfloat4 gbuffer1 : SV_Target1; // Normal.xy, Smoothness, Occlusionfloat4 gbuffer2 : SV_Target2; // Emission.rgbfloat4 gbuffer3 : SV_Target3; // Reserved (optional)};GBufferOutput frag (v2f i){GBufferOutput o;float3 albedo = tex2D(_MainTex, i.uv).rgb * _Color.rgb;float3 normalWS = normalize(i.normalWS);float metallic = _Metallic;float smoothness = _Smoothness;float occlusion = 1.0;o.gbuffer0 = float4(albedo, metallic);o.gbuffer1 = float4(normalWS.xy, smoothness, occlusion);o.gbuffer2 = float4(_EmissionColor.rgb, 1.0);o.gbuffer3 = 0; // Reserved (deferred fog, etc)return o;}ENDHLSL}}FallBack "Diffuse"
}

如果是在内置管线,那就差别很多了(主要在光照模型变了,所以需要的输出标准Gbuffer的格式变了)

在 Built-in Deferred 渲染中,你的 Shader 必须满足以下要求:

要求描述
包含 Tags { "LightMode" = "Deferred" } 的 Pass用于写入 GBuffer,Unity 的内置 Deferred 渲染器会调用它
输出标准 GBuffer 格式(Albedo, Normal, Specular)Unity 内部定义格式
或者直接使用 #pragma surface + deferred 修饰符✅ 推荐方式,最简单稳定

就比如直接使用surface shader作为光照计算基础,

Shader "Custom/BuiltinDeferredMinimal"
{Properties{_MainTex ("Albedo", 2D) = "white" {}_Color ("Color", Color) = (1,1,1,1)_Glossiness ("Smoothness", Range(0,1)) = 0.5_Metallic ("Metallic", Range(0,1)) = 0.0}SubShader{Tags { "RenderType"="Opaque" }LOD 200CGPROGRAM// Surface Shader 开启延迟渲染#pragma surface surf Standard fullforwardshadows deferredsampler2D _MainTex;fixed4 _Color;half _Glossiness;half _Metallic;struct Input{float2 uv_MainTex;};void surf (Input IN, inout SurfaceOutputStandard o){fixed4 c = tex2D(_MainTex, IN.uv_MainTex) * _Color;o.Albedo = c.rgb;o.Metallic = _Metallic;o.Smoothness = _Glossiness;o.Alpha = 1;}ENDCG}FallBack "Diffuse"
}
元素含义
#pragma surface surf Standard deferred表明使用延迟路径生成 GBuffer(Unity 内部处理)
SurfaceOutputStandardUnity 内部的数据结构,自动输出 GBuffer
不需要手动写 GBufferUnity 自动处理你返回的 Albedo、Normal、Specular 等分量
✅ 光照将统一在 Unity Lighting Pass 中处理包括所有光源影响(延迟的最大优势)

⚠️ 如果你想写自定义 GBuffer 的 Vertex/Fragment Shader

那就麻烦多了,必须:

  1. 使用 Pass { Tags { "LightMode"="Deferred" } }

  2. 自己输出标准 GBuffer 结构到 SV_Target0~2

  3. 遵循 Unity 的内置 GBuffer 格式:

Render Target内容
SV_Target0Albedo.rgb + Occlusion
SV_Target1Specular + Roughness
SV_Target2Normal + Emission(可选)

这适合高手使用,Unity 并不推荐在内置管线中这么做。

“如果我也想像 URP 一样,在内置管线用 V/F shader 输出 GBuffer,会差很多吗?”

你无法更改 GBuffer 结构(必须符合 Unity 的写死格式)

Unity 内置延迟渲染的 GBuffer 结构如下(固定):

Render Target内容(通道布局)
SV_Target0Albedo.rgb + Occlusion (a)
SV_Target1Specular.rgb + Roughness (a)
SV_Target2Normal.xyz + unused
SV_Target3reserved (only HDRP uses)

而在 URP 中,你可以完全自定义这 4 个 GBuffer 的格式,比如你可以让 GBuffer2 专门放 clearcoat 或 anisotropy。

你不能重写 Lighting Pass 的任何逻辑

Unity Built-in Deferred Lighting 是引擎内部的 C++ 层代码,你无法修改

  • Blinn-Phong vs Cook-Torrance

  • 灯光衰减函数

  • 阴影查找方式

  • IBL/反射探针混合规则

在 URP 中,这一切都可以在 DeferredLighting.hlslLighting.hlsl 里修改

写法不算差很多,但“灵活性差很多”

如果要写一份

Shader "Custom/BuiltinDeferredVF"
{Properties{_MainTex ("Albedo", 2D) = "white" {}_Color ("Color Tint", Color) = (1,1,1,1)_SpecColor ("Specular Color", Color) = (0.04, 0.04, 0.04, 1)_Glossiness ("Smoothness", Range(0,1)) = 0.5_Occlusion ("Occlusion", Range(0,1)) = 1.0}SubShader{Tags { "RenderType" = "Opaque" }LOD 100Pass{Name "Deferred"Tags { "LightMode" = "Deferred" }CGPROGRAM#pragma vertex vert#pragma fragment frag#include "UnityCG.cginc"struct appdata{float4 vertex : POSITION;float3 normal : NORMAL;float2 uv : TEXCOORD0;};struct v2f{float2 uv : TEXCOORD0;float3 normalWS : TEXCOORD1;float4 pos : SV_POSITION;};sampler2D _MainTex;float4 _Color;float4 _SpecColor;float _Glossiness;float _Occlusion;v2f vert (appdata v){v2f o;o.pos = UnityObjectToClipPos(v.vertex);o.uv = v.uv;o.normalWS = UnityObjectToWorldNormal(v.normal);return o;}struct FragOutput{float4 rt0 : SV_Target0; // Albedo.rgb + Occlusionfloat4 rt1 : SV_Target1; // Specular.rgb + Smoothnessfloat4 rt2 : SV_Target2; // Normal.xyz};FragOutput frag(v2f i){FragOutput o;float3 albedo = tex2D(_MainTex, i.uv).rgb * _Color.rgb;float3 normal = normalize(i.normalWS);o.rt0 = float4(albedo, _Occlusion);o.rt1 = float4(_SpecColor.rgb, _Glossiness);o.rt2 = float4(normal, 1.0); // 第四分量通常未用return o;}ENDCG}}FallBack "Diffuse"
}
http://www.lryc.cn/news/597301.html

相关文章:

  • JavaEE Spring框架的概述与对比无框架下的优势
  • 大模型开发
  • 【Ansible】Ansible 管理 Elasticsearch 集群启停
  • NAPI node-addon-api 编译报错 error C1083: “napi.h”: No such file or directory
  • 【esp32s3】GPIO 寄存器 开发解析
  • MACOS安装配置Gradle
  • 垃圾回收介绍
  • static 关键字的 特殊性
  • 双流join 、 Paimon Partial Update 和 动态schema
  • 【硬件-笔试面试题】硬件/电子工程师,笔试面试题-2,(电路分析/MOS管)
  • OpenLayers 快速入门(四)View 对象
  • PyTorch中nn.Module详解和综合代码示例
  • 大模型提示词漏洞攻防实战:从注入攻击到智能免疫系统的进化之路
  • mac电脑搭载c、c++环境(基于vs code)
  • 在mac 上zsh 如何安装最新的 go 工具
  • GRE实验
  • 微软Fabric重塑数据管理:Forrester报告揭示高ROI
  • 「iOS」——KVC
  • linxu CentOS 配置nginx
  • 【音视频学习】四、深入解析视频技术中的YUV数据存储方式:从原理到实践
  • 开源UI生态掘金:从Ant Design二次开发到行业专属组件的技术变现
  • 7月23日华为机考真题第二题-200分
  • 7月23日华为机考真题第一题100分
  • 关于原车一键启动升级手机控车的核心信息及注意事项
  • 将AI协作编程从“碰运气”的提示工程(Prompt Engineering)提升到“可预期”的上下文工程(Context Engineering)
  • 驯服AI的“魔法咒语”:Prompt提示词工程使用教程
  • [特殊字符] 从数据库无法访问到成功修复崩溃表:一次 MySQL 故障排查实录
  • 显微科研中的关键选择:不同显微镜相机技术特性与应用适配性全面解析
  • SpringBoot Stream实战指南
  • Django学习之旅--第13课:Django模型关系进阶与查询优化实战