【光照】UnityURP[天空盒]原理与[动态天空盒]实现
【从UnityURP开始探索游戏渲染】专栏-直达
技术原理与核心机制
- 立方体贴图映射:天空盒本质是包裹场景的立方体纹理映射,通过六个面的HDR图像(前、后、左、右、上、下)构成全景环境。URP渲染管线中,天空盒被定义为无限远的背景,始终跟随摄像机移动但不受视锥体裁剪。
- 光照交互:天空盒直接影响全局光照计算,其颜色和亮度参与环境光遮蔽、反射探针等计算。动态天空盒通过调整太阳高度角(
mainLight.direction.y
)实现昼夜交替的光照变化。 - 程序化生成:URP支持通过Shader代码动态生成天空盒,例如使用
smoothstep
函数平滑过渡昼夜状态,基于worldDir.y
计算天顶与地平线渐变颜色(如lerp(_DayBottomColor, _DayTopColor, verticalPos)
)。
发展历史关键节点
- 静态天空盒阶段:早期Unity仅支持预烘焙的立方体贴图,需手动配置六张纹理。
- 动态天空盒引入:2018年URP管线加入程序化天空盒支持,允许通过代码实时调整天空参数。
- HDRP/URP分化:2020年后,URP优化了移动端性能,采用简化版大气散射模型,而HDRP保留物理精确模拟。
解决的问题
- 性能优化:相比传统3D天空模型,天空盒仅消耗1次绘制调用。
- 环境一致性:确保远距离背景与光照系统同步(如昼夜切换时环境光自动适配)。
- 艺术控制:支持HDR图像和程序化参数(如
_Exposure
曝光值)调整氛围。
URP实现示例
以下动态天空盒Shader关键代码实现昼夜交替:
hlsl
// 计算太阳高度状态(0=深夜,1=正午)
float sunNightStep = smoothstep(-0.3, 0.25, _MainLight.direction.y);
// 天空颜色分层混合
float3 skyColor = lerp(_NightColor, _DayColor, sunNightStep);
// 地平线光晕效果
float horizonGlow = pow(saturate(1 - absY), _HorizonSharpness);
动态天空盒完整实现
核心实现架构
Shader基础结构
使用URP的Unlit Shader模板,定义天空球体顶点着色器计算世界空间坐标,片段着色器实现颜色混合逻辑。关键参数包括:
hlsl
float3 _SunDirection;
float4 _DayColor, _NightColor;
float _HorizonSharpness;
昼夜控制机制
通过
_SunDirection.y
值判断昼夜状态,结合smoothstep
函数实现平滑过渡。太阳位置由主光源方向控制,月亮位置取反方向。
完整代码实现
DynamicSkybox.shader
Shader "URP/DynamicSkybox"
{
Properties {
_SunTex ("Sun Texture", 2D) = "white" {}
_MoonTex ("Moon Texture", 2D) = "white" {}
_DayTopColor ("Day Top", Color) = (0.37,0.74,1,1)
_DayBottomColor ("Day Bottom", Color) = (0.89,0.96,1,1)
_NightExposure ("Night Exposure", Range(0,5)) = 1
} SubShader {
Tags { "Queue"="Background" "RenderType"="Background" } 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 positionCS : SV_POSITION;
float3 positionWS : TEXCOORD0;
}; Varyings vert(Attributes v) {
Varyings o;
o.positionCS = TransformObjectToHClip(v.positionOS.xyz);
o.positionWS = TransformObjectToWorld(v.positionOS.xyz);
return o;
} float3 _SunDirection;
sampler2D _SunTex, _MoonTex;
float4 _DayTopColor, _DayBottomColor;
float _NightExposure; half4 frag(Varyings i) : SV_Target {
float3 viewDir = normalize(i.positionWS);
float sunDot = saturate(dot(viewDir, _SunDirection));
float nightFactor = smoothstep(0.1, -0.1, _SunDirection.y); // 天空颜色混合
float verticalPos = saturate(viewDir.y * 0.5 + 0.5);
float3 daySky = lerp(_DayBottomColor, _DayTopColor, verticalPos);
float3 nightSky = _NightColor * _NightExposure;
float3 skyColor = lerp(daySky, nightSky, nightFactor); // 太阳/月亮绘制
float sunMask = step(0.999, sunDot);
float moonMask = step(0.999, -sunDot);
float4 celestialBody = sunMask * tex2D(_SunTex, i.uv) +
moonMask * tex2D(_MoonTex, i.uv); return float4(skyColor + celestialBody.rgb, 1);
}
ENDHLSL
}
}
}
SkyboxController.cs
using UnityEngine;
using UnityEngine.Rendering; public class SkyboxController : MonoBehaviour {
[SerializeField] private Light _mainLight;
[SerializeField] private Material _skyboxMaterial;
[SerializeField] private float _dayDuration = 120f; private float _currentTime; void Update() {
_currentTime += Time.deltaTime / _dayDuration;
_currentTime %= 1f; // 计算太阳高度角(0-1对应日出到日落)
float sunAngle = Mathf.Lerp(-0.5f, 1.5f, _currentTime);
_mainLight.transform.rotation = Quaternion.Euler(sunAngle * 180f, 0, 0); // 更新Shader参数
_skyboxMaterial.SetVector("_SunDirection", _mainLight.transform.forward);
RenderSettings.skybox = _skyboxMaterial; // 动态调整光照强度
float lightIntensity = Mathf.Clamp01(sunAngle * 2f);
_mainLight.intensity = lightIntensity;
}
}
CloudNoise.shader
// 云噪声生成Shader需单独实现
Shader "URP/CloudNoise" {
Properties { _NoiseScale ("Noise Scale", Float) = 1 }
SubShader {
// 云噪声生成逻辑...
}
}
关键实现细节
太阳轨迹计算
- 通过
Mathf.Lerp(-0.5f, 1.5f, _currentTime)
实现太阳从地平线下升起再落下的完整周期,y值小于0时进入夜晚阶段。
- 通过
性能优化技巧
- 使用
step()
替代if
判断天体可见性 - 通过
lerp
实现颜色平滑过渡避免突变 - 云层采用分形噪声算法降低采样次数
- 使用
天气系统集成
可扩展
_WeatherDensity
参数控制云层厚度,结合_RainIntensity
实现雨天效果,通过材质参数动画控制天气过渡。
配置流程
- 创建URP渲染管线资产
- 将DynamicSkybox.shader赋给天空盒材质
- 绑定主方向光到SkyboxController脚本
- 在Lighting窗口设置环境光源模式为Skybox
该方案支持实时昼夜循环、动态天气切换,在移动端可保持60FPS以上性能。如需更复杂效果可集成Altos插件实现体积云等高级特性
配置流程
资源准备:
- 导入HDR全景图(如PolyHaven免费资源)或六面体纹理。
材质创建:
- 选择
Skybox/Procedural
类型,绑定至Lighting窗口的Environment
面板。
动态控制:
通过C#脚本修改
RenderSettings.skybox
材质属性,如:csharp
RenderSettings.skybox.SetFloat("_Rotation", Time.time * 0.1f);// 自动旋转
该技术显著提升了开放世界游戏的时空表现力,同时保持移动端高性能。现代URP进一步整合了云层扰动、大气散射等效果,扩展了程序化生成的可能性.
【从UnityURP开始探索游戏渲染】专栏-直达
(欢迎点赞留言探讨,更多人加入进来能更加完善这个探索的过程,)
【光照】UnityURP[天空盒]原理与[动态天空盒]实现的更多相关文章
- DirectX11 With Windows SDK--23 立方体映射:动态天空盒的实现
前言 上一章的静态天空盒已经可以满足绝大部分日常使用了.但对于自带反射/折射属性的物体来说,它需要依赖天空盒进行绘制,但静态天空盒并不会记录周边的物体,更不用说正在其周围运动的物体了.因此我们需要在运 ...
- Hibernate学习--hibernate延迟加载原理(动态代理)
在正式说hibernate延迟加载时,先说说一个比较奇怪的现象吧:hibernate中,在many-to-one时,如果我们设置了延迟加载,会发现我们在eclipse的调试框中查看one对应对象时,它 ...
- spring5——Aop的实现原理(动态代理)
spring框架的核心之一AOP,面向切面编程是一种编程思想.我对于面向切面编程的理解是:可以让我们动态的控制程序的执行流程及执行结果.spring框架对AOP的实现是为了使业务逻辑之间实现分离,分离 ...
- mybatis源码分析(5)-----拦截器的实现原理(动态代理+责任链)
写在前面 MyBatsi 的拦截器模式是基于代理的代理模式.并且myBatis 的插件开发也是以拦截器的形式集成到myBatis 当中. MyBatis 的拦截器已经插件是在org.apache.ib ...
- AOP底层实现原理,动态代理如何动态
代理 指定另外一个主体代替原来的某个主体去执行某个事物 代理执行的人 需要代理的人 需要代理的事情是一定要做的 但是被代理的人没有时间或自己做的不专业 静态代理: 父母朋友帮忙物色找对象 代理人掌握需 ...
- 【redis】redis底层数据结构原理--简单动态字符串 链表 字典 跳跃表 整数集合 压缩列表等
redis有五种数据类型string.list.hash.set.zset(字符串.哈希.列表.集合.有序集合)并且自实现了简单动态字符串.双端链表.字典.压缩列表.整数集合.跳跃表等数据结构.red ...
- Redis核心原理-简单动态字符串SDS
SDS简介 Redis是C语言编写的,但没有使用c语言的字符串结构,而是自己实现了一套简单动态字符串 simple dynamic string 简称SDS,SDS兼容C语言的字符串类型,原理类似Ja ...
- 30个类手写Spring核心原理之动态数据源切换(8)
本文节选自<Spring 5核心原理> 阅读本文之前,请先阅读以下内容: 30个类手写Spring核心原理之自定义ORM(上)(6) 30个类手写Spring核心原理之自定义ORM(下)( ...
- Mybatis映射原理,动态SQL,log4j
1.理清mybatis中的#和$之间的区别? #{ }:表示一个预处理参数,参数类型不定,是根据传入的参数类型来设定的. 类似于JDBC中的? 特例使用,模糊查询:(针对oracle): and us ...
- dubbo实现原理之动态编译
Dubbo为了实现基于spi思想的扩展特性,特别是能够灵活添加额外功能,对于扩展或则策略选择的设配类能够动态生成.对于一些需求已知的类如Protocal,它们的设配类代码dubbo可以直接的提供,但是 ...
随机推荐
- Day1 备战CCF-CSP练习
Day 1 201403-1 题目描述 有 \(N\) 个非零且各不相同的整数.请你编一个程序求出它们中有多少对相反数(\(a\) 和 \(-a\) 为一对相反数). 输入格式 第一行包含一个正整数 ...
- 【Spring三级缓存解密】如何优雅解决循环依赖难题
引言 在Spring框架的日常开发中,循环依赖问题如同一个幽灵,时不时困扰着开发者.当Bean A依赖Bean B,而Bean B又依赖Bean A时,传统的创建流程会陷入死锁.本文将深入剖析Spri ...
- SciTech-Mathmatics-Probability+Statistics: Assumptions, $\large P-value$, $\large overall\ F-value$ and $\large Null\ and\ Alternative\ Hypothesis$ for $\large Linear\ Regression\ Model$
Null and Alternative Hypothesis for Linear Regression Linear regression is a technique we can use to ...
- SSL/TLS的认证和加密问题
基本概念 TLS TLS(Transport Layer Security) 是保证数据在互联网上安全传输的加密协议:保证数据在传输的过程中中间的人无法解密,无法修改.TLS 要解决的问题就是,能证明 ...
- 搜索&记忆化(重复子问题,逻辑相同)
中序遍历:左儿子,我,右儿子 点击查看代码 void dfs(int u) { if(u > n) return ; dfs(u + 1); cout << u << e ...
- Archlinux Gnome桌面下Codeblocks无法运行的解决方案之一
如题,之一是因为造成编译运行报错的原因之一 首先我们通过pacman或者yay装codeblocks是不会带xterm的(像debian系的就会),xterm是什么呢?就是Setting->En ...
- AtCoder Beginner Contest 417 (A-E题解)
比赛链接 总体总结 A,B题纯模拟 C题 公式一步转换 D题 五题里面最难的,dp预处理+二分 E题 排序+BFS 题解 A题 A Substring #include <bits/stdc++ ...
- python3表达式(匹配分割URL)
1.例子:匹配分割URL 反向引用还可以将通用资源指示符 (URI) 分解为其组件.假定您想将下面的 URI 分解为协议(ftp.http 等等).域地址和页/路: http://www.run ...
- MAUI学习记录
.NET MAUI学习笔记 参考文档 需创建新项目,可以点击上面的链接,进行查看 CommunityToolkit.Mvvm 官网文档 本次学习使用的是8.0.0版本,进行学习基础知识 第一步:功能实 ...
- 个人主页V1.1
简单的个人主页 参考了Github上一些开源项目资源 预览: live at https://yukirinll.github.io/Personal-Profile/ 预览图: 代码: https: ...