动态天空
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)返回透过薄雾观察到的线性颜色,供不会自动应用薄雾的着色器使用。
- GLSL
- WGSL
#include "dynamicSkyAtmospherePS"
uniform mat3 dynSkyRotation;
vec3 skyColor(vec3 worldDir) {
vec3 dir = worldDir * dynSkyRotation;
dir.x = -dir.x;
vec3 ambient;
return dynamicSkyBase(dir, ambient);
}
#include "dynamicSkyAtmospherePS"
uniform dynSkyRotation: mat3x3f;
fn skyColor(worldDir: vec3f) -> vec3f {
var dir = worldDir * uniform.dynSkyRotation;
dir.x = -dir.x;
var ambient: vec3f;
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 };
限制
示例
昼夜循环中的解析式大气,包含星星、月亮和随风飘动的体积云,为场景生成光照,并同步太阳灯光。
一天中的沙漠地貌,薄雾让远处地面逐渐融入天空颜色。