
1. 项目概述为什么要在URP里自己写反射探针Shader在Unity的通用渲染管线URP里做项目尤其是涉及到金属、玻璃、水面这类高光材质时反射探针是绕不开的核心技术。Unity内置的Lit Shader虽然功能强大但当你需要一些定制化的效果比如让反射带上特定色调、根据距离混合不同探针、或者实现一些非物理的“风格化”反射时内置方案就显得捉襟见肘了。这时候自己动手写一个能正确采样和使用反射探针的Shader就成了进阶开发的必备技能。我最近就在一个风格化科幻项目里遇到了这个问题场景中有大量动态变化的霓虹灯和广告牌需要物体表面的反射能实时、平滑地融合多个探针的捕捉结果并且反射强度要能受角色距离影响。Unity URP内置的URP/LitShader虽然支持反射探针但其混合逻辑是黑盒无法满足这种精细控制。于是我不得不从头梳理URP下反射探针的采样、解码和应用流程踩了不少坑也总结了一套行之有效的方案。这篇文章我就把手把手带你写一个能在URP里正常工作的反射探针Shader。我会从最基础的立方体贴图采样讲起涵盖URP特有的SHADERGRAPH_SAMPLE_SCENE宏、反射向量的计算修正、以及如何与URP的PBR光照模型整合。最后我会附上完整的、可运行的HLSL代码并重点分享几个我实际开发中遇到的“坑”和解决方案比如在移动端上采样性能的权衡、Box Projection的正确启用以及如何避免因坐标系差异导致的反射错位。无论你是想深入学习URP的渲染机制还是急需一个可定制的反射Shader来解决项目难题这篇文章都能给你提供清晰的路径和可复用的代码。2. 核心原理与URP管线适配解析2.1 反射探针的本质从立方体贴图到着色器采样反射探针的核心产出是一张立方体贴图。你可以把它想象成一个放在场景特定位置的、拥有六个方向上下左右前后摄像头的特殊摄像机阵列。这个“摄像机”会捕捉它周围360度的环境景象并将这六个面的图像打包成一张CubeMap。当我们的Shader需要计算某个像素的反射颜色时它不会真的去“看”场景而是根据该像素的反射向量去这张预先烘焙好的CubeMap里查找对应的颜色。这个过程在Shader里主要分三步计算反射向量根据表面法线Normal和观察方向View Direction利用反射公式计算出光线从摄像机出发打到表面后反射出去的方向。采样立方体贴图使用上一步计算出的反射向量作为UV对反射探针对应的CubeMap进行纹理采样。应用与混合将采样到的颜色根据材质的金属度、光滑度等参数进行调制然后与漫反射等其他光照结果混合。在Built-in渲染管线中Unity提供了一系列内置变量如unity_SpecCube0和辅助函数如UNITY_SAMPLE_TEXCUBE来简化这些操作。但在URP中为了更高的灵活性和跨平台一致性很多“黑盒”操作被暴露出来需要我们更清晰地理解数据流。2.2 URP下的数据获取解码与采样宏URP没有直接暴露像unity_SpecCube0这样完整的采样器状态。相反它通过一套更模块化的方式提供数据。反射探针的CubeMap数据被编码在unity_ProbeVolume和相关的参数中但更通用的方式是使用URP Shader Library提供的宏。最关键的一个宏是SHADERGRAPH_SAMPLE_SCENE或其相关变体。这个宏是URP Shader Graph生成代码时使用的它内部封装了根据物体位置、法线等信息从当前生效的反射探针可能多个中获取反射颜色的复杂逻辑。对于我们手写Shader虽然不能直接照搬Graph的生成代码但需要理解其背后的两个关键函数SampleReflectionProbe这个函数负责根据给定的反射向量、位置、探针索引等参数从正确的探针中采样CubeMap。BoxProjectedCubemapDirection如果反射探针启用了Box Projection盒投影这个函数会根据探针的包围盒Box Size和Box Offset来修正反射向量使得反射效果在非无限大的封闭空间内更加准确。这是实现室内真实反射的关键。在代码中我们通常需要从URP的核心库中引入这些函数。一个典型的做法是在Shader的HLSL代码开头包含必要的头文件例如#include Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl以及光照相关的头文件这些头文件内部会声明或定义我们需要的采样函数。注意URP的不同版本如12.x, 14.x其函数名和包含路径可能有细微差别。我提供的代码基于较新的URP版本如2022.3 LTS使用的URP 14.x如果你在旧版本上遇到编译错误可能需要检查对应版本的Shader Library文档或已有Shader示例。2.3 反射向量的计算从世界空间到探针空间计算反射向量的标准公式是reflect(-viewDir, normal)。这里有一个关键点所有参与计算的向量都必须在同一个坐标系下并且这个坐标系需要与反射探针存储CubeMap时使用的坐标系一致。在Unity中立方体贴图通常基于世界空间World Space生成。因此我们的法线Normal和观察方向View Direction也需要转换到世界空间再进行计算。法线转换顶点着色器输出的法线通常是物体空间Object Space或切线空间Tangent Space。我们需要在片元着色器中使用TransformObjectToWorldNormal()或TransformTangentToWorld()等函数将其转换到世界空间。观察方向计算观察方向是从表面点指向摄像机的向量。我们可以通过_WorldSpaceCameraPos - positionWS来计算其中positionWS是片元在世界空间中的位置。记得对这个向量进行归一化normalize。执行反射计算得到世界空间下的归一化法线normalWS和观察方向viewDirWS后使用reflect(-viewDirWS, normalWS)得到世界空间的反射向量reflDirWS。这个reflDirWS就是我们要用来采样CubeMap的原始方向。如果启用了Box Projection还需要用BoxProjectedCubemapDirection函数对它进行修正。3. Shader框架搭建与关键代码实现3.1 Shader属性与基础结构定义我们首先搭建一个最基本的URP Unlit Shader框架并添加控制反射效果的属性。这里我们暂时不实现完整的PBR专注于反射探针的集成。Shader Custom/URPReflectionProbeDemo { Properties { // 基础颜色 _BaseColor (Base Color, Color) (1,1,1,1) _BaseMap (Base Map, 2D) white {} // 反射控制 _ReflectionIntensity (Reflection Intensity, Range(0, 1)) 0.5 _ReflectionTint (Reflection Tint, Color) (1,1,1,1) _Smoothness (Smoothness, Range(0, 1)) 0.5 [Toggle(_BOX_PROJECTION)] _BoxProjection (Enable Box Projection, Float) 0 } SubShader { Tags { RenderTypeOpaque RenderPipelineUniversalPipeline } Pass { Name ForwardLit Tags { LightModeUniversalForward } HLSLPROGRAM #pragma vertex vert #pragma fragment frag // URP核心库 #include Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl #include Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl // 反射探针相关 #include Packages/com.unity.render-pipelines.universal/ShaderLibrary/UnityInput.hlsl // 着色器变体 #pragma shader_feature_local _BOX_PROJECTION // 定义纹理采样器 TEXTURE2D(_BaseMap); SAMPLER(sampler_BaseMap); // 定义属性变量 CBUFFER_START(UnityPerMaterial) float4 _BaseColor; float4 _BaseMap_ST; // 纹理的缩放和偏移 float _ReflectionIntensity; float4 _ReflectionTint; float _Smoothness; CBUFFER_END // 顶点着色器输入结构 struct Attributes { float4 positionOS : POSITION; float3 normalOS : NORMAL; float2 texcoord : TEXCOORD0; }; // 片元着色器输入结构顶点着色器输出 struct Varyings { float4 positionCS : SV_POSITION; float2 uv : TEXCOORD0; float3 positionWS : TEXCOORD1; float3 normalWS : TEXCOORD2; }; // 顶点着色器 Varyings vert(Attributes input) { Varyings output; // 将顶点位置从物体空间转换到齐次裁剪空间 VertexPositionInputs vertexInput GetVertexPositionInputs(input.positionOS.xyz); output.positionCS vertexInput.positionCS; output.positionWS vertexInput.positionWS; // 将法线从物体空间转换到世界空间 VertexNormalInputs normalInput GetVertexNormalInputs(input.normalOS); output.normalWS normalInput.normalWS; // 处理纹理UV output.uv TRANSFORM_TEX(input.texcoord, _BaseMap); return output; } // 片元着色器核心反射逻辑将在这里实现 half4 frag(Varyings input) : SV_Target { // 基础颜色采样 half4 baseColor SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, input.uv) * _BaseColor; // TODO: 在这里实现反射探针采样逻辑 half3 reflectionColor half3(0,0,0); // 临时返回基础颜色 return half4(baseColor.rgb, 1.0); } ENDHLSL } } }这个框架定义了必要的属性、包含了URP的核心库并搭建了顶点和片元着色器的基本数据流。接下来我们将在frag函数中填充最关键的反射采样逻辑。3.2 核心反射采样函数的实现现在我们在片元着色器中实现反射采样的核心逻辑。我们将创建一个名为SampleReflection的函数来封装所有细节。// 在frag函数之前添加这个函数定义 half3 SampleReflection(float3 positionWS, float3 normalWS, float smoothness) { // 1. 计算世界空间下的观察方向 float3 viewDirWS GetWorldSpaceNormalizeViewDir(positionWS); // 2. 计算世界空间下的反射向量 float3 reflDirWS reflect(-viewDirWS, normalWS); // 3. 应用Box Projection如果启用 #ifdef _BOX_PROJECTION // 这里需要获取当前影响该片元的反射探针的索引和包围盒信息。 // 在URP中这通常通过GetProbeVolume或类似机制获取。 // 为了简化示例我们假设使用第一个全局探针并通过Unity内置变量获取其参数。 // 注意这是一个简化示例实际项目中可能需要更复杂的探针选择逻辑。 // unity_SpecCube0_ProbePosition, unity_SpecCube0_BoxMin, unity_SpecCube0_BoxMax // 是Built-in管线中的变量在URP中不一定直接可用。 // URP更推荐使用其提供的函数库。 // 以下代码块展示了概念在实际URP Shader中需要替换为正确的函数调用。 // float3 boxMin unity_SpecCube0_BoxMin; // float3 boxMax unity_SpecCube0_BoxMax; // float3 probePos unity_SpecCube0_ProbePosition; // reflDirWS BoxProjectedCubemapDirection(reflDirWS, positionWS, probePos, boxMin, boxMax); // 更实际的URP做法是使用GetProbeVolumeSamplingData和SampleProbeVolume等函数。 // 由于这部分代码较长且版本依赖性强我们将在下一节详细展开。 // 此处先注释掉保证编译通过。 #endif // 4. 采样反射探针 // 在URP中我们使用GlossyEnvironmentReflection函数。 // 它内部会处理LOD根据粗糙度、解码HDR等操作。 half3 reflectVector reflDirWS; half perceptualRoughness 1.0 - smoothness; // 将光滑度转换为感知粗糙度 half mip PerceptualRoughnessToMipmapLevel(perceptualRoughness); // 计算Mipmap级别 // 这是最关键的一步采样环境反射。 // GlossyEnvironmentReflection函数需要反射向量、位置、粗糙度、遮挡等信息。 // 它返回已经解码的RGB颜色。 half3 reflection GlossyEnvironmentReflection(reflectVector, positionWS, perceptualRoughness, 1.0, positionWS); // 另一种更直接的方式是使用SampleSH和反射探针数据混合但对于镜面反射 // GlossyEnvironmentReflection是标准做法。 return reflection; } // 修改后的frag函数 half4 frag(Varyings input) : SV_Target { // 基础颜色采样 half4 baseColor SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, input.uv) * _BaseColor; // 确保法线是归一化的 float3 normalWS normalize(input.normalWS); // 采样反射颜色 half3 reflectionColor SampleReflection(input.positionWS, normalWS, _Smoothness); // 应用反射强度和色调 reflectionColor * _ReflectionIntensity * _ReflectionTint.rgb; // 简单叠加基础颜色 反射颜色 // 更真实的PBR模型会使用菲涅尔方程来混合这里为了演示使用简单加法 half3 finalColor baseColor.rgb reflectionColor; return half4(finalColor, 1.0); }上面的代码勾勒出了核心流程但其中关于Box Projection和GlossyEnvironmentReflection函数的具体使用是URP中的难点和易错点。这两个部分我们需要深入展开。3.3 完整可用的反射采样与Box Projection集成为了让Shader真正可用我们需要正确定义和获取反射探针的数据。在URP中这通常通过UnityGlobalIllumination相关的函数和数据结构来完成。下面是一个更完整、更贴近实际项目的SampleReflection函数实现。// 首先确保包含了必要的头文件。Core.hlsl和Lighting.hlsl通常已包含。 // 我们可能需要显式包含Reflection探针相关的实用工具头文件如果存在。 // 注意URP版本不同具体函数可能位于不同文件。以下代码基于URP 12 / 14。 // 在Properties和CBUFFER之后定义可能需要的全局变量。 // URP通常通过UnityReflectionProbes和UnityProbeVolume等名称提供数据。 // 但更安全的方式是使用URP Shader库提供的访问函数。 // 修改后的SampleReflection函数 half3 SampleReflection(float3 positionWS, float3 normalWS, float smoothness, float occlusion) { // 计算反射向量与之前相同 float3 viewDirWS GetWorldSpaceNormalizeViewDir(positionWS); float3 reflDirWS reflect(-viewDirWS, normalWS); // 将光滑度转换为感知粗糙度 half perceptualRoughness 1.0 - smoothness; // **关键部分使用URP的GlossyEnvironmentReflection函数** // 这个函数的完整签名可能类似于 // half3 GlossyEnvironmentReflection(float3 reflectVector, float3 positionWS, half perceptualRoughness, half occlusion, float3 normalWS); // 不同版本参数顺序可能不同请以实际URP版本的头文件为准。 // 参数解释 // reflectVector: 世界空间的反射方向。 // positionWS: 世界空间的位置用于探针混合和Box Projection。 // perceptualRoughness: 感知粗糙度用于选择CubeMap的Mip层级。 // occlusion: 环境光遮蔽(AO)值用于减弱反射。 // (有些版本可能还需要normalWS用于某些计算) half3 reflection GlossyEnvironmentReflection(reflDirWS, positionWS, perceptualRoughness, occlusion, normalWS); // 这个函数内部已经处理了 // 1. 根据位置选择最合适的反射探针或混合多个探针。 // 2. 如果探针启用了Box Projection会自动应用。 // 3. 根据粗糙度对CubeMap进行正确的Mipmap采样即模糊反射。 // 4. 对HDR Cubemap进行解码。 return reflection; } // 为了支持自定义的Box Projection开关我们需要更底层的控制。 // 我们可以自己实现一个简化版的Box Projection计算但这通常复杂且容易出错。 // 更推荐的做法是如果你需要强制启用或禁用Box Projection去调整场景中反射探针组件的设置。 // 在Shader中GlossyEnvironmentReflection会读取探针的配置数据。 // 修改frag函数加入遮挡因子这里先用1.0表示无遮挡 half4 frag(Varyings input) : SV_Target { half4 baseColor SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, input.uv) * _BaseColor; float3 normalWS normalize(input.normalWS); // 假设没有额外的遮挡信息occlusion设为1.0 float occlusion 1.0; half3 reflectionColor SampleReflection(input.positionWS, normalWS, _Smoothness, occlusion); reflectionColor * _ReflectionIntensity * _ReflectionTint.rgb; // 一个更真实的混合方式使用菲涅尔近似Schlick近似 // 计算基础菲涅尔因子F0对于电介质非金属通常为0.04 half3 F0 half3(0.04, 0.04, 0.04); // 我们可以用金属度来调制F0但这里为了简化假设为非金属。 // 计算视角与法线夹角的余弦值 float3 viewDirWS GetWorldSpaceNormalizeViewDir(input.positionWS); float VdotN saturate(dot(viewDirWS, normalWS)); // Schlick菲涅尔近似 half3 fresnelFactor F0 (1.0 - F0) * pow(1.0 - VdotN, 5.0); // 最终颜色 漫反射部分 * (1 - 菲涅尔) 镜面反射部分 * 菲涅尔 // 漫反射部分我们简单用基础颜色 half3 diffusePart baseColor.rgb * (1 - fresnelFactor); half3 specularPart reflectionColor * fresnelFactor; half3 finalColor diffusePart specularPart; return half4(finalColor, 1.0); }这个版本的SampleReflection函数利用了URP内置的GlossyEnvironmentReflection它是与URP光照系统集成的最佳实践。它会自动处理探针选择、混合、Box Projection和粗糙度映射省去了我们大量底层工作。4. 避坑指南与性能优化实战4.1 常见编译错误与缺失宏定义在编写过程中你可能会遇到各种编译错误。以下是一些典型问题及解决方案GlossyEnvironmentReflection未定义原因没有包含正确的头文件或者URP版本较旧函数名不同。解决首先确认包含了#include Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl。然后在Unity编辑器中可以右键点击该#include行选择“Go To Definition”查看该头文件内具体的函数签名。在新版URP中这个函数可能位于UnityGlobalIllumination.hlsl中。如果是这样你需要额外包含#include Packages/com.unity.render-pipelines.universal/ShaderLibrary/UnityGlobalIllumination.hlsl。GetWorldSpaceNormalizeViewDir未定义原因同样是因为头文件缺失。解决这个函数通常定义在ShaderVariablesFunctions.hlsl中而该文件又会被Lighting.hlsl包含。确保你的包含顺序正确通常先包含Core.hlsl再包含Lighting.hlsl。Shader报错提示采样器或纹理重复定义原因URP核心库内部已经定义了TEXTURECUBE和采样器如sampler_Linear_Repeat如果你的Shader代码中重复定义会导致冲突。解决不要在你的Shader中再次定义TEXTURECUBE(unity_SpecCube0)之类的全局纹理。URP通过其内部系统管理这些资源。直接使用GlossyEnvironmentReflection这样的高层函数即可。4.2 Box Projection不生效或效果错误这是反射探针Shader开发中最常见的“坑”。现象在室内场景反射看起来像是无限远处的环境没有贴合墙壁和天花板。原因1最常见场景中的反射探针组件没有勾选“Box Projection”选项。Shader代码再正确如果探针数据本身不支持盒投影也无法生效。检查与解决在Unity编辑器中选中场景中的反射探针Reflection Probe。在Inspector面板中找到“Runtime Settings”折叠栏。确保“Box Projection”复选框被勾选。调整“Box Size”和“Box Offset”使其紧密贴合房间的墙壁。反射效果只会在该盒子范围内被正确投影。原因2Shader中计算反射向量的坐标系错误。Box Projection修正函数需要世界空间的位置和反射向量。确保你的positionWS和reflDirWS是在世界空间下计算的并且normalWS是归一化的。4.3 移动端性能优化要点在手机上对立方体贴图进行高精度采样和复杂计算可能成为性能瓶颈。降低采样精度GlossyEnvironmentReflection内部会根据粗糙度选择Mipmap级别。确保你的反射探针CubeMap在导入设置中生成了Mipmap。对于移动端可以考虑强制使用较低级别的Mipmap来采样牺牲一些反射的清晰度换取性能。这可以通过在Shader中手动指定一个基础的粗糙度偏移来实现但更简单的方法是降低反射探针的“Resolution”如从128降到64或32。减少探针数量与更新频率实时Realtime反射探针每一帧都可能更新消耗巨大。尽量使用烘焙Baked或自定义Custom探针。对于动态物体可以设置探针的“Refresh Mode”为“Via Scripting”或“On Awake”而不是“Every Frame”。使用“Time Slicing”将更新分摊到多帧完成。简化Shader计算在片元着色器中进行的reflect、normalize、以及GlossyEnvironmentReflection内部的复杂查询都是计算开销。如果物体距离摄像机很远或者屏幕占比较小可以考虑在顶点着色器中计算反射向量然后通过插值传递给片元着色器INTERPOLATE虽然精度会下降但能节省大量计算。使用部分精度在移动端Shader中将不必要的float精度改为half甚至fixed如果支持。例如颜色值、强度系数等完全可以使用half。4.4 反射与场景光照的整合我们上面的示例将反射颜色简单与基础色相加这虽然直观但并不符合物理。在一个完整的PBR Shader中反射镜面反射部分应该与直接光照、间接漫反射光照等一起通过BRDF方程整合。如果你想将我们的反射Shader升级为一个简化的PBR Shader需要分离漫反射与高光基础颜色_BaseColor应被视为反照率。对于非金属电介质反照率就是漫反射颜色对于金属反照率通常很暗大部分光被反射。引入金属度添加一个_Metallic属性。在计算菲涅尔因子F0时将其从固定的0.04改为lerp(0.04, _BaseColor.rgb, _Metallic)。金属的F0就是其反照率。使用BRDF函数URP的Lighting.hlsl提供了如DirectBRDF()之类的函数来计算直接光照的镜面反射。我们的环境反射来自探针应作为间接高光部分加入。能量守恒确保漫反射和高光反射的总和不会超过入射光能。简单的Lambert漫反射 镜面反射模型本身不守恒这也是为什么需要基于物理的BRDF模型。对于大多数定制化需求我们的示例Shader已经足够它能够从URP的反射探针系统中正确采样到环境反射并允许你通过_ReflectionIntensity、_ReflectionTint和_Smoothness进行艺术化的控制。当你需要更真实的物理效果时再考虑将其嵌入完整的PBR光照框架中。5. 完整HLSL代码整合与使用说明以下是整合了所有要点、添加了必要注释、并经过基础测试的完整Shader代码。你可以将其复制到一个新的Shader文件中命名为“URPReflectionProbeDemo.shader”然后应用到材质球上进行测试。Shader Custom/URPReflectionProbeDemo { Properties { [MainColor] _BaseColor(Base Color, Color) (1,1,1,1) [MainTexture] _BaseMap(Base Map, 2D) white {} _Smoothness(Smoothness, Range(0, 1)) 0.5 _ReflectionIntensity(Reflection Intensity, Range(0, 2)) 1.0 _ReflectionTint(Reflection Tint, Color) (1,1,1,1) // 注意Box Projection的启用依赖于场景中探针本身的设置。 // 这个Toggle主要用于调试或强制覆盖某些逻辑本例中未强制覆盖。 // [Toggle(_BOX_PROJECTION)] _BoxProjection (Enable Box Projection, Float) 0 } SubShader { Tags { RenderTypeOpaque RenderPipelineUniversalPipeline UniversalMaterialType Lit // 声明为Lit类型便于URP识别 IgnoreProjectorTrue } LOD 300 Pass { Name ForwardLit Tags { LightModeUniversalForward } HLSLPROGRAM #pragma vertex vert #pragma fragment frag // 着色器变体 #pragma multi_compile _ _MAIN_LIGHT_SHADOWS #pragma multi_compile _ _MAIN_LIGHT_SHADOWS_CASCADE #pragma multi_compile _ _ADDITIONAL_LIGHTS_VERTEX _ADDITIONAL_LIGHTS #pragma multi_compile_fragment _ _ADDITIONAL_LIGHT_SHADOWS #pragma multi_compile_fragment _ _SHADOWS_SOFT #pragma multi_compile_fragment _ _REFLECTION_PROBE_BOX_PROJECTION // 这个变体很重要 #pragma multi_compile _ LIGHTMAP_ON #pragma multi_compile _ DIRLIGHTMAP_COMBINED #pragma multi_compile_fog // URP核心库 #include Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl #include Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl #include Packages/com.unity.render-pipelines.universal/ShaderLibrary/UnityGlobalIllumination.hlsl struct Attributes { float4 positionOS : POSITION; float3 normalOS : NORMAL; float2 texcoord : TEXCOORD0; UNITY_VERTEX_INPUT_INSTANCE_ID }; struct Varyings { float4 positionCS : SV_POSITION; float2 uv : TEXCOORD0; float3 positionWS : TEXCOORD1; float3 normalWS : TEXCOORD2; half3 viewDirWS : TEXCOORD3; half fogFactor : TEXCOORD4; #if defined(REQUIRES_VERTEX_SHADOW_COORD_INTERPOLATOR) float4 shadowCoord : TEXCOORD5; #endif UNITY_VERTEX_INPUT_INSTANCE_ID UNITY_VERTEX_OUTPUT_STEREO }; TEXTURE2D(_BaseMap); SAMPLER(sampler_BaseMap); CBUFFER_START(UnityPerMaterial) float4 _BaseMap_ST; half4 _BaseColor; half _Smoothness; half _ReflectionIntensity; half4 _ReflectionTint; CBUFFER_END Varyings vert(Attributes input) { Varyings output (Varyings)0; UNITY_SETUP_INSTANCE_ID(input); UNITY_TRANSFER_INSTANCE_ID(input, output); UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(output); VertexPositionInputs vertexInput GetVertexPositionInputs(input.positionOS.xyz); VertexNormalInputs normalInput GetVertexNormalInputs(input.normalOS); output.positionCS vertexInput.positionCS; output.positionWS vertexInput.positionWS; output.normalWS normalInput.normalWS; output.viewDirWS GetWorldSpaceNormalizeViewDir(vertexInput.positionWS); output.uv TRANSFORM_TEX(input.texcoord, _BaseMap); output.fogFactor ComputeFogFactor(vertexInput.positionCS.z); #if defined(REQUIRES_VERTEX_SHADOW_COORD_INTERPOLATOR) output.shadowCoord GetShadowCoord(vertexInput); #endif return output; } half4 frag(Varyings input) : SV_Target { UNITY_SETUP_INSTANCE_ID(input); UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(input); // 采样基础纹理和颜色 half4 baseColorTex SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, input.uv); half3 baseColor baseColorTex.rgb * _BaseColor.rgb; half alpha baseColorTex.a * _BaseColor.a; // 准备光照计算所需数据 float3 positionWS input.positionWS; half3 normalWS normalize(input.normalWS); half3 viewDirWS SafeNormalize(input.viewDirWS); // 计算反射向量 half3 reflectVector reflect(-viewDirWS, normalWS); // 将光滑度转换为感知粗糙度 half perceptualRoughness 1.0 - _Smoothness; // **核心采样环境反射来自反射探针和天空盒** // GlossyEnvironmentReflection 函数内部处理了 // 1. 根据位置选择/混合反射探针。 // 2. 自动应用Box Projection如果探针启用且变体支持。 // 3. 根据粗糙度进行Mipmap采样。 // 4. 与天空盒反射混合。 half3 reflectColor GlossyEnvironmentReflection(reflectVector, positionWS, perceptualRoughness, 1.0, normalWS); // 应用自定义的反射强度和色调 reflectColor * _ReflectionIntensity * _ReflectionTint.rgb; // 简化版光照仅环境反射 基础颜色 // 这里没有计算直接光仅展示反射效果 half3 finalColor baseColor.rgb; // 漫反射部分简化 // 使用一个简单的菲涅尔近似来混合反射 half fresnelTerm pow(1.0 - saturate(dot(normalWS, viewDirWS)), 5.0); fresnelTerm 0.04 (1.0 - 0.04) * fresnelTerm; // 基于电介质的F00.04 finalColor lerp(finalColor, reflectColor, fresnelTerm * _Smoothness); // 应用雾效 finalColor MixFog(finalColor, input.fogFactor); return half4(finalColor, alpha); } ENDHLSL } // 可选的阴影投射Pass保证物体能投射阴影 Pass { Name ShadowCaster Tags{LightMode ShadowCaster} ZWrite On ZTest LEqual ColorMask 0 Cull[_Cull] HLSLPROGRAM #pragma vertex ShadowPassVertex #pragma fragment ShadowPassFragment #include Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl #include Packages/com.unity.render-pipelines.universal/ShaderLibrary/Shadows.hlsl float3 _LightDirection; struct Attributes { float4 positionOS : POSITION; float3 normalOS : NORMAL; float2 texcoord : TEXCOORD0; UNITY_VERTEX_INPUT_INSTANCE_ID }; struct Varyings { float2 uv : TEXCOORD0; float4 positionCS : SV_POSITION; }; float4 GetShadowPositionHClip(Attributes input) { float3 positionWS TransformObjectToWorld(input.positionOS.xyz); float3 normalWS TransformObjectToWorldNormal(input.normalOS); float4 positionCS TransformWorldToHClip(ApplyShadowBias(positionWS, normalWS, _LightDirection)); #if UNITY_REVERSED_Z positionCS.z min(positionCS.z, positionCS.w * UNITY_NEAR_CLIP_VALUE); #else positionCS.z max(positionCS.z, positionCS.w * UNITY_NEAR_CLIP_VALUE); #endif return positionCS; } Varyings ShadowPassVertex(Attributes input) { Varyings output; UNITY_SETUP_INSTANCE_ID(input); output.uv TRANSFORM_TEX(input.texcoord, _BaseMap); output.positionCS GetShadowPositionHClip(input); return output; } half4 ShadowPassFragment(Varyings input) : SV_TARGET { Alpha(SampleAlbedoAlpha(input.uv, TEXTURE2D_ARGS(_BaseMap, sampler_BaseMap)).a, _BaseColor, _Cutoff); return 0; } ENDHLSL } } FallBack Universal Render Pipeline/Lit }使用步骤在Unity项目中创建一个新的Shader文件粘贴上述代码。创建一个材质球使用这个新Shader。在场景中放置一个或多个反射探针。确保其类型Baked/Custom/Realtime符合你的需求并且如果需要在有限空间内获得准确反射请勾选Box Projection并调整包围盒大小。将材质球赋予给一个网格物体如Sphere或Cube。调整材质属性Smoothness控制反射的清晰度。值越高反射越清晰粗糙度越低。Reflection Intensity控制反射颜色的强度。Reflection Tint给反射颜色叠加一个色调。确保场景有光照环境天空盒或其他环境光反射探针才能捕捉到内容。通过这个Shader你应该能看到物体表面正确反映了由反射探针捕捉到的周围环境。这个Shader是一个强大的起点你可以在此基础上继续集成直接光照、更复杂的BRDF、法线贴图、视差效果等构建出属于你自己的、功能丰富的URP材质。