跳到主要内容

动态天空

DynamicSkyEffect 替代相机原本的场景天空,渲染受太阳照亮的解析式日间大气、星星和月亮组成的夜空,以及可选的随风飘动的云。太阳位置决定时刻,效果可同步一盏平行光,也可从天空生成场景光照,并为场景添加相应薄雾。

动态天空并非 CameraFrame 内置效果,需创建后通过 addEffect 添加:

const sky = new DynamicSkyEffect(app.graphicsDevice);
sky.light = sunEntity.light;
sky.cloudType = CLOUDTYPE_VOLUMETRIC;
cameraFrame.addEffect(sky);
cameraFrame.update();

编辑器中由脚本添加,参见编辑器中的动态天空。

与所有效果一样,设置在下一次 CameraFrame#update 生效。移动太阳的昼夜循环需每帧调用:

let time = 0;
app.on('update', (dt) => {
time += dt;
sky.elevation = Math.sin(time * 0.1) * 60;
cameraFrame.update();
});

太阳与时刻​

太阳的方位角和仰角以度计,决定一天中的时刻。仰角低于 0 时,太阳在地平线以下,呈现夜空。rotation 绕竖直轴旋转整个天空,太阳和月亮随之一起旋转。

效果让 light 指定的平行光在方向、颜色、强度及阴影上与天空同步。白天以 sunIntensity 从太阳方向照射,并由云减弱;夜晚淡变为 moonIntensity 和 moonColor 指定的月光。天空亮度也随 sunIntensity 缩放,用于匹配相机曝光。

日间天空​

属性说明
turbidity大气浑浊度,1–20。越高天空越乳白,太阳周围光晕越大。
rayleigh瑞利散射量,0–4,控制天空的蓝色程度。
mieCoefficient米氏散射量,0–0.1,控制太阳周围光晕亮度。
mieDirectionalG光晕聚集在太阳周围的紧密程度,0–1。
luminance天空相对太阳光的亮度。为 1 时,天空按晴空亮度照亮场景,同一曝光可兼顾天空和日照地面。
twilightGlow太阳刚落到地平线以下时,地平线暖色辉光的强度。

夜空​

属性说明
nightColor, nightBrightness夜空颜色和亮度。
starBrightness星星亮度,0 禁用。
starDensity天空单元中包含星星的比例,0–1。
starSize星星尺寸缩放。
moonDirection指向月亮的方向,随天空旋转。
moonSize月盘角半径,单位为度。
moonGlow月盘亮度。

云​

默认无云,通过 cloudType 添加:

  • CLOUDTYPE_FLAT:单层平面噪声加简单着色,开销低。天空着色器逐像素计算,与其他天空内容一样,因此也适用于 XR。
  • CLOUDTYPE_VOLUMETRIC:在三维噪声层中射线步进的体积云,是天空的主要开销来源,以较低分辨率渲染到独立纹理。首次使用时由 CPU 生成噪声。
属性说明
cloudCoverage天空被云覆盖的比例,0–1,从晴朗到阴天。
cloudSoftness云的柔和度,0.01–1,从边缘清晰的厚云到半透明薄云。
cloudOpacity云的不透明度,0–1。
cloudBrightness照在云上的太阳光亮度。
cloudNightBrightness夜间云上月光的亮度。
cloudSize云图案重复的距离,米,越大则云越大。
cloudHeight云层高度,米。
cloudThickness体积云层厚度,米。
cloudSunDimming完全覆盖时云遮挡太阳光的比例,使灯光变暗。
windSpeed, windDirection云飘动速度(米/秒)及方向(度),随应用时间移动。

两个属性控制体积云开销:

  • cloudQuality,0–1,设置穿过云层和朝向光源的步数。开销大致线性增长,从 0.5 到 1 约翻倍。较低值边缘更模糊,着色更平。
  • cloudResolution,0.25–1,相对场景的云渲染分辨率。云本身柔和,因此降低分辨率能显著节约开销,细节损失很小。

照亮场景​

启用 lighting 时,效果从天空生成用于材质光照的环境图集,赋给 Scene#envAtlas。只应由主相机的一个效果执行。

生成光照有开销,因此只在天空变化足够大时重新生成:

  • lightingThreshold:太阳移动多少角度后更新。
  • lightingCloudThreshold:云移动距离相对自身尺寸的比例阈值。默认 0,禁用此触发。
  • lightingInterpolation:从上次结果混合到最新结果,平滑跟随渐变天空,而非立即切换。
  • bakeLighting:在下一帧前主动重新生成,处理不会自动触发更新的变化。

lightingResolution 指生成光照所用天空分辨率,atlasSize 指环境图集尺寸。更大图集可让光滑表面反射更清晰。

未使用此效果的相机,例如平面反射相机,会从该图集渲染场景天空,即动态天空的静态图像。

薄雾​

启用 haze 时,效果为相机场景添加空气透视:表面随距离逐渐融入背后天空颜色。朝向低空太阳呈暖色,背向呈蓝色,日落后跟随夜空渐变。薄雾在基准高度最密,随高度上升变稀,因此山谷朦胧,高处视野清晰。

sky.haze = true;
sky.hazeDensity = 0.0002;
sky.hazeHeight = 300;
sky.hazeBaseHeight = 0;
cameraFrame.update();
属性说明
hazeDensity基准高度的每世界单位密度,即单位距离内散射光的比例。该高度水平观察时,约在 3 / density 距离被遮蔽 95%。
hazeHeight密度下降到约三分之一所需的高度,世界单位。越低则薄雾越贴地。
hazeBaseHeight薄雾最密的基准高度,世界单位,通常为海平面等场景最低地面。低于它时会继续变密。

密度应保持较低。薄雾取地平线附近的天空颜色,比日照地面亮得多,因此少量薄雾也会明显提亮远处表面。

薄雾替代相机的 Scene#fog 或 CameraComponent#fog。启用期间相机使用效果自己的雾,禁用后恢复原设置,其他相机不受影响。

  • StandardMaterial 等光照材质会受薄雾影响。
  • 粒子和高斯泼溅在该相机上不带雾渲染。
  • 将 useFog 设为 false,或定义 DYNAMIC_SKY_NO_HAZE,可让材质退出薄雾并无雾渲染:
material.setDefine('DYNAMIC_SKY_NO_HAZE', true);
material.update();

启停薄雾会重新编译该相机渲染的光照材质着色器,因此应预先配置,避免游戏中频繁切换。

效果通过替换图形设备全部材质使用的 fogPS 添加薄雾。自行替换 fogPS 的材质不受影响,但在该相机下会发现其 FOG 宏为 DYNAMICSKYHAZE。

其他着色器中的天空​

效果每帧在图形设备上设置天空 uniform,让其他材质计算同一天空,例如水面反射。GLSL 和 WGSL 代码块通过这些值计算天空:

  • dynamicSkyAtmospherePS:dynamicSkyBase(dir, ambient) 返回指定方向天空辐亮度,不含太阳盘、星星和月亮,并将 ambient 设置为附近天空光。方向位于天空空间:世界方向经过天空旋转(dynSkyRotation)后翻转 X。
  • dynamicSkyHazePS:dynamicSkyHaze(color, cameraPos, worldPos) 返回透过薄雾观察到的线性颜色,供不会自动应用薄雾的着色器使用。
#include "dynamicSkyAtmospherePS"
uniform mat3 dynSkyRotation;

vec3 skyColor(vec3 worldDir) {
vec3 dir = worldDir * dynSkyRotation;
dir.x = -dir.x;
vec3 ambient;
return dynamicSkyBase(dir, ambient);
}

CPU 上,getSkyColor 计算相同方向的天空,供脚本光照计算使用,例如天空对粒子的照明:

const zenith = sky.getSkyColor(Vec3.UP);

存在多个动态天空时,材质使用最后更新的天空。

XR​

XR 中天空和平面云像场景其他内容一样逐视图渲染。体积云则只为相机视图渲染一张纹理,各眼从中读取。某只眼视野超出相机视野时,纹理边缘没有云可读,会产生拉伸。因此 XR 应使用 CLOUDTYPE_FLAT。

编辑器中的动态天空​

dynamic-sky.mjs 的 dynamicSkyEffect 脚本,与 cameraFrame 脚本放在同一相机实体,向其 CameraFrame 添加效果。属性分为 Sun(太阳)、Day(白天)、Night(夜晚)、Clouds(云)和 Lighting(光照)。Sun 组的 Light 指定与太阳同步的平行光实体。

脚本不自动驱动时间,而是每帧将属性复制到效果。因此可在同一实体的自定义脚本中更改这些属性来移动太阳:

import { Script } from 'playcanvas';

class DayNightCycle extends Script {
static scriptName = 'dayNightCycle';

time = 0;

update(dt) {
this.time += dt;
const sky = this.entity.script.dynamicSkyEffect;
sky.sun.elevation = Math.sin(this.time * 0.1) * 60;
}
}

export { DayNightCycle };

薄雾不是脚本属性。应设置脚本在 initialize 中创建的 effect:

import { Script } from 'playcanvas';

class SkyHaze extends Script {
static scriptName = 'skyHaze';

postInitialize() {
const sky = this.entity.script.dynamicSkyEffect.effect;
sky.haze = true;
sky.hazeDensity = 0.0002;
sky.hazeHeight = 300;
}
}

export { SkyHaze };

限制​

  • XR 不支持体积云,参见 XR。
  • 天空不与 CameraFrame 的体积雾联动:云不向雾中投影,雾也不受天空光照。
  • 薄雾不影响粒子和高斯泼溅;它们在该相机上无雾渲染。

示例​

昼夜循环中的解析式大气,包含星星、月亮和随风飘动的体积云,为场景生成光照,并同步太阳灯光。

动态天空

一天中的沙漠地貌,薄雾让远处地面逐渐融入天空颜色。

程序化天空