自由学习记录(61)
使用了 #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 200CGPROGRAM// 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 assumeuniformscalingUNITY_INSTANCING_BUFFER_START(Props)// put more per-instance properties hereUNITY_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 colorfixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;o.Albedo = c.rgb;// Metallic and smoothness come from slider variableso.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 OnZTest LEqualCull OffCGPROGRAM#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;}
菲涅尔反射描述的是:光在穿过两种介质交界面时,反射与折射的能量比例会根据视角(入射角)变化。
它既不是单独的“反射”,也不是“折射”,而是决定“反射多少、折射多少”的规则。
相关文章:
自由学习记录(61)
使用了 #pragma multi_compile_fwdbase 这条编译指令启用了 Unity 内部用于主光源阴影支持的一组关键词变体,如: SHADOWS_SCREEN(屏幕空间阴影贴图) SHADOWS_DEPTH(深度图阴影) SHADOWS_SOFT(…...
深入了解linux系统—— 基础IO(下)
前言 在基础IO(上)中,我们了解了文件相关的系统调用;以及文件描述符是什么,和操作系统是如何将被打开的文件管理起来的。 本篇文章来继续学习文件相关的知识 重定向 在了解重定向之前,我们先来看这样的…...
Flink Table SQL
Apache Flink 提供了强大的 Table API 和 SQL 接口,用于统一处理批数据和流数据。它们为开发者提供了类 SQL 的编程方式,简化了复杂的数据处理逻辑,并支持与外部系统集成。 🧩 一、Flink Table & SQL 核心概念 概念描述Table…...
【Git】基本操作
【简介】 Git是一种“版本控制器”, 可以用于记录每次的修改以及版本的迭代 其可以控制电脑上所有格式的文件,方便地查看文件的每个小修改版本都修改了什么内容,但前提条件是被管理的文件需要放在对应的git仓库(又名“版本库”&…...
【八股战神篇】MySQL高频面试题
目录 专栏简介 一 什么是索引 延伸 1 索引的底层使用的是什么数据结构? 2 MySQL 索引分类有哪些? 3 什么字段适合创建索引? 4 索引失效的场景 5 什么是最左匹配原则? 二 为什么 InnoDB 存储引擎选用 B 树而不是 B 树呢&a…...
服务器防文件上传手写waf
一、waf的目录结构,根据自己目录情况进行修改 二、创建文件夹以及文件 sudo mkdir -p /www/server/waf-monitor sudo mkdir -p /www/server/waf-monitor/quarantine #创建文件夹 chmod 755 /www/server/waf-monitor #赋权cd /www/server/waf-monitor/touch waf-m…...
ElasticSearch-集群
本篇文章依据ElasticSearch权威指南进行实操和记录 1,空集群 即不包含任何节点的集群 集群大多数分为两类,主节点和数据节点 主节点 职责:主节点负责管理集群的状态,例如分配分片、添加和删除节点、监控节点故障等。它们不直接…...
Android开发——原生渲染方案实现 PDF 预览功能
Android开发——原生渲染方案实现 PDF 预览功能 1. 引言2. 原生渲染方案核心设计:从数据到视图3. 混合文档容器:ViewPager2 与适配器设计1. 引言 在移动应用开发中,PDF 预览是文档处理场景的核心需求之一。Android 生态提供了多元化的技术方案,从系统级简版预览到原生渲染…...
Java求职者面试:从Spring Boot到微服务的技术点解析
Java求职者面试:从Spring Boot到微服务的技术点解析 场景:互联网医疗-预约挂号系统 面试官: “小明,我们今天的场景是一个互联网医疗的预约挂号系统。我们需要支持高并发的用户预约操作,同时保证数据一致性和系统的高…...
操作系统听课笔记之进程的概念
引入新的概念,为什么不能叫程序 内存中进程Image实例: stack: 局部变量(函数弹出来没有了) data: 全局变量(共享) 静态变量 heap: malloc分配的内存 从数据结构和算法角度解决问题: 设计相应的数据结构和设计算法 数据结构: 进程PCB 算法:创建进程和进程通信各种操作在线内…...
【基于Spring Boot 的图书购买系统】深度讲解 用户注册的前后端交互,Mapper操作MySQL数据库进行用户持久化
引言 在现代Web应用中,用户注册功能是用户与应用交互的入口。一个高效、安全且用户友好的注册系统不仅能吸引用户,还能为后续功能(如个性化服务)奠定基础。本博客将通过一个实际案例,展示如何使用HTML、JavaScript、j…...
Spark,连接MySQL数据库,添加数据,读取数据
以下是使用 Spark/SparkSQL 连接 MySQL 数据库、添加数据和读取数据的完整示例(需提前准备 MySQL 驱动包): 一、环境准备 1. 下载 MySQL 驱动 - 下载 mysql-connector-java-8.0.33.jar (或对应版本),放…...
ubuntu的虚拟机上的网络图标没有了
非正常的关机导致虚拟机连接xshell连接不上,ping也ping不通。网络的图标也没有了。 记录一下解决步骤 1、重启服务 sudo systemctl restart NetworkManager 2、图标显示 sudo nmcli network off sudo nmcli network on 3、sudo dhclient ens33 //(网卡) …...
Linux系统:ext2文件系统的核心概念和结构
本节重点 块、块组、分区的引入块组的构成inode与inode Table路径解析与路径缓存机制目录与文件名在文件系统中的存储分区的初始化与挂载 一、ext2文件系统 1.1 “块”的引入 在前言部分我们说扇区是磁盘硬件的最小读写单位,通常为512字节,但是在操作…...
Python 装饰器详解
装饰器是 Python 中一种强大的语法特性,它允许在不修改原函数代码的情况下动态地扩展函数的功能。装饰器本质上是一个高阶函数,它接受一个函数作为参数并返回一个新的函数。 基本装饰器 1. 简单装饰器示例 def my_decorator(func):def wrapper():prin…...
Docker配置容器开机自启或服务重启后自启
要将一个 Docker 容器设置为开机自启,你可以使用 docker update 命令或配置 Docker 服务来实现。以下是两种常见的方法: 方法 1:使用 docker update 设置容器自动重启 使用 docker update 设置容器为开机自启 你可以使用以下命令,…...
20250518 黎曼在三维空间中总结的一维二维的规律,推广到高维度合适吗?有没有人提出反对意见
黎曼在三维空间中总结的一维二维的规律,推广到高维度合适吗?有没有人提出反对意见 黎曼几何在数学物理中的广泛应用,尤其是在广义相对论和高维空间理论中,确实是建立在黎曼在三维空间中的推广基础上的。不过,这种推广…...
使用AI 生成PPT 最佳实践方案对比
文章大纲 一、专业AI生成工具(推荐新手)**1. 推荐工具详解****2. 操作流程优化****3. 优势与局限**二、代码生成方案(开发者推荐)**1. Python-pptx进阶用法****2. GitHub推荐**三、混合工作流(平衡效率与定制)**1. 工具链升级****2. 示例Markdown结构**四、网页转换方案(…...
【Docker】Docker Compose方式搭建分布式协调服务(Zookeeper)集群
开发分布式应用时,往往需要高度可靠的分布式协调,Apache ZooKeeper 致力于开发和维护开源服务器,以实现高度可靠的分布式协调。具体内容见zookeeper官网。现代应用往往使用云原生技术进行搭建,如何用Docker搭建Zookeeper集群,这里介绍使用Docker Compose方式搭建分布…...
R for Data Science(3)
R for Data Science以下是关于网页内容的详细笔记: 1. 章节概览 章节主题:数据转换(Data Transformation)核心内容:介绍如何使用 R 中的 dplyr 包进行数据转换,包括对数据框的行、列和组的操作࿰…...
深入浅出Hadoop:大数据时代的“瑞士军刀”
深入浅出Hadoop:大数据时代的“瑞士军刀” 在当今这个数据爆炸的时代,每天产生的数据量已经远超人类的想象。从社交媒体的互动到电商平台的交易记录,从物联网设备的实时监控到科学研究的实验数据,大数据已经成为推动各行各业变革…...
《Python星球日记》 第94天:走近自动化训练平台
名人说:路漫漫其修远兮,吾将上下而求索。—— 屈原《离骚》 创作者:Code_流苏(CSDN)(一个喜欢古诗词和编程的Coder😊) 目录 一、自动化训练平台简介1. Kubeflow Pipelines2. TensorFlow Extended (TFX) 二…...
MetaMask安装及使用-使用水龙头获取测试币的坑?
常见的异常有: 1.unable to request drip, please try again later. 2.You must hold at least 1 LINK on Ethereum Mainnet to request native tokens. 3.The address provided does not have sufficient historical activity or balance on the Ethereum Mainne…...
软件架构之--论微服务的开发方法1
论微服务的开发方法1 摘要 2023年 2月,本人所在集团公司承接了长三角地区某省渔船图纸电子化审查系统项目开发,该项目旨在为长三角地区渔船建造设计院、以及渔船图纸审查机构提供一个便捷的渔船图纸电子化审查服务平台。在此项目中,我作为项目组成员参与项目的建设工作,并…...
SOLID 面对象设计的五大基本原则
SOLID 原则的价值 原则核心价值解决的问题SRP职责分离,提高内聚性代码臃肿、牵一发而动全身OCP通过扩展而非修改实现变化频繁修改现有代码导致的风险LSP确保子类行为的一致性继承滥用导致的系统不稳定ISP定制化接口,避免依赖冗余接口过大导致的实现负担…...
游戏引擎学习第293天:移动Familiars
回顾并为今天的内容定下基调 我们正在做一款完整的游戏,今天的重点是“移动模式”的正式化处理。目前虽然移动机制大致能运作,但写法相对粗糙,不够严谨,我们希望将其清理得更规范,更可靠一点。 目前脑逻辑࿰…...
《沙尘暴》观影记:当家庭成为人性的修罗场
起初点开《沙尘暴》,不过是想在碎片时间里寻个消遣,毕竟短剧的篇幅显得轻松无负担。未曾想,这看似简短的故事却如一场裹挟着砂砾的风暴,在心底掀起层层涟漪,让我忍不住在家庭教育、人性幽微处反复踱步沉思。 一、风暴眼…...
牛客网NC21989:牛牛学取余
牛客网NC21989:牛牛学取余 📝 题目描述 ⏱️ 限制条件 时间限制:C/C/Rust/Pascal 1秒,其他语言2秒空间限制:C/C/Rust/Pascal 32 M,其他语言64 M输入范围:两个整数,在int范围内 📥…...
王者荣耀游戏测试场景题
如何测试一个新英雄:方法论与实践维度 测试一个新英雄不仅仅是“打打打”,而是一套完整的测试流程,包括设计文档验证、功能验证、数值验证、性能验证、交互验证等。可以从以下多个角度展开: 🔍 1. 方法论维度 ✅ 测试…...
Spring Boot 与 RabbitMQ 的深度集成实践(二)
集成步骤详解 配置 RabbitMQ 连接信息 在 Spring Boot 项目中,通常在application.properties或application.yml文件中配置 RabbitMQ 的连接信息。以application.yml为例,配置如下: spring: rabbitmq: host: localhost port: 5672 usern…...
医疗信息系统安全防护体系的深度构建与理论实践融合
一、医疗数据访问系统的安全挑战与理论基础 1.1 系统架构安全需求分析 在医疗信息系统中,基于身份标识的信息查询功能通常采用分层架构设计,包括表现层、应用层和数据层。根据ISO/IEC 27001信息安全管理体系要求,此类系统需满足数据保密性…...
多模态大语言模型arxiv论文略读(八十)
## MMWorld: Towards Multi-discipline Multi-faceted World Model Evaluation in Videos ➡️ 论文标题:MMWorld: Towards Multi-discipline Multi-faceted World Model Evaluation in Videos ➡️ 论文作者:Xuehai He, Weixi Feng, Kaizhi Zheng, Yuji…...
FFmpeg:多媒体处理的终极利器
FFmpeg详细介绍 1. 定义与基本概述 FFmpeg是一套开源的跨平台多媒体处理工具集,最初由法国程序员Fabrice Bellard于2000年开发,其名称源自“Fast Forward MPEG”,体现了其高效处理MPEG格式的能力。它不仅是命令行工具,还包含多个库和开发套件,支持视频转码、剪辑、合并、…...
【Leetcode】取余/2的幂次方
给定一个非负整数 num,反复将各个位上的数字相加,直到结果为一位数。返回这个结果。 示例 1: 输入: num 38 输出: 2 解释: 各位相加的过程为: 38 --> 3 8 --> 11 11 --> 1 1 --> 2 由于 2 是一位数,所以返回 2。 …...
程序代码篇---ESP32的数据采集
文章目录 前言 前言 本文简单介绍了ESP32可以怎样采集数据。...
系统架构设计(十三):虚拟机体系结构风格
概念 虚拟机(Virtual Machine)体系结构风格,是指将整个系统抽象为一台“虚拟机”,通过解释或模拟的方式运行应用程序。 它本质上提供了一种“平台中立”的运行环境,典型代表就是 Java 虚拟机(JVM…...
lvs-dr部署
实验准备: 准备4台设备,1台作为客户机,3台作为服务器,服务器中1台作为调度器,2台作为后端真实访问服务器。并关闭所有防火墙与核心防护。 systemctl stop firewalld setenforce 0 实验开始 调度器配置 yum -y ins…...
数据库blog2_数据结构与效率
🌿计算机中的数据————存储结构与逻辑结构 🍂存储结构(物理结构) 定义:存储结构是指数据在计算机存储器中的实际存储方式,由计算机硬件特性决定。它涉及到数据的物理位置和存储顺序。存储结构直接影响数…...
聊天室项目总结
已实现的功能点: 存在的问题: 1.没有实现有含金量的创新功能点 2.太过于依赖工具,不喜欢自己看文章总结对知其然而不知其所以然,自己的理解比较少,懒于去思考 3.太过于依赖他人,自己的想法有点少&#x…...
数据结构:二叉树一文详解
数据结构:二叉树一文详解 前言一、二叉树的基本概念与结构特性1.1 二叉树的定义1.2 二叉树的特殊类型1.3 二叉树的性质 二、二叉树的遍历方式2.1 前序遍历(Pre-order Traversal)2.2 中序遍历(In-order Traversal)2.3 后序遍历&…...
2025年- H28-Lc136- 24.两两交换链表中的节点(链表)---java版
1.题目描述 2.思路 cur指针要先放在虚拟头节点,才能去操作第一个数和第二个数 先判断偶数个节点,再判断奇数个节点,否则会犯空指针异常。 (1)如果节点是偶数个节点,只要满足curr.nextnull,就说…...
ubuntu18.04通过cuda_11.3_xxx.run安装失败,电脑黑屏解决办法
项目场景: ubuntu18.04跑DG-SLAM相关代码,安装lietorch包报错,需要用到GPU。 问题描述 跑代码需要cuda11.3,系统里面有另外一个版本,运行cuda_11.3_xxx.run,同时也选择了driver,安装成功后&am…...
Linux之基础IO
目录 一、理解 "文件" 1.1、狭义理解 1.2、广义理解 1.3、文件操作的归类认知 1.4、系统角度 二、回顾C语言接口 2.1、打开文件 2.2、写文件 2.3、读文件 2.4、stdin & stdout & stderr 2.6、打开文件的方式 三、系统文件I/O 3.1、一种传递标志…...
上位机知识篇---涂鸦智能云平台
文章目录 前言 前言 本文简单介绍了涂鸦智能云平台。...
InfluxDB 3 Core + Java 11 + Spring Boot:打造高效物联网数据平台
一、 引言:为什么选择InfluxDB 3? 项目背景: 在我们的隧道风机监控系统中,实时数据的采集、存储和高效查询是至关重要的核心需求。风机运行产生的振动、倾角、电流、温度等参数是典型的时序数据,具有高并发写入、数据…...
Kubernetes控制平面组件:Kubelet详解(七):容器网络接口 CNI
云原生学习路线导航页(持续更新中) kubernetes学习系列快捷链接 Kubernetes架构原则和对象设计(一)Kubernetes架构原则和对象设计(二)Kubernetes架构原则和对象设计(三)Kubernetes控…...
Pandas 构建并评价聚类模型② 第六章
构建并评价聚类模型 构建并评价聚类模型一、数据读取与准备(代码6 - 6部分)结果代码解析 二、Kmeans聚类(代码6 - 6部分)结果代码解析 三、数据降维可视化(代码6 - 6部分)结果代码解析 四、FMI评价…...
【simulink】IEEE33节点系统潮流分析模型
目录 主要内容 程序内容 2.1 33节点simulink模型一览 2.2 节点模型图 下载链接 主要内容 该仿真采用simulink模型对33节点网络进行模拟仿真,在simulink模型中定义了33节点系统的电阻、电抗、节点连接关系等参数,通过控制块来实现信号连接关系&…...
彻底解决docker代理配置与无法拉取镜像问题
为什么会有这篇文章? 博主在去年为部署dify研究了docker,最后也是成功部署,但是因为众所周知的原因,卡ziji脖子 ,所以期间遇到各种网络问题的报错,好在最后解决了. 但时隔一年,博主最近因为学习原因又一次使用docker,原本解决的问题却又没来由的出现,且和之前有很多不同(有时…...
Linux 安装 Unreal Engine
需要对在unreal engine官网进行绑定github账号,然后到unreal engine github仓库中进行下载对应的版本,并进行安装unreal engine官网 github地址...