天空盒这个东西,说简单也简单,六张图贴上去就能跑;说难也难,想让它在一天之内从清晨到黄昏再到星夜,云层跟着风向流动,远处地平线还有一层薄雾在缓慢呼吸,那就不是随手拖几个材质能搞定的事了。我最近刚把一个动态天空盒的完整流程跑通,从AIGC生成全景素材开始,到Unity3D里用Shader把时间、云层、光照串起来,中间踩了不少坑,也攒了一些在官方文档里翻不到的经验。这篇内容适合两类人:一类是刚接触Unity3D、想给自己的小项目加个像样天空的开发者;另一类是做AIGC内容、想把生成的图真正用进实时渲染管线里的朋友。我会把每一步为什么这么做、参数怎么算、哪里容易翻车都讲清楚,你照着做基本能复现。
1. 为什么动态天空盒值得单独折腾
1.1 静态天空盒的天花板在哪里
很多人做Unity3D项目,天空盒就是去资源商店下一套免费的,或者拿手机拍一张全景图往Skybox材质里一塞,完事。这种做法在固定视角、固定时间的场景里没问题,比如一个室内展示或者一个白天的户外小游戏。但只要你涉及时间流逝、天气变化、或者玩家会抬头看天,静态天空盒的破绽就全露出来了。
最明显的问题是光照不匹配。你贴了一张正午蓝天白云的图,但场景里的方向光角度是傍晚的,阴影拉得很长,天空却亮得刺眼,整个画面就会有一种说不出的违和感。人眼对天空的亮度和色温极其敏感,这种不匹配哪怕外行也能一眼看出来。第二个问题是重复感。静态天空盒的云是死的,玩家盯着看几秒就会发现云不动、光不变,沉浸感瞬间打折。第三个问题是分辨率与性能的矛盾。你想要高清全景图,一张8K的HDR全景图动辄几十兆,加载慢、显存占用高;降低分辨率吧,抬头一看全是马赛克。
所以动态天空盒的核心价值,不是"好看",而是"可信"。它让天空成为一个随时间、随环境变化的活系统,而不是一张贴纸。
1.2 AIGC在这个流程里到底解决了什么
以前做动态天空盒,要么用程序化噪声生成云(比如基于Perlin噪声的体积极云),要么用真实拍摄的多时段全景图序列。前者对Shader功底要求高,调参调到怀疑人生;后者成本高,你得在同一个地点蹲守一整天,还得保证天气条件一致。
AIGC的介入,把"素材获取"这个最耗时的环节压缩了。你可以用文生图模型,输入"清晨薄雾中的温带草原,地平线有积云,天空呈淡金色渐变,全景图,等距柱状投影"这样的提示词,几分钟就能拿到一张可用的全景素材。更关键的是,你可以用同一组提示词,只改时间描述,批量生成清晨、正午、黄昏、夜晚四个时段的图,它们之间的风格一致性远比你去网上东拼西凑要强。
但这里有个必须说清楚的边界:AIGC生成的是"素材",不是"成品"。它给你的是一张静态的等距柱状投影图,你要把它变成动态天空盒,还得靠Unity3D的Shader去做时间混合、云层流动、光照联动。AIGC负责"像",Shader负责"动",两者缺一不可。
1.3 等距柱状投影:全景图的底层逻辑
在动手之前,得先搞明白全景图为什么是那个奇怪的长条形状。等距柱状投影(Equirectangular Projection)是把一个球面展开成平面的方式,横轴对应经度(0到360度),纵轴对应纬度(-90到90度)。所以一张标准的全景图,宽高比必须是2:1,比如4096x2048或者8192x4096。
这个投影有个特点:越靠近上下两端(也就是天顶和天底),图像的横向拉伸越严重。你在全景图顶部看到的那片被拉得老长的云,贴到球面上之后其实是正常比例。理解这一点很重要,因为你在用AIGC生成素材时,如果模型不理解等距柱状投影,它可能会在顶部画一些比例正常的云,贴上去之后就会变得又扁又宽,非常奇怪。
提示:生成全景素材时,提示词里一定要明确写"equirectangular projection"或"360 panorama",否则模型很可能给你生成一张普通的宽幅风景图,贴到天空盒上会严重变形。
2. 用AIGC生成可用的全景天空素材
2.1 提示词的结构化写法
我试过很多种提示词写法,最后总结出一个比较稳的结构:投影方式 + 场景描述 + 时间光照 + 云层形态 + 风格约束。举个例子:
equirectangular 360 panorama, temperate grassland at dawn, thin morning mist near horizon, golden gradient sky, scattered cumulus clouds, soft volumetric light, photorealistic, high detail, 2:1 aspect ratio这里每一段都有作用。"equirectangular 360 panorama"是告诉模型投影方式;"temperate grassland at dawn"定场景和时间;"thin morning mist near horizon"是地平线细节,这个在天空盒里特别重要,因为地平线是玩家视线最常停留的区域;"scattered cumulus clouds"定云的类型,积云比层云更有立体感;"photorealistic"是风格约束,如果你想要卡通渲染风格就换成"stylized"或"anime style"。
我建议一次生成四个时段:清晨、正午、黄昏、夜晚。提示词只改时间部分,其他保持一致。这样四张图的色调和云层风格会有延续性,后面做时间混合时过渡才自然。
2.2 生成参数的取舍
不同工具的参数名不一样,但核心就几个:分辨率、采样步数、提示词引导强度。分辨率我建议至少2048x1024,能上4096x2048更好。有人会问,我用1024x512行不行?行,但贴到天空盒上之后,地平线附近的细节会糊成一片,尤其是云层的边缘,会有明显的锯齿和色块。
采样步数方面,步数越高细节越丰富,但边际收益递减。我实测下来,30到40步是一个比较甜的区间,再往上加,云层的形态不会有质的变化,但生成时间翻倍。提示词引导强度(CFG Scale)建议在7到9之间,太低会偏离提示词,太高会让画面变得过饱和、对比度过强,贴到天空盒上会显得很假。
还有一个容易被忽略的点:种子(Seed)。如果你对某一张生成结果很满意,记下它的种子,然后微调提示词重新生成,可以在保持整体风格的前提下做局部优化。这个技巧在做多时段序列时特别有用。
2.3 后期处理:让素材真正能用
AIGC直接生成的图,通常不能直接拿来用,有几个问题要处理。
第一是接缝。等距柱状投影的左右边缘在球面上是接在一起的,但AIGC生成的图左右边缘往往对不上,贴上去会有一条明显的竖直接缝。处理方法是在图像编辑软件里,把左边缘的一条区域复制到右边缘,用渐变蒙版做融合。更省事的办法是在生成时就让画面左右对称,比如提示词里加"seamless horizontal wrap",但效果不稳定,还是后期处理靠谱。
第二是地平线位置。天空盒的地平线应该大致在图像垂直方向的中间偏下一点,因为人眼习惯的地平线略低于画面中心。如果AIGC把地平线画得太高或太低,需要在后期做裁剪或平移。
第三是动态范围。如果你要做HDR天空盒,需要把生成图转成HDR格式,或者至少在Unity里用HDR材质去提亮天空。普通LDR图直接贴上去,天空会显得灰蒙蒙的,没有那种阳光穿透云层的通透感。
| 处理项 | 常见问题 | 处理方式 |
|---|---|---|
| 左右接缝 | 竖直线明显 | 边缘复制+渐变融合 |
| 地平线位置 | 偏高或偏低 | 裁剪或垂直平移 |
| 动态范围 | 天空发灰 | 转HDR或材质提亮 |
| 顶部拉伸 | 云层变形 | 生成时强调投影方式 |
3. Unity3D里的Shader实现:让天空动起来
3.1 天空盒Shader的基本骨架
Unity3D里自定义天空盒,核心是写一个Shader,然后在场景的Lighting设置里把Skybox Material换成你的材质。天空盒Shader和普通Shader最大的区别是,它渲染的是一个包裹整个场景的球体或立方体,而且通常要关闭深度写入、关闭剔除。
一个最基础的天空盒Shader结构是这样的:在顶点着色器里,把模型空间的顶点方向传出去;在片元着色器里,用这个方向去采样全景图。采样等距柱状投影图需要把方向向量转成UV坐标,公式是:
float2 dirToEquirect(float3 dir) { float u = atan2(dir.z, dir.x) / (2 * PI) + 0.5; float v = asin(dir.y) / PI + 0.5; return float2(u, v); }这个公式是等距柱状投影的核心。atan2算水平角度,asin算垂直角度,然后归一化到0到1的UV范围。理解这个公式,后面做时间混合、云层偏移都靠它。
3.2 多时段混合:让天空随时间过渡
有了四个时段的素材,接下来就是让它们在时间轴上平滑过渡。思路很简单:用一张渐变纹理或者一个时间参数,控制当前应该显示哪个时段,以及在两个时段之间做插值。
我用的方案是用一个_TimeOfDay参数,范围0到1,对应一天24小时。0是午夜,0.25是清晨,0.5是正午,0.75是黄昏。然后根据这个参数算出当前处于哪两个时段之间,以及混合权重。
float4 BlendSky(float timeOfDay, float2 uv) { float scaledTime = timeOfDay * 4.0; int indexA = floor(scaledTime) % 4; int indexB = (indexA + 1) % 4; float blend = frac(scaledTime); float4 colorA = SAMPLE_TEXTURE2D(_SkyTexA, sampler_SkyTexA, uv); float4 colorB = SAMPLE_TEXTURE2D(_SkyTexB, sampler_SkyTexB, uv); return lerp(colorA, colorB, blend); }实际实现时,四个时段的纹理不能都用SAMPLE_TEXTURE2D硬采样,那样会有分支判断,性能不好。更好的做法是用纹理数组(Texture2DArray)或者把四张图拼成一张图集,用UV偏移去采样。我一开始用的是分支判断,在PC上跑没问题,但到了移动端就掉帧,后来改成图集方案才顺畅。
注意:混合的时候不要直接对两张图做线性插值就完事,那样中间过渡会显得很平。可以在插值权重上做一个smoothstep,让过渡的中间段更快、两端更慢,视觉上更接近真实的光线变化。
3.3 云层流动:给天空加一点"活气"
时间混合解决了"变"的问题,但天空还是静的。要让云动起来,有两种思路:一种是让UV随时间偏移,整个天空图缓慢旋转;另一种是单独提取云层,做独立的流动。
第一种方案最简单,在采样UV上加一个时间偏移:
uv.x += _Time.y * _CloudSpeed;但这样有个问题:整个天空图都在动,包括地平线和地面部分,看起来像是天空在转,而不是云在飘。而且等距柱状投影的UV偏移在接缝处会有跳变。
第二种方案更真实,但复杂一些。你需要把云层从全景图里分离出来,可以用AIGC生成一张带透明通道的云层图,或者在Shader里用亮度阈值把云提取出来。然后对云层单独做UV偏移,底层天空保持不动。这样云在飘,天空不动,视觉上就对了。
我实际用的是混合方案:底层天空做极缓慢的旋转(模拟地球自转的错觉),云层做较快的水平流动。两层叠加,效果比单一方案好很多。
3.4 光照联动:天空和场景要"通气"
动态天空盒如果只是自己变,场景里的光照不跟着变,那还是假的。Unity3D里可以通过RenderSettings在运行时动态调整环境光、雾效颜色、方向光强度,让它们和天空盒的时间参数同步。
具体做法是写一个C#脚本,挂在场景里,每帧根据_TimeOfDay去更新:
void UpdateLighting(float timeOfDay) { float sunAngle = (timeOfDay - 0.25f) * 360f; sunLight.transform.rotation = Quaternion.Euler(sunAngle, -30f, 0); float intensity = Mathf.Clamp01(Mathf.Sin(timeOfDay * Mathf.PI)); sunLight.intensity = intensity * 1.5f; RenderSettings.ambientIntensity = Mathf.Lerp(0.2f, 1.0f, intensity); RenderSettings.fogColor = skyMaterial.GetColor("_HorizonColor"); }这段代码的关键是让方向光的旋转角度和天空盒的时间参数对应起来。太阳在天空中的位置,决定了方向光的照射角度,两者必须一致,否则阴影方向会和天空的亮部对不上,一眼假。
雾效颜色从天空盒材质里取地平线颜色,这样远处的地面和天空能自然融合,不会出现地面是灰的、天空是蓝的这种割裂感。
4. 性能优化与移动端适配
4.1 纹理压缩与显存占用
四张4096x2048的全景图,如果不压缩,显存占用是4张 x 4096 x 2048 x 4字节,大约128MB。这在PC上还能接受,在移动端就是灾难。必须做纹理压缩。
Unity3D里常用的压缩格式,PC上用DXT1或DXT5,移动端用ASTC或ETC2。天空盒图通常不需要alpha通道,用DXT1或ASTC 6x6就够了。压缩之后显存占用能降到原来的四分之一到六分之一。
但压缩会带来画质损失,尤其是天空这种大面积渐变的区域,容易出现色带。解决办法是在Shader里加一点抖动(dithering),用噪声打散色带。这个技巧很实用,几行代码就能让压缩后的天空看起来干净很多。
4.2 LOD与分辨率切换
不是所有时候都需要全分辨率。当玩家低头看地面时,天空盒的采样精度可以降低。可以根据摄像机朝向和天空的夹角,动态切换不同分辨率的纹理。抬头看天时用高分辨率,平视或低头时用低分辨率。
这个方案在移动端特别有效,因为移动端玩家的视线大部分时间不在天上。实现上可以用两个材质,根据角度阈值切换,或者用Mipmap的Bias参数动态调整。
4.3 移动端的Shader简化
移动端GPU对复杂Shader很敏感。我一开始把PC上的Shader直接搬到移动端,结果帧率从60掉到30。后来做了几项简化:去掉分支判断,改用图集采样;把浮点精度从float降到half;去掉一些视觉上不明显的效果,比如云层的细节噪声。
还有一点,移动端尽量用SAMPLE_TEXTURE2D而不是tex2D,前者在支持平台上有更好的优化。另外,天空盒的渲染顺序要设对,确保它在不透明物体之前渲染,避免Overdraw。
| 优化项 | PC方案 | 移动端方案 |
|---|---|---|
| 纹理格式 | DXT1/DXT5 | ASTC 6x6 |
| 采样方式 | 分支判断 | 图集+UV偏移 |
| 浮点精度 | float | half |
| 分辨率切换 | 可选 | 强烈建议 |
| 云层细节 | 多层噪声 | 单层简化 |
5. 那些文档里不会写的踩坑记录
5.1 接缝问题比想象中顽固
我一开始以为把左右边缘对齐就完事了,结果贴上去之后,接缝处还是有一条淡淡的竖线。排查了很久才发现,问题出在纹理采样的过滤模式上。当UV接近0或1时,双线性过滤会同时采样到两端的像素,如果两端颜色有细微差异,就会在接缝处形成一条线。
解决办法是在Shader里手动处理接缝区域的采样,或者在导入纹理时把Wrap Mode设成Repeat,让引擎自动处理循环。但Repeat模式在等距柱状投影的顶部和底部会有问题,因为那里是极点,UV会收敛。所以更稳妥的做法是在后期处理时就把接缝做得足够好,让差异小到看不出来。
5.2 时间混合的"鬼影"
做四时段混合时,我遇到过一个诡异的现象:在清晨和正午的过渡中间,天空里会出现一层淡淡的、不属于任何时段的云影。后来发现是因为两张图的云层位置不完全一致,插值的时候云层叠加在一起,形成了"鬼影"。
这个问题的根源在AIGC生成时,四张图的云层分布是独立的,没有关联。解决办法有两个:一是生成时用同一张底图做局部修改,保持云层位置一致;二是在Shader里对云层做单独的混合,底层天空混合,云层只做淡入淡出,不做位置插值。我用的第二种方案,效果可以接受。
5.3 方向光和天空的"时差"
前面提到方向光要和天空同步,但实际做的时候发现,如果方向光的角度完全跟着时间参数走,在清晨和黄昏时,太阳角度很低,方向光几乎平行于地面,场景里的阴影会拉得极长,而且很多物体会被完全背光,看起来一片黑。
真实世界里,清晨和黄昏的光线虽然角度低,但强度并不弱,而且有很强的散射。所以在Shader里,我把方向光的最低强度设了一个下限,不让它降到0,同时增加了环境光的比例,模拟散射效果。这个调整让清晨和黄昏的场景看起来自然多了。
5.4 AIGC素材的"风格漂移"
用同一组提示词生成四个时段,理论上风格应该一致,但实际生成出来,夜晚那张图往往和白天三张的风格差异较大。可能是因为模型对夜晚场景的理解和白天不同,导致色调、对比度、细节程度都有偏差。
我的处理方式是在后期做色彩匹配,用白天图的色调曲线去校正夜晚图。另外,在提示词里对夜晚场景加一些约束,比如"same composition as daytime, only lighting changed",能一定程度上减少漂移。
6. 从跑通到好用:几个提升质感的小技巧
6.1 地平线雾效的叠加
天空盒的地平线区域,如果直接用地平线的像素颜色,会显得很"硬"。真实世界里,地平线附近有一层大气散射形成的雾,颜色比天空略暖,透明度渐变。我在Shader里加了一层额外的雾效,用地平线颜色和一个噪声纹理做混合,让地平线区域有一层柔和的过渡。
这个效果在清晨和黄昏特别明显,能让天空和地面的衔接自然很多。实现上就是在采样天空颜色之后,根据UV的垂直位置,叠加一层带噪声的雾色。
6.2 星空的闪烁
夜晚的天空如果只有静态的星星,会很死板。我在夜晚时段的纹理上叠加了一层微弱的噪声,用时间参数去调制噪声的强度,模拟星星的闪烁。强度要控制得很低,否则会像屏幕噪点。
另外,星星的分布也有讲究。真实的星空,星星的密度和亮度是不均匀的,银河区域更密集。如果AIGC生成的星空太均匀,可以在后期用一张噪声图去调制星星的亮度,让某些区域更亮、某些区域更暗。
6.3 云层的边缘光
当太阳角度较低时,云层的边缘会被照亮,形成一圈金色的轮廓光。这个效果在Shader里可以用云层的梯度去模拟:计算云层亮度和周围天空亮度的差值,在差值大的地方叠加一层暖色。
这个技巧能让云层看起来更有体积感,尤其是在黄昏时段,效果非常明显。实现上需要多采样几次,性能开销略大,建议只在PC端开启。
6.4 时间流速的控制
最后说一个体验层面的细节:时间流速。如果时间流速太快,天空变化像快进,玩家会觉得晕;太慢又感觉不到变化。我实测下来,一个完整的昼夜循环控制在10到20分钟比较合适,具体取决于游戏节奏。如果是休闲类,可以慢一点;如果是生存类,可以快一点。
另外,时间流速最好不是恒定的,清晨和黄昏的变化可以慢一些,正午和午夜的变化可以快一些,这样玩家在视觉最丰富的时段停留更久,体验更好。
这套流程我从头到尾跑了一遍,最大的感受是:AIGC解决了素材问题,但真正让天空"活"起来的,还是Shader里那些看似不起眼的细节——接缝的处理、混合的曲线、光照的联动、雾效的叠加。每一个单独看都不复杂,但组合在一起,才能让玩家抬头看天的时候,不会觉得那只是一张贴图。如果你也在做类似的东西,建议先把基础的时间混合跑通,再逐步叠加云层流动、光照联动、雾效这些效果,不要一上来就追求全功能,那样很容易在某个细节上卡住,失去耐心。