Unity Shader内置变量全解析与应用指南
1. Unity Shader 内置变量全景解析
在Unity引擎的渲染管线中,Shader内置变量就像一座隐藏的宝库。这些由Unity自动提供的全局参数,涵盖了从时间戳到摄像机位置、从光照信息到屏幕尺寸等关键数据。掌握它们意味着你可以在不依赖脚本通信的情况下,直接访问渲染所需的核心环境参数。
我曾在多个项目中因为不熟悉内置变量而走了弯路——比如手动传递摄像机位置导致性能瓶颈,或是重复计算屏幕UV坐标造成精度损失。实际上Unity已经为我们封装好了这些常用数据,关键在于知道在什么时机调用哪些变量。
2. 空间变换类内置变量详解
2.1 模型与视图矩阵组
Unity提供了完整的矩阵变换链,这是Shader编程的基础设施:
// 模型空间到世界空间变换矩阵 float4x4 unity_ObjectToWorld; // 世界空间到模型空间逆矩阵 float4x4 unity_WorldToObject; // 摄像机视图矩阵(世界→视图空间) float4x4 UNITY_MATRIX_V; // 投影矩阵(视图→裁剪空间) float4x4 UNITY_MATRIX_P;实际应用案例:当需要实现世界空间下的顶点偏移时,正确的计算顺序应该是:
- 在顶点着色器中获取模型空间坐标
- 通过unity_ObjectToWorld转换到世界空间
- 进行世界空间运算后输出
重要提示:在URP/HDRP中矩阵命名可能不同,例如使用GetVertexPositionInputs()获取变换矩阵更可靠
2.2 复合矩阵与特殊变体
除了基础矩阵,这些组合矩阵能极大简化复杂计算:
// MVP矩阵的乘积版本 float4x4 UNITY_MATRIX_MVP; // 已废弃 float4x4 UNITY_MATRIX_VP; // 视图+投影 // 用于法线变换的逆转置矩阵 float3x3 unity_WorldToObject_IT;在表面着色器中处理法线时,必须使用逆转置矩阵保证缩放变形后的正确光照计算:
float3 worldNormal = mul(unity_WorldToObject_IT, v.normal);3. 时间与动画控制变量
3.1 基础时间参数
这些变量让Shader具备时间感知能力:
// 自场景加载后的时间(秒) float _Time; // 时间缩放版本(受Time.timeScale影响) float _SinTime; // sin(_Time) float _CosTime; // cos(_Time) // 平滑增量时间(适合UV动画) float _DeltaTime;典型应用:实现周期性脉冲效果
float pulse = sin(_Time.y * 3.0) * 0.5 + 0.5; o.Emission = _GlowColor * pulse;3.2 高级时间控制
对于需要精确时序的场景:
// 不受时间缩放影响的实时时间 float _UnscaledTime; // 编辑器模式下正确的动画时间 float _EditorTime;VR项目中常见问题:当游戏暂停时,某些特效应该继续播放。此时就需要切换使用_UnscaledTime而非默认的_Time。
4. 光照与渲染环境变量
4.1 前向渲染光源数据
在ForwardBase Pass中可访问的主光源参数:
// 主光源颜色和方向(已考虑强度) float4 _WorldSpaceLightPos0; float4 _LightColor0; // 环境光照和反射探针数据 float4 unity_AmbientSky; float4 unity_SHAr; // 球谐光照系数实测技巧:在移动端应优先使用球谐光照而非实时光源:
half3 ambient = ShadeSH9(float4(worldNormal,1));4.2 阴影与光照贴图
烘焙光照相关参数:
// 光照贴图UV坐标 float4 unity_LightmapST; // 动态物体光照探针强度 float unity_DynamicLightmapST;混合实时与烘焙光照的经典方案:
#ifdef LIGHTMAP_ON color *= DecodeLightmap(UNITY_SAMPLE_TEX2D(unity_Lightmap, uv)); #endif5. 屏幕与后期处理参数
5.1 屏幕空间数据
实现屏幕特效必备的变量组:
// 屏幕像素尺寸(1/Width, 1/Height) float4 _ScreenParams; // 裁剪空间到屏幕空间的转换系数 float4 _ZBufferParams;边缘检测Shader示例:
float2 pixelSize = 1.0 / _ScreenParams.xy; float depth = LinearEyeDepth(SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, uv));5.2 后期处理交互
与Post Processing Stack协同工作的参数:
// 色彩空间转换系数 float3 unity_ColorSpaceLuminance; // 雾效参数组 float4 unity_FogParams;HDR颜色处理规范做法:
#if defined(UNITY_COLORSPACE_GAMMA) color = LinearToGammaSpace(color); #endif6. 平台兼容性处理方案
6.1 图形API差异处理
不同渲染后端的关键差异:
// 纹理采样坐标修正 float4 _ProjectionParams; // x=1/-1 (翻转投影) float4 _ScreenParams; // yz=屏幕尺寸 // Vulkan/Metal需要特殊处理 #if defined(SHADER_API_VULKAN) // 特定平台代码 #endif6.2 多管线适配策略
URP/HDRP中的替代方案:
// 在SRP中获取主光源的正确方式 Light mainLight = GetMainLight(); // 摄像机位置获取 float3 worldSpaceCameraPos = _WorldSpaceCameraPos;迁移建议:使用ShaderLibrary核心函数而非直接访问内置变量
7. 实战问题排查指南
7.1 常见变量失效场景
矩阵相乘顺序错误:
// 错误做法(OpenGL与DX结果不同) float4 pos = mul(UNITY_MATRIX_MVP, v.vertex); // 正确做法(使用UnityObjectToClipPos) float4 pos = UnityObjectToClipPos(v.vertex);时间变量跳动问题: 在Edit→Project Settings→Time中调整Maximum Delta Time
7.2 调试输出技巧
在Shader中打印变量值:
// 将世界坐标映射为可视化颜色 o.Albedo = frac(worldPos * 0.1);使用Frame Debugger逐帧检查变量传递:
- Window→Analysis→Frame Debugger
- 选择对应DrawCall查看输入参数
8. 性能优化专项
8.1 变量访问成本分析
通过Shader Inspector查看生成的GLSL代码:
- unity_ObjectToWorld → 通常直接替换为MATRIX_M
- _Time.y → 可能被优化为通用时间寄存器
8.2 移动端特别处理
避免在片段着色器中进行矩阵运算:
// 顶点着色器计算并传递 v2f vert(appdata v) { v2f o; o.worldPos = mul(unity_ObjectToWorld, v.vertex); return o; }在片元着色器中直接使用插值后的worldPos,而非重复计算
9. 版本迁移适配方案
9.1 Unity 5.x到2018.x变化
- unity_Scale已移除(现在缩放包含在unity_ObjectToWorld中)
- _LightMatrix0需要替换为新的光照系统API
9.2 Built-in到URP迁移
| 旧变量 | 新方案 |
|---|---|
| _Time | _TimeParameters.x |
| _LightColor0 | GetMainLight().color |
| FOG_COORD | 使用URP的Fog.hlsl |
10. 扩展应用案例库
10.1 高级溶解效果
结合时间与屏幕参数:
float dissolve = tex2D(_NoiseTex, uv).r; dissolve += _Time.y * _Speed; clip(dissolve - _Threshold);10.2 程序化水面
使用多重时间变量制造复杂波动:
float wave1 = sin(_Time.y * 0.5 + worldPos.x); float wave2 = cos(_UnscaledTime * 1.2 + worldPos.z); float height = (wave1 + wave2) * 0.1;在Shader开发中,我逐渐养成了查阅UnityCG.cginc源码的习惯——这是理解内置变量最权威的方式。比如最新的Unity版本中,许多传统变量已被包装成更安全的函数形式(如UnityObjectToWorldDir),这既保证了兼容性又提升了类型安全。