MIT 许可证 NuGet

首页 / 文档 / 光照与天空

光照与天空

光照是一个你可以添加的列表和一个你可以配置的天空。两者每帧装配进 一个所有通道都读取的单一缓冲区,所以阴影、环境光遮蔽、全局光照和 天空本身不可能对太阳的位置各执一词。

添加一个光源

BaseApp.Lighting 是一个 SceneLighting 实例:一个 LightSource 对象列表加上一个常量环境项。对象出现时添加光源, 对象离开时移除它们 — 没有逐帧重建可做。

Lighting.Ambient = new Vector4(0.12f, 0.13f, 0.16f, 1f);

Lighting.Add(new LightSource
{
    Kind = LightKind.Spot,
    Name = "porch",
    Position = new Vector3(24f, 6f, 7.5f),
    Direction = Vector3.Normalize(new Vector3(0f, -1f, -0.4f)),
    Color = new Vector4(1f, 0.86f, 0.66f, 1f),
    Intensity = 40f,
    Range = 24f,
    InnerConeAngle = 0.25f,      // radians
    OuterConeAngle = 0.55f,
    CastShadows = true,
    Priority = 10
});

Kind 是 Directional、Point 或 Spot。IsOpen 默认为 true,所以 关掉一个光源会让它留在列表里而退出烘焙 — 比移除再重新添加更便宜, 也更不容易出错。

八个光源,第九个就是不存在

SceneLightParams.MaxLights 是 8,而且这是一个硬性的 GPU 限制:uniform 缓冲区有八个槽。当场景里有更多时,烘焙按 Priority 排序并取前八个,平局时按到相机的距离 裁决。不会有警告、异常或变暗。如果一个光源重要,给它高优先级。

每帧只有一个点状光源得到阴影贴图,无论其他光源上的 CastShadows 怎么说。一个自带 KHR_lights_punctual 光源的 glTF 文件会自己追加它们,所以导入的 室内场景可能吃掉整个预算 — 见 模型与动画。

你实际会碰的阴影

三个太阳级联和一个点状槽共享一个方形深度图集。 RenderQuality 上有十四个阴影旋钮;实际上你只会调两个。

var q = RenderQuality.Current;

q.ShadowDistance = 220f;      // how far the sun casts; default 40
q.ShadowSoftnessTexels = 2f;  // Vogel-disk radius, in shadow texels
分辨率跟随距离,而不是反过来

调高 ShadowDistance 会把同一个图集摊到更大的 世界上,所以阴影纹素变大、接触点变软。如果你扩展距离后阴影变糊, 调高 ShadowAtlasSize — 但那个是初始化 值,所以它属于应用构造函数,而不是设置界面的滑块。

粉刺和彼得潘问题由 ShadowNormalOffset(默认 1.5 纹素)处理,而不是把深度 推离光源,这就是为什么深度偏差可以保持很小。完整表格在 光照与阴影。

天空就是一个光源

开启程序化天空后,太阳和月亮的方向来自大气模型, 而不是你放置的光源。Atmosphere 是一个静态的 物理参数类;首先值得了解的是天体。

成员默认含义
SunDirection、MoonDirection上、下有昼夜循环运行时由它驱动。
SunIrradiance12到达地面的太阳强度。
MoonIrradiance0.6月亮强度。刻意不为零,让夜晚可以通行。
NightAirglow0.004微弱的自发光,让无月之夜不至于纯黑。
StarRadiance0.15星星亮度;StarRotation 和 StarPoleAxis 决定星场朝向。
CloudsSkyState.Clear云层集合。见下文。
ViewAltitudeKm0.2相机在大气中所处的位置,它会改变地平线。

Rayleigh 和 Mie 系数、地面和大气半径以及臭氧参数也都在那里,设为地球值。 它们是日落看起来像日落、而无需任何手工渐变的原因, 它们也是你应该一次只改一个的原因。

天气,作为一种插值

云是一个 SkyState:一组带覆盖度、高度、 厚度和反照率的层。有四个预设,而有用的操作不是挑一个, 而是在两个之间混合。

// A preset.
Atmosphere.Clouds = SkyState.Overcast;

// Or a weather curve: t moves the sky continuously from fair to stormy.
Atmosphere.Clouds = SkyState.Lerp(SkyState.Fair, SkyState.Storm, t);

默认是 SkyState.Clear,它有零个层,所以 从不提及云的应用不为它们付出任何代价。逐层的风偏移在 Atmosphere.CloudWindOffsetKm 里,这就是一个层 漂移而无需重新生成噪声场的方式。

一天中的时刻

WorldSettings 拥有时钟, DayNightCycle 做换算。速度和起始小时遵循 与渲染质量相同的双面形态:供应用构造函数使用的静态 Default* 字段、可经由设置往返的实例属性, 以及作为运行时读取的 Current。

// In the app constructor. At the default speed of 0.01 a day takes 100 seconds.
WorldSettings.DefaultDayNightSpeed = 0.004f;
WorldSettings.DefaultStartHour = 5.5f;

// Anywhere afterwards:
float phase = DayNightCycle.PhaseFromHour(18f);   // dusk
float hour  = DayNightCycle.HourFromPhase(phase);

模型是一个十二小时白天的春分简化,所以 SunriseHour 是 6,正午落在 12,午夜落在 0,没有修正。 月亮按自己的 SynodicDays 周期 29.53 运行, 所以它随游戏内的月份盈亏,而不是每天重复。

接下来读什么

渲染质量覆盖其余的旋钮,包括 启动时读取一次的值与每帧都可以写入的值之间的区别 — 这是这套 API 最常见的一个早期错误。

管线本身的推理见 光照与阴影、 天空、大气与天气和 全局光照与 AO。