尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
Unity程序化天空球Shader完整拆解:从零打造天空之城风格化场景
“天空之城”这个需求看起来只是搭个背景真正动手时我才意识到最考验人的是头顶那片天。Unity默认的天空球其实不差但一旦场景里出现悬浮岛、云海和纵深光线它立刻露怯云的层次撑不起来、太阳方向和主光对不上、地平线颜色发灰怎么调都少了那种“天空之城”的味道。于是我从Shader到地编从内置管线到URP迁移完整走了一遍自建天空球加配套资源的流程。这篇文章就把这套方案完整拆给你包括技术选型理由、关键Shader代码、浮空岛摆放逻辑、云海粒子参数以及我踩过的接缝、颜色断层和打包翻车坑。适合正做风格化天空场景的开发者也适合刚入门Unity想搞懂天空球原理的朋友。1. 项目拆解天空之城到底由哪些模块组成1.1 天空球在场景中的定位与“天空之城”视觉要素先聊清楚天空球是干嘛的。天空球本质是一个无限远的包围体相机在场景里移动时它不会产生视差所以你看到的永远是同一片天。Unity里Camera挂上Skybox组件或者渲染管线的Volume里指定Skybox类型就能把天空球渲染到场景背景层顺序上它先于所有不透明物体绘制这也是为什么半透明云层放在场景里时要注意和天空球的遮挡关系。“天空之城”的视觉核心我拆成了四块垂直方向上的悬浮岛群、脚下和远方的云海、干净而有层次的天穹渐变、一个能和场景阴影方向对齐的太阳。难点不是单块内容而是四者之间的匹配。你单独调岛没问题单独调天空也没问题合在一起就容易崩——太阳方向对不上、云海吃光严重、地平线被浮空岛切出硬边。所以我的思路是把天空球当作整个场景的光照与色调基准先定天再定地。这里有个很多人忽略的点内置渲染管线中环境光默认会从天空球采样。也就是说改变天空球颜色等于同时改了场景阴影里的冷暖和漫反射基础色。我在项目中踩过一回天空球调成了浓橙色黄昏但场景里的平行光和阴影温度没跟上去结果亮部是夕阳、暗部是白日光画面非常脏。后面我会讲怎么把主光、环境光和天空球做联动。1.2 四种天空球方案的取舍程序化、六面贴图、全景HDR、自定义ShaderUnity里做天空球有几种常见路子我按实际项目的可控程度排一遍。第一种是内置程序化天空盒。参数少调整快适合临时预览和写实风格的地面场景。缺点很明确做不了多层云做不了艺术化色带太阳圆盘也不能自由控制。如果你要的是“天空之城”这种高度风格化氛围它基本撑不住。第二种是六面贴图天空盒。把相机放在一个立方体中心渲染前后左右上下六张图再拼成天空盒。好处是美术可控性极强可以雕刻出非常复杂的云层和光线适合固定机位或第三人称视角。坏处是做成时间变化非常麻烦六张图要逐一替换或做插值资源体积也不小。而且天空盒贴图方向搞错的话接缝问题会让人抓狂。第三种是全景HDR贴图。用高动态范围的全景图做天空球光影信息丰富配合后处理很容易出效果。缺点和六面贴图类似重量大动画难做网上很多免费全景图还有暗角或压缩痕迹放大后云层边缘容易糊。第四种就是我这篇重点讲的——自定义Shader程序化天空球。不依赖复杂贴图用方向向量直接在GPU里算颜色、云层和太阳光晕所有效果都是运行时参数可以随便做昼夜循环、云层运动和风格化渐变。资源体积几乎为零调参效率极高。我最终选了第四种搭配少量云噪声贴图是因为这个方案能同时满足三个项目刚需昼夜循环、动态云、从黄昏到夜幕的色调联动。下面详细讲实现。2. 程序化天空球Shader从零手写一套可调参数的天穹2.1 Shader骨架用方向向量画天穹而不是贴图先给一个能跑起来的基础框架。这个Shader不贴常规UV纹理而是把每个片元的“世界方向向量”当作输入再根据这个方向计算颜色。Shader Custom/SkyCitySky { Properties { _ZenithColor(天顶颜色, Color) (0.2, 0.45, 0.9, 1) _HorizonColor(地平线颜色, Color) (0.95, 0.9, 0.8, 1) _GroundColor(地面下方颜色, Color) (0.55, 0.5, 0.4, 1) _SunDir(太阳方向, Vector) (0.4, 0.7, -0.3, 0) _SunColor(太阳颜色, Color) (1, 0.95, 0.8, 1) _SunRadius(太阳半径, Range(0.001, 0.1)) 0.008 _SunSoftness(太阳边缘柔化, Range(0.0001, 0.5)) 0.004 _CloudTex(云噪声贴图, 2D) white {} _CloudColor(云颜色, Color) (1, 1, 1, 1) _CloudScale(云层缩放, Range(0.5, 10)) 2.5 _CloudCoverage(云量, Range(0, 1)) 0.4 } SubShader { Tags { QueueBackground RenderTypeBackground PreviewTypeSkybox } Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include UnityCG.cginc struct appdata { float4 vertex : POSITION; }; struct v2f { float4 pos : SV_POSITION; float3 worldDir : TEXCOORD0; }; v2f vert (appdata v) { v2f o; o.pos UnityObjectToClipPos(v.vertex); o.worldDir mul((float3x3)unity_ObjectToWorld, v.vertex.xyz); return o; } half4 frag (v2f i) : SV_Target { half3 dir normalize(i.worldDir); half h dir.y; // 垂直方向渐变地平线到天顶 half3 grad lerp(_GroundColor, _HorizonColor, smoothstep(-0.05, 0.0, h)); grad lerp(grad, _ZenithColor, pow(saturate(h), 0.6)); // 太阳圆盘 half3 sunDir normalize(_SunDir.xyz); half sunDot dot(dir, sunDir); half sun smoothstep(_SunRadius, _SunRadius _SunSoftness, sunDot); half3 col grad sun * _SunColor; // 云层将方向向量映射到采样坐标 half2 cloudUV dir.xz * 0.5 0.5; half clouds tex2D(_CloudTex, cloudUV * _CloudScale).r; col lerp(col, _CloudColor * grad, clouds * _CloudCoverage); return half4(col, 1.0); } ENDCG } } }这段Shader的核心逻辑只有三步用dir.y做地平线渐变用点乘做太阳圆盘用方向向量的xz分量采样云噪声。把这样的Shader挂在材质上再把这个材质拖到Camera的Skybox或者URP的Volume里Skybox覆盖层场景立刻变成一个可调参数的天穹。有些初学者会在这里犯迷糊为什么顶点着色器里不用常规的MVP模型变换因为天空球不能跟着相机平移走否则你往前走走天也跟着动完全穿帮。做法是让天空球始终围绕相机只旋转不位移。使用ObjectToWorld矩阵只是为了把立方体顶点的方向转到世界空间相机永远在中心所以不需要做平移。2.2 云层、太阳光晕、昼夜循环的联动逻辑基础Shader能看见天穹了但要撑起“天空之城”的氛围还得加三块内容。第一块是云层运动。上面代码里我直接取dir.xz做UV这会导致云在天顶附近拉伸变形。想要更好效果可以加一层球面旋转再做采样float2 rotateUV(float2 uv, float2 pivot, float angle) { float2 delta uv - pivot; float c cos(angle); float s sin(angle); return float2(delta.x * c - delta.y * s, delta.x * s delta.y * c) pivot; } float2 cloudUV rotateUV(dir.xz * 0.5 0.5, 0.5, _CloudAngle); half clouds tex2D(_CloudTex, cloudUV * _CloudScale).r;_CloudAngle由C#脚本按时间累加云就会整体转起来。这个方案比用_Time.y直接加在采样坐标上更自然能避免云在边缘被撕扯。第二块是太阳光晕。太阳圆盘做完之后我习惯再叠一层非常宽的高光half glow pow(saturate(sunDot), _GlowPower); col _SunGlowColor * glow * _GlowIntensity;_GlowPower在3到8之间会比较柔和做黄昏时把_GlowIntensity拉高可以模拟大范围晚霞。这个方法在移动端也能跑因为只是几次幂运算。第三块是昼夜循环。我写了一个C#脚本按时间插值天空球材质参数和主光参数public class SkyTODController : MonoBehaviour { public Material skyboxMaterial; public Light mainLight; public Gradient zenithGradient; public Gradient horizonGradient; public Gradient lightGradient; [Range(0f, 1f)] public float dayTime 0.5f; private void Update() { skyboxMaterial.SetColor(_ZenithColor, zenithGradient.Evaluate(dayTime)); skyboxMaterial.SetColor(_HorizonColor, horizonGradient.Evaluate(dayTime)); skyboxMaterial.SetVector(_SunDir, mainLight.transform.forward); mainLight.color lightGradient.Evaluate(dayTime); RenderSettings.ambientLight horizonGradient.Evaluate(dayTime) * 0.3f; } }注意我把天空球太阳方向设成和主光Transform.forward一致。这一步非常关键如果天空球上的太阳位置和场景平行光方向不一致你会发现物体阴影指向一个方向太阳高光却从另一个方向亮过来观感极差。现在这样做无论你白天转到夜晚阳光角度和阴影方向永远同步。3. 天空之城地编实操浮空岛、云海与光照匹配3.1 悬浮岛建模与快速摆放工作流天空球的底层逻辑搭完后进入地编环节。悬浮岛我试过三种做法Unity内置的Terrain导出切块、ProBuilder手动拉、Blender雕刻。最终项目里用的是Blender雕刻加导入原因是悬浮岛需要很强的“上平下尖”轮廓Terrain切块容易保留底部切片边缘ProBuilder手动拉又太慢。条件有限但也想快速出效果的话可以用ProBuilder做分段圆锥顶部面拉成不规则圆形加几个环切再随意挪动顶点做出起伏底部拉出一个尖锥。关键是底部要比顶部窄很多视觉上才像“被拔起来的一整块地皮”。顶部给一点草绿或石板色底部用深灰岩石色色阶拉开后空气感马上就出来了。摆放规则我总结三条。第一竖向上“大岛在下、小岛在上”。大块头放底部能压住构图小块头悬浮在上方增加层次不会显得头重脚轻。第二横向错开纵深让摄像机在不移动只旋转的情况下能看到前后多层岛屿穿插云海从缝隙里露出来。第三远离主视觉点的小岛可以用极低模替代不加碰撞体不投阴影只用来填充远景密度。每个岛我是以Prefab组织的Prefab根节点挂一个空的“IslandAnchor”下面挂Mesh和Collider。需要做漂浮动画时让IslandAnchor沿Y轴做正弦运动就好网格和碰撞体一起动不会出现角色踩在岛边、岛却原地不动的异步问题。3.2 多层云海方案对比与粒子调优云海是“天空之城”最容易出氛围的地方也是最容易翻车的地方。我试过三种方案直接给结论。方案A是平面贴图云层。把一张大云贴图铺在场景底部的几个半透明面上随相机缓慢移动位置。优点是开销极低缺点是视角一旦压低平坦的贴图云海立刻穿帮只能做远景。方案B是粒子云海。新建ParticleSystemRender Mode设为Billboard材质使用带Alpha的云噪声纹理Emission在每秒30到80之间。给粒子加上一点Size Over Lifetime让云从出生到消散逐渐膨胀、变透明整体看就像云海在流动。粒子云最大好处是能靠在场景中近景和远景之间相机移动时依然有立体感。方案C是体积云用光线步进逐像素采样三维噪声。效果确实顶级但开销巨大手机项目慎用。我自己只在PC端Demo里试过美术完成后帧数直接掉了近一半最后还是换回了粒子加面片混合。最终我采用的是“天空球底部渐变 外围面片云海 中近景粒子”的三层组合天空球GroundColor承担最远处的雾霭基础色面片云海负责中远景的成片云层粒子云在玩家身边制造漂浮感。这样摄像机不管往哪个方向看云海都有连续性和体量感。粒子调优有几个容易忽略的参数Start Lifetime建议在6到12秒太短云会在眼前瞬间消失Size Over Lifetime曲线要“小到大再到小”中间平台期稍长这样云的寿命大部分时间处在可见状态材质的Alpha通道要柔和硬边云贴图会在一远一近之间出现明显的“纸片感”。3.3 光照、环境色与后处理调色一揽子方案这节是很多项目效果“土”和“透”的分水岭。天空球做完后需要回头把所有光照参数对齐。方向光是场景的主光我在脚本里已经把它的forward同步到天空球太阳方向。在此基础上阴影强度建议在0.8到1之间太软会损失悬浮岛的体积感太硬又和云海柔光矛盾。阴影距离按场景纵深设置常见问题是想让远景岛也投影结果阴影距离拉得很大内存和Draw Call一起涨。环境光注意一下内置管线里Legacy 3D项目的Ambient Mode默认是Skybox天空球程序化颜色会持续驱动环境漫反射URP里通常要手动设置Environment Lighting的Source把Skybox作为环境光来源或直接Specified Gradient。冷暖要跟着天色走——白天天顶偏蓝、地平线偏暖黄昏则整体向橙色偏移同时把主光Color也调暖场景里的反射和阴影才会统一。后处理我用的方案是Bloom加Color Grading加TonemappingBloom Threshold放到1.2左右这样只有太阳、云层亮部和岛缘高光会泛光不会把整片天空糊成一片Color Grading的饱和度和对比度各加15到25让云海和岛屿轮廓更清晰Tonemapping选中性或ACES对应风格看具体需求如果是梦幻天空之城风格ACES曲线偏微妙的暖感会更有味道。4. 踩坑实录天空球项目中常见的六类问题4.1 接缝、摩尔纹和颜色断层为何会出现程序化天空球只要Shader逻辑对本身没有贴图接缝问题。但如果你在云层采样时用了常规2D纹理问题就来了方向向量的xz或球面UV在接缝处会跳变云层出现一条突变的竖线。云噪声贴图的Wrap Mode一定要选Repeat然后做一层模糊处理云的低频部分跨缝隙过渡才会自然。如果仍然裂开把采样从dir.xz换成完整的球面UV公式float2 sphericalUV(float3 dir) { float u atan2(dir.z, dir.x) / 6.28318 0.5; float v asin(clamp(dir.y, -1.0, 1.0)) / 3.14159 0.5; return float2(u, v); }不过atan2和asin在Fragment里性能开销稍高移动端尽量少用可以只在PC端或高端机开启。摩尔纹通常来自云层贴图分辨率过高或者细节太锐利。云本身就是模糊大块的形体不需要高清贴图。我把云噪声图控制在256乘256开启MipMap和Bilinear过滤反而比2048贴图更不容易闪烁。记住原则云层贴图要“低频要足够低”让每一处像素变化都缓慢平滑。颜色断层就不离谱了天顶到地平线如果是一个平滑但单调的渐变在8位色深的移动屏幕上会看到明显的色带。解决办法是在天空球Shader输出前叠加一点Dithering抖动float dither (frac(sin(dot(i.pos.xy _Time.y, float2(12.9898, 78.233))) * 43758.5453) * 2.0 - 1.0) / 255.0; col dither;这样渐变带被打散肉眼看过去是自然的颗粒感而不是一条条色阶。这个技巧我在PC和手机上都用过效果稳定。4.2 URP管线迁移与Shader改写方向如果你的项目用的是URP上面那段CGPROGRAM代码不能直接跑。URP的Shader库和内置管线不同主要改动有三处。第一处是把CGPROGRAM换成HLSLPROGRAM把include从UnityCG.cginc换成Universal RP的Core.hlsl第二处是顶点变换函数从UnityObjectToClipPos换成TransformObjectToHClip第三处是在SubShader的Tag里声明UniversalPipeline让URP正确识别这个Pass。SubShader { Tags { RenderPipelineUniversalPipeline QueueBackground RenderTypeBackground } Pass { HLSLPROGRAM #pragma vertex vert #pragma fragment frag #include Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl struct Attributes { float4 positionOS : POSITION; }; struct Varyings { float4 positionHCS : SV_POSITION; float3 worldDir : TEXCOORD0; }; Varyings vert(Attributes input) { Varyings output; output.positionHCS TransformObjectToHClip(input.positionOS.xyz); output.worldDir TransformObjectToWorld(input.positionOS.xyz); return output; } ... } }URP里挂自定义天空球的方式也有点不一样不是直接把材质拖到Camera的Skybox组件而是在场景Volume里添加Skybox Override然后把材质指定给它。很多人刚迁过来时找不到在哪设置卡了半天这里一并说清。另外注意如果在URP下你同时用了Linear颜色空间天空球程序化渐变会比内置管线更亮一些这是正常的因为Shader计算在Linear空间里进行颜色值不再被二次校正。后面调色时别被这个差异带偏按“最终输出看起来OK”而不是“数值和原来一致”为准。4.3 资源导入规范与Build打包避坑天空之城资源不算重但打包规范不做好Build阶段会出各种莫名其妙的问题。云噪声贴图要避免用PNG存高动态范围数据PNG只支持8位固定范围。太阳和云层这种需要高亮区域的贴图用TGA或EXR存16位。贴图格式上按平台选择Android用ASTCiOS用ASTC也行PC用BC7。导入设置里把MipMap开启除了云层这种大面积模糊低频内容低频图开MipMap有助于减少远处摩尔纹。Shader Variant是另一个坑。如果你的天空球Shader没有任何材质在场景中引用或者引用了但Shader从未进入构建时编译列表Build出来天空球会以洋红错误显示。保险做法是把这个Shader加进Project Settings的Graphics Settings里的Always Included Shaders列表无论它有没有被引用构建一定带上它。还有一个和资源无关但十有八九会发生的Build坑场景Far Clip Plane太小。天空之城场景纵深大相机默认能见距离只有1000米左右远处的悬浮岛会被裁掉玩家视角边缘出现“天空一半、岛屿截断”的断层。Camera的Far Clip在飞行类或开放场景里最好给到2000到3000阴影距离不必跟着拉满否则阴影贴图精度会被摊薄。我一般在主相机上单独放一个组件运行时强制设置Far和Fixed Shadow Distance。5. 把天空球方案复用到更多风格与玩法5.1 参数化改造从黄昏到赛博朋克的四种天空程序化天空球最大的优势是复用性极强。一套Shader改几组参数就是完全不同的世界氛围。做黄昏古堡时我把天顶颜色改成深紫红地平线色改成橙金色太阳半径调大光晕强度提高到2.5云层覆盖率降到0.2。出来的效果像是太阳正在坠入云海古堡轮廓在逆光里变成剪影氛围一下就成立了。做清晨群岛时天顶是淡蓝青色地平线是浅玫瑰色主光稍微冷一些太阳半径缩小、边缘柔化调大云层覆盖率调到0.5。整个场景会让玩家觉得空气中带着湿度太阳刚升起还没完全刺眼。做赛博朋克风格时我把天空球跑到了另一个极端地平线用亮青色天顶用深蓝黑太阳方向其实已经没有自然意义了我把它当成一个“发光体”用配合大量粒子光点和Bloom造出人造天际线的感觉。反正它的本质就是一个始终跟随相机的着色特效你完全可以把“太阳”当成“月球”、“巨型广告牌”、“能量潮汐”等任意发光对象。做暴风雨前兆时云量拉到0.9云颜色往灰蓝色靠太阳方向压得很低光晕强度降下来同时把GroundColor调成暗棕色。再加上后处理压暗高光场景就有一种山雨欲来的压迫感。5.2 天空球与玩法机制的联动示例最后分享一个我实际做过的小联动。在一个飞行玩法原型里玩家控制一艘小船在空中移动我用世界方向向量给云层加了一个偏移值玩家向前飞时云层UV整体向身后偏移模拟穿云的视觉阻挡玩家绕圈时云层角度也跟着改变。实现上只是每帧向Shader传一个由输入方向计算的偏移量却给飞行带来了很强的速度感和“世界在动”的沉浸感。另一个玩法联动是昼夜对战斗的影响。夜晚时把天空球参数切到低亮度同时降低场景中的可视距离怪物的警戒范围扩大天亮后天空球回到明亮状态AI警戒范围缩小。这套逻辑通过一个全局时间变量驱动天空球、主光、AI状态都从这里读值不会出现某一系统不同步的问题。天空球在这里不只是背景而是整个游戏循环的节奏开关。我个人做类似项目时的体会有两点一是别迷信复杂贴图和成品资源程序化天空球虽然一开始写起来有点门槛但它让你对画面的每个像素都有控制权调参调得多了一眼就知道当前问题出在天空还是地编二是把参数集中在一个脚本里管理不要在Shade、Material、Light组件各处撒否则项目稍微改版参数对不齐的情况一定会再次出现。这套方案在我手里的多个风格化场景里反复用过改动成本很低效果一直很稳。
RELATED

相关推荐

AI论文写作翻车?从大模型原理到提示词公式,手把手教你调教AI

AI论文写作翻车?从大模型原理到提示词公式,手把手教你调教AI

前几天有个朋友发来一段AI写的论文初稿,气呼呼地问我:为什么自己让AI写的论文一眼假,全是“正确的废话”?我看了他给AI的那条指令,只有五个字:帮我写篇论文。我当场就乐了——这真不能全怪AI,换…

📅 2026/10/19 2:30:30
Windows绿色软件部署全指南:从压缩包到开始菜单

Windows绿色软件部署全指南:从压缩包到开始菜单

经常有朋友和同事问我,Windows下绿色软件到底怎么部署最省心。他们的处境五花八门:公司电脑没有管理员权限,想用的工具却不少;自己机器装了上百个软件,重装系统后要折腾一整天才能恢复;还有人想把常用工具放…

📅 2026/10/19 2:30:30
加固交换机与工业交换机有何不同?JSW-8016G4硬件设计、环网协议与现场部署指南

加固交换机与工业交换机有何不同?JSW-8016G4硬件设计、环网协议与现场部署指南

前些日子跟一个做风电运维的朋友聊起站点网络改造,他说入冬之后箱变里面那台商用交换机已经换了两次电源模块,第三次干脆开不了机。后来换成加固交换机,一个冬天过去连端口都没掉过链子。这让我想起手头经常接触的 JSW-8016G4 这类加固交换机…

📅 2026/10/19 2:30:30
MORE NEWS

更多资讯

📰

LangChain实战指南:从核心抽象到企业知识库问答落地

我最早接触LangChain框架时,态度其实是有些抵触的。当时我刚用原生提示词做了一个内部工单自动分类的原型,把所有工具说明、输出规则全部塞进了一段将近三千字的提示词里,靠着正则和反复调整措辞勉强跑通。结果过了不到两周,需求一…

📰

Lambda架构实战:批处理与流处理融合的实时数据平台设计

1. 为什么我们绕不开Lambda架构第一次接触Lambda架构是在一个用户行为分析项目里。当时业务方要求:既要看实时在线人数和分钟级转化率,又要跑过去一整年的历史数据做同期对比。团队一开始想用纯流式方案硬扛,结果发现历史数据回溯时状态大得离…

📰

Spring Boot招聘数据可视化系统开发实战:从数据采集到可视化大屏

做完一套基于 Spring Boot 的招聘数据可视化系统,被我身边的同学拿去当毕业设计,前后折腾了两三个星期。今天不打算写那种“课程设计说明书”式的文章,就纯粹从实操角度,把这类系统从需求分析、技术选型、数据库设计、可视化实现到…

📰

AI生成PPT效率提升300%:职场人必看的Aipy实操指南

职场人必看:我用Aipy把PPT效率提升了300%先说结论:PPT做得慢,从来不是手速问题,而是决策问题。找模板要决策、排版对齐要决策、图表样式要决策、字体配色要决策,每一步都在消耗时间。我用了Aipy以后,最大的…

📰

d3dx9_42.dll丢失怎么办?官方DirectX运行库修复方法详解

“我们的系统经常出现d3dx9_42.dll丢失问题 免费下载方法分享”这个标题我盯着看了很久。说实话,几乎每周都能在群里、论坛里、甚至同事的工位上看到类似的求助。有人直接甩一个“dll下载网站”的链接,有人让重装系统,还有人干脆把整个游戏删…

📰

PFC2D热力耦合岩石热损伤模拟:从颗粒流代码到参数标定

简介:这份资源面向岩石力学、地下工程与地热开发方向的科研人员和研究生,提供PFC2D5.0颗粒流离散元在热力耦合作用下岩石热损伤模拟的案例代码与解析材料,适合已具备离散元基础、希望深入掌握热力耦合建模的中高级学习者。压缩包共10个文件&a…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬