使用了 #pragma multi_compile_fwdbase
这条编译指令启用了 Unity 内部用于主光源阴影支持的一组关键词变体,如:
-
SHADOWS_SCREEN
(屏幕空间阴影贴图) -
SHADOWS_DEPTH
(深度图阴影) -
SHADOWS_SOFT
(软阴影)
使用了 TRANSFER_SHADOW(o)
和 SHADOW_COORDS(n)
这两句宏做了两件事:
-
TRANSFER_SHADOW(o)
:在vert()
阶段把阴影贴图坐标写入插值结构; -
SHADOW_COORDS(3)
:在插值结构中预留了一个TEXCOORD3
来存储阴影坐标。
使用了 UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos)
这是关键:
这个宏会根据场景中的光源和阴影贴图内容,对当前片元应用阴影遮蔽计算,输出值存入
atten
。
在你的 frag()
函数里这句生效:
(ambient + (diffuse + specular) * atten + i.vertexLight)
说明:
-
如果当前片元处于阴影中,
atten
就会接近 0; -
光照(diffuse + specular)会被大幅削弱,从而呈现出“阴影下变暗”的效果;
-
ambient 和
vertexLight
不受阴影影响,保持基本亮度(这也是你看到阴影下仍有些亮度的原因)。
ShadowCaster Pass 是用于“投射阴影”的,不是“接收阴影”的。
功能 | 所需 Pass | 是否必须你手动写 | 说明 |
---|---|---|---|
✅ 接收阴影(Receive Shadows) | ForwardBase Pass + 阴影宏(如 UNITY_LIGHT_ATTENUATION ) | 可自己实现或依赖 fallback | 表示“自己会变暗”,接受 shadow map 的遮蔽值 |
✅ 投射阴影(Cast Shadows) | ShadowCaster Pass | 必须手动写(或使用 fallback) | 表示“自己会在别的物体上留下阴影”,写入 shadow map |
详细机制:Unity 阴影的运行流程(Forward 渲染)
-
Unity 在渲染阴影贴图之前,会遍历场景中所有物体;
-
它寻找所有含有:
Tags { "LightMode" = "ShadowCaster" }
的 Shader;
-
如果你当前的材质没有 ShadowCaster:
-
Unity 就跳过这个物体;
-
也可能跳过整个 Shadow Pass 的构建(如果全场都没 ShadowCaster);
-
最终导致 阴影贴图是空的;
-
-
在
ForwardBase
Pass 中,虽然你调用了:UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);
但这个
atten
的计算要基于阴影贴图。如果贴图是空的(没采到任何阴影源),它就始终为 1(即无阴影遮挡)。
RenderQueue
值确实是:
-
Geometry
:2000 -
AlphaTest
:2450 -
Transparent
:3000
因此,2500 并不是 AlphaTest + 50
,而是介于 AlphaTest
和 Transparent
之间的一个值。Unity 将 RenderQueue
值 小于或等于 2500 的物体视为不透明物体,大于 2500 的物体视为透明物体。
根据 Unity 官方文档:
“对于索引小于或等于 2500(即
Geometry + 500
)的队列,Unity 使用OpaqueSortMode.FrontToBack
进行排序。对于索引大于 2500 的队列,Unity 使用TransparencySortMode.Default
进行排序。”
这意味着:
-
RenderQueue
≤ 2500:物体被视为不透明,参与深度测试和遮挡计算,能够正确接收阴影。 -
RenderQueue
> 2500:物体被视为透明,通常不写入深度缓冲区,可能无法正确接收阴影。
-
阴影接收能力:在 Unity 的内置渲染管线中,只有不透明物体(Render Queue ≤ 2500)才能正确接收主光源的阴影。当物体的渲染队列值大于 2500 时,Unity 会将其视为透明物体,通常不会在其上执行阴影衰减计算,导致这些物体无法正确接收阴影。
-
深度写入行为:透明物体通常设置为不写入深度缓冲区(
ZWrite Off
),这会影响后续物体的深度测试,可能导致渲染顺序错误或视觉上的穿透现象。 -
渲染顺序和排序方式:不透明物体使用前向排序(Front-to-Back),有助于提高渲染效率和正确的遮挡关系;而透明物体使用默认的透明排序方式,主要根据与摄像机的距离进行排序,以实现正确的混合效果。
-
渲染效果的可控性:如果需要在透明物体上实现正确的阴影接收效果,可能需要采用特殊的渲染技术,如多 Pass 渲染,其中一个 Pass 写入深度信息,另一个 Pass 进行颜色混合。
Queue ≤ 2500 | Queue > 2500 |
---|---|
默认 ZWrite On ,写入深度缓冲区 | 默认 ZWrite Off ,不写入深度 |
使用 Front-to-Back 排序(提前遮挡加速) | 使用 Back-to-Front 排序(为混合效果) |
能用于遮挡其他物体并参与深度比较 | 通常无法遮挡别的东西,容易被穿透 |
-
渲染从 Queue ≤ 2500 的不透明物体开始;
-
这些物体默认启用
ZWrite On
,所以可以按照前后关系写入 ZBuffer; -
不透明物体使用 Front-to-Back 排序,从而尽早遮挡后面的像素,提升效率;
-
所有不透明物体渲染完后,ZBuffer 已经填好;
-
接着开始渲染 Queue > 2500 的透明物体;
-
透明物体一般
ZWrite Off
,所以不会覆盖 ZBuffer; -
它们使用 Back-to-Front 排序,逐层叠加实现混合,参考当前 ZBuffer 做深度测试(ZTest);
-
最终渲染出正确的遮挡 +透明混合结果。
🧠 要注意的 几个扩展点或容易误解的点:
1. ✅ ZBuffer 在透明阶段不再更新,但仍用于判断遮挡
即:透明物体虽然 ZWrite Off
,但 ZTest 是 On,依然会“看到前面不透明物体的遮挡”。
2. ✅ 如果多个透明物体互相重叠,排序失误就会造成视觉错误
这就是 Unity 中的“透明排序问题”本质,需要手动调整 Queue 或使用深度预写法(Dual Pass)。
3. ✅ 有些透明特效(如玻璃)会启用 ZWrite On
强行写深度
→ 这会让它能遮住后面透明物体,但失去正常混合层叠能力。
Surface Shader
Surface Shader 是什么?
它是 Unity 提供的一种 高级语法糖,用一套结构化的语法,自动帮你生成 Vertex/Fragment Shader,并处理光照、阴影、GI、光照贴图、SH、ShadowCaster 等行为。
✔ Surface Shader 不是通往现代图形编程的桥梁,但它是理解 Unity 内置光照管线、与美术系统融合的重要入口。建议作为知识模型建立,不推荐当作主战工具使用。
+---------------------------------------------------+
| #pragma surface |
| 选择光照模型(Standard等)/是否启用阴影/透明性 |
+---------------------------------------------------+
↓ ↓
自动生成 Pass 自动编译变体
↓
+------------------------+ +------------------------+
| struct Input | | SurfaceOutputStandard |
| - uv_MainTex | | - Albedo |
| - viewDir | | - Normal |
| - worldPos | | - Metallic/Smoothness |
+------------------------+ +------------------------+
↓ ↓
surf(IN, out) ←———————你只控制这块逻辑——————
Meta Pass 是 Unity 用于光照贴图(Lightmap)和全局光照(GI)烘焙的特殊用途 Pass,
它不是用于实时渲染,而是为光照贴图计算提供材质信息的。
什么是 Meta Pass?
-
Unity 在进行光照贴图、全局光照(GI)烘焙、环境探针、反射探针计算时,不使用 ForwardBase / Deferred。
-
它会在烘焙阶段,单独执行所有 Shader 中 标记了
"LightMode" = "Meta"
的 Pass。 -
Unity 把每个材质的 Meta Pass 渲染到一张特殊纹理上,用来分析:
-
Albedo
-
Emission
-
Transparency(部分 GI 系统也采样 alpha)
-
🔧 Meta Pass 渲染什么?
它渲染的是材质的基础反射属性,而不是实时光照。
属性 | 用法 |
---|---|
o.Albedo | 用于光照贴图基础颜色计算 |
o.Emission | 用于记录自发光颜色(对光照贴图和环境探针有贡献) |
o.Alpha | 某些混合模式下用于调节影响程度(比如 Cutout) |
// 这段宏是 Surface Shader 编译器生成的,用于 Meta Pass:
#include "UnityMetaPass.cginc"
return UnityMetaFragment(metaIN);
实时渲染使用的 Pass:
-
LightMode = "ForwardBase"
:主光源渲染 -
LightMode = "ForwardAdd"
:附加光源渲染 -
LightMode = "ShadowCaster"
:生成阴影贴图 -
LightMode = "Deferred"
:延迟渲染路径使用的 GBuffer 输出
这些都是 Frame-by-Frame、实时参与屏幕渲染的。
❌ 但 GI / Lightmap 不走这条路线:
-
它不会调用 ForwardBase Pass 中你定义的光照计算逻辑
-
它不关心实时光照行为,而是关心“材质在静态环境中反射/发光的颜色”
-
所以会跳过你写在 ForwardBase 中的各种 BlinnPhong、PBR 模拟、透明混合逻辑
光照贴图(Lightmap)和全局光照(GI)是什么?
-
光照贴图:是一个离线烘焙好的纹理,用来记录光照结果,而非实时计算。贴在静态物体上,减少 GPU 压力。
-
全局光照(GI):指光之间的间接反射,Unity 会烘焙出多个探针 / 立方体贴图 / 辐射缓存。
这些都不是实时光照,而是预先计算完,用采样实现的。
环境探针 / 反射探针是什么?
-
环境光探针(Light Probes):
-
存储的是空间点位处的 Spherical Harmonics 环境光数据(主要用于动态物体受环境光影响)
-
-
反射探针(Reflection Probes):
-
存储的是 cubemap,供标准材质中的反射通道采样用
-
也来自离线渲染一张“天空图”
-
这些探针计算的时候,需要知道材质的基础颜色和自发光,这时候 Unity 会专门走你写的 "LightMode" = "Meta"
Pass 来采样。
“在图形学上,离线渲染(offline rendering)和实时渲染(real-time rendering)是两种相互对立的渲染范式。为什么 Unity 会把类似离线用途的 Meta Pass 放进和实时渲染同一个 Shader 文件中?这是否违反了两者的原本定义?”
首先我们明确 标准图形学中的“离线渲染” 和“实时渲染” 的定义区别
项目 | 实时渲染(Real-Time) | 离线渲染(Offline) |
---|---|---|
应用领域 | 游戏、交互、VR | 电影、CG动画、渲染农场 |
性能目标 | 每秒 30~240 帧以上 | 几分钟~几小时生成一帧 |
精度追求 | 尽量快 + 接近真实 | 光照物理尽可能真实 |
渲染特性 | 简化光照、近似反射、烘焙静态光 | Path Tracing、GI、多次反弹光 |
使用引擎 | Unity、Unreal、OpenGL | Arnold、RenderMan、Cycles、Octane |
输出类型 | 实时显示到屏幕 | 渲染出视频帧或高质量贴图 |
Unity 语境中,“Meta Pass 所代表的‘离线渲染’”其实是**“实时渲染流程中的一段非实时准备阶段”**
Unity 不是在用电影工业意义上的“离线渲染”:
❌ “计算 10 分钟渲染一帧的电影级画质”
而是在做:
✅ “为了加快实时运行时效率,提前在编辑器中用一帧一帧的方式,把光照、探针、反射贴图等静态信息先烘焙好。”
这个过程虽然运行在编辑器中、非实时进行,但本质上还是在为 实时渲染服务。
它们必须共享以下信息:
-
材质属性:贴图、颜色、金属度、粗糙度、透明度……
-
渲染控制权:哪些表面参与烘焙、哪些只参与实时渲染
Meta Pass 是在特定渲染模式下由 Unity Editor 的烘焙流程明确触发的,**和场景中是否“出现在当前视野”无关。
[Lighting 窗口 → 点击 Generate Lighting]
↓
[Unity 开始进行 GI / Lightmap 烘焙]
↓
[遍历所有 Baked Static / Reflection Probe 包围体内的物体]
↓
[使用内置烘焙摄像机,开启 Meta 渲染模式]
↓
[调用该物体使用材质的 Shader → 执行 "LightMode"="Meta" 的 Pass]
↓
[采集 surf() 函数输出的 o.Albedo, o.Emission, o.Alpha]
↓
[输出到临时光照贴图 / 探针采样缓冲区]
↓
[GI 系统计算反弹 / 探针插值 / Lightmap UV 映射]
↓
[生成最终 Lightmap / Probe 数据,供运行时采样]
① 触发时机
-
手动点击 Lighting 窗口的
Generate Lighting
-
改动了场景中需要烘焙的物体(比如模型或材质)
-
修改了 Reflection Probe / Light Probe / Lightmap 参数
-
使用 [Scene View → Baked GI Preview]、[Reflection Probe Preview] 等功能
② 目标对象筛选机制
Unity 会扫描场景中:
-
被设置为
Static
且勾选Lightmap Static
的物体; -
包含在 Reflection Probe 或 Light Probe Group 采样范围内的物体;
-
使用了任何带有 Meta Pass 的材质。
Pass {
Tags { "LightMode" = "Meta" }
...
}
如果找到,则执行这个 Pass(调用其中的 surf() 函数)
④ Meta Pass 渲染的输出结构
Meta Pass 不会输出到主帧缓冲,而是:
输出目标 | 内容 | 说明 |
---|---|---|
Meta Color Buffer | Albedo | 表面颜色(不含光照) |
Meta Emission Buffer | Emission | 自发光,用于 GI 源 |
Alpha Buffer(可选) | Alpha | 用于 Cutout / 透明遮罩判断(如叶片) |
#include "UnityMetaPass.cginc"
void surf(Input IN, inout SurfaceOutputStandard o) {
o.Albedo = tex2D(_MainTex, IN.uv_MainTex).rgb;
o.Emission = _EmissionColor.rgb;
o.Alpha = 1.0;
}fixed4 frag(v2f_surf IN) : SV_Target {
UnityMetaInput meta;
meta.Albedo = o.Albedo;
meta.Emission = o.Emission;
return UnityMetaFragment(meta);
}
#include "UnityMetaPass.cginc"
引入了 Unity 内部提供的封装代码,用来辅助采集 Meta 信息
包含了
UnityMetaFragment()
等函数,它们把你的Albedo
和Emission
输出正确地写入烘焙系统需要的缓冲区fixed4 frag(v2f_surf IN) : SV_Target {
UnityMetaInput meta;
meta.Albedo = o.Albedo;
meta.Emission = o.Emission;
return UnityMetaFragment(meta);
}
这部分是 Meta Pass 中实际向 Meta 渲染目标缓冲区 写入的地方。UnityMetaInput 是 Unity 定义的结构体,通常有这些字段:
.Albedo(基础颜色)
.Emission(自发光)
.Alpha(可选,控制透明剔除时用)
UnityMetaFragment() 是 Unity 封装的函数,它会:
判断当前是正在生成 Lightmap 还是 Probe
把 meta.Albedo 和 meta.Emission 写入烘焙专用的 FrameBuffer
自动处理透明剔除(如 AlphaTest)的剪裁判断
⑤ Meta Pass 数据被用来做什么?
目标系统 | 使用方式 |
---|---|
☀️ 光照贴图 Lightmap | 作为每个像素基础反射颜色,参与 GI 反弹计算 |
🔦 Light Probe(SH 探针) | 采样物体表面颜色,用于生成球谐系数(SH) |
🪞 Reflection Probe(反射贴图) | 生成近似 cubemap 反射纹理 |
🎨 Scene View GI 预览 | 允许在编辑器中实时查看 GI 结果 |
球谐光照是一种用“数学方式描述从四面八方来的光”的压缩表达形式,
它的起源是:我们想在实时渲染中模拟复杂、方向性强的间接光(Global Illumination),但又不能用巨大的存储和计算量,于是采用球谐函数进行近似编码。
我想要让一个点位(或探针)保存“来自四面八方的光照分布”信息,这样动态物体也能感受到场景中的全局光(比如天花板反射、墙面反弹的光)。但如果我真的在每个方向上采样一个颜色,那我就需要存下一个球面纹理,这既慢又占内存。
球谐函数(Spherical Harmonics)
-
用一组数学函数(球谐基)来近似表达球面上的任意方向分布
-
类似于在声音中用正弦波做傅里叶变换表达频率 → 它是对方向空间光照的频域压缩
🧪 在图形学中的用法
🎯 用来编码的内容:
-
一个点上,从各个方向来的环境光照(通常用于 Light Probe)
-
一个贴图区域上接收到的间接光照(通常用于 Lightmap baking)
-
甚至可以用来编码 BRDF 的方向性(高级用途)
✨ 用来解码时:
-
传入一个法线方向(比如动态物体的表面法线)
-
乘上 SH 系数,得到该方向上应该接受到的环境光颜色
🛠️ 在 Unity 中的实现
Unity 会:
-
在烘焙时计算场景中多个 Light Probe 的球谐系数(通常使用 2 阶或 3 阶球谐,共 9 个系数,每个 RGB 通道独立)
-
在运行时,对动态物体的每个顶点做 SH 解码,乘以其法线方向,得到环境光颜色
Light Probe 的目标:
预先在空间中的某些点采集全方向环境光照,然后在运行时用球谐函数插值,给动态物体使用。
→ 起源自 Precomputed Radiance Transfer (PRT) 和 Spherical Harmonics Lighting,尤其是 2002 年 Sloan 等人的论文:
“Precomputed Radiance Transfer for Real-Time Rendering in Dynamic, Low-Frequency Lighting Environments”
🎯 Reflection Probe 的目标:
在一些代表性位置预先拍摄 CubeMap,并在运行时使用贴近的环境贴图给材质反射。
→ 起源于 Image-Based Lighting(IBL) 和 Environment Mapping 技术,最早在电影工业中使用 CubeMap 拍摄替代全局光。
“静态物体”和“动态物体”这两个词在 Unity(和一般实时渲染系统)中不仅是分类标签,它们背后代表的是完全不同的渲染路径、烘焙机制、Shader 数据输入源和优化策略。
类型 | 静态物体(Static) | 动态物体(Dynamic) |
---|---|---|
意义 | 不会在运行时移动、旋转、缩放 | 会变换位置、动画、生成等 |
标志设置 | 勾选 Static 标签 | 默认都为动态 |
烘焙处理 | 可进行 Lightmap 烘焙 | 不可被烘焙,只能实时照明 |
GI 系统处理 | 可作为光照贡献源(参与反弹) | 通常只作为光照接收者 |
数据输入 | 使用 Lightmap UV,访问 _LightMap | 使用 Light Probe 插值,访问 unity_SH |
Shader "Custom/SurfaceShader"
{
Properties
{
_Color ("Color", Color) = (1,1,1,1)
_MainTex ("Albedo (RGB)", 2D) = "white" {}
_Glossiness ("Smoothness", Range(0,1)) = 0.5
_Metallic ("Metallic", Range(0,1)) = 0.0
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 200
CGPROGRAM
// Physically based Standard lighting model, and enable shadows on all light types
#pragma surface surf Ocean fullforwardshadows vertex:vert finalcolor:final
// Use shader model 3.0 target, to get nicer looking lighting
#pragma target 3.0
#include "UnityPBSLighting.cginc"
sampler2D _MainTex;
struct Input
{
float2 uv_MainTex;
};
half _Glossiness;
half _Metallic;
fixed4 _Color;
half4 LightingOcean(SurfaceOutputStandard s, half3 lightDir, half atten)
{
fixed4 c;
fixed diff = max (0, dot (s.Normal, lightDir));
c.rgb = s.Albedo * _LightColor0.rgb * diff * atten;
c.a = s.Alpha;
return c;
}
// Add instancing support for this shader. You need to check 'Enable Instancing' on materials that use the shader.
// See https://docs.unity3d.com/Manual/GPUInstancing.html for more information about instancing.
// #pragma instancing_options assumeuniformscaling
UNITY_INSTANCING_BUFFER_START(Props)
// put more per-instance properties here
UNITY_INSTANCING_BUFFER_END(Props)
void vert (inout appdata_full v)
{
}
void final(Input IN, SurfaceOutputStandard o, inout fixed4 color)
{
}
void surf (Input IN, inout SurfaceOutputStandard o)
{
// Albedo comes from a texture tinted by color
fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
o.Albedo = c.rgb;
// Metallic and smoothness come from slider variables
o.Metallic = _Metallic;
o.Smoothness = _Glossiness;
o.Alpha = c.a;
}
ENDCG
}
FallBack "Diffuse"
}
什么是 Shader Model?
-
Shader Model 是由 GPU 驱动程序定义的一套功能等级规范,决定了:
-
可使用的纹理数量
-
指令数量上限
-
是否支持动态分支、纹理 LOD 等功能
-
Shader Model | 代表意义 | Unity 常用用途 |
---|---|---|
2.0 | 最基础,最多 8 个纹理、无动态分支 | 非常过时,仅老硬件兼容 |
3.0 ✅ | 支持动态分支、32 个寄存器等现代特性 | Surface Shader 最小推荐 |
4.0/4.5 | 支持结构体 buffer,compute shader | HDRP / DX11+ 平台 |
5.0 | 完全现代 GPU 语言 | 高端特效/Compute Shader |
#include "UnityPBSLighting.cginc"
PBS 是什么?
PBS = Physically Based Shading(物理基础光照)
这个文件中包含:
-
Unity 的内建 BRDF 函数(Cook-Torrance 模型)
-
标准 Metallic Workflow 的参数设置
-
支持 Unity 标准光照路径的函数(如
LightingStandard
,LightingStandardSpecular
)
不过你这段代码虽然引入了 PBS,但实际上没有使用 PBR 模型,而是定义了自己的简化光照函数 LightingOcean
。因此这个 include 虽然保留着,是冗余的,可以移除(除非你从里面引用函数或结构)。
#pragma surface surf Ocean fullforwardshadows vertex:vert finalcolor:final
这行是 Surface Shader 的核心入口定义。下面来 分解每一部分含义:
#pragma surface surf Ocean ...
部分 | 作用 |
---|---|
surf | Surface 函数名:你的 void surf(Input IN, inout SurfaceOutputStandard o) |
Ocean | 自定义光照函数名:你定义的 half4 LightingOcean(...) 会替代默认光照计算方式 |
fullforwardshadows
启用所有类型光源的阴影支持(包括 Spot、Point、Directional 光),否则默认只开启主方向光阴影。
✅ 它会自动插入 ShadowCaster
pass,不需要你手动写投影逻辑。
vertex:vert
指定一个 自定义顶点函数
void vert(inout appdata_full v)
,允许你在顶点阶段执行变形、UV 操作等。
✅ 自动为你生成:
-
ShadowCaster Pass(支持投射阴影)
-
ForwardBase、ForwardAdd、Meta 等完整 Pass
-
GI(光照贴图、Light Probe 支持)
-
反射探针、动态光照、逐像素/逐顶点混合
-
Lightmap、SH9 相关数据插入
-
多光源处理
-
内置变量(如
o.Albedo
,o.Emission
)绑定到渲染流程
❗你需要放弃的“控制”:
-
你不能精细控制每个 Pass 的具体结构(除非你自己写 Custom Shader)
-
你必须按 Unity 规定的结构写
surf()
函数,不能随意输出颜色 -
如果想做多 RenderTarget(MRT)、延迟渲染 GBuffer 输出 → Surface Shader 不适合,得改用 ShaderLab 手写多个 Pass
-
如果使用 Unity 内建模型(如
Standard
):-
会自动支持:
-
Reflection Probe(支持这个是会让unity做些什么)
-
环境光贴图(这个贴图是怎么产生的,传给这个处理需要哪些数据)
-
BRDF Lookup(这是干什么的)
-
GI 烘焙联动(为什么叫这个)
-
-
-
如果你用 自定义光照函数名:
-
Unity 不会插入其默认 PBR 结构
-
你必须在
LightingXxx()
中手动处理光照、阴影、Attenuation 等 -
少了如
SpecularColor
,FresnelTerm
等自动变量绑定(自动绑定的变量是怎么来的,为什么可以自动绑定,是unity做的吗)
-
可选参数(surface 指令中的附加关键词)
这些是在 #pragma surface
后面你可以加的关键词,用于控制 Shader 自动生成时的附加行为。
关键词 | 作用 | 举例 | 限制或功能 |
---|---|---|---|
alpha | 启用透明(需要输出 o.Alpha ) | #pragma surface surf Standard alpha | 会自动生成 Blend、AlphaTest 支持 |
alphatest:_Cutoff | 启用透明裁剪,使用某个属性控制 | alphatest:_Cutoff | 与 clip() 配合使用(这个启动之后是干什么的,透明裁剪是一个功能吗,还是变量) |
addshadow | 即使是 Unlit 也能生成 ShadowCaster pass | 用于自定义 Shader 也能投影阴影 | |
fullforwardshadows ✅ | 支持所有类型光源的阴影(包括点光、聚光) | 默认只支持主光源方向光阴影 | |
vertex:xxx | 使用自定义顶点函数 | vertex:vert | 必须提供 void vert(...) |
finalcolor:xxx | 使用自定义最终颜色函数 | finalcolor:final | 会在输出颜色前调用 |
keepalpha | 保留 Alpha,不自动写入 1 | 常用于透明混合 | |
nofog | 禁用内置雾效 | ||
noambient | 不使用全局环境光(Unity 的 Ambient Light) | 用于 Stylized Shader | |
noshadow | 禁止生成阴影 Pass | 轻量场景可用 | |
exclude_path:deferred | 只在 Forward 渲染中启用 | 避免延迟路径下编译 |
Shader 中启用 Reflection Probe 时,Unity 插入了哪些变量与贴图?如何控制使用哪一个 Reflection Probe?
Unity 会在编译时自动为支持反射的 Shader 插入 Reflection Probe 相关的 Cubemap 采样变量、采样函数,以及物体 → 环境贴图 的映射逻辑。**你不需要手动采样贴图,但你必须使用 Unity 支持的光照模型(如 Standard
)或遵循它的约定。
插入以下 采样函数:
half3 Unity_GlossyEnvironment (UNITY_GLOSSY_ENV_DATA data, half3 reflUVW, half perceptualRoughness);
-
reflUVW
:你计算出来的反射方向(world space) -
perceptualRoughness
:由_Glossiness
推导而来 -
UNITY_GLOSSY_ENV_DATA
:包含unity_SpecCube0_HDR
的结构体包装
Unity 如何决定使用哪张 Reflection Probe?
这是由 Unity 的 C++ 渲染系统在运行时做的(不是你 Shader 自己判断):
步骤:
-
每个物体在场景中,Unity 会计算它当前所处的位置
-
查找最近的 Reflection Probe(包围盒内的 Probe)
-
进行 Cubemap 的混合/插值(最多两个 Probe)##(怎么插值的,probe是什么,还有具体的数量?这种图cubemap是skybox的相关产物吗,需要再静态物体上才能生成吗,这中sampletexture的形成方式和一般的vf shader下写像素到缓冲区有什么区别)
-
把这个结果 绑定到材质的
unity_SpecCube0
中 -
如果没有 Reflection Probe,默认使用 Skybox 的环境贴图(probe是有很多种?各种之间的共性是什么,都能做到什么,)
When you add a new scene, Unity automatically creates a hidden default Reflection Probe that stores Unity’s built-in Default Skybox cubemap.
天空盒影响光照颜色
Unity 是什么时候绑定这张 Cubemap 的?
你在 Shader 中只是用到了 samplerCUBE unity_SpecCube0;
,它的贴图绑定早在 DrawCall 提交前由 Unity 内部完成。
在 Built-in 渲染管线 + 使用 Surface Shader 或标准光照模型 时,Unity 自动插入以下变量:
🌐 环境反射(Reflection Probe)
插入的变量 | 类型 | 说明 |
---|---|---|
samplerCUBE unity_SpecCube0 | cubemap 采样器 | 当前物体使用的反射探针贴图(Cubemap) |
float4 unity_SpecCube0_HDR | HDR 解码参数 | 用于解码反射贴图中的 RGBM 编码数据 |
float3 unity_SpecCube0_BoxMin | Box 投影区域最小点 | 用于 Box Projection 模式的插值 |
float3 unity_SpecCube0_BoxMax | Box 投影区域最大点 | 同上 |
float4 unity_SpecCube0_ProbePosition | 反射探针的世界坐标 | Box Projection 使用中心点 |
这些变量可由 Unity_GlossyEnvironment()
函数在 Unity 标准光照模型中调用来使用。
这些变量 不会出现在你写的 Shader 源码中,而是:
-
通过
#pragma surface surf Standard
等指令 -
被 Unity 的 Surface Shader 编译器插入(包括引用了
UnityPBSLighting.cginc
或UnityGlobalIllumination.cginc
等) -
仅在 Built-in RP 中有效。URP 和 HDRP 使用不同的绑定机制(CBuffer)
你 无法在 Shader 中主动控制使用哪个 Probe,它是由 Unity 的 C++ 渲染系统运行时决定的:
运行时流程:
-
查找最近的 Reflection Probe
-
如果物体在某个 Probe 的包围盒(box projection)中 → 使用它。
-
如果有多个合适的 Probe → 最多混合两个(加权插值)。
-
-
插值并绑定 Cubemap
-
Unity 在 CPU 端计算混合结果 → 绑定成纹理传入
unity_SpecCube0
。
-
-
Shader 中使用
unity_SpecCube0
即可采样最终贴图
Unity 默认会添加一个 隐藏的反射探针,它使用场景的 Skybox 来生成 Cubemap:
-
这个隐藏 Probe 会作为 fallback 使用。
-
如果 Lighting 设置中选择了 “Generate Lighting”,会生成一个新的 default Reflection Probe。
-
该贴图仍然会传给
unity_SpecCube0
。
Reflection Probe 是 Unity 提供的一个场景组件,其作用是:
-
捕捉周围环境并生成一个 Cubemap
-
用于在物体表面模拟真实反射
✔️ Probe 类型:
-
Baked:在编辑器里通过 Lighting 面板烘焙一次,生成贴图并保存为资源。
-
Realtime:运行时根据摄像机或自身位置动态渲染六个方向生成 Cubemap(性能开销大)。
-
Custom:手动设置 Cubemap 纹理。
这些探针会将捕获到的图像 编码成 HDR 格式(RGBM、LogLuv),并存储为 Cubemap。
如果你没有添加任何 Reflection Probe:
-
Unity 会使用场景中的 Skybox 来自动生成一张全局环境 Cubemap。
-
这个默认 Cubemap 会被绑定到内置变量
unity_SpecCube0
。 -
通常发生在你使用 Standard Shader / Surface Shader 并启用反射时。
内置变量名 | 含义 |
---|---|
samplerCUBE unity_SpecCube0 | 当前可用的环境反射贴图(Cubemap) |
float4 unity_SpecCube0_HDR | 用于 HDR 反射贴图解码(RGBM 编码) |
float3 unity_SpecCube0_BoxMin/Max | Box Projection 使用的投影边界 |
float4 unity_SpecCube0_ProbePosition | Box Projection 使用的探针中心位置 |
这些变量并非你手动写入,而是:
-
在使用 Surface Shader(如
#pragma surface surf Standard
)时 -
Unity 编译器会自动插入
你可以使用 Unity 提供的函数(标准 PBR Shader 已内置):
// 计算反射向量 float3 worldRefl = reflect(-viewDir, normal); // 采样环境贴图 half4 env = UNITY_SAMPLE_TEXCUBE_LOD(unity_SpecCube0, worldRefl, mipLevel); // 解码 HDR half3 reflColor = DecodeHDR(env, unity_SpecCube0_HDR);
这就是标准 BRDF 光照模型中实现高光反射的重要一环。
如果有说贴图错误可以检查模型是否generate shadow uv
-
☠️ 一旦你在运行时改变 Baked 或 Mixed 光源的方向,贴图就不再有效或不更新,因为烘焙贴图是基于烘焙时的光源方向预计算生成的。
-
Unity 会发出警告(或直接无效),因为静态光照贴图只支持“不可变”光源。
✅ Realtime(完全动态):
-
每一帧都根据光源位置/颜色/强度来计算光照。
-
显著消耗性能,但适合可变场景(如手电筒、日夜变化)。
🟡 Mixed(混合光照):
-
静态物体 → 光照信息预烘焙(Lightmap or Light Probe)。
-
动态物体 → 使用实时光照(通常只计算主光源 + 近似阴影)。
-
“Shadowmask” 或 “Subtractive” 模式控制阴影来源。
🔒 Baked(完全静态):
-
灯光在烘焙时作用于场景,之后不再实时影响任何物体。
-
非常适合场景建筑、山体等完全不动的静态元素。
-
实时性能开销接近 0。
当灯光设置为 Baked 时:
-
Unity 只在 烘焙阶段 让这盏灯影响 静态物体。
-
在运行时,这盏灯 完全不会参与实时渲染。
-
所以:
-
✅ 静态物体 → 光照来自 Lightmap 或 Light Probe(提前烘焙好了)
-
❌ 动态物体 → 不受这盏灯影响
-
❌ 不产生实时阴影
-
当灯光设置为 Realtime 时:
-
Unity 每一帧实时计算光照和阴影,这盏灯:
-
✅ 会影响静态物体(通过实时计算)
-
✅ 会影响动态物体
-
✅ 可以投射阴影
-
❌ 不参与光照贴图的生成(不会被烘焙)
-
当灯光设置为 Mixed 时:
-
静态物体:受这盏灯影响的光照和阴影 可以被烘焙进贴图中。
-
动态物体:仍然可以接受这盏灯的实时光照,并且可以产生实时阴影(配置 Shadowmask / Subtractive 时可能略有不同)。
-
一句话:静态预烘焙,动态实时计算。
gi,将光设置成baked,将物体设置为light static,static也行,然后关注阴影区域
模式 | 静态物体:光照 | 静态物体:阴影 | 动态物体:光照 | 动态物体:阴影 | 特点总结 |
---|---|---|---|---|---|
Baked Indirect | 💡 实时直接光🟡 烘焙间接光(lightmap / probe) | ❌ 不生成 shadowmask,阴影全实时 | 💡 实时直接光🟡 无间接反射 | ☑️ 实时阴影(全动态) | 性能较差,与distance的关系是,超出distance之后里面的直接不渲染了,连baked都不留 |
Shadowmask | 🟢 直接光烘焙 / 实时(视实际设置)🟡 间接光烘焙 | ✅ shadowmask 贴图中保存静态物体投射的阴影 | 💡 实时直接光🟡 probe 提供间接光估算 | ☑️ 实时阴影 + shadowmask 混合📏(近距离实时,远距离烘焙) | 推荐默认选项,质量与性能平衡 |
Subtractive | 🟢 直接光烘焙(仅主光)🟡 间接光烘焙 | ✅ 静态阴影完全 baked⚠️ 只支持一盏主方向光 | 💡 只有主光源能照亮🟡 无间接光 | ☑️ 仅主光源有实时阴影 | 极限优化,只建议用于低端硬件或卡通场景 |
-
最终的 shadowmask 使用方式还取决于质量设置中的 Shadowmask Mode(Distance 或 Shadowmask):
设置名 | 描述 |
---|---|
Shadowmask | 所有阴影混合使用 shadowmask 和实时阴影 |
Distance Shadowmask | 靠近摄像机的使用实时阴影,远处使用 shadowmask 降低负担 |
开了gi的效果
关掉之后
移动之后问题就可以看出了
一个物体可以产生多个 ShadowCaster。
Stats
窗口中的 Shadow Casters 数量不严格等于游戏物体数,因为它统计的是参与阴影渲染的渲染元素(Draw Calls 级别的 Shadow Pass 实例),而不是单一的物体个数。
mixed lighting设置为subtractive
场景只有这一个动态物体
此时light设置的是Runtime
改为 mixed--subtractive 模式,之后右边的cube 实时光照的点光源不再产生阴影
左边的因为是烘焙出来的,所以缓存依然存在
shadowmask----
当没有设置成distance时,相比于subtractive,变化的是点光源这些非主方向光的光源,也可以照射物体并且投射出阴影
但此时仍然不是实时的,也就是如果物体移动到别的物体的遮蔽下,是不会更新别的物体照射到自己身上的阴影的
镜头靠近到1m之内,才更新实时阴影
如果你用 Unlit
Shader 或者自写了一个没有 Lightmap 支持的 Surface Shader
,即使场景设置了 Baked Lighting、Mixed Lighting,物体也完全不会受到影响。
Unity 的 Lighting Settings 和 Shader 不是互相覆盖的,而是“Shader 决定了 Lighting 是否生效”。Lighting 设置只是“提供可能”,Shader 才是“是否用上”的决定者。
写这种及时切换的函数降低性能的消耗
如果在物体遮蔽下立刻换成distance shadowmask
(反正可以实时用unity脚本切换)
shadowmask在这种情况下会相对真实的处理,而不是直接的阴影叠加
subtractive纯纯的叠加,没有其他的合理性调整
-
Unity 不会自动用 Shadowmap 给静态物体投影,这通常通过 Shadowmask 技术实现(混合光照时)。
-
Lightmap 中可包含 Shadowmask 信息(由混合灯光预烘焙而来),但它只是“静态阴影权重”,不是实时判断的遮挡。
-
Fresnel Term(菲涅尔反射系数)
-
Geometry Term(遮蔽和遮挡)
-
NDF(Normal Distribution Function)(微表面分布)
为了加速这些计算,Unity 使用了一张预生成的 2D 查找表纹理 —— unity_BRDFLut
(又名 UnityStandardBRDF
)。
inout
的含义:
在 HLSL/Cg 中,inout
是一种参数修饰符,表示这个参数是“输入 + 输出”:
-
输入(in):调用函数时传入的值可以被读取;
-
输出(out):函数内部可以修改它的值,修改后的结果在函数外也能访问;
-
inout
综合两者,即:传进去,改完还能带回来。
在 Surface Shader 中,这用于对 SurfaceOutputStandard o
进行材质计算,让你在 surf()
函数里修改 Albedo
、Smoothness
、Alpha
等字段。
Input
是什么、它来自哪里?
它并不是像 appdata
一样来自 Unity 的 cginc
文件,而是 Unity 在编译 Surface Shader 时 动态“合成”的一个中间结构,用于将 Unity 自动插入的顶点插值变量传递给你的 surf()
函数。
📦 它的行为可以总结为:
-
你在
Input
结构体中声明什么字段; -
Unity 就会根据你写的字段,自动在 vertex → fragment 插值中生成对应代码;
-
不声明就不会有,声明了就会自动插值并传入
surf()
函数。
struct Input {
float2 uv_MainTex; // 会插入 TEXCOORD0
float3 worldPos; // 会插入 worldPos 并计算好
float3 viewDir; // 会插入 view direction
};
这个 Input
能不能自己声明并扩展?
✔ 可以修改、重命名、扩展字段,但:
-
名字必须叫
Input
(除非你通过#pragma surface surf Standard input:MyInputStruct
修改绑定); -
字段名称最好用 Unity 支持的特定字段,如
uv_MainTex
,worldPos
,否则不会自动插值。
它和 appdata
有什么区别?
项目 | appdata | Input |
---|---|---|
阶段 | 顶点着色器输入 | 片元阶段 surf() 输入 |
位置 | 须使用 Unity 定义的 appdata_full 等结构 | 可自己定义成员(受限) |
内容 | 顶点属性,如 POSITION , NORMAL , TEXCOORD0 | 插值后的结果,如 worldPos , uv_MainTex |
HLSL 支持的参数修饰符有:
修饰符 | 含义 | 默认 | 常用场合 |
---|---|---|---|
in | 只读传入 | 默认值 | 常用于输入数据,如 position |
out | 写出传值 | 否 | 多输出目标 |
inout | 引用读写 | 否 | 如 surf() 中的 o 参数 |
为什么另外的surface shader部分和outline pass都没有执行出来,这不是同一个shader里多个pass的执行先后顺序的问题,是renderqueue里,opaque 下前的物体shader在前
v2f vert(appdata_base v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex); // <- 这是拷贝值
...
return o;
}
v 是结构体 appdata_base 的一个拷贝副本
即便你在函数中改变了 v.vertex、v.normal,也不会影响模型顶点本身
GPU 在每个顶点执行一次这个 vert 函数时,都会分发副本数据给它处理
-
in
:只读引用(常规情况) -
out
:只写引用(函数返回该值) -
inout
:可读可写引用(传引用并修改)
surf()这种函数相当于自己的二次定义,所以实际上如果去掉前面的inout,就和appdata一样是一份值拷贝了
void surf(Input IN, SurfaceOutputStandard o)
那这个 o 变量就不再是引用,而是一份拷贝。结果是:
你对 o 所做的任何赋值和修改,都不会返回给 Unity 内部光照流程使用
不会影响最终的表面渲染结果,等于“白写”
这就和你在 vert(appdata v) 中拿到的是输入数据副本是一个道理。
float toon = floor(difLight * _Steps) / _Steps;
difLight = lerp(difLight, toon, _ToonEffect);
floor(difLight * _Steps) / _Steps 就是把连续的光照值 离散化(比如只分 3 阶灰度),这就是 toon 的精髓。
_ToonEffect 控制了“真实光照”和“toon 化光照”的权重插值,越接近 1 就越卡通。
Standard
使用 Unity Standard PBR 光照模型(Physically Based Rendering)。它和 Lambert
, BlinnPhong
等传统模型不同,支持:
-
Metallic / Smoothness 工作流
-
Unity 内置的环境光照、反射探针、BRDF Lookup
-
更真实的光照表现
noshadow
阻止 Unity 生成 ShadowCaster Pass。也就是说:
-
该物体 不会投射阴影
-
适用于一些特殊材质(如透明、效果 Shader)
✅ addshadow
启用阴影接收支持,即使光照模型不是 Unity 默认支持的(如 Unlit 自定义光照函数):
-
即使是
CustomLighting
模式也能接收阴影 -
自动引入
UNITY_LIGHT_ATTENUATION
宏等逻辑
这在你使用 LightingXxx
自定义函数时尤为重要。
Unity 的 Surface Shader 会“自动生成”一组 Pass,包括:
-
ForwardBase Pass
-
ForwardAdd Pass(用于额外光源)
-
ShadowCaster Pass(用于投射阴影) ← 默认自动生成
-
Meta Pass(用于烘焙 GI / Lightmap)
-
Deferred Pass(如果支持)
-
Shadow Collector / DepthOnly(如果需要)
这全部是由 Surface Shader 的编译器生成的,你只需要写 #pragma surface ...
,Unity 会自动编译出这些 pass。
Unity 会根据 Pass 上的 Tags { "LightMode" = "Xxx" }
来决定每个用途该调用哪一个 Pass。
比如:
LightMode | 用途 |
---|---|
ShadowCaster | 投射阴影时调用 |
ForwardBase | 渲染主光源 + 环境光 |
ForwardAdd | 渲染附加光源(加亮 Blend) |
Meta | 用于 Lightmap 烘焙 |
只要你写的 Pass 有正确的 LightMode
,Unity 就会使用它,而不使用自动生成的(或跳过生成)。
为什么花草 Shader 常常用 noshadow
?
1. 避免产生“不自然的投影块”
花草材质大多是用透明裁剪(AlphaTest)+ 单张贴图来模拟许多叶片或花瓣,但它们本身是一个扁平的面。
如果它投射了阴影(默认会生成 ShadowCaster Pass
):
-
投射的阴影就是 这个面的一整块阴影。
-
即使你已经做了
clip()
或alphatest:_Cutoff
,阴影通常仍然是硬边矩形块,而不是贴图上花瓣的形状。 -
整个草坪会变得“不透气”、阴影非常“实”。
➡️ 所以很多项目会禁用花草投射阴影,让它们更通透自然。
如果用了 alpha:blend
,强烈建议也写 RenderType="Transparent"
,否则 Unity 可能会在错误的渲染队列中处理该对象,导致 ZTest 错误或后处理丢失。
❌ 问题本质:
alpha:blend
自动设置了 ZWrite Off
(关闭写入深度),这意味着:
虽然你加了
addshadow
,Unity 编译时仍不会生成 ShadowCaster Pass,或者生成了也不会工作,因为透明物体默认是不会参与阴影投射的。
clip()
本身不依赖 Tags
clip 本身不是赋值给谁,而是直接控制当前像素是否被丢弃,进而影响 Unity 是否生成阴影。
Tags
会影响 Unity 怎么处理这个 Shader 的不同 Pass,间接影响阴影是否生效。
Tags 作用 | 是否影响阴影 |
---|---|
"RenderType"="Transparent" | ✅ 可能让 Unity 忽略生成 ShadowCaster |
"RenderType"="TransparentCutout" | ✅ Unity 认为这个 Shader 支持剪切透明,因此会自动生成 ShadowCaster |
FallBack "Diffuse" 的作用:补充了一个默认不透明的 ShadowCaster
-
Diffuse
是 Unity 内置的一个旧式不透明 Shader -
它默认携带了一个 ShadowCaster Pass,它用当前物体的网格来生成一个简单投影(通常是一个完整的几何阴影方块)
-
所以你看到的是 fallback 提供的阴影,而不是你的主 shader 生成的阴影
手动添加 ShadowCaster Pass(适合你坚持用 alpha:blend
)
你可以在 SubShader
外手动添加:
Pass
{
Name "ShadowCaster"
Tags { "LightMode" = "ShadowCaster" }
ZWrite On
ZTest LEqual
Cull Off
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct v2f {
float4 pos : SV_POSITION;
};
v2f vert(appdata_base v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
return o;
}
float frag(v2f i) : SV_Target
{
return 0;
}
ENDCG
}
塞一个“实体轮廓”的阴影投射。
不过要注意,这会让透明部分也投影(不真实)
ShadowCaster Pass 的 fragment 函数根本不管你返回什么
在 Unity 的 ShadowCaster Pass 里:
最终决定是否投射阴影的,是你有没有写入深度值,而不是你
return
什么。
-
ShadowCaster Pass 是带有
LightMode = "ShadowCaster"
的 Pass。 -
它的 fragment shader 通常返回的是 float 类型(不是颜色)。
-
但这个返回值并不会写入屏幕颜色缓冲(RenderTarget),而是被忽略掉。
-
真正关键的是该像素是否被 discard(或者 clip 掉),以及是否写入深度。
-
clip(...)
决定了某个 fragment 是否写入 ShadowMap -
被 clip 掉的 fragment 就像没存在一样 → 不参与遮挡光线
-
没被 clip 掉的 → 写入 depth → 会在阴影中投射
为什么必须写INTERNAL_DATA?
因为 Unity 的 Surface Shader 编译器(不是我们写的 fragment shader)会依赖于 Input
结构中是否包含 INTERNAL_DATA
来:
-
决定是否生成对阴影、环境光、反射探针等的处理支持
-
能否正常使用
TRANSFER_SHADOW
、SHADOW_COORDS
等宏
如果你用了光照模型(如 Standard
、Lambert
),并想使用 Unity 的完整光照(阴影、实时光照、环境球),你必须声明 INTERNAL_DATA
,否则 Unity 编译器就无法给你注入正确的数据流。
struct Input
{
float2 uv_MainTex;
float3 worldNormal;
INTERNAL_DATA
};
void surf(Input IN, inout SurfaceOutputStandard o)
{
fixed4 c = tex2D(_MainTex, IN.uv_MainTex);
o.Albedo = c.rgb;
o.Alpha = c.a;
}
这段代码在 Unity 编译 Surface Shader 时,INTERNAL_DATA 会让 Input 带上 _ShadowCoord、worldPos 等字段(可能是 TEXCOORD2~TEXCOORD6),从而在后续光照阶段启用阴影和 GI。
这个宏做的事是:
传输从当前顶点到光源的 shadow map 坐标
在你的
Input
结构体中插入一个float4 _ShadowCoord
,用TEXCOORD1
通道传输。
fixed3 wNormal = WorldNormalVector(IN, normalTex);
切线空间中的法线(normal map 提供的)转换成世界空间法线(world space normal)
IN
-
是你的
Input
结构体的实例(传进来的插值数据)。 -
它必须包含以下字段,供
WorldNormalVector
使用:-
worldNormal
:表面原始法线(Surface Shader 自动插入) -
tangent
(可选):如果你用切线空间 normal map
-
float3 worldN;
worldN.x = dot(_unity_tbn_0, o.Normal);
worldN.y = dot(_unity_tbn_1, o.Normal);
worldN.z = dot(_unity_tbn_2, o.Normal);
worldN = normalize(worldN);
o.Normal = worldN;
它们是 Unity 自动传给你的 TBN 矩阵行向量(通常是 float3):
名字 | 含义 |
---|---|
_unity_tbn_0 | Tangent 方向(T) |
_unity_tbn_1 | Bitangent(B) |
_unity_tbn_2 | Normal(N) |
TBN 构成了从切线空间到世界空间的变换矩阵。
你传入的 o.Normal
是在切线空间中采样出的法线(例如从 normal map 得到的 UnpackNormal()
值),例如 (0, 0, 1)
为“垂直贴图面”的默认方向。
Baked Indirect
-
直射光: 实时计算(只针对动态物体)
-
间接光: 来自 Lightmap
-
阴影: 只有静态物体间的烘焙阴影,动态物体无法投影阴影也不会接收 Shadowmask 中的阴影
-
✅ 用于性能优先、对阴影不敏感的场景
折射
v2f vert(appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldViewDir = UnityWorldSpaceViewDir(o.worldPos);
o.worldRefr = refract(-normalize(o.worldViewDir), normalize(o.worldNormal), _RefracRotio);
UNITY_TRANSFER_FOG(o,o.vertex);
return o;
}
菲涅尔反射描述的是:光在穿过两种介质交界面时,反射与折射的能量比例会根据视角(入射角)变化。
它既不是单独的“反射”,也不是“折射”,而是决定“反射多少、折射多少”的规则。