Unity Shader实现2D游戏天空日夜循环:从渐变到星辰的动态环境
1. 项目概述:为什么要在2D游戏里做日夜循环?
做2D游戏,尤其是像素风、手绘风或者横版卷轴游戏的时候,我们总想让世界“活”起来。静态的背景图看久了难免单调,而一个能随时间流逝、从晨曦微露到繁星满天的动态天空,无疑是提升游戏沉浸感和氛围感最直接、最有效的手段之一。这就是“2D天空日夜循环”要解决的核心问题:让2D游戏的天空背景不再是死板的一张图,而是一个有生命、会呼吸的有机体。
你可能会问,3D游戏里用天空盒(Skybox)做日夜循环不是更简单吗?确实,3D引擎处理光照和天空有天然优势。但在2D世界里,我们没有三维空间和复杂的光照模型,一切视觉效果都得靠“画”出来。传统做法可能是准备多张不同时间点的天空序列帧,然后用动画控制器去切换。这种方法简单直接,但缺点也很明显:资源量大(美术要画很多张图)、过渡生硬(帧与帧之间跳变)、且难以实现平滑、无限循环的时间流逝感。
所以,我们转向了Shader。用Shader来实现2D天空的日夜循环,本质上是用程序算法去“实时绘制”天空。我们只需要1到2张基础纹理(比如一张日间天空渐变图,一张星空图),然后通过Shader中的时间变量,动态地混合颜色、移动纹理、改变亮度,就能模拟出太阳东升西落、星辰隐现、天空色彩从湛蓝到橙红再到深紫的完整过程。这种方法资源消耗极低,效果平滑且高度可控,还能轻松地与游戏内的时间系统(比如游戏时钟)挂钩,实现真正的动态环境。
最近在社区里,关于Unity Shader和2D特效的讨论一直很热,无论是想优化游戏性能,还是追求更独特的视觉风格,掌握Shader编程都成了进阶必备技能。这个项目,就是一次绝佳的实战演练。
2. 核心思路与方案设计:从美术资源到Shader算法
在动手写代码之前,我们必须把整个效果在脑子里“演算”一遍。一个完整的2D日夜循环,可以拆解成几个核心的视觉层,以及驱动它们变化的时间系统。
2.1 视觉层分解:天空不是一层“皮”
我们不能把天空想象成单一的一层颜色。一个富有层次感的天空,至少包含以下几个部分:
- 地平线渐变层:这是天空的底色。白天是从头顶的天蓝色渐变到地平线附近的亮白色或浅黄色;傍晚则是从深蓝色渐变到火红色或紫色。这个层决定了天空的基本色调。
- 太阳/月亮层:一个发光的圆形,沿着预定的轨迹(通常是椭圆或正弦曲线)在天空移动。它的颜色、亮度和大小会随时间变化。
- 云层:可以是静态的细节纹理,也可以是动态飘动的元素。它的亮度和颜色会受太阳位置影响(比如背光面暗,迎光面亮,傍晚被染成红色)。
- 星空层:在夜晚显现。不是简单的贴图显示,最好能让星星有微弱的闪烁效果,以增加生动性。
我们的Shader需要有能力分层处理这些元素,并进行动态混合。
2.2 驱动核心:时间系统与参数化控制
所有变化的核心驱动力是“时间”。我们需要在Shader中定义一个归一化的时间变量,比如_CycleTime,范围是[0, 1],代表一个完整日夜周期的进度。0代表午夜,0.25代表清晨,0.5代表正午,0.75代表黄昏,1又回到午夜。
有了这个时间变量,我们就可以用数学函数来驱动其他参数:
- 太阳高度角:可以用
sin(_CycleTime * PI)来模拟太阳从地平线升起、到顶点、再落下的平滑运动。 - 天空颜色:我们需要定义几个关键时间点的颜色(如午夜蓝、黎明橙、正午蓝、黄昏红),然后用时间在它们之间进行平滑插值(
lerp)。 - 星星透明度:在白天完全透明(alpha=0),在夜晚完全显现(alpha=1),在黎明和黄昏有一个平滑的淡入淡出过渡。
注意:这里的时间
_CycleTime最好由C#脚本从游戏逻辑中传递进来,而不是用Shader内部的_Time.y。这样我们可以完全控制游戏内时间流逝的速度(比如现实1分钟等于游戏内1小时),并且可以方便地实现“时间暂停”、“时间加速”等游戏功能。
2.3 方案选型:片段着色器(Fragment Shader)为主
对于2D天空,我们通常使用一个覆盖全屏或背景区域的Quad(四边形)或Sprite。因此,选择Surface Shader(表面着色器)有点大材小用,它更适合与3D光照模型交互。而Unlit Shader(无光照着色器)或自定义的片段着色器是我们的最佳选择,因为它完全由我们控制颜色输出,计算高效,且易于理解。
我们将编写一个Unlit Shader,在片段着色器中,为屏幕上的每一个像素计算其最终颜色。计算过程就是综合上述各层的贡献:先计算基础渐变,再叠加太阳光晕,然后混合云层,最后在夜晚叠加星空。
3. 核心Shader实现详解:从渐变到星辰
接下来,我们进入最核心的部分:用HLSL/ShaderLab语言一步步构建这个天空着色器。我会假设你已有基础的Shader语法知识,并着重解释每个步骤的设计意图和数学原理。
3.1 构建基础天空渐变
这是天空的底色。我们不在纹理里画死渐变,而是用算法生成。
// 在片段着色器中 float2 uv = i.uv; // 假设UV的v坐标(0到1)代表从地平线(0)到天顶(1) float time = _CycleTime; // 归一化时间 // 1. 定义关键时间点的天空顶色和地平线色 float3 dayTopColor = float3(0.2, 0.5, 1.0); // 白天顶:天蓝 float3 dayHorizonColor = float3(0.8, 0.9, 1.0); // 白天地平线:亮白 float3 sunsetTopColor = float3(0.05, 0.05, 0.2); // 日落顶:深蓝紫 float3 sunsetHorizonColor = float3(1.0, 0.4, 0.1); // 日落地平线:橙红 float3 nightTopColor = float3(0.0, 0.0, 0.1); // 夜晚顶:近乎黑色 float3 nightHorizonColor = float3(0.1, 0.1, 0.3); // 夜晚地平线:暗蓝 // 2. 根据时间混合出当前时刻的顶色和地平线色 // 简化模型:将一天分为白天、黄昏、夜晚三个区间 float3 currentTopColor; float3 currentHorizonColor; if (time < 0.25 || time > 0.75) { // 夜晚时段 // 在午夜附近,使用纯夜晚色;在黎明/黄昏附近,向白天/日落色过渡 float nightFactor = smoothstep(0.75, 1.0, time) + smoothstep(0.0, 0.25, time); currentTopColor = lerp(nightTopColor, sunsetTopColor, nightFactor); currentHorizonColor = lerp(nightHorizonColor, sunsetHorizonColor, nightFactor); } else if (time < 0.5) { // 上午到正午 float dayFactor = smoothstep(0.25, 0.5, time); currentTopColor = lerp(sunsetTopColor, dayTopColor, dayFactor); currentHorizonColor = lerp(sunsetHorizonColor, dayHorizonColor, dayFactor); } else { // 下午到黄昏 float sunsetFactor = smoothstep(0.5, 0.75, time); currentTopColor = lerp(dayTopColor, sunsetTopColor, sunsetFactor); currentHorizonColor = lerp(dayHorizonColor, sunsetHorizonColor, sunsetFactor); } // 3. 根据像素的垂直位置(uv.y)在当前顶色和地平线色之间插值,形成垂直渐变 float verticalGradient = pow(uv.y, _GradientPower); // 使用pow函数控制渐变曲线,让地平线附近变化更平缓 float3 skyColor = lerp(currentHorizonColor, currentTopColor, verticalGradient);这里用smoothstep函数来实现颜色之间的平滑过渡,避免生硬的跳变。_GradientPower是一个可调参数,pow(uv.y, _GradientPower)中,当_GradientPower > 1时,地平线附近的颜色占比会更大,天空看起来更“开阔”。
3.2 模拟太阳(或月亮)的绘制与运动
太阳是一个高光点。我们完全用数学来画一个圆形,并让它动起来。
// 计算太阳在屏幕空间中的位置(x坐标随时间正弦变化,y坐标随时间余弦变化,模拟弧形轨迹) float sunX = 0.5 + 0.4 * sin(time * PI); // 在屏幕X轴中间左右摆动 float sunY = 0.2 + 0.6 * (cos(time * PI) * 0.5 + 0.5); // Y轴从0.2到0.8再回到0.2,形成弧形 float2 sunPos = float2(sunX, sunY); // 计算当前像素到太阳中心的距离 float distToSun = distance(uv, sunPos); // 绘制太阳:一个平滑的圆形光晕 float sunRadius = _SunRadius; // 基础半径 float sunSoftness = _SunSoftness; // 边缘软化度 // 使用smoothstep创建从1(中心)到0(边缘)的平滑过渡 float sunMask = 1.0 - smoothstep(sunRadius - sunSoftness, sunRadius + sunSoftness, distToSun); // 太阳的颜色和亮度也随时间变化 float3 sunColor = _SunColor; float sunIntensity = _SunBaseIntensity; // 正午最亮,清晨黄昏减弱,夜晚消失 sunIntensity *= (1.0 - abs(time - 0.5) * 2.0); // 一个简单的三角波模拟亮度变化 sunIntensity = max(0, sunIntensity); // 确保不为负 // 将太阳光晕叠加到天空颜色上(使用加法混合,模拟发光效果) float3 finalColor = skyColor + sunColor * sunMask * sunIntensity;实操心得:太阳的轨迹不一定非得是标准的正弦/余弦。你可以用动画曲线(Animation Curve)在C#中计算好位置再传给Shader,这样能做出更符合美术需求的、非对称的日出日落轨迹,比如日出快、日落慢。
_SunSoftness参数非常重要,一个硬边的圆看起来像贴图,一个柔和的光晕才更像真实的光源。
3.3 动态云层的集成与光照影响
云层通常需要纹理。我们准备一张带有Alpha通道的云纹理。
// 采样云纹理,可能使用两个不同缩放和速度的UV,进行叠加以产生更复杂、不重复的运动 float2 cloudUV1 = uv * _CloudScale + float2(_Time.y * _CloudSpeed1, 0); float2 cloudUV2 = uv * _CloudScale * 0.7 + float2(_Time.y * _CloudSpeed2, 0.1); // 第二个层稍作偏移 float4 cloudSample1 = tex2D(_CloudTex, cloudUV1); float4 cloudSample2 = tex2D(_CloudTex, cloudUV2); // 混合两层云,使用最大值混合(max)可以保留较亮的云层细节,避免叠加后变糊 float cloudMask = max(cloudSample1.a, cloudSample2.a); // 云的颜色受太阳位置影响(模拟光照) float3 cloudColor = _CloudColor; // 简单的光照模拟:计算云层UV点与太阳方向的点积,越朝向太阳越亮 float2 toSunDir = sunPos - uv; float sunLightFactor = dot(normalize(toSunDir), float2(0, 1)); // 假设太阳光主要从上往下照 sunLightFactor = saturate(sunLightFactor + 0.3); // 加个基础值,让背光面不完全黑 cloudColor *= sunLightFactor; // 将云层叠加到天空上(使用透明度混合,Alpha混合) finalColor = lerp(finalColor, cloudColor, cloudMask * _CloudOpacity);这里的关键是让云动起来(通过_Time.y偏移UV)以及受光影响。更复杂的模拟还可以根据太阳高度角来改变云的整体亮度和色调(傍晚的云是红的)。
3.4 星空与星星闪烁效果
星空是夜晚的主角。我们同样使用一张星空纹理,但会做一些处理让它更生动。
// 采样星空纹理 float4 starsSample = tex2D(_StarsTex, uv * _StarsScale); float starsMask = starsSample.r; // 假设星星在R通道 // 决定星星的可见度:基于时间,在夜晚完全显示,在白天完全隐藏 float nightLerpFactor = smoothstep(0.8, 1.0, time) + smoothstep(0.0, 0.2, time); // 在夜晚时段接近1,白天接近0 nightLerpFactor = saturate(nightLerpFactor); // 星星闪烁效果:利用时间和噪声纹理,或者简单的正弦波,改变星星的亮度 float twinkle = sin(_Time.y * 5.0 + uv.x * 100.0) * 0.5 + 0.5; // 一个简单的随机闪烁模拟 twinkle = lerp(0.3, 1.2, twinkle); // 闪烁亮度范围 starsMask *= twinkle; // 将星星叠加到画面上(加法混合,只在夜晚显示) finalColor += starsSample.rgb * starsMask * nightLerpFactor * _StarsIntensity;注意事项:星空纹理最好是黑底白星(或透明底白星)。叠加时使用加法(
+),这样星星才能“亮”起来。闪烁效果不宜过强或频率过高,否则会显得虚假。这里用uv.x * 100.0来给不同位置的星星一个相位差,避免所有星星同步闪烁。
4. 完整Shader代码结构与参数曝光
将以上所有部分组合起来,并加上必要的Properties和Pass,就得到了一个完整的Shader。为了让美术或策划能够方便地调整效果,我们需要把关键参数暴露在材质面板上。
Shader "Custom/2DDayNightCycleSky" { Properties { [Header(Sky Gradient)] _DayTopColor ("Day Top Color", Color) = (0.2, 0.5, 1.0, 1.0) _DayHorizonColor ("Day Horizon Color", Color) = (0.8, 0.9, 1.0, 1.0) _SunsetTopColor ("Sunset Top Color", Color) = (0.05, 0.05, 0.2, 1.0) _SunsetHorizonColor ("Sunset Horizon Color", Color) = (1.0, 0.4, 0.1, 1.0) _NightTopColor ("Night Top Color", Color) = (0.0, 0.0, 0.1, 1.0) _NightHorizonColor ("Night Horizon Color", Color) = (0.1, 0.1, 0.3, 1.0) _GradientPower ("Gradient Power", Range(0.5, 3.0)) = 1.5 [Header(Sun)] _SunColor ("Sun Color", Color) = (1.0, 0.9, 0.7, 1.0) _SunRadius ("Sun Radius", Range(0.01, 0.2)) = 0.05 _SunSoftness ("Sun Softness", Range(0.001, 0.1)) = 0.02 _SunBaseIntensity ("Sun Base Intensity", Range(0, 3)) = 1.0 _CycleTime ("Day Cycle Time (0-1)", Range(0, 1)) = 0.0 // 这个通常由脚本控制 [Header(Clouds)] _CloudTex ("Cloud Texture", 2D) = "white" {} _CloudColor ("Cloud Color", Color) = (1,1,1,1) _CloudScale ("Cloud Scale", Range(0.1, 5)) = 1.0 _CloudSpeed1 ("Cloud Speed 1", Float) = 0.01 _CloudSpeed2 ("Cloud Speed 2", Float) = 0.02 _CloudOpacity ("Cloud Opacity", Range(0, 1)) = 0.5 [Header(Stars)] _StarsTex ("Stars Texture", 2D) = "black" {} _StarsIntensity ("Stars Intensity", Range(0, 2)) = 1.0 _StarsScale ("Stars Scale", Range(0.5, 2)) = 1.0 } SubShader { Tags { "RenderType"="Opaque" "Queue"="Background" } LOD 100 Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" // 变量声明(与Properties对应)... // 顶点着色器输入输出结构体... // 顶点着色器(简单传递UV)... // 片段着色器(包含上述所有计算逻辑)... // 最终输出 finalColor ENDCG } } FallBack "Diffuse" }在Unity中,将这个Shader赋给一个材质球,然后将材质球赋给一个覆盖摄像机背景的Quad,你就能在场景中看到基础的天空了。通过调整材质面板上的参数,你可以创造出从晴朗午后到暴风雨前夜的各种天空 mood。
5. C#驱动脚本:连接游戏逻辑与Shader
Shader本身是“被动”的,它需要一个外部力量来驱动_CycleTime的变化。这个力量就来自C#脚本。
using UnityEngine; [ExecuteInEditMode] // 方便在编辑器模式下预览 public class DayNightCycleController : MonoBehaviour { public Renderer skyRenderer; // 承载天空材质的Renderer(比如Quad上的) public float dayDurationInSeconds = 60f; // 游戏内一整天对应的真实秒数 private float currentCycleTime = 0f; // 当前周期时间 (0-1) private MaterialPropertyBlock propertyBlock; void Start() { propertyBlock = new MaterialPropertyBlock(); skyRenderer.GetPropertyBlock(propertyBlock); // 使用PropertyBlock避免创建材质实例 } void Update() { // 更新时间 currentCycleTime += Time.deltaTime / dayDurationInSeconds; currentCycleTime %= 1.0f; // 保持在0-1循环 // 将时间传递给Shader if (propertyBlock != null && skyRenderer != null) { skyRenderer.GetPropertyBlock(propertyBlock); propertyBlock.SetFloat("_CycleTime", currentCycleTime); skyRenderer.SetPropertyBlock(propertyBlock); } // 可选:根据时间触发游戏内事件(如清晨怪物刷新,夜晚路灯亮起) HandleGameEvents(); } // 提供给其他脚本调用的接口,用于获取时间、设置时间等 public float GetCurrentTime() => currentCycleTime; public void SetTime(float normalizedTime) { currentCycleTime = Mathf.Clamp01(normalizedTime); } public bool IsNightTime() => currentCycleTime > 0.75f || currentCycleTime < 0.25f; void HandleGameEvents() { // 示例:在特定时间点触发事件 if (Mathf.Abs(currentCycleTime - 0.25f) < 0.01f) // 清晨 { // Debug.Log("黎明到来!"); } } }重要技巧:这里使用了
MaterialPropertyBlock来修改Shader属性,而不是直接修改material.SetFloat。这是最佳实践。MaterialPropertyBlock允许你修改Renderer的材质属性而不创建新的材质实例(Material Instance),这对于性能优化至关重要,尤其是在移动平台或需要大量重复物体的场景中。直接修改material属性会导致Unity为该Renderer创建一个独有的材质副本,如果场景中有1000个使用同一天空材质的物体,就会产生1000个材质实例,造成巨大的内存和渲染开销。而PropertyBlock不会。
6. 性能优化与进阶技巧
一个看起来酷炫的效果如果让游戏卡顿,就得不偿失了。下面是一些针对此Shader的优化和进阶思路。
6.1 性能瓶颈分析与优化
- 纹理采样次数:我们的Shader采样了两次云纹理和一次星空纹理。对于移动平台,这不算多,但仍有优化空间。可以考虑将星空和云的基础图案合并到一张纹理的不同通道(如R通道存星星,G通道存云),减少一次采样。或者,在低端设备上通过Shader变体(Shader Variant)关闭云层或星空的动态效果。
- 数学计算:
sin,cos,pow,smoothstep都是相对耗时的操作,但在片段着色器中每个像素都会计算。对于太阳光晕,我们可以考虑在顶点着色器中计算太阳位置和基础亮度,然后通过插值传给片段着色器,减少逐像素计算量。对于天空渐变,如果屏幕分辨率固定,甚至可以预计算一张小尺寸的1D渐变纹理(Ramp Texture),根据时间选择不同的行,然后根据uv.y采样,用一次纹理采样代替复杂的颜色插值计算。 - 使用PropertyBlock:如上节所述,务必使用
MaterialPropertyBlock来更新_CycleTime等动态参数,这是避免运行时材质实例爆炸的关键。 - 针对平台调整精度:在移动端,可以考虑将部分
float运算降级为half甚至fixed(在支持的情况下),以提升运算速度。
6.2 效果增强与扩展思路
- 大气散射模拟(进阶):更真实的天空颜色变化源于瑞利散射和米氏散射。你可以引入更复杂的公式,根据太阳与观察者视线之间的角度来计算天空各部分的颜色,而不是简单的几个关键色插值。这会让黄昏时的天空色彩过渡更加科学和华丽。
- 动态天气系统集成:将天气参数(如雨、雾、云量)也作为Uniform变量传入Shader。例如,增加一个
_CloudCoverage参数来控制云层纹理的显示强度和范围;增加一个_FogColor和_FogDensity来模拟雾效,让地平线处的物体与天空颜色融合。 - 与2D光照系统交互:如果你的2D游戏使用了像Unity 2D URPLit这样的渲染管线,你可以让太阳的位置影响场景中2D灯光的全局颜色和强度。在C#脚本中,根据
_CycleTime计算出一个环境光颜色和强度,然后赋值给RenderSettings.ambientLight或2D光照系统的全局光参数。 - 后处理叠加:在后期,可以加入一个全屏的色调调整(Color Grading)或泛光(Bloom)效果。让正午的画面更鲜艳明亮,夜晚的画面对比度更高并带有微弱的蓝色调,日落的画面泛光效果更强,以突出太阳的辉煌。
7. 常见问题与调试实录
在实际操作中,你肯定会遇到各种奇怪的问题。这里记录几个我踩过的坑和解决方法。
7.1 效果在Game视图不更新,或者更新闪烁
- 问题描述:在Scene视图一切正常,但Game视图的天空是静止的,或者变化不连贯。
- 排查步骤:
- 首先检查C#脚本是否挂载并启用,
skyRenderer变量是否正确赋值。 - 检查是否使用了
MaterialPropertyBlock。一个常见的巨坑是:如果你在编辑器模式下直接修改了材质球(Material Asset)的_CycleTime属性,然后运行游戏,脚本通过PropertyBlock设置的值可能会被材质球自身的默认值覆盖,导致无效。确保材质球上的_CycleTime默认值为0,并且脚本是唯一修改它的地方。 - 检查Update方法中的时间计算逻辑,特别是取模运算
%,确保currentCycleTime不会变成NaN或无限大。 - 如果是闪烁,可能是多个脚本或渲染顺序冲突。确保承载天空的Quad的渲染队列(Queue)是“Background”,并且其Z位置在摄像机远裁剪面之内,但又在所有其他场景物体之后。
- 首先检查C#脚本是否挂载并启用,
7.2 太阳/月亮在屏幕边缘被裁剪
- 问题描述:太阳运动到屏幕左/右或上边缘时,突然消失了一半。
- 原因与解决:这是因为我们计算太阳位置 (
sunPos) 时,使用的是归一化的UV坐标 (0,1)。当太阳中心靠近UV边界(0或1)时,其光晕的一部分就落在了UV范围之外(比如sunPos.x - radius < 0),这部分不会被渲染。 - 解决方案:在片段着色器中计算太阳贡献时,不要简单地丢弃UV范围外的像素。或者,更简单的方法是,确保太阳的运动轨迹(
sunX,sunY)始终留出足够的光晕半径空间。例如,将sunX的计算改为0.5 + 0.35 * sin(...),把摆动幅度从0.4降到0.35,给边缘留出空间。
7.3 云层移动速度太快或出现明显重复纹理
- 问题描述:云看起来像快进一样飞驰,或者移动一段距离后能看到明显的纹理重复接缝。
- 调试方法:
- 速度问题:调整
_CloudSpeed1和_CloudSpeed2。它们的值通常非常小(如0.01),因为_Time.y的单位是秒,UV偏移量是每秒钟移动多少个纹理单位。值太大就会飞起来。 - 重复纹理问题:这是使用单一纹理平铺(Tiling)的固有缺陷。解决方法有几种:
- 使用更大的纹理:减少缩放
_CloudScale,让单张纹理覆盖更大屏幕区域,重复感自然降低。 - 使用程序化噪声:用Simplex或Perlin噪声函数代替纹理来生成云,这样天生就是无重复的。但这会显著增加Shader计算复杂度。
- 多层叠加与旋转:除了用两个不同速度的UV,还可以让第二层云纹理旋转一个角度(如15度),再叠加,这样重复图案会更难被察觉。
- 使用遮罩纹理:准备一张大的、不重复的云形状遮罩图,然后在其上用噪声函数生成细节,这是折中的方案。
- 使用更大的纹理:减少缩放
- 速度问题:调整
7.4 在URP/HDRP中无法工作
- 问题描述:为Built-in渲染管线写的Shader,直接拿到URP(通用渲染管线)或HDRP(高清渲染管线)中,材质显示粉红色(Missing Shader)。
- 解决方案:URP/HDRP有自己的一套Shader框架和库函数。你需要将Shader重写为URP的Shader Graph(可视化)或者手写HLSL代码但遵循URP的库和路径。对于这个2D天空效果,使用Shader Graph来实现会非常直观,你可以用节点搭建出完全相同的逻辑,并且URP Shader Graph天生支持
Time节点和PropertyBlock的参数传递。这是将效果迁移到现代渲染管线的最佳路径。
最后,调试Shader时,帧调试器(Frame Debugger)和可视化调试工具是救命稻草。你可以临时将Shader中的某个中间变量(如sunMask,nightLerpFactor)直接输出为最终颜色,来检查它的值是否符合预期。例如,在开发阶段,你可以添加一个调试模式开关,在材质面板上勾选后,最终输出就变成float4(sunMask, sunMask, sunMask, 1),这样你就能在屏幕上直观地看到太阳光晕的形状和范围是否正确,这是排查问题最快的方法。
