@babylonjs-toolkit/next 9.34.10 → 9.34.100

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -143,7 +143,7 @@ import { UnityScrollBar } from './unityscrollbar';
143
143
  import { UnityDropdownMenu } from './unitydropdownmenu';
144
144
  import { RectAreaLight } from '@babylonjs/core/Lights/rectAreaLight';
145
145
  export class SceneManager {
146
- static get Version() { return "9.34.10 - R1"; }
146
+ static get Version() { return "9.34.100 - R1"; }
147
147
  static get Copyright() { return "All rights reserved (c) 2024 Mackey Kinard"; }
148
148
  static IsPrecompilingShaders() {
149
149
  return SceneManager.PrecompileShaders !== false;
@@ -9198,7 +9198,6 @@ export class CVTOOLS_unity_metadata {
9198
9198
  return;
9199
9199
  const levels = [];
9200
9200
  const rendererCounts = [];
9201
- let multiRenderer = false;
9202
9201
  let totalMeshes = 0;
9203
9202
  for (let index = 0; index < levelCount; index++) {
9204
9203
  const entry = lodentries[index];
@@ -9212,8 +9211,6 @@ export class CVTOOLS_unity_metadata {
9212
9211
  meshes.push(lodnode);
9213
9212
  }
9214
9213
  });
9215
- if (meshes.length > 1)
9216
- multiRenderer = true;
9217
9214
  totalMeshes += meshes.length;
9218
9215
  rendererCounts.push(meshes.length);
9219
9216
  levels.push({ distance: thresholds[index], meshes: meshes });
@@ -9221,14 +9218,15 @@ export class CVTOOLS_unity_metadata {
9221
9218
  if (totalMeshes === 0)
9222
9219
  return;
9223
9220
  const fadeMode = UnityLodGroups.ParseFadeMode(metadata.fademode);
9224
- const nativeCandidate = (useDistances === false && multiRenderer === false && fadeMode === UnityLodGroups.FADE_NONE
9221
+ const requiresSwitcher = UnityLodGroups.RequiresSwitcher(levels.map((level) => level.meshes), fadeMode);
9222
+ const nativeCandidate = (useDistances === false && requiresSwitcher === false
9225
9223
  && levels[0].meshes.length === 1 && levels[0].meshes[0] instanceof Mesh && !(levels[0].meshes[0] instanceof InstancedMesh));
9226
9224
  if (nativeCandidate === true && this._setupNativeCoverageLevelOfDetail(scene, transform, levels, lodBias, metadata) === true) {
9227
9225
  }
9228
9226
  else if (useDistances === false) {
9229
9227
  this._setupLevelOfDetailSwitcher(scene, transform, levels, rendererCounts, UnityLodGroups.MODE_COVERAGE, lodBias, metadata);
9230
9228
  }
9231
- else if (multiRenderer === true || fadeMode !== UnityLodGroups.FADE_NONE) {
9229
+ else if (requiresSwitcher === true) {
9232
9230
  this._setupLevelOfDetailSwitcher(scene, transform, levels, rendererCounts, UnityLodGroups.MODE_DISTANCE, lodBias, metadata);
9233
9231
  }
9234
9232
  else {
@@ -9735,10 +9733,14 @@ export class CVTOOLS_unity_metadata {
9735
9733
  return { mesh: skyboxMesh, texture: skyboxTexture };
9736
9734
  };
9737
9735
  const pbSkyClass = HdrpPhysicallyBasedSky;
9738
- const hdrpSkyType = (HdrpRendering.IsParityExport(metadata) === true && metadata.hdrp.sky != null && typeof metadata.hdrp.sky.type === "string") ? metadata.hdrp.sky.type.toLowerCase() : null;
9736
+ const physicalSky = PhysicalSkyBlock.SkyOf(metadata);
9737
+ const hdrpSkyType = (physicalSky != null && typeof physicalSky.type === "string") ? physicalSky.type.toLowerCase() : null;
9739
9738
  const liveSky = (pbSkyClass != null && hdrpSkyType === "physicallybased")
9740
9739
  ? pbSkyClass.TryCreate(babylonScene, metadata, root, { rendergroup: rendergroup, bakedSky: hasBakedSkyFaces ? (() => buildBakedSky().mesh) : null })
9741
9740
  : null;
9741
+ const volumetricClouds = VolumetricClouds;
9742
+ if (volumetricClouds != null)
9743
+ volumetricClouds.TryCreate(babylonScene, metadata, root);
9742
9744
  const graphSky = (liveSky == null && hdrpSkyType !== "none" && typeof skybox.customMaterial === "string" && skybox.bags != null && ShaderGraphSky != null)
9743
9745
  ? ShaderGraphSky.CreateGraphSkybox(babylonScene, skybox.bags, { size: skysize, rendergroup: rendergroup, skyfog: skyfog, tags: skytags, environment: (skybox.environment == null && babylonScene.environmentTexture == null) })
9744
9746
  : null;
@@ -9845,8 +9847,10 @@ export class CVTOOLS_unity_metadata {
9845
9847
  const hasDiffuseIbl = Array.isArray(environment.sh) && environment.sh.length === 27;
9846
9848
  const useUnitySH = (mode === 0 && hasDiffuseIbl === true);
9847
9849
  const environmentLevel = (useUnitySH ? (specIbl * scale * SPECULAR_SCALE) : specIbl * SPECULAR_SCALE) * HdrpRendering.Indirect(babylonScene).reflection;
9850
+ const hasEnvironmentFile = (envinfo != null && envinfo !== "");
9851
+ const ambientScale = LightingConversions.EnvironmentAmbientScale(diffuseIbl, hasEnvironmentFile ? environmentLevel : null, units, HdrpRendering.IsParity(babylonScene));
9848
9852
  if (useUnitySH)
9849
- this.generateSphericalHarmonics(babylonScene, environment.sh, diffuseIbl, (envinfo != null && envinfo !== "") ? environmentLevel : null);
9853
+ this.generateSphericalHarmonics(babylonScene, environment.sh, ambientScale, hasEnvironmentFile ? environmentLevel : null);
9850
9854
  if (envinfo != null && envinfo !== "") {
9851
9855
  try {
9852
9856
  const root = (this._rootUrl != null) ? this._rootUrl : "/";
@@ -9954,6 +9958,8 @@ export class CVTOOLS_unity_metadata {
9954
9958
  }
9955
9959
  if (metadata.shaderglobals != null)
9956
9960
  ShaderGlobals.LoadSceneGlobals(babylonScene, metadata.shaderglobals);
9961
+ ShaderGlobals.LoadSceneDrivers(babylonScene, metadata.shaderglobaldrivers);
9962
+ ShaderGraphRuntime.RecordExportReport(babylonScene, metadata.exportreport);
9957
9963
  if (metadata.lights2d != null && Light2DTexture != null)
9958
9964
  Light2DTexture.Load(babylonScene, metadata.lights2d);
9959
9965
  if (metadata.renderfeatures != null && ShaderGraphPass != null)
@@ -15431,6 +15437,8 @@ export class LightingConversions {
15431
15437
  static UnityLinearLightColor(light) {
15432
15438
  if (light == null || light.diffuse == null)
15433
15439
  return new Color3(0.0, 0.0, 0.0);
15440
+ if (light.metadata != null && light.metadata.toolkit != null && light.metadata.toolkit.linearDiffuse === true)
15441
+ return light.diffuse.clone();
15434
15442
  const rider = (light.metadata != null && light.metadata.toolkit != null) ? light.metadata.toolkit.colorTemperature : null;
15435
15443
  const d = light.diffuse;
15436
15444
  const channel = (value, factor) => {
@@ -15443,6 +15451,18 @@ export class LightingConversions {
15443
15451
  const tb = (rider != null) ? rider.b : 1.0;
15444
15452
  return new Color3(channel(d.r, tr), channel(d.g, tg), channel(d.b, tb));
15445
15453
  }
15454
+ static LinearizeLightColor(light, scene) {
15455
+ const sceneMetadata = (scene != null && scene.metadata != null) ? scene.metadata.toolkit : null;
15456
+ const toolkit = (light != null && light.metadata != null) ? light.metadata.toolkit : null;
15457
+ if (sceneMetadata == null || toolkit == null || toolkit.linearDiffuse === true)
15458
+ return false;
15459
+ const isLinearProject = sceneMetadata.colorspace != null && ("" + sceneMetadata.colorspace).toLowerCase() === "linear";
15460
+ if (!isLinearProject || HdrpRendering.IsParityExport(sceneMetadata) === true)
15461
+ return false;
15462
+ light.diffuse = LightingConversions.UnityLinearLightColor(light);
15463
+ toolkit.linearDiffuse = true;
15464
+ return true;
15465
+ }
15446
15466
  static SetShadowFade(scene, fade) {
15447
15467
  if (scene == null || !Array.isArray(fade) || fade.length < 2)
15448
15468
  return;
@@ -15898,6 +15918,10 @@ export class LightingConversions {
15898
15918
  return Math.max(0.0, reflection);
15899
15919
  return Math.min(levelMax, Math.max(0.0, reflection));
15900
15920
  }
15921
+ static EnvironmentAmbientScale(ambientIntensity, environmentLevel, units, isParity) {
15922
+ const isNormalised = units === LightingConversions.PhysicalUnits && environmentLevel != null && environmentLevel > 0;
15923
+ return (isNormalised && isParity !== true) ? ambientIntensity / environmentLevel : ambientIntensity;
15924
+ }
15901
15925
  static WarnBakedSkyWithoutEnvironment(skybox) {
15902
15926
  if (skybox == null || skybox.bakedsky !== true || skybox.environment != null)
15903
15927
  return false;
@@ -17610,6 +17634,7 @@ export class MetadataParser {
17610
17634
  babylonLight.metadata.toolkit.hdrpSky = component.hdrpsky;
17611
17635
  babylonLight.metadata.toolkit.intensityFactor = intensityFactor;
17612
17636
  babylonLight.metadata.toolkit.colorTemperature = (component.colortemperature != null) ? Utilities.ParseColor3(component.colortemperature, Color3.White()) : null;
17637
+ LightingConversions.LinearizeLightColor(babylonLight, scene);
17613
17638
  if (component.cookie != null)
17614
17639
  UnityLightCookies.Apply(scene, babylonLight, entity, component.cookie);
17615
17640
  babylonLight.shadowEnabled = false;
@@ -18098,6 +18123,10 @@ export class OpaqueSceneColor {
18098
18123
  }
18099
18124
  return all;
18100
18125
  }
18126
+ static CopyConversionFor(imageProcessing) {
18127
+ const isEncodedByMaterials = imageProcessing != null && imageProcessing.isEnabled === true && imageProcessing.applyByPostProcess !== true;
18128
+ return isEncodedByMaterials ? OpaqueSceneColor.COPY_TO_LINEAR : OpaqueSceneColor.COPY_PLAIN;
18129
+ }
18101
18130
  static QueueSamples(subMeshes, materials) {
18102
18131
  if (subMeshes == null || materials == null || materials.size === 0)
18103
18132
  return false;
@@ -18168,7 +18197,7 @@ export class OpaqueSceneColor {
18168
18197
  if (!copier.isReady())
18169
18198
  return;
18170
18199
  engine.unBindFramebuffer(current, true);
18171
- copier.copy(current.texture, destination);
18200
+ copier.copy(current.texture, destination, OpaqueSceneColor.CopyConversionFor(scene.imageProcessingConfiguration));
18172
18201
  if (target.__tkMips === true && target.getInternalTexture() != null && typeof engine.generateMipmaps === "function")
18173
18202
  engine.generateMipmaps(target.getInternalTexture());
18174
18203
  engine.bindFramebuffer(current, undefined, undefined, undefined, true);
@@ -18179,6 +18208,27 @@ export class OpaqueSceneColor {
18179
18208
  OpaqueSceneColor.Ratio = null;
18180
18209
  OpaqueSceneColor.StoreKey = "__tkOpaqueSceneColor";
18181
18210
  OpaqueSceneColor.UseCopy = true;
18211
+ OpaqueSceneColor.COPY_PLAIN = 0;
18212
+ OpaqueSceneColor.COPY_TO_LINEAR = 1;
18213
+ export class PhysicalSkyBlock {
18214
+ static UrpBlock(metadata) {
18215
+ const block = (metadata != null) ? metadata.urp : null;
18216
+ return (block != null && typeof block.version === "number" && block.version >= PhysicalSkyBlock.UrpBlockVersion) ? block : null;
18217
+ }
18218
+ static SkyOf(metadata) {
18219
+ if (HdrpRendering.IsParityExport(metadata) === true)
18220
+ return metadata.hdrp.sky;
18221
+ const block = PhysicalSkyBlock.UrpBlock(metadata);
18222
+ return (block != null) ? block.sky : null;
18223
+ }
18224
+ static FogOf(metadata) {
18225
+ if (HdrpRendering.IsParityExport(metadata) === true)
18226
+ return metadata.hdrp.fog;
18227
+ const block = PhysicalSkyBlock.UrpBlock(metadata);
18228
+ return (block != null) ? block.fog : null;
18229
+ }
18230
+ }
18231
+ PhysicalSkyBlock.UrpBlockVersion = 1;
18182
18232
  export class FixedStepClock {
18183
18233
  constructor(fixedDeltaTime = FixedStepClock.DEFAULT_FIXED_DELTA_TIME, maximumDeltaTime = FixedStepClock.DEFAULT_MAXIMUM_DELTA_TIME) {
18184
18234
  this.accumulatedTime = 0;
@@ -19447,7 +19497,9 @@ export class SceneShaderWarmup {
19447
19497
  SceneShaderWarmup.SkipFrustumClipping(scene, frustumState);
19448
19498
  const drawHidden = drawWholeScene && ownPipelines && options.includeHiddenMeshes === true && SceneShaderWarmup.DrawHiddenMeshesOnReveal === true;
19449
19499
  const shownHiddenMeshes = drawHidden ? SceneShaderWarmup.ShowHiddenMeshesForDraws(scene) : [];
19450
- const punctualShadowDraws = (drawWholeScene && ownPipelines) ? SceneShaderWarmup.DrawPunctualShadowMapsFully(scene) : [];
19500
+ const fullShadowDraws = (drawWholeScene && ownPipelines) ? SceneShaderWarmup.DrawShadowMapsFully(scene) : [];
19501
+ const lodCycle = (drawWholeScene && ownPipelines) ? SceneShaderWarmup.CycleNativeLodLevels(scene) : null;
19502
+ const settleFrames = Math.max(SceneShaderWarmup.PIPELINE_SETTLE_FRAMES, (lodCycle != null) ? lodCycle.length : 0);
19451
19503
  const shadowListWraps = SceneShaderWarmup.KeepHiddenCastersInShadowMaps(scene, shownHiddenMeshes);
19452
19504
  const scaleVariantDraw = (drawWholeScene && ownPipelines) ? SceneShaderWarmup.AlternateScaleVariants(scene) : null;
19453
19505
  const materialVariantDraw = (drawWholeScene && ownPipelines) ? SceneShaderWarmup.AlternateMaterialVariants(scene) : null;
@@ -19458,10 +19510,11 @@ export class SceneShaderWarmup {
19458
19510
  const stageFraction = (requested > startRequested) ? (compiled - startCompiled) / (requested - startRequested) : 0;
19459
19511
  const pipelineStep = { step: "pipelines", ready: compiled - startCompiled, total: requested - startRequested };
19460
19512
  report(effects.ready + compiled - startCompiled, effects.total + requested - startRequested + 1, effectShare + (1 - effectShare) * stageFraction * 0.99, pipelineStep);
19461
- }).then((timedOut) => {
19513
+ }, settleFrames).then((timedOut) => {
19462
19514
  SceneShaderWarmup.RestoreFrustumClipping(scene, frustumState);
19463
19515
  SceneShaderWarmup.RestoreShadowCasterLists(shadowListWraps);
19464
- SceneShaderWarmup.RestorePunctualShadowMaps(punctualShadowDraws);
19516
+ SceneShaderWarmup.RestoreFullShadowMaps(fullShadowDraws);
19517
+ SceneShaderWarmup.RestoreNativeLodLevels(scene, lodCycle);
19465
19518
  SceneShaderWarmup.RestoreHiddenMeshes(scene, shownHiddenMeshes);
19466
19519
  SceneShaderWarmup.RestoreScaleVariants(scene, scaleVariantDraw);
19467
19520
  SceneShaderWarmup.RestoreMaterialVariants(scene, materialVariantDraw);
@@ -21181,13 +21234,15 @@ export class SceneShaderWarmup {
21181
21234
  wrap.shadowMap.getCustomRenderList = wrap.customList;
21182
21235
  }
21183
21236
  }
21184
- static DrawPunctualShadowMapsFully(scene) {
21237
+ static DrawShadowMapsFully(scene) {
21185
21238
  const draws = [];
21186
21239
  for (const shadowMap of SceneShaderWarmup.CollectShadowMaps(scene)) {
21187
21240
  const renderGate = shadowMap;
21188
- if (renderGate._tkSkipInvisible !== true)
21241
+ const isGated = renderGate._tkSkipInvisible === true;
21242
+ if (!isGated && !SceneShaderWarmup.HasCasterCullingList(shadowMap))
21189
21243
  continue;
21190
- renderGate._tkPrewarmDrawAll = true;
21244
+ if (isGated)
21245
+ renderGate._tkPrewarmDrawAll = true;
21191
21246
  draws.push({ shadowMap: shadowMap, customList: shadowMap.getCustomRenderList });
21192
21247
  shadowMap.getCustomRenderList = null;
21193
21248
  try {
@@ -21197,7 +21252,11 @@ export class SceneShaderWarmup {
21197
21252
  }
21198
21253
  return draws;
21199
21254
  }
21200
- static RestorePunctualShadowMaps(draws) {
21255
+ static HasCasterCullingList(shadowMap) {
21256
+ const customList = shadowMap.getCustomRenderList;
21257
+ return customList != null && (customList.tkCasterCulling === true || customList.tkTerrainCulling === true);
21258
+ }
21259
+ static RestoreFullShadowMaps(draws) {
21201
21260
  for (const draw of draws) {
21202
21261
  draw.shadowMap._tkPrewarmDrawAll = false;
21203
21262
  if (draw.shadowMap.getCustomRenderList == null)
@@ -21429,6 +21488,53 @@ export class SceneShaderWarmup {
21429
21488
  draw.frameObserver = null;
21430
21489
  SceneShaderWarmup.SetScaleVariantForced(draw.entries, false);
21431
21490
  }
21491
+ static NativeLodSequence(mesh) {
21492
+ if (!(mesh instanceof Mesh) || mesh.isDisposed())
21493
+ return [];
21494
+ const levels = mesh.getLODLevels();
21495
+ if (levels == null || levels.length === 0)
21496
+ return [];
21497
+ const sequence = [mesh];
21498
+ for (const level of levels) {
21499
+ if (level.mesh != null && !level.mesh.isDisposed() && sequence.indexOf(level.mesh) < 0)
21500
+ sequence.push(level.mesh);
21501
+ }
21502
+ return (sequence.length > 1) ? sequence : [];
21503
+ }
21504
+ static CycleNativeLodLevels(scene) {
21505
+ if (SceneShaderWarmup.WarmNativeLodLevelsOnReveal !== true)
21506
+ return null;
21507
+ const sequences = new Map();
21508
+ let length = 0;
21509
+ for (const mesh of scene.meshes) {
21510
+ const sequence = SceneShaderWarmup.NativeLodSequence(mesh);
21511
+ if (sequence.length === 0)
21512
+ continue;
21513
+ sequences.set(mesh, sequence);
21514
+ length = Math.max(length, sequence.length);
21515
+ }
21516
+ if (sequences.size === 0)
21517
+ return null;
21518
+ const previousSelector = scene.customLODSelector || null;
21519
+ const cycle = { sequences: sequences, length: length, frameIndex: 0, previousSelector: previousSelector, selector: null, frameObserver: null };
21520
+ cycle.selector = (mesh, camera) => {
21521
+ const sequence = sequences.get(mesh);
21522
+ if (sequence != null)
21523
+ return sequence[cycle.frameIndex % sequence.length];
21524
+ return (previousSelector != null) ? previousSelector(mesh, camera) : mesh.getLOD(camera);
21525
+ };
21526
+ cycle.frameObserver = scene.onAfterRenderObservable.add(() => { cycle.frameIndex++; });
21527
+ scene.customLODSelector = cycle.selector;
21528
+ return cycle;
21529
+ }
21530
+ static RestoreNativeLodLevels(scene, cycle) {
21531
+ if (cycle == null)
21532
+ return;
21533
+ scene.onAfterRenderObservable.remove(cycle.frameObserver);
21534
+ cycle.frameObserver = null;
21535
+ if (scene.customLODSelector === cycle.selector)
21536
+ scene.customLODSelector = cycle.previousSelector;
21537
+ }
21432
21538
  static SetScaleVariantForced(entries, forced) {
21433
21539
  const prototypeUpdate = AbstractMesh.prototype._updateNonUniformScalingState;
21434
21540
  for (const entry of entries) {
@@ -21572,8 +21678,8 @@ export class SceneShaderWarmup {
21572
21678
  }
21573
21679
  return phases;
21574
21680
  }
21575
- static WaitForPipelinesPerCamera(scene, pipelines, deadline, phases, onProgress) {
21576
- return SceneShaderWarmup.RunCameraPhasesAsync(scene, phases, false, () => SceneShaderWarmup.WaitForPipelines(scene, pipelines, deadline, onProgress), (timedOut) => timedOut);
21681
+ static WaitForPipelinesPerCamera(scene, pipelines, deadline, phases, onProgress, settleFrames = SceneShaderWarmup.PIPELINE_SETTLE_FRAMES) {
21682
+ return SceneShaderWarmup.RunCameraPhasesAsync(scene, phases, false, () => SceneShaderWarmup.WaitForPipelines(scene, pipelines, deadline, onProgress, settleFrames), (timedOut) => timedOut);
21577
21683
  }
21578
21684
  static RunCameraPhasesAsync(scene, phases, noPhaseRun, runStep, isTimedOut) {
21579
21685
  const runPhase = (phaseIndex, previous) => {
@@ -21591,7 +21697,7 @@ export class SceneShaderWarmup {
21591
21697
  };
21592
21698
  return runPhase(0, noPhaseRun);
21593
21699
  }
21594
- static WaitForPipelines(scene, pipelines, deadline, onProgress) {
21700
+ static WaitForPipelines(scene, pipelines, deadline, onProgress, settleFrames = SceneShaderWarmup.PIPELINE_SETTLE_FRAMES) {
21595
21701
  return new Promise((resolve) => {
21596
21702
  let quietFrames = 0;
21597
21703
  let lastRequested = -1;
@@ -21622,7 +21728,7 @@ export class SceneShaderWarmup {
21622
21728
  quietFrames = quiet ? quietFrames + 1 : 0;
21623
21729
  if (onProgress != null)
21624
21730
  onProgress(pipelines.compiled, pipelines.requested);
21625
- if (quietFrames >= SceneShaderWarmup.PIPELINE_SETTLE_FRAMES)
21731
+ if (quietFrames >= settleFrames)
21626
21732
  finish(false);
21627
21733
  });
21628
21734
  timer = setTimeout(() => finish(true), Math.max(0, deadline - SceneShaderWarmup.NowMs()));
@@ -22179,6 +22285,7 @@ SceneShaderWarmup.BuildScenePipelinesOnReveal = true;
22179
22285
  SceneShaderWarmup.DrawHiddenMeshesOnReveal = true;
22180
22286
  SceneShaderWarmup.WarmEmptyThinInstancesOnReveal = true;
22181
22287
  SceneShaderWarmup.WarmScaleVariantsOnReveal = true;
22288
+ SceneShaderWarmup.WarmNativeLodLevelsOnReveal = true;
22182
22289
  SceneShaderWarmup.OPTIONAL_UNIT_GRACE_MS = 3000;
22183
22290
  SceneShaderWarmup.GIVEN_UP_NAMES_SHOWN = 10;
22184
22291
  SceneShaderWarmup.LateCompileReporting = null;
@@ -23815,6 +23922,27 @@ export class ShaderGlobals {
23815
23922
  ShaderGlobals._keywords.add(k);
23816
23923
  ShaderGlobals._version++;
23817
23924
  }
23925
+ static LoadSceneDrivers(scene, drivers) {
23926
+ if (scene == null || drivers == null || drivers.length === 0)
23927
+ return;
23928
+ const observer = scene.onBeforeRenderObservable.add(() => {
23929
+ const deltaSeconds = ShaderGlobals.GetClock(scene).deltaTime;
23930
+ for (const driver of drivers)
23931
+ ShaderGlobals.AdvanceDriver(driver, deltaSeconds);
23932
+ });
23933
+ scene.onDisposeObservable.addOnce(() => { scene.onBeforeRenderObservable.remove(observer); });
23934
+ }
23935
+ static AdvanceDriver(driver, deltaSeconds) {
23936
+ const entry = ShaderGlobals._vectors.get(driver.name);
23937
+ if (entry == null)
23938
+ return;
23939
+ const componentName = ShaderGlobals.DriverComponentNames[driver.component];
23940
+ let advancedValue = entry.value[componentName] + driver.rate * deltaSeconds;
23941
+ if (driver.wrap > 0 && advancedValue > driver.wrap)
23942
+ advancedValue -= driver.wrap;
23943
+ entry.value[componentName] = advancedValue;
23944
+ ShaderGlobals._version++;
23945
+ }
23818
23946
  static GetClock(scene) {
23819
23947
  if (scene == null)
23820
23948
  return { time: 0, deltaTime: 0, smoothDeltaTime: 0, frame: 0 };
@@ -23849,6 +23977,7 @@ export class ShaderGlobals {
23849
23977
  return clock;
23850
23978
  }
23851
23979
  }
23980
+ ShaderGlobals.DriverComponentNames = ["x", "y", "z", "w"];
23852
23981
  ShaderGlobals._version = 0;
23853
23982
  ShaderGlobals._floats = new Map();
23854
23983
  ShaderGlobals._vectors = new Map();
@@ -25073,6 +25202,23 @@ export class ShaderGraphRuntime {
25073
25202
  loadTexture(textures[k], (texture) => { clone.setTextureValue(k, texture); });
25074
25203
  return clone;
25075
25204
  }
25205
+ static RecordExportReport(scene, entries) {
25206
+ if (entries == null || entries.length === 0)
25207
+ return;
25208
+ const record = ShaderGraphRuntime.Record(scene);
25209
+ if (record == null)
25210
+ return;
25211
+ record.exportReport = entries;
25212
+ ShaderGraphRuntime.ScheduleSceneSummary(scene);
25213
+ }
25214
+ static PrintExportReport(record) {
25215
+ const entries = record.exportReport;
25216
+ console.groupCollapsed("Export report: " + entries.length + " items not carried");
25217
+ for (const entry of entries)
25218
+ console.log(entry.feature + " - " + entry.reason + ": " + entry.items.join(", "));
25219
+ console.groupEnd();
25220
+ record.exportReport = null;
25221
+ }
25076
25222
  static FlushSceneSummary(scene) {
25077
25223
  const record = ShaderGraphRuntime._records.get(scene != null ? scene.uniqueId : -1);
25078
25224
  if (record == null || record.scene !== scene)
@@ -25092,6 +25238,8 @@ export class ShaderGraphRuntime {
25092
25238
  const report = (ShaderGraphRuntime.ReportDeviations != null) ? ShaderGraphRuntime.ReportDeviations
25093
25239
  : (SceneManager != null && typeof SceneManager.IsDebugMode === "function" ? SceneManager.IsDebugMode() : true);
25094
25240
  const n = record.materials.size, m = record.lastResorts.size;
25241
+ if (report && record.exportReport != null)
25242
+ ShaderGraphRuntime.PrintExportReport(record);
25095
25243
  if (report && (n > 0 || m > 0)) {
25096
25244
  console.groupCollapsed("Shader Graph: " + n + " materials with deviations, " + m + " last resort" + (record.flushed ? " (built after the scene summary)" : ""));
25097
25245
  record.materials.forEach((entry, name) => {
@@ -29818,8 +29966,9 @@ export class UnityLodGroups {
29818
29966
  static ExportedSize(root, metadata) {
29819
29967
  if (root == null || metadata == null || !(metadata.lodsize > 0))
29820
29968
  return null;
29821
- const c = metadata.lodcenter;
29822
- const localPoint = (Array.isArray(c) && c.length >= 3) ? new Vector3(c[0], c[1], c[2]) : Vector3.Zero();
29969
+ const exportedCenter = metadata.lodcenter;
29970
+ const hasCenter = (Array.isArray(exportedCenter) && exportedCenter.length >= 3);
29971
+ const localPoint = hasCenter ? new Vector3(0 - exportedCenter[0], exportedCenter[1], exportedCenter[2]) : Vector3.Zero();
29823
29972
  const scale = new Vector3();
29824
29973
  root.computeWorldMatrix(true).decompose(scale, null, null);
29825
29974
  const largest = Math.max(Math.abs(scale.x), Math.abs(scale.y), Math.abs(scale.z));
@@ -29830,6 +29979,35 @@ export class UnityLodGroups {
29830
29979
  const useDistances = !hasCoverages && (distances != null && distances.length > 0);
29831
29980
  return { useDistances: useDistances, thresholds: useDistances ? distances : (hasCoverages ? coverages : null) };
29832
29981
  }
29982
+ static LevelsShareTransform(master, details) {
29983
+ const masterMatrix = master.computeWorldMatrix(true);
29984
+ for (const detail of details) {
29985
+ if (UnityLodGroups.MatricesShareTransform(masterMatrix, detail.computeWorldMatrix(true)) === false)
29986
+ return false;
29987
+ }
29988
+ return true;
29989
+ }
29990
+ static MatricesShareTransform(first, second) {
29991
+ const offset = Vector3.Distance(first.getTranslation(), second.getTranslation());
29992
+ if (offset > UnityLodGroups.NATIVE_TRANSFORM_TOLERANCE_METERS)
29993
+ return false;
29994
+ for (const element of UnityLodGroups.BASIS_ELEMENTS) {
29995
+ if (Math.abs(first.m[element] - second.m[element]) > UnityLodGroups.NATIVE_BASIS_TOLERANCE)
29996
+ return false;
29997
+ }
29998
+ return true;
29999
+ }
30000
+ static RequiresSwitcher(meshLevels, fadeMode) {
30001
+ if (fadeMode !== UnityLodGroups.FADE_NONE)
30002
+ return true;
30003
+ if (meshLevels.some((meshes) => meshes.length > 1))
30004
+ return true;
30005
+ const master = meshLevels[0][0];
30006
+ if (master == null)
30007
+ return false;
30008
+ const details = meshLevels.slice(1).map((meshes) => meshes[0]).filter((mesh) => mesh != null);
30009
+ return UnityLodGroups.LevelsShareTransform(master, details) === false;
30010
+ }
29833
30011
  static ParseFadeMode(mode) {
29834
30012
  const m = (mode != null) ? String(mode).toLowerCase() : "";
29835
30013
  if (m === "crossfade")
@@ -29992,21 +30170,25 @@ export class UnityLodGroups {
29992
30170
  addNativeGroup(master, details, coverages, worldSize) {
29993
30171
  if (!(master instanceof Mesh) || master instanceof InstancedMesh || coverages == null)
29994
30172
  return false;
29995
- const n = Math.min(coverages.length, details.length + 1);
29996
- if (n <= 0)
30173
+ if (master.instances.length > 0)
30174
+ return false;
30175
+ const levelCount = Math.min(coverages.length, details.length + 1);
30176
+ if (levelCount <= 0)
29997
30177
  return false;
29998
- for (let k = 0; k < n - 1; k++) {
29999
- const d = details[k];
30000
- if (!(d instanceof Mesh) || d instanceof InstancedMesh || d === master || d._masterMesh != null || details.indexOf(d) !== k)
30178
+ for (let k = 0; k < levelCount - 1; k++) {
30179
+ const detail = details[k];
30180
+ if (!(detail instanceof Mesh) || detail instanceof InstancedMesh || detail === master || detail._masterMesh != null || details.indexOf(detail) !== k)
30001
30181
  return false;
30002
30182
  }
30003
- for (let k = 1; k < n; k++)
30183
+ if (UnityLodGroups.LevelsShareTransform(master, details.slice(0, levelCount - 1)) === false)
30184
+ return false;
30185
+ for (let k = 1; k < levelCount; k++)
30004
30186
  master.addLODLevel(1e6 + k, details[k - 1]);
30005
- master.addLODLevel(1e6 + n, null);
30187
+ master.addLODLevel(1e6 + levelCount, null);
30006
30188
  const all = master.getLODLevels();
30007
30189
  const entries = [];
30008
- for (let i = 0; i < n; i++) {
30009
- const mesh = (i < n - 1) ? details[i] : null;
30190
+ for (let i = 0; i < levelCount; i++) {
30191
+ const mesh = (i < levelCount - 1) ? details[i] : null;
30010
30192
  let found = null;
30011
30193
  for (const level of all) {
30012
30194
  if (level.mesh === mesh && entries.indexOf(level) === -1) {
@@ -30016,7 +30198,7 @@ export class UnityLodGroups {
30016
30198
  }
30017
30199
  entries.push(found);
30018
30200
  }
30019
- this._native.push({ master: master, entries: entries, coverages: coverages.slice(0, n), worldSize: worldSize });
30201
+ this._native.push({ master: master, entries: entries, coverages: coverages.slice(0, levelCount), worldSize: worldSize });
30020
30202
  this._nativeKey[0] = NaN;
30021
30203
  return true;
30022
30204
  }
@@ -30266,6 +30448,9 @@ UnityLodGroups.FADE_NONE = 0;
30266
30448
  UnityLodGroups.FADE_CROSSFADE = 1;
30267
30449
  UnityLodGroups.FADE_SPEEDTREE = 2;
30268
30450
  UnityLodGroups.FADE_SECONDS = 0.5;
30451
+ UnityLodGroups.NATIVE_TRANSFORM_TOLERANCE_METERS = 0.005;
30452
+ UnityLodGroups.NATIVE_BASIS_TOLERANCE = 0.001;
30453
+ UnityLodGroups.BASIS_ELEMENTS = [0, 1, 2, 4, 5, 6, 8, 9, 10];
30269
30454
  UnityLodGroups.DeferLoadFadeClones = true;
30270
30455
  UnityLodGroups.FadeCloneSliceMs = 8;
30271
30456
  UnityLodGroups._cloneQueues = new Map();
@@ -69897,16 +70082,36 @@ export class HdrpFogPass {
69897
70082
  return HdrpFogPass.Uniforms.concat((S != null && Array.isArray(S.AtmosphereUniforms)) ? S.AtmosphereUniforms : [], ["scale"]);
69898
70083
  }
69899
70084
  static FogBlock(scene) {
69900
- const block = (HdrpRendering != null) ? HdrpRendering.Block(scene) : null;
70085
+ if (HdrpRendering == null)
70086
+ return null;
70087
+ const fog = PhysicalSkyBlock.FogOf(HdrpRendering.SceneMetadata(scene));
70088
+ if (fog != null && typeof fog.enabled === "boolean")
70089
+ return fog;
70090
+ const block = HdrpRendering.Block(scene);
69901
70091
  return (block != null && block.fog != null && typeof block.fog === "object") ? block.fog : null;
69902
70092
  }
69903
70093
  static SkyOf(scene) {
69904
70094
  const live = (HdrpPhysicallyBasedSky != null) ? HdrpPhysicallyBasedSky.Get(scene) : null;
69905
70095
  if (live != null)
69906
70096
  return live;
69907
- const block = (HdrpRendering != null) ? HdrpRendering.Block(scene) : null;
70097
+ if (HdrpRendering == null)
70098
+ return null;
70099
+ const sky = PhysicalSkyBlock.SkyOf(HdrpRendering.SceneMetadata(scene));
70100
+ if (sky != null)
70101
+ return sky;
70102
+ const block = HdrpRendering.Block(scene);
69908
70103
  return (block != null) ? block.sky : null;
69909
70104
  }
70105
+ static BindChain(effect, scene, fog) {
70106
+ if (HdrpRendering.IsParity(scene) === true) {
70107
+ effect.setFloat4("fogChain", 0, 0, 0, 0);
70108
+ return;
70109
+ }
70110
+ const water = (MeshWaterSurface != null) ? MeshWaterSurface.Get(scene) : null;
70111
+ const isDisplayEncoded = scene.imageProcessingConfiguration.applyByPostProcess !== true;
70112
+ const isFogOn = fog != null && fog.enabled === true;
70113
+ effect.setFloat4("fogChain", isDisplayEncoded ? 1 : 0, (water != null) ? 1 : 0, (water != null) ? water.getSurfaceHeight() : 0, isFogOn ? 1 : 0);
70114
+ }
69910
70115
  static Frame(scene, fog) {
69911
70116
  const s = scene;
69912
70117
  const renderId = scene.getRenderId();
@@ -70204,6 +70409,113 @@ fn hdrpFogAmbient(dir: vec3f) -> vec3f {
70204
70409
  + uniforms.fogAmb4.rgb * (d.x * d.y) + uniforms.fogAmb5.rgb * (d.y * d.z) + uniforms.fogAmb6.rgb * (3.0 * d.z * d.z - 1.0)
70205
70410
  + uniforms.fogAmb7.rgb * (d.x * d.z) + uniforms.fogAmb8.rgb * (d.x * d.x - d.y * d.y);
70206
70411
  }
70412
+ `;
70413
+ }
70414
+ static AerialPerspectiveGlsl() {
70415
+ return `
70416
+ vec3 fogRescaledChapman(vec2 z, vec2 Z, float c) {
70417
+ float sinTheta = sqrt(clamp(1.0 - c * c, 0.0, 1.0));
70418
+ vec2 ch = vec2(pbChapmanUpper(z.x, abs(c)) * exp(Z.x - z.x), pbChapmanUpper(z.y, abs(c)) * exp(Z.y - z.y));
70419
+ if (c < 0.0) {
70420
+ vec2 z0 = z * sinTheta;
70421
+ ch = vec2(2.0 * pbChapmanHorizontal(z0.x) * exp(Z.x - z0.x), 2.0 * pbChapmanHorizontal(z0.y) * exp(Z.y - z0.y)) - ch;
70422
+ }
70423
+ return vec3(ch, 0.0);
70424
+ }
70425
+ vec3 fogApOpticalDepth(vec3 O, vec3 V, float t) {
70426
+ float R = pbAtmo0.x;
70427
+ vec2 n = vec2(pbAtmo1.x, pbAtmo1.z);
70428
+ vec2 H = vec2(pbAtmo1.y, pbAtmo1.w);
70429
+ float r = length(O);
70430
+ vec3 N = O / r;
70431
+ float cosChi = dot(N, V);
70432
+ vec2 Z = R * n;
70433
+ vec3 P1 = O + t * V;
70434
+ float r1 = length(P1);
70435
+ float cosChi1 = dot(P1, V) / r1;
70436
+ float cosChi0 = (cosChi1 >= 0.0) ? cosChi : -cosChi;
70437
+ vec2 ch0 = fogRescaledChapman(r * n, Z, cosChi0).xy;
70438
+ vec2 z1 = r1 * n;
70439
+ vec2 ch1 = vec2(pbChapmanUpper(z1.x, abs(cosChi1)) * exp(Z.x - z1.x), pbChapmanUpper(z1.y, abs(cosChi1)) * exp(Z.y - z1.y));
70440
+ vec2 od = abs(ch0 - ch1) * H;
70441
+ float ozone = pbOzoneOpticalDepth(r, cosChi, t);
70442
+ return od.x * pbAirExt.xyz + od.y * pbAirExt.w + ozone * pbOzone0.xyz;
70443
+ }
70444
+ void fogAerialPerspective(vec2 uv, vec3 O, vec3 dir, float tFrag, out vec3 apColor, out vec3 apOpacity) {
70445
+ float r = length(O);
70446
+ float cosChi = dot(O / r, dir);
70447
+ float tAp = min(tFrag, 128000.0);
70448
+ float off = 1.0 / 128.0;
70449
+ float s = sqrt(max(tAp / 128000.0 - off * off, 0.0)) - off;
70450
+ float layer = clamp(s * 64.0 - 0.5, 0.0, 63.0);
70451
+ float l0 = floor(layer);
70452
+ float l1 = min(l0 + 1.0, 63.0);
70453
+ apColor = mix(textureLod(apLut, vec3(uv, l0), 0.0).rgb, textureLod(apLut, vec3(uv, l1), 0.0).rgb, layer - l0);
70454
+ apOpacity = vec3(1.0) - exp(-fogApOpticalDepth(O, dir, tAp));
70455
+ apOpacity = pbDesaturate(apOpacity, pbZenith.w) * pbArt.x;
70456
+ float chi = acos(clamp(cosChi, -1.0, 1.0));
70457
+ float hor = acos(clamp(pbCosHorizon(r), -1.0, 1.0));
70458
+ float nrm = clamp(1.0 - chi / max(hor, 0.0001), 0.0, 1.0);
70459
+ float te = exp(pbArt.y * nrm) * pbArt.z - pbArt.z;
70460
+ apColor *= mix(pbHorizon.xyz, pbZenith.xyz, te);
70461
+ }
70462
+ `;
70463
+ }
70464
+ static AerialPerspectiveWgsl() {
70465
+ return `
70466
+ fn fogRescaledChapman(z: vec2f, Z: vec2f, c: f32) -> vec2f {
70467
+ let sinTheta: f32 = sqrt(clamp(1.0 - c * c, 0.0, 1.0));
70468
+ var ch: vec2f = vec2f(pbChapmanUpper(z.x, abs(c)) * exp(Z.x - z.x), pbChapmanUpper(z.y, abs(c)) * exp(Z.y - z.y));
70469
+ if (c < 0.0) {
70470
+ let z0: vec2f = z * sinTheta;
70471
+ ch = vec2f(2.0 * pbChapmanHorizontal(z0.x) * exp(Z.x - z0.x), 2.0 * pbChapmanHorizontal(z0.y) * exp(Z.y - z0.y)) - ch;
70472
+ }
70473
+ return ch;
70474
+ }
70475
+ fn fogApOpticalDepth(O: vec3f, V: vec3f, t: f32) -> vec3f {
70476
+ let R: f32 = uniforms.pbAtmo0.x;
70477
+ let n: vec2f = vec2f(uniforms.pbAtmo1.x, uniforms.pbAtmo1.z);
70478
+ let H: vec2f = vec2f(uniforms.pbAtmo1.y, uniforms.pbAtmo1.w);
70479
+ let r: f32 = length(O);
70480
+ let N: vec3f = O / r;
70481
+ let cosChi: f32 = dot(N, V);
70482
+ let Z: vec2f = R * n;
70483
+ let P1: vec3f = O + t * V;
70484
+ let r1: f32 = length(P1);
70485
+ let cosChi1: f32 = dot(P1, V) / r1;
70486
+ let cosChi0: f32 = select(-cosChi, cosChi, cosChi1 >= 0.0);
70487
+ let ch0: vec2f = fogRescaledChapman(r * n, Z, cosChi0);
70488
+ let z1: vec2f = r1 * n;
70489
+ let ch1: vec2f = vec2f(pbChapmanUpper(z1.x, abs(cosChi1)) * exp(Z.x - z1.x), pbChapmanUpper(z1.y, abs(cosChi1)) * exp(Z.y - z1.y));
70490
+ let od: vec2f = abs(ch0 - ch1) * H;
70491
+ let ozone: f32 = pbOzoneOpticalDepth(r, cosChi, t);
70492
+ return od.x * uniforms.pbAirExt.xyz + od.y * uniforms.pbAirExt.w + ozone * uniforms.pbOzone0.xyz;
70493
+ }
70494
+ struct FogAerial {
70495
+ color: vec3f,
70496
+ opacity: vec3f,
70497
+ };
70498
+ fn fogAerialPerspective(uv: vec2f, O: vec3f, dir: vec3f, tFrag: f32) -> FogAerial {
70499
+ let r: f32 = length(O);
70500
+ let cosChi: f32 = dot(O / r, dir);
70501
+ let tAp: f32 = min(tFrag, 128000.0);
70502
+ let off: f32 = 1.0 / 128.0;
70503
+ let s: f32 = sqrt(max(tAp / 128000.0 - off * off, 0.0)) - off;
70504
+ let layer: f32 = clamp(s * 64.0 - 0.5, 0.0, 63.0);
70505
+ let l0: f32 = floor(layer);
70506
+ let l1: f32 = min(l0 + 1.0, 63.0);
70507
+ let c0: vec3f = textureSampleLevel(apLut, apLutSampler, uv, i32(l0), 0.0).rgb;
70508
+ let c1: vec3f = textureSampleLevel(apLut, apLutSampler, uv, i32(l1), 0.0).rgb;
70509
+ var apColor: vec3f = mix(c0, c1, vec3f(layer - l0));
70510
+ var apOpacity: vec3f = vec3f(1.0) - exp(-fogApOpticalDepth(O, dir, tAp));
70511
+ apOpacity = pbDesaturate(apOpacity, uniforms.pbZenith.w) * uniforms.pbArt.x;
70512
+ let chi: f32 = acos(clamp(cosChi, -1.0, 1.0));
70513
+ let hor: f32 = acos(clamp(pbCosHorizon(r), -1.0, 1.0));
70514
+ let nrm: f32 = clamp(1.0 - chi / max(hor, 0.0001), 0.0, 1.0);
70515
+ let te: f32 = exp(uniforms.pbArt.y * nrm) * uniforms.pbArt.z - uniforms.pbArt.z;
70516
+ apColor *= mix(uniforms.pbHorizon.xyz, uniforms.pbZenith.xyz, vec3f(te));
70517
+ return FogAerial(apColor, apOpacity);
70518
+ }
70207
70519
  `;
70208
70520
  }
70209
70521
  static GlslHelpers(prefix) {
@@ -70251,6 +70563,7 @@ fn ${prefix}PhaseHG(g: f32, c: f32) -> f32 {
70251
70563
  precision highp sampler2DArray;
70252
70564
  #endif
70253
70565
  precision highp float;
70566
+ #define FOG_DISPLAY_GAMMA 2.2
70254
70567
  varying vec2 vUV;
70255
70568
  uniform sampler2D textureSampler;
70256
70569
  uniform sampler2D depthSampler;
@@ -70270,40 +70583,14 @@ ${HdrpFogPass.AmbientDeclarations(false)}uniform vec4 fogEye;
70270
70583
  uniform vec4 fogForward;
70271
70584
  uniform vec4 fogViewport;
70272
70585
  uniform vec4 fogInput;
70586
+ uniform vec4 fogChain;
70273
70587
  uniform mat4 fogInvViewProj;
70274
70588
  uniform mat4 fogEnvMatrix;
70275
70589
  #ifdef HDRP_FOG_AP
70276
70590
  uniform sampler2DArray apLut;
70277
70591
  uniform vec4 fogApCamera;
70278
70592
  ${ap}
70279
- vec3 fogRescaledChapman(vec2 z, vec2 Z, float c) {
70280
- float sinTheta = sqrt(clamp(1.0 - c * c, 0.0, 1.0));
70281
- vec2 ch = vec2(pbChapmanUpper(z.x, abs(c)) * exp(Z.x - z.x), pbChapmanUpper(z.y, abs(c)) * exp(Z.y - z.y));
70282
- if (c < 0.0) {
70283
- vec2 z0 = z * sinTheta;
70284
- ch = vec2(2.0 * pbChapmanHorizontal(z0.x) * exp(Z.x - z0.x), 2.0 * pbChapmanHorizontal(z0.y) * exp(Z.y - z0.y)) - ch;
70285
- }
70286
- return vec3(ch, 0.0);
70287
- }
70288
- vec3 fogApOpticalDepth(vec3 O, vec3 V, float t) {
70289
- float R = pbAtmo0.x;
70290
- vec2 n = vec2(pbAtmo1.x, pbAtmo1.z);
70291
- vec2 H = vec2(pbAtmo1.y, pbAtmo1.w);
70292
- float r = length(O);
70293
- vec3 N = O / r;
70294
- float cosChi = dot(N, V);
70295
- vec2 Z = R * n;
70296
- vec3 P1 = O + t * V;
70297
- float r1 = length(P1);
70298
- float cosChi1 = dot(P1, V) / r1;
70299
- float cosChi0 = (cosChi1 >= 0.0) ? cosChi : -cosChi;
70300
- vec2 ch0 = fogRescaledChapman(r * n, Z, cosChi0).xy;
70301
- vec2 z1 = r1 * n;
70302
- vec2 ch1 = vec2(pbChapmanUpper(z1.x, abs(cosChi1)) * exp(Z.x - z1.x), pbChapmanUpper(z1.y, abs(cosChi1)) * exp(Z.y - z1.y));
70303
- vec2 od = abs(ch0 - ch1) * H;
70304
- float ozone = pbOzoneOpticalDepth(r, cosChi, t);
70305
- return od.x * pbAirExt.xyz + od.y * pbAirExt.w + ozone * pbOzone0.xyz;
70306
- }
70593
+ ${HdrpFogPass.AerialPerspectiveGlsl()}
70307
70594
  #endif
70308
70595
  ${HdrpFogPass.AmbientGlsl()}${HdrpFogPass.GlslHelpers("hdrpFog")}${HdrpFogPass.LocalGlsl()}
70309
70596
  vec3 hdrpFogColor(vec3 dir, float dist) {
@@ -70356,29 +70643,28 @@ void main(void) {
70356
70643
  opacity = vec3(1.0 - trCamera);
70357
70644
  }
70358
70645
  #ifdef HDRP_FOG_AP
70359
- if (!isSky && fogApCamera.w > 0.5 && fogEye.w > 0.5) {
70360
- vec3 O = fogApCamera.xyz;
70361
- float r = length(O);
70362
- float cosChi = dot(O / r, dir);
70363
- float tAp = min(tFrag, 128000.0);
70364
- float off = 1.0 / 128.0;
70365
- float s = sqrt(max(tAp / 128000.0 - off * off, 0.0)) - off;
70366
- float layer = clamp(s * 64.0 - 0.5, 0.0, 63.0);
70367
- float l0 = floor(layer);
70368
- float l1 = min(l0 + 1.0, 63.0);
70369
- vec3 apColor = mix(textureLod(apLut, vec3(vUV, l0), 0.0).rgb, textureLod(apLut, vec3(vUV, l1), 0.0).rgb, layer - l0);
70370
- vec3 apOpacity = vec3(1.0) - exp(-fogApOpticalDepth(O, dir, tAp));
70371
- apOpacity = pbDesaturate(apOpacity, pbZenith.w) * pbArt.x;
70372
- float chi = acos(clamp(cosChi, -1.0, 1.0));
70373
- float hor = acos(clamp(pbCosHorizon(r), -1.0, 1.0));
70374
- float nrm = clamp(1.0 - chi / max(hor, 0.0001), 0.0, 1.0);
70375
- float te = exp(pbArt.y * nrm) * pbArt.z - pbArt.z;
70376
- apColor *= mix(pbHorizon.xyz, pbZenith.xyz, te);
70646
+ // fogChain.w: a URP sky with its fog on gives sky pixels aerial perspective up to the fog distance, as Unity's opaque pass does
70647
+ if ((!isSky || fogChain.w > 0.5) && fogApCamera.w > 0.5 && fogEye.w > 0.5) {
70648
+ vec3 apColor = vec3(0.0);
70649
+ vec3 apOpacity = vec3(0.0);
70650
+ fogAerialPerspective(vUV, fogApCamera.xyz, dir, tFrag, apColor, apOpacity);
70377
70651
  color = color + (vec3(1.0) - opacity) * apColor;
70378
70652
  opacity = opacity + (vec3(1.0) - opacity) * apOpacity;
70379
70653
  }
70380
70654
  #endif
70381
- gl_FragColor = vec4(src.rgb * (vec3(1.0) - opacity) + color, src.a);
70655
+ // fogChain.yz: a URP sky fogs before transparents, so a ray that meets the water before the opaque depth keeps its colour
70656
+ if (fogChain.y > 0.5 && dir.y < 0.0 && fogEye.y > fogChain.z) {
70657
+ float tWater = (fogChain.z - fogEye.y) / dir.y;
70658
+ if (isSky || tWater < tFrag) {
70659
+ color = vec3(0.0);
70660
+ opacity = vec3(0.0);
70661
+ }
70662
+ }
70663
+ // fogChain.x: the chain holds display values, so the fog composites in linear and encodes again
70664
+ vec3 scene = (fogChain.x > 0.5) ? pow(src.rgb, vec3(FOG_DISPLAY_GAMMA)) : src.rgb;
70665
+ vec3 fogged = scene * (vec3(1.0) - opacity) + color;
70666
+ if (fogChain.x > 0.5) fogged = pow(clamp(fogged, vec3(0.0), vec3(1.0)), vec3(1.0 / FOG_DISPLAY_GAMMA));
70667
+ gl_FragColor = vec4(fogged, src.a);
70382
70668
  }
70383
70669
  `;
70384
70670
  }
@@ -70386,6 +70672,7 @@ void main(void) {
70386
70672
  const S = HdrpPbSkyShaders;
70387
70673
  const ap = (S != null) ? (S.WgslUniforms() + S.WgslCommon()) : "";
70388
70674
  return `varying vUV: vec2f;
70675
+ const FOG_DISPLAY_GAMMA: f32 = 2.2;
70389
70676
  var textureSamplerSampler: sampler;
70390
70677
  var textureSampler: texture_2d<f32>;
70391
70678
  var depthSamplerSampler: sampler;
@@ -70407,6 +70694,7 @@ ${HdrpFogPass.AmbientDeclarations(true)}uniform fogEye: vec4f;
70407
70694
  uniform fogForward: vec4f;
70408
70695
  uniform fogViewport: vec4f;
70409
70696
  uniform fogInput: vec4f;
70697
+ uniform fogChain: vec4f;
70410
70698
  uniform fogInvViewProj: mat4x4f;
70411
70699
  uniform fogEnvMatrix: mat4x4f;
70412
70700
  #ifdef HDRP_FOG_AP
@@ -70414,34 +70702,7 @@ var apLutSampler: sampler;
70414
70702
  var apLut: texture_2d_array<f32>;
70415
70703
  uniform fogApCamera: vec4f;
70416
70704
  ${ap}
70417
- fn fogRescaledChapman(z: vec2f, Z: vec2f, c: f32) -> vec2f {
70418
- let sinTheta: f32 = sqrt(clamp(1.0 - c * c, 0.0, 1.0));
70419
- var ch: vec2f = vec2f(pbChapmanUpper(z.x, abs(c)) * exp(Z.x - z.x), pbChapmanUpper(z.y, abs(c)) * exp(Z.y - z.y));
70420
- if (c < 0.0) {
70421
- let z0: vec2f = z * sinTheta;
70422
- ch = vec2f(2.0 * pbChapmanHorizontal(z0.x) * exp(Z.x - z0.x), 2.0 * pbChapmanHorizontal(z0.y) * exp(Z.y - z0.y)) - ch;
70423
- }
70424
- return ch;
70425
- }
70426
- fn fogApOpticalDepth(O: vec3f, V: vec3f, t: f32) -> vec3f {
70427
- let R: f32 = uniforms.pbAtmo0.x;
70428
- let n: vec2f = vec2f(uniforms.pbAtmo1.x, uniforms.pbAtmo1.z);
70429
- let H: vec2f = vec2f(uniforms.pbAtmo1.y, uniforms.pbAtmo1.w);
70430
- let r: f32 = length(O);
70431
- let N: vec3f = O / r;
70432
- let cosChi: f32 = dot(N, V);
70433
- let Z: vec2f = R * n;
70434
- let P1: vec3f = O + t * V;
70435
- let r1: f32 = length(P1);
70436
- let cosChi1: f32 = dot(P1, V) / r1;
70437
- let cosChi0: f32 = select(-cosChi, cosChi, cosChi1 >= 0.0);
70438
- let ch0: vec2f = fogRescaledChapman(r * n, Z, cosChi0);
70439
- let z1: vec2f = r1 * n;
70440
- let ch1: vec2f = vec2f(pbChapmanUpper(z1.x, abs(cosChi1)) * exp(Z.x - z1.x), pbChapmanUpper(z1.y, abs(cosChi1)) * exp(Z.y - z1.y));
70441
- let od: vec2f = abs(ch0 - ch1) * H;
70442
- let ozone: f32 = pbOzoneOpticalDepth(r, cosChi, t);
70443
- return od.x * uniforms.pbAirExt.xyz + od.y * uniforms.pbAirExt.w + ozone * uniforms.pbOzone0.xyz;
70444
- }
70705
+ ${HdrpFogPass.AerialPerspectiveWgsl()}
70445
70706
  #endif
70446
70707
  ${HdrpFogPass.AmbientWgsl()}${HdrpFogPass.WgslHelpers("hdrpFog")}${HdrpFogPass.LocalWgsl()}
70447
70708
  fn hdrpFogColor(dir: vec3f, dist: f32) -> vec3f {
@@ -70497,31 +70758,29 @@ fn main(input: FragmentInputs) -> FragmentOutputs {
70497
70758
  opacity = vec3f(1.0 - trCamera);
70498
70759
  }
70499
70760
  #ifdef HDRP_FOG_AP
70500
- if (!isSky && uniforms.fogApCamera.w > 0.5 && uniforms.fogEye.w > 0.5) {
70501
- let O: vec3f = uniforms.fogApCamera.xyz;
70502
- let r: f32 = length(O);
70503
- let cosChi: f32 = dot(O / r, dir);
70504
- let tAp: f32 = min(tFrag, 128000.0);
70505
- let off: f32 = 1.0 / 128.0;
70506
- let s: f32 = sqrt(max(tAp / 128000.0 - off * off, 0.0)) - off;
70507
- let layer: f32 = clamp(s * 64.0 - 0.5, 0.0, 63.0);
70508
- let l0: f32 = floor(layer);
70509
- let l1: f32 = min(l0 + 1.0, 63.0);
70510
- let c0: vec3f = textureSampleLevel(apLut, apLutSampler, uv, i32(l0), 0.0).rgb;
70511
- let c1: vec3f = textureSampleLevel(apLut, apLutSampler, uv, i32(l1), 0.0).rgb;
70512
- var apColor: vec3f = mix(c0, c1, vec3f(layer - l0));
70513
- var apOpacity: vec3f = vec3f(1.0) - exp(-fogApOpticalDepth(O, dir, tAp));
70514
- apOpacity = pbDesaturate(apOpacity, uniforms.pbZenith.w) * uniforms.pbArt.x;
70515
- let chi: f32 = acos(clamp(cosChi, -1.0, 1.0));
70516
- let hor: f32 = acos(clamp(pbCosHorizon(r), -1.0, 1.0));
70517
- let nrm: f32 = clamp(1.0 - chi / max(hor, 0.0001), 0.0, 1.0);
70518
- let te: f32 = exp(uniforms.pbArt.y * nrm) * uniforms.pbArt.z - uniforms.pbArt.z;
70519
- apColor *= mix(uniforms.pbHorizon.xyz, uniforms.pbZenith.xyz, vec3f(te));
70761
+ // fogChain.w: a URP sky with its fog on gives sky pixels aerial perspective up to the fog distance, as Unity's opaque pass does
70762
+ if ((!isSky || uniforms.fogChain.w > 0.5) && uniforms.fogApCamera.w > 0.5 && uniforms.fogEye.w > 0.5) {
70763
+ let aerial: FogAerial = fogAerialPerspective(uv, uniforms.fogApCamera.xyz, dir, tFrag);
70764
+ let apColor: vec3f = aerial.color;
70765
+ let apOpacity: vec3f = aerial.opacity;
70520
70766
  color = color + (vec3f(1.0) - opacity) * apColor;
70521
70767
  opacity = opacity + (vec3f(1.0) - opacity) * apOpacity;
70522
70768
  }
70523
70769
  #endif
70524
- fragmentOutputs.color = vec4f(src.rgb * (vec3f(1.0) - opacity) + color, src.a);
70770
+ // fogChain.yz: a URP sky fogs before transparents, so a ray that meets the water before the opaque depth keeps its colour
70771
+ if (uniforms.fogChain.y > 0.5 && dir.y < 0.0 && uniforms.fogEye.y > uniforms.fogChain.z) {
70772
+ let tWater: f32 = (uniforms.fogChain.z - uniforms.fogEye.y) / dir.y;
70773
+ if (isSky || tWater < tFrag) {
70774
+ color = vec3f(0.0);
70775
+ opacity = vec3f(0.0);
70776
+ }
70777
+ }
70778
+ // fogChain.x: the chain holds display values, so the fog composites in linear and encodes again
70779
+ var scene: vec3f = src.rgb;
70780
+ if (uniforms.fogChain.x > 0.5) { scene = pow(src.rgb, vec3f(FOG_DISPLAY_GAMMA)); }
70781
+ var fogged: vec3f = scene * (vec3f(1.0) - opacity) + color;
70782
+ if (uniforms.fogChain.x > 0.5) { fogged = pow(clamp(fogged, vec3f(0.0), vec3f(1.0)), vec3f(1.0 / FOG_DISPLAY_GAMMA)); }
70783
+ fragmentOutputs.color = vec4f(fogged, src.a);
70525
70784
  }
70526
70785
  `;
70527
70786
  }
@@ -70602,6 +70861,7 @@ fn main(input: FragmentInputs) -> FragmentOutputs {
70602
70861
  effect.setFloat4("fogE", frame.e[0], frame.e[1], frame.e[2], frame.e[3]);
70603
70862
  effect.setFloat4("fogSunDir", frame.sunDir[0], frame.sunDir[1], frame.sunDir[2], frame.sunDir[3]);
70604
70863
  effect.setFloat4("fogSun", frame.sun[0], frame.sun[1], frame.sun[2], frame.sun[3]);
70864
+ HdrpFogPass.BindChain(effect, scene, state.fog);
70605
70865
  effect.setFloat4("fogEnv", frame.env[0], frame.env[1], frame.env[2], frame.env[3]);
70606
70866
  effect.setFloat4("fogSky", frame.sky[0], frame.sky[1], frame.sky[2], frame.sky[3]);
70607
70867
  for (let i = 0; i < HdrpFogPass.AmbientVectors; i++)
@@ -70633,7 +70893,7 @@ fn main(input: FragmentInputs) -> FragmentOutputs {
70633
70893
  }
70634
70894
  }
70635
70895
  HdrpFogPass.ShaderName = "toolkitHdrpFog";
70636
- HdrpFogPass.Uniforms = ["fogA", "fogB", "fogC", "fogD", "fogE", "fogSunDir", "fogSun", "fogEnv", "fogSky", "fogAmb0", "fogAmb1", "fogAmb2", "fogAmb3", "fogAmb4", "fogAmb5", "fogAmb6", "fogAmb7", "fogAmb8", "fogEye", "fogForward", "fogViewport", "fogInput", "fogInvViewProj", "fogEnvMatrix", "fogApCamera"];
70896
+ HdrpFogPass.Uniforms = ["fogA", "fogB", "fogC", "fogD", "fogE", "fogSunDir", "fogSun", "fogEnv", "fogSky", "fogAmb0", "fogAmb1", "fogAmb2", "fogAmb3", "fogAmb4", "fogAmb5", "fogAmb6", "fogAmb7", "fogAmb8", "fogEye", "fogForward", "fogViewport", "fogInput", "fogInvViewProj", "fogEnvMatrix", "fogApCamera", "fogChain"];
70637
70897
  HdrpFogPass.Samplers = ["depthSampler", "fogEnvSampler", "apLut"];
70638
70898
  HdrpFogPass.AmbientVectors = 9;
70639
70899
  HdrpFogPass.registered = false;
@@ -71258,6 +71518,8 @@ export class HdrpPbSkyShaders {
71258
71518
  #define PB_TWO_PI 6.28318530718
71259
71519
  #define PB_HALF_PI 1.57079632679
71260
71520
  #define PB_INV_PI 0.31830988618
71521
+ #define PB_DISPLAY_GAMMA 0.45454545454
71522
+ #define PB_NO_BODY_HIT 3.0e38
71261
71523
  #define PB_FLT_MIN 1.175494351e-38
71262
71524
  #define PB_FLT_EPS 5.960464478e-8
71263
71525
  float pbSat(float x) { return clamp(x, 0.0, 1.0); }
@@ -71687,6 +71949,10 @@ vec2 pbCloudLighting${X}(vec2 uv) {
71687
71949
  ` + HdrpPbSkyShaders.GlslCloudUniforms() + `#ifdef PB_CLOUDS
71688
71950
  uniform sampler2D cloudTex;
71689
71951
  #endif
71952
+ #ifdef PBSKY_SPACE_EMISSION
71953
+ uniform samplerCube spaceCube;
71954
+ uniform vec4 pbSpace;
71955
+ #endif
71690
71956
  ` + HdrpPbSkyShaders.GlslCommon() + `
71691
71957
  vec2 pbMapSkyView(float cosChi, vec3 V) {
71692
71958
  float coord = pbFastACos(cosChi) / PB_HALF_PI;
@@ -71781,6 +72047,12 @@ vec3 pbRenderSky(vec3 d) {
71781
72047
  }
71782
72048
  }
71783
72049
  }
72050
+ #ifdef PBSKY_SPACE_EMISSION
72051
+ if (lookAbove && tFrag >= PB_NO_BODY_HIT) {
72052
+ vec3 spaceDirection = vec3((pbSpace.y * d.x - pbSpace.z * d.z) * pbSpace.w, d.y, pbSpace.z * d.x + pbSpace.y * d.z);
72053
+ radiance += textureLod(spaceCube, spaceDirection, 0.0).rgb * pbSpace.x;
72054
+ }
72055
+ #endif
71784
72056
  vec3 skyColor = vec3(0.0);
71785
72057
  vec3 skyOpacity = vec3(0.0);
71786
72058
  if (lookAbove) {
@@ -71803,7 +72075,12 @@ vec3 pbRenderSky(vec3 d) {
71803
72075
  }
71804
72076
  void main(void) {
71805
72077
  vec3 d = normalize(vDir);
71806
- gl_FragColor = vec4(pbRenderSky(d) * pbArt.w, 1.0);
72078
+ // pbCounts.w: a URP sky on an LDR camera. Its world has no ground below the horizon, where the sky pass has no in-scatter
72079
+ if (pbCounts.w > 0.5) d.y = max(d.y, 0.0);
72080
+ vec3 color = pbRenderSky(normalize(d)) * pbArt.w;
72081
+ // the same flag: the camera has no HDR chain, so the sky encodes for display as materials do
72082
+ if (pbCounts.w > 0.5) color = pow(clamp(color, vec3(0.0), vec3(1.0)), vec3(PB_DISPLAY_GAMMA));
72083
+ gl_FragColor = vec4(color, 1.0);
71807
72084
  }
71808
72085
  `;
71809
72086
  }
@@ -71830,6 +72107,8 @@ const PB_PI: f32 = 3.14159265359;
71830
72107
  const PB_TWO_PI: f32 = 6.28318530718;
71831
72108
  const PB_HALF_PI: f32 = 1.57079632679;
71832
72109
  const PB_INV_PI: f32 = 0.31830988618;
72110
+ const PB_DISPLAY_GAMMA: f32 = 0.45454545454;
72111
+ const PB_NO_BODY_HIT: f32 = 3.0e38;
71833
72112
  const PB_FLT_MIN: f32 = 1.175494351e-38;
71834
72113
  const PB_FLT_EPS: f32 = 5.960464478e-8;
71835
72114
  fn pbSat(x: f32) -> f32 { return clamp(x, 0.0, 1.0); }
@@ -72272,6 +72551,11 @@ fn pbCloudLighting${X}(uv: vec2f) -> vec2f {
72272
72551
  var cloudTexSampler: sampler;
72273
72552
  var cloudTex: texture_2d<f32>;
72274
72553
  #endif
72554
+ #ifdef PBSKY_SPACE_EMISSION
72555
+ var spaceCubeSampler: sampler;
72556
+ var spaceCube: texture_cube<f32>;
72557
+ uniform pbSpace: vec4f;
72558
+ #endif
72275
72559
  ` + HdrpPbSkyShaders.WgslCommon() + `
72276
72560
  fn pbMapSkyView(cosChi: f32, V: vec3f) -> vec2f {
72277
72561
  let coord: f32 = pbFastACos(cosChi) / PB_HALF_PI;
@@ -72367,6 +72651,12 @@ fn pbRenderSky(d: vec3f) -> vec3f {
72367
72651
  }
72368
72652
  }
72369
72653
  }
72654
+ #ifdef PBSKY_SPACE_EMISSION
72655
+ if (lookAbove && tFrag >= PB_NO_BODY_HIT) {
72656
+ let spaceDirection: vec3f = vec3f((uniforms.pbSpace.y * d.x - uniforms.pbSpace.z * d.z) * uniforms.pbSpace.w, d.y, uniforms.pbSpace.z * d.x + uniforms.pbSpace.y * d.z);
72657
+ radiance += textureSampleLevel(spaceCube, spaceCubeSampler, spaceDirection, 0.0).rgb * uniforms.pbSpace.x;
72658
+ }
72659
+ #endif
72370
72660
  var skyColor: vec3f = vec3f(0.0);
72371
72661
  var skyOpacity: vec3f = vec3f(0.0);
72372
72662
  if (lookAbove) {
@@ -72389,8 +72679,13 @@ fn pbRenderSky(d: vec3f) -> vec3f {
72389
72679
  }
72390
72680
  @fragment
72391
72681
  fn main(input: FragmentInputs) -> FragmentOutputs {
72392
- let d: vec3f = normalize(fragmentInputs.vDir);
72393
- fragmentOutputs.color = vec4f(pbRenderSky(d) * uniforms.pbArt.w, 1.0);
72682
+ var d: vec3f = normalize(fragmentInputs.vDir);
72683
+ // pbCounts.w: a URP sky on an LDR camera. Its world has no ground below the horizon, where the sky pass has no in-scatter
72684
+ if (uniforms.pbCounts.w > 0.5) { d.y = max(d.y, 0.0); }
72685
+ var color: vec3f = pbRenderSky(normalize(d)) * uniforms.pbArt.w;
72686
+ // the same flag: the camera has no HDR chain, so the sky encodes for display as materials do
72687
+ if (uniforms.pbCounts.w > 0.5) { color = pow(clamp(color, vec3f(0.0), vec3f(1.0)), vec3f(PB_DISPLAY_GAMMA)); }
72688
+ fragmentOutputs.color = vec4f(color, 1.0);
72394
72689
  }
72395
72690
  `;
72396
72691
  }
@@ -72643,17 +72938,16 @@ export class HdrpPhysicallyBasedSky {
72643
72938
  return (sky != null && sky.disposed !== true) ? sky : null;
72644
72939
  }
72645
72940
  static Decide(metadata, engine) {
72646
- if (HdrpRendering.IsParityExport(metadata) === false)
72647
- return { live: false, key: null, reason: "not an HDRP parity export" };
72648
- const sky = metadata.hdrp.sky;
72941
+ const sky = PhysicalSkyBlock.SkyOf(metadata);
72942
+ const isUrp = PhysicalSkyBlock.UrpBlock(metadata) != null && !HdrpRendering.IsParityExport(metadata);
72649
72943
  if (sky == null)
72650
- return { live: false, key: null, reason: "no hdrp.sky block (export older than the live sky)" };
72944
+ return { live: false, key: null, reason: "no physical sky block" };
72651
72945
  const type = (typeof sky.type === "string") ? sky.type.toLowerCase() : "";
72652
72946
  if (type !== "physicallybased")
72653
72947
  return { live: false, key: null, reason: "sky type " + (type || "unknown") };
72654
72948
  if (HdrpPhysicallyBasedSky.IsSupported(engine) === false)
72655
72949
  return { live: false, key: "hdrp:pbsky:unsupported", reason: "no WebGL2 / WebGPU half-float render target" };
72656
- if (sky.volumetricclouds === true)
72950
+ if (isUrp === false && sky.volumetricclouds === true)
72657
72951
  return { live: false, key: "hdrp:pbsky:volumetricclouds", reason: "an active VolumetricClouds is not ported (the bake carries it)" };
72658
72952
  return { live: true, key: null, reason: "live" };
72659
72953
  }
@@ -72665,11 +72959,12 @@ export class HdrpPhysicallyBasedSky {
72665
72959
  if (decision.key != null)
72666
72960
  LightingConversions.WarnOnce(decision.key, "HDRP PhysicallyBasedSky: " + decision.reason + " - the baked sky is used");
72667
72961
  if (decision.key != null)
72668
- HdrpPhysicallyBasedSky.AttachBakedSkySunAttenuation(scene, metadata.hdrp.sky);
72962
+ HdrpPhysicallyBasedSky.AttachBakedSkySunAttenuation(scene, PhysicalSkyBlock.SkyOf(metadata));
72669
72963
  return null;
72670
72964
  }
72671
72965
  try {
72672
- return HdrpPhysicallyBasedSky.Create(scene, metadata.hdrp.sky, null, { root: root, bakedSky: (options != null) ? options.bakedSky : null, rendergroup: (options != null) ? options.rendergroup : 0, environmentSh: (metadata.skybox != null && metadata.skybox.environment != null) ? metadata.skybox.environment.sh : null });
72966
+ const environment = (metadata.skybox != null) ? metadata.skybox.environment : null;
72967
+ return HdrpPhysicallyBasedSky.Create(scene, PhysicalSkyBlock.SkyOf(metadata), null, { root: root, bakedSky: (options != null) ? options.bakedSky : null, rendergroup: (options != null) ? options.rendergroup : 0, environmentSh: (environment != null) ? environment.sh : null });
72673
72968
  }
72674
72969
  catch (e) {
72675
72970
  LightingConversions.WarnOnce("hdrp:pbsky:create", "HDRP PhysicallyBasedSky could not be created (" + ((e && e.message) ? e.message : e) + ") - the baked sky is used");
@@ -72755,6 +73050,13 @@ export class HdrpPhysicallyBasedSky {
72755
73050
  }
72756
73051
  return precomputationHash !== state.lastPrecompute || lightHash !== state.lastLight || state.requested === true;
72757
73052
  }
73053
+ static ProbeRenderOutcome(capturing, pendingFilter, drewSky) {
73054
+ if (capturing === true)
73055
+ return (drewSky === true) ? HdrpPhysicallyBasedSky.ProbeCaptureAccepted : HdrpPhysicallyBasedSky.ProbeCaptureRetry;
73056
+ if (pendingFilter === true && drewSky !== true)
73057
+ return HdrpPhysicallyBasedSky.ProbeCaptureRetry;
73058
+ return HdrpPhysicallyBasedSky.ProbeRenderKept;
73059
+ }
72758
73060
  static SelectAmbientSh(skyAmbientMode, readback, exported) {
72759
73061
  if (skyAmbientMode === 1)
72760
73062
  return (readback != null) ? readback : exported;
@@ -72843,6 +73145,7 @@ export class HdrpPhysicallyBasedSky {
72843
73145
  this.filtering = null;
72844
73146
  this.pendingFilter = false;
72845
73147
  this.capturing = false;
73148
+ this.probeDrawsSky = false;
72846
73149
  this.shReading = false;
72847
73150
  this.readbackHarmonics = null;
72848
73151
  this.exportedHarmonics = null;
@@ -72857,6 +73160,7 @@ export class HdrpPhysicallyBasedSky {
72857
73160
  this.startTime = Date.now();
72858
73161
  this.scene = scene;
72859
73162
  this.sky = sky || {};
73163
+ this.sunIntensityScale = (typeof this.sky.sunintensityscale === "number") ? this.sky.sunintensityscale : 1;
72860
73164
  this.options = options;
72861
73165
  this.fixedLights = lights;
72862
73166
  this.coefficients = HdrpPbSkyMath.Coefficients(this.sky);
@@ -72907,16 +73211,23 @@ export class HdrpPhysicallyBasedSky {
72907
73211
  defines.push("#define PB_CLOUDS");
72908
73212
  if (layers.length >= 2)
72909
73213
  defines.push("#define PB_CLOUDS2");
73214
+ this.spaceCube = this.loadSpaceEmission();
73215
+ if (this.spaceCube != null)
73216
+ defines.push("#define PBSKY_SPACE_EMISSION");
72910
73217
  const material = new ShaderMaterial("HDRP PBSky Material", scene, { vertex: HdrpPbSkyShaders.SkyName, fragment: HdrpPbSkyShaders.SkyName }, {
72911
73218
  attributes: ["position"],
72912
- uniforms: ["world", "viewProjection"].concat(HdrpPbSkyShaders.AtmosphereUniforms, HdrpPbSkyShaders.CloudUniforms),
72913
- samplers: ["skyViewLut", "cloudTex"],
73219
+ uniforms: ["world", "viewProjection", "pbSpace"].concat(HdrpPbSkyShaders.AtmosphereUniforms, HdrpPbSkyShaders.CloudUniforms),
73220
+ samplers: ["skyViewLut", "cloudTex", "spaceCube"],
72914
73221
  defines: defines,
72915
73222
  shaderLanguage: language
72916
73223
  });
72917
73224
  material.backFaceCulling = false;
72918
73225
  material.disableDepthWrite = true;
72919
73226
  material.setTexture("skyViewLut", this.skyViewLut);
73227
+ if (this.spaceCube != null) {
73228
+ material.setTexture("spaceCube", this.spaceCube);
73229
+ material.setVector4("pbSpace", this.spaceEmissionUniform());
73230
+ }
72920
73231
  if (layers.length > 0) {
72921
73232
  for (let i = 0; i < layers.length; i++)
72922
73233
  this.cloudMaps.push(this.loadCloudMap(layers[i]));
@@ -73055,7 +73366,7 @@ export class HdrpPhysicallyBasedSky {
73055
73366
  let exposure = 1;
73056
73367
  const body = new Array(HdrpPbSkyShaders.BodyVec4s * HdrpPbSkyShaders.MaxBodies * 4).fill(0);
73057
73368
  const peSafe = (pe > 0) ? pe : 1;
73058
- const lux = (l) => l.intensity / peSafe;
73369
+ const lux = (l) => (l.intensity * this.sunIntensityScale) / peSafe;
73059
73370
  const sunFor = (self) => {
73060
73371
  let best = null, max = 0;
73061
73372
  for (const l of this.scene.lights)
@@ -73128,7 +73439,7 @@ export class HdrpPhysicallyBasedSky {
73128
73439
  U.pbHorizon = [horizon[0], horizon[1], horizon[2], (typeof p.colorsaturation === "number") ? p.colorsaturation : 1];
73129
73440
  U.pbZenith = [zenith[0], zenith[1], zenith[2], (typeof p.alphasaturation === "number") ? p.alphasaturation : 1];
73130
73441
  U.pbArt = [(typeof p.alphamultiplier === "number") ? p.alphamultiplier : 1, exp[0], exp[1], pe];
73131
- U.pbCounts = [lightCount, lights.length, 1, 0];
73442
+ U.pbCounts = [lightCount, lights.length, 1, this.encodesForDisplay() ? 1 : 0];
73132
73443
  U.pbBody = body;
73133
73444
  this.writeCloudUniforms(lights);
73134
73445
  }
@@ -73163,14 +73474,16 @@ export class HdrpPhysicallyBasedSky {
73163
73474
  }
73164
73475
  }
73165
73476
  cloudAmbient() {
73166
- const down = new Vector3(0, -1, 0);
73477
+ return this.ambientAt(new Vector3(0, -1, 0));
73478
+ }
73479
+ ambientAt(direction) {
73167
73480
  if (this.ambientPolynomial != null)
73168
- return HdrpPbSkyMath.EvaluatePolynomial(this.ambientPolynomial, down);
73481
+ return HdrpPbSkyMath.EvaluatePolynomial(this.ambientPolynomial, direction);
73169
73482
  if (this.exportedHarmonics == null)
73170
73483
  this.exportedHarmonics = this.parseExportedAmbient();
73171
73484
  if (this.exportedHarmonics == null)
73172
73485
  return new Color3(0, 0, 0);
73173
- return HdrpPbSkyMath.EvaluatePolynomial(LightingConversions.PolynomialFromPreScaled(this.exportedHarmonics, 1), down);
73486
+ return HdrpPbSkyMath.EvaluatePolynomial(LightingConversions.PolynomialFromPreScaled(this.exportedHarmonics, 1), direction);
73174
73487
  }
73175
73488
  cloudLayers() {
73176
73489
  const cl = this.sky.cloudlayer;
@@ -73178,6 +73491,22 @@ export class HdrpPhysicallyBasedSky {
73178
73491
  return [];
73179
73492
  return cl.layerlist.filter((l) => l != null && l.cloudmap != null && typeof l.cloudmap.url === "string").slice(0, 2);
73180
73493
  }
73494
+ loadSpaceEmission() {
73495
+ const url = (this.sky.params != null) ? this.sky.params.spaceemissionurl : null;
73496
+ if (typeof url !== "string")
73497
+ return null;
73498
+ const root = (typeof this.options.root === "string") ? this.options.root : "";
73499
+ const cube = new CubeTexture(root + url, this.scene, null, false, null, null, null, Constants.TEXTUREFORMAT_RGBA, false, ".env");
73500
+ cube.name = "HDRP PBSky Space Emission";
73501
+ return cube;
73502
+ }
73503
+ spaceEmissionUniform() {
73504
+ const params = this.sky.params;
73505
+ const rotation = Array.isArray(params.spacerotation) ? params.spacerotation : [0, 0, 0];
73506
+ const radians = Tools.ToRadians(rotation[1]);
73507
+ const multiplier = (typeof params.spaceemissionmultiplier === "number") ? params.spaceemissionmultiplier : 1;
73508
+ return new Vector4(multiplier, Math.cos(radians), Math.sin(radians), this.scene.useRightHandedSystem ? -1 : 1);
73509
+ }
73181
73510
  loadCloudMap(layer) {
73182
73511
  const url = ((typeof this.options.root === "string") ? this.options.root : "") + layer.cloudmap.url;
73183
73512
  const tex = new Texture(url, this.scene, true, true, Texture.BILINEAR_SAMPLINGMODE);
@@ -73209,7 +73538,10 @@ export class HdrpPhysicallyBasedSky {
73209
73538
  this.applyMaterialUniforms();
73210
73539
  return;
73211
73540
  }
73212
- this.material.setVector4("pbCounts", new Vector4(counts[0], counts[1], (this.sky.includesuninbaking === true) ? 1 : 0, counts[3]));
73541
+ this.material.setVector4("pbCounts", new Vector4(counts[0], counts[1], (this.sky.includesuninbaking === true) ? 1 : 0, 0));
73542
+ }
73543
+ encodesForDisplay() {
73544
+ return HdrpRendering.IsParity(this.scene) !== true && this.scene.imageProcessingConfiguration.applyByPostProcess !== true;
73213
73545
  }
73214
73546
  applyMaterialUniforms() {
73215
73547
  const m = this.material;
@@ -73238,7 +73570,7 @@ export class HdrpPhysicallyBasedSky {
73238
73570
  const p = this.sky.params || {};
73239
73571
  if (p.renderingmode === 1)
73240
73572
  LightingConversions.WarnOnce("hdrp:pbsky:material", "HDRP PhysicallyBasedSky: the Material rendering mode (a Shader Graph sky) renders the default live atmosphere");
73241
- if (p.hasgroundtextures === true || p.hasspaceemission === true)
73573
+ if (p.hasgroundtextures === true || (p.hasspaceemission === true && typeof p.spaceemissionurl !== "string"))
73242
73574
  LightingConversions.WarnOnce("hdrp:pbsky:textures", "HDRP PhysicallyBasedSky: ground / space emission textures are not rendered");
73243
73575
  for (const layer of this.cloudLayers())
73244
73576
  if (typeof layer.distortion === "number" && layer.distortion !== 0)
@@ -73271,20 +73603,28 @@ export class HdrpPhysicallyBasedSky {
73271
73603
  scene.customRenderTargets.push(probe.cubeTexture);
73272
73604
  const p = this.sky.params || {};
73273
73605
  probe.cubeTexture.onBeforeRenderObservable.add(() => {
73274
- if (this.capturing !== true)
73275
- return;
73606
+ const subMesh = (this.mesh.subMeshes != null) ? this.mesh.subMeshes[0] : null;
73607
+ this.probeDrawsSky = (subMesh != null) ? this.material.isReadyForSubMesh(this.mesh, subMesh) : this.material.isReady(this.mesh);
73276
73608
  const art = this.uniforms.pbArt, counts = this.uniforms.pbCounts;
73277
73609
  if (art != null)
73278
73610
  this.material.setVector4("pbArt", new Vector4(art[0], art[1], art[2], 1));
73279
73611
  if (counts != null)
73280
- this.material.setVector4("pbCounts", new Vector4(counts[0], counts[1], (this.sky.includesuninbaking === true) ? 1 : 0, counts[3]));
73612
+ this.material.setVector4("pbCounts", new Vector4(counts[0], counts[1], (this.sky.includesuninbaking === true) ? 1 : 0, 0));
73281
73613
  });
73282
73614
  probe.cubeTexture.onAfterRenderObservable.add((faceIndex) => {
73283
- if (this.capturing !== true || (typeof faceIndex === "number" && faceIndex < 5))
73615
+ if (typeof faceIndex === "number" && faceIndex < 5)
73284
73616
  return;
73285
- this.capturing = false;
73286
73617
  this.applyMaterialUniforms();
73287
- this.pendingFilter = true;
73618
+ const state = HdrpPhysicallyBasedSky.ProbeRenderOutcome(this.capturing, this.pendingFilter, this.probeDrawsSky);
73619
+ if (state === HdrpPhysicallyBasedSky.ProbeCaptureAccepted) {
73620
+ this.capturing = false;
73621
+ this.pendingFilter = true;
73622
+ }
73623
+ else if (state === HdrpPhysicallyBasedSky.ProbeCaptureRetry) {
73624
+ this.capturing = true;
73625
+ this.pendingFilter = false;
73626
+ probe.cubeTexture.resetRefreshCounter();
73627
+ }
73288
73628
  });
73289
73629
  this.exportedHarmonics = this.parseExportedAmbient();
73290
73630
  }
@@ -73487,6 +73827,8 @@ export class HdrpPhysicallyBasedSky {
73487
73827
  this.apTarget = engine.createRenderTargetTexture({ width: size[0], height: size[1], layers: size[2] }, { generateMipMaps: false, type: this.textureType, samplingMode: Constants.TEXTURE_BILINEAR_SAMPLINGMODE, format: Constants.TEXTUREFORMAT_RGBA, generateDepthBuffer: false, generateStencilBuffer: false, label: "HDRP PBSky Aerial Perspective" });
73488
73828
  this.apTexture = new BaseTexture(scene, this.apTarget.texture);
73489
73829
  this.apTexture.name = "HDRP PBSky Aerial Perspective";
73830
+ this.apTexture.wrapU = Constants.TEXTURE_CLAMP_ADDRESSMODE;
73831
+ this.apTexture.wrapV = Constants.TEXTURE_CLAMP_ADDRESSMODE;
73490
73832
  LightingConversions.WarnOnce("hdrp:pbsky:aerialshadow", "HDRP PhysicallyBasedSky: the aerial perspective has no shadowed in-scatter (the sun shadow map is not sampled)");
73491
73833
  }
73492
73834
  if (!this.apPass.isReady())
@@ -73575,7 +73917,7 @@ export class HdrpPhysicallyBasedSky {
73575
73917
  catch (e) { }
73576
73918
  for (const light of this.attenuated)
73577
73919
  HdrpPhysicallyBasedSky.RestoreLight(light);
73578
- const disposables = [this.msPass, this.viewPass, this.cloudPass, this.renderer, this.msLut, this.skyViewLut, this.cloudTex, this.material, this.mesh].concat(this.cloudMaps);
73920
+ const disposables = [this.msPass, this.viewPass, this.cloudPass, this.renderer, this.msLut, this.skyViewLut, this.cloudTex, this.spaceCube, this.material, this.mesh].concat(this.cloudMaps);
73579
73921
  for (const d of disposables) {
73580
73922
  try {
73581
73923
  if (d != null)
@@ -73594,6 +73936,9 @@ HdrpPhysicallyBasedSky.AerialPerspectiveSize = [32, 32, 64];
73594
73936
  HdrpPhysicallyBasedSky.AerialMaxDistance = 128000;
73595
73937
  HdrpPhysicallyBasedSky.MeshName = "HDRP Physically Based Sky";
73596
73938
  HdrpPhysicallyBasedSky.OnEnvironmentUpdated = new Observable();
73939
+ HdrpPhysicallyBasedSky.ProbeRenderKept = 0;
73940
+ HdrpPhysicallyBasedSky.ProbeCaptureAccepted = 1;
73941
+ HdrpPhysicallyBasedSky.ProbeCaptureRetry = 2;
73597
73942
  HdrpPhysicallyBasedSky.RealtimeMinFrames = 9;
73598
73943
  HdrpPhysicallyBasedSky.ShProjectionFaceSize = 64;
73599
73944
  HdrpPhysicallyBasedSky.CaptureFaceCount = 6;
@@ -91904,6 +92249,238 @@ LightProbeNetwork.DebugProbeSize = 0.1;
91904
92249
  LightProbeNetwork.InspectorSection = true;
91905
92250
  LightProbeNetwork.OnNetworkChangedObservable = new Observable();
91906
92251
  LightProbeNetwork.Sections = ["positions", "probesh", "tetraindices", "tetraadjacency", "renderersh", "rendererocclusion", "rendereranchor"];
92252
+ export class MeshWaterSurface extends ScriptComponent {
92253
+ constructor(transform, scene, properties = {}, alias = "TOOLKIT.MeshWaterSurface") {
92254
+ super(transform, scene, properties, alias);
92255
+ this.waterProperties = null;
92256
+ this.isUnderwater = false;
92257
+ this.fogColorChannels = [0, 0, 0];
92258
+ this.surfaceFogColor = new Color3(0, 0, 0);
92259
+ this.surfaceFogDensity = 0;
92260
+ this.surfaceFogStart = 0;
92261
+ this.surfaceFogEnd = 0;
92262
+ this.shownNodes = [];
92263
+ this.shownParticles = [];
92264
+ this.volumeNodes = [];
92265
+ this.flippedNodes = [];
92266
+ this.followers = [];
92267
+ this.followPosition = new Vector3(0, 0, 0);
92268
+ }
92269
+ static Get(scene) {
92270
+ if (scene == null)
92271
+ return null;
92272
+ return MeshWaterSurface.Registry.get(scene.uniqueId) ?? null;
92273
+ }
92274
+ awake() {
92275
+ this.waterProperties = this.getProperties();
92276
+ if (MeshWaterSurface.Registry.has(this.scene.uniqueId)) {
92277
+ Tools.Warn("MeshWaterSurface: '" + this.transform.name + "' replaces the scene's first water surface");
92278
+ }
92279
+ MeshWaterSurface.Registry.set(this.scene.uniqueId, this);
92280
+ }
92281
+ ready() {
92282
+ const properties = this.getProperties();
92283
+ if (properties.underwater == null)
92284
+ return;
92285
+ for (const node of this.findNodes(properties.underwater.shownodes)) {
92286
+ for (const component of SceneManager.GetComponents(node) ?? [])
92287
+ ScriptComponent.AwakeInstance(component);
92288
+ }
92289
+ }
92290
+ start() {
92291
+ const underwater = this.waterProperties.underwater;
92292
+ if (underwater == null)
92293
+ return;
92294
+ this.shownNodes = this.findNodes(underwater.shownodes);
92295
+ this.shownParticles = this.findParticles(this.shownNodes);
92296
+ this.volumeNodes = this.findNodes(underwater.volumenodes);
92297
+ this.flippedNodes = this.findNodes(underwater.flipnodes).map((node) => this.prepareFlip(node));
92298
+ this.followers = this.prepareFollowers(underwater.follownodes);
92299
+ this.update();
92300
+ }
92301
+ update() {
92302
+ if (this.waterProperties == null || this.waterProperties.underwater == null)
92303
+ return;
92304
+ const camera = this.scene.activeCamera;
92305
+ if (camera == null)
92306
+ return;
92307
+ const isCameraUnder = this.isCameraUnderwater(camera);
92308
+ if (isCameraUnder !== this.isUnderwater) {
92309
+ this.isUnderwater = isCameraUnder;
92310
+ if (isCameraUnder)
92311
+ this.enterUnderwater();
92312
+ else
92313
+ this.leaveUnderwater();
92314
+ }
92315
+ if (!isCameraUnder)
92316
+ return;
92317
+ this.updateFog(this.waterProperties.underwater.sealevel - camera.globalPosition.y);
92318
+ this.updateFollowers(camera);
92319
+ }
92320
+ destroy() {
92321
+ if (this.isUnderwater)
92322
+ this.leaveUnderwater();
92323
+ this.isUnderwater = false;
92324
+ if (MeshWaterSurface.Registry.get(this.scene.uniqueId) === this)
92325
+ MeshWaterSurface.Registry.delete(this.scene.uniqueId);
92326
+ }
92327
+ getSeaLevel() {
92328
+ return this.waterProperties.sealevel;
92329
+ }
92330
+ getSurfaceHeight() {
92331
+ const properties = this.waterProperties;
92332
+ const clockSeconds = ShaderGlobals.GetClock(this.scene).time;
92333
+ return properties.sealevel + Math.sin(clockSeconds * properties.wavespeed) * properties.waveamplitude;
92334
+ }
92335
+ getCameraHeight(camera) {
92336
+ if (camera == null)
92337
+ return 0;
92338
+ return camera.globalPosition.y - this.getSurfaceHeight();
92339
+ }
92340
+ isCameraUnderwater(camera) {
92341
+ if (camera == null)
92342
+ return false;
92343
+ const underwater = this.waterProperties.underwater;
92344
+ const crossingHeight = (underwater != null) ? underwater.sealevel : this.getSurfaceHeight();
92345
+ return camera.globalPosition.y <= crossingHeight;
92346
+ }
92347
+ ProjectPointOnWaterSurface(searchParameters) {
92348
+ if (searchParameters == null || searchParameters.targetPositionWS == null)
92349
+ return null;
92350
+ const target = searchParameters.targetPositionWS;
92351
+ const targetX = Array.isArray(target) ? target[0] : target.x;
92352
+ const targetZ = Array.isArray(target) ? target[2] : target.z;
92353
+ const projectedPosition = new Vector3(targetX, this.getSurfaceHeight(), targetZ);
92354
+ return {
92355
+ projectedPositionWS: projectedPosition,
92356
+ normalWS: Vector3.Up(),
92357
+ candidateLocationWS: projectedPosition.clone(),
92358
+ currentDirectionWS: Vector3.Zero(),
92359
+ numIterations: 1,
92360
+ error: 0
92361
+ };
92362
+ }
92363
+ FindWaterSurfaceHeight(searchParameters) {
92364
+ return this.ProjectPointOnWaterSurface(searchParameters);
92365
+ }
92366
+ findNodes(nodeIds) {
92367
+ const nodes = [];
92368
+ for (const nodeId of nodeIds ?? []) {
92369
+ const node = SceneManager.GetTransformNodeByID(this.scene, nodeId);
92370
+ if (node != null)
92371
+ nodes.push(node);
92372
+ }
92373
+ return nodes;
92374
+ }
92375
+ findParticles(nodes) {
92376
+ const particles = [];
92377
+ for (const node of nodes) {
92378
+ const system = SceneManager.GetComponent(node, MeshWaterSurface.PARTICLES_CLASS);
92379
+ if (system != null)
92380
+ particles.push(system);
92381
+ }
92382
+ return particles;
92383
+ }
92384
+ prepareFlip(node) {
92385
+ if (node.isWorldMatrixFrozen)
92386
+ node.unfreezeWorldMatrix();
92387
+ if (node.rotationQuaternion == null)
92388
+ node.rotationQuaternion = Quaternion.FromEulerVector(node.rotation);
92389
+ const surfaceRotation = node.rotationQuaternion.clone();
92390
+ const flipRotation = Quaternion.FromEulerAngles(Math.PI, 0, 0);
92391
+ return { node: node, surfaceRotation: surfaceRotation, underwaterRotation: flipRotation.multiply(surfaceRotation) };
92392
+ }
92393
+ prepareFollowers(follownodes) {
92394
+ const followers = [];
92395
+ for (const follower of follownodes ?? []) {
92396
+ const node = SceneManager.GetTransformNodeByID(this.scene, follower.node);
92397
+ if (node == null)
92398
+ continue;
92399
+ if (follower.scale != null)
92400
+ node.scaling.set(follower.scale[0], follower.scale[1], follower.scale[2]);
92401
+ followers.push({ node: node, offset: Vector3.FromArray(follower.offset) });
92402
+ }
92403
+ return followers;
92404
+ }
92405
+ enterUnderwater() {
92406
+ const underwater = this.waterProperties.underwater;
92407
+ if (underwater.fogcolors.length > 0)
92408
+ this.writeUnderwaterFog(underwater);
92409
+ for (const node of this.shownNodes)
92410
+ node.setEnabled(true);
92411
+ for (const particles of this.shownParticles)
92412
+ particles.play();
92413
+ this.setVolumesEnabled(true);
92414
+ for (const flipped of this.flippedNodes)
92415
+ flipped.node.rotationQuaternion.copyFrom(flipped.underwaterRotation);
92416
+ }
92417
+ leaveUnderwater() {
92418
+ if (this.waterProperties.underwater.fogcolors.length > 0)
92419
+ this.restoreSurfaceFog();
92420
+ for (const particles of this.shownParticles)
92421
+ particles.stop(true, MeshWaterSurface.STOP_AND_CLEAR);
92422
+ for (const node of this.shownNodes)
92423
+ node.setEnabled(false);
92424
+ this.setVolumesEnabled(false);
92425
+ for (const flipped of this.flippedNodes)
92426
+ flipped.node.rotationQuaternion.copyFrom(flipped.surfaceRotation);
92427
+ }
92428
+ writeUnderwaterFog(underwater) {
92429
+ const scene = this.scene;
92430
+ this.surfaceFogColor.copyFrom(scene.fogColor);
92431
+ this.surfaceFogDensity = scene.fogDensity;
92432
+ this.surfaceFogStart = scene.fogStart;
92433
+ this.surfaceFogEnd = scene.fogEnd;
92434
+ const isExponentialSquared = scene.fogMode === Scene.FOGMODE_EXP2;
92435
+ const densityScale = isExponentialSquared ? SceneManager.FogExp2DensityScale : SceneManager.FogExpDensityScale;
92436
+ scene.fogDensity = underwater.fogdensity * densityScale;
92437
+ scene.fogStart = underwater.fogstart;
92438
+ scene.fogEnd = underwater.fogend * MeshWaterSurface.LINEAR_FOG_END_SCALE;
92439
+ }
92440
+ restoreSurfaceFog() {
92441
+ const scene = this.scene;
92442
+ scene.fogColor.copyFrom(this.surfaceFogColor);
92443
+ scene.fogDensity = this.surfaceFogDensity;
92444
+ scene.fogStart = this.surfaceFogStart;
92445
+ scene.fogEnd = this.surfaceFogEnd;
92446
+ }
92447
+ setVolumesEnabled(enabled) {
92448
+ for (const node of this.volumeNodes) {
92449
+ node.setEnabled(enabled);
92450
+ const volume = SceneManager.GetComponent(node, MeshWaterSurface.POST_PROCESSOR_CLASS);
92451
+ if (volume != null)
92452
+ volume.SetVolumeEnabled(enabled);
92453
+ }
92454
+ }
92455
+ updateFog(depthMeters) {
92456
+ const underwater = this.waterProperties.underwater;
92457
+ if (underwater.fogcolors.length === 0)
92458
+ return;
92459
+ const depthFraction = Scalar.Clamp(depthMeters / underwater.fogdepth, 0, 1);
92460
+ const keyPosition = depthFraction * (underwater.fogcolors.length - 1);
92461
+ const lowerKey = Math.floor(keyPosition);
92462
+ const upperKey = Math.min(lowerKey + 1, underwater.fogcolors.length - 1);
92463
+ const keyBlend = keyPosition - lowerKey;
92464
+ for (let channel = 0; channel < 3; channel++) {
92465
+ const gradientValue = Scalar.Lerp(underwater.fogcolors[lowerKey][channel], underwater.fogcolors[upperKey][channel], keyBlend);
92466
+ const litValue = gradientValue * underwater.suncolor[channel];
92467
+ const addedValue = underwater.fogcolormultiplier[channel];
92468
+ this.fogColorChannels[channel] = (addedValue >= 0) ? Scalar.Clamp(litValue + addedValue, 0, 1) : litValue;
92469
+ }
92470
+ this.scene.fogColor.set(this.fogColorChannels[0], this.fogColorChannels[1], this.fogColorChannels[2]);
92471
+ }
92472
+ updateFollowers(camera) {
92473
+ for (const follower of this.followers) {
92474
+ camera.globalPosition.addToRef(follower.offset, this.followPosition);
92475
+ follower.node.setAbsolutePosition(this.followPosition);
92476
+ }
92477
+ }
92478
+ }
92479
+ MeshWaterSurface.POST_PROCESSOR_CLASS = "TOOLKIT.PostProcessor";
92480
+ MeshWaterSurface.PARTICLES_CLASS = "TOOLKIT.ShurikenParticles";
92481
+ MeshWaterSurface.STOP_AND_CLEAR = 1;
92482
+ MeshWaterSurface.LINEAR_FOG_END_SCALE = 2;
92483
+ MeshWaterSurface.Registry = new Map();
91907
92484
  export class NavigationAgent extends ScriptComponent {
91908
92485
  isReady() { return this.m_agentReady; }
91909
92486
  isNavigating() { return (this.m_navStatus === NavMoveStatus.Moving); }
@@ -108703,6 +109280,7 @@ export class PostProcessor extends ScriptComponent {
108703
109280
  const props = this.getProperties();
108704
109281
  this.cameraOwner = PostProcessingContract.isCameraOwner(props);
108705
109282
  this.volumeProperties = this.cameraOwner ? null : PostProcessingContract.parseVolume(props);
109283
+ this.volumeEnabled = !this.isSwitchedVolume();
108706
109284
  this.volumeLayer = SceneManager.GetTransformLayer(this.transform);
108707
109285
  PostProcessor.Registry.push(this);
108708
109286
  if (PostProcessor.GlobalInstance == null)
@@ -108939,7 +109517,8 @@ export class PostProcessor extends ScriptComponent {
108939
109517
  PostProcessor.warnOnce("cameras:" + selection.mode, selection.warning);
108940
109518
  const hasLocal = entries.some(e => e.properties.isglobal === false && e.properties.bounds != null);
108941
109519
  const hasCinemachine = volumes.some((volume) => volume.isCinemachineVolume());
108942
- const liveBlend = hasLocal || hasCinemachine;
109520
+ const hasSwitched = volumes.some((volume) => volume.isSwitchedVolume());
109521
+ const liveBlend = hasLocal || hasCinemachine || hasSwitched;
108943
109522
  const localLuts = liveBlend ? entries.map((e) => {
108944
109523
  const lut = PostProcessingContract.normalise(e.properties).lut;
108945
109524
  return (lut != null && lut.identity !== true && typeof lut.url === "string" && lut.url !== "") ? lut : null;
@@ -109034,6 +109613,9 @@ export class PostProcessor extends ScriptComponent {
109034
109613
  isCinemachineVolume() {
109035
109614
  return this.volumeProperties != null && this.volumeProperties.role === PostProcessingContract.CINEMACHINE_VOLUME_ROLE;
109036
109615
  }
109616
+ isSwitchedVolume() {
109617
+ return this.volumeProperties != null && this.volumeProperties.role === PostProcessingContract.SWITCHED_VOLUME_ROLE;
109618
+ }
109037
109619
  IsVolumeEnabled() {
109038
109620
  if (this.volumeEnabled === false)
109039
109621
  return false;
@@ -109648,7 +110230,7 @@ export class PostProcessor extends ScriptComponent {
109648
110230
  applyHdrpFog(camera, textureType) {
109649
110231
  if (camera == null || this.scene == null || HdrpRendering == null || HdrpFogPass == null)
109650
110232
  return;
109651
- if (HdrpRendering.IsParity(this.scene) !== true)
110233
+ if (HdrpRendering.IsParity(this.scene) !== true && PhysicalSkyBlock.UrpBlock(HdrpRendering.SceneMetadata(this.scene)) == null)
109652
110234
  return;
109653
110235
  const fog = HdrpFogPass.FogBlock(this.scene);
109654
110236
  if (!HdrpFogMath.PassNeeded(fog, HdrpFogPass.SkyOf(this.scene)))
@@ -115080,6 +115662,7 @@ export class PostProcessingContract {
115080
115662
  }
115081
115663
  PostProcessingContract.VOLUME_TYPES = ["BuiltIn", "URP", "HDRP"];
115082
115664
  PostProcessingContract.CINEMACHINE_VOLUME_ROLE = "cinemachine";
115665
+ PostProcessingContract.SWITCHED_VOLUME_ROLE = "switched";
115083
115666
  PostProcessingContract.GRADING_MODES = ["LowDefinitionRange", "HighDefinitionRange", "External"];
115084
115667
  PostProcessingContract.RENDERING_PATH_DEFERRED = "DeferredShading";
115085
115668
  PostProcessingContract.RENDERING_PATH_FORWARD = "Forward";
@@ -130843,6 +131426,20 @@ export class RigidbodyPhysics extends ScriptComponent {
130843
131426
  RigidbodyPhysics.ConfigRigidbodyPhysics(scene, entity, false, false, physics, mass, centerOfMass);
130844
131427
  return body;
130845
131428
  }
131429
+ static MarkCompoundChildForDisposal(entity) {
131430
+ if (RigidbodyPhysics.RendersItself(entity) === false) {
131431
+ Utilities.AddLoaderItemMarkedForDisposal(entity);
131432
+ return;
131433
+ }
131434
+ const colliderMesh = SceneManager.FindChildTransformNode(entity, (entity.name + ".Collider"), SearchType.ExactMatch, true);
131435
+ if (colliderMesh != null)
131436
+ Utilities.AddLoaderItemMarkedForDisposal(colliderMesh);
131437
+ }
131438
+ static RendersItself(node) {
131439
+ if (!(node instanceof AbstractMesh) || node.getTotalVertices() <= 0 || node.name.endsWith(".Collider"))
131440
+ return false;
131441
+ return node.metadata != null && node.metadata.toolkit != null && node.metadata.toolkit.renderer != null;
131442
+ }
130846
131443
  static CreateCompoundPhysicsShapeAndBody(scene, root, entity, element, impostortype, staticfriction, dynamicfriction, restitution, fcombine, rcombine, sitems, item, center, complex, trigger, persist, layer, filter) {
130847
131444
  let center_xx = 0;
130848
131445
  let center_yy = 0;
@@ -130853,7 +131450,7 @@ export class RigidbodyPhysics extends ScriptComponent {
130853
131450
  const rootScale = RigidbodyPhysics.GetColliderScale(root);
130854
131451
  const entityScale = (entity === root) ? rootScale : RigidbodyPhysics.RoundColliderScale(entity.scaling);
130855
131452
  if (persist === false && complex === true)
130856
- Utilities.AddLoaderItemMarkedForDisposal(entity);
131453
+ RigidbodyPhysics.MarkCompoundChildForDisposal(entity);
130857
131454
  if (impostortype == RigidbodyPhysics.ConvexHullImpostor) {
130858
131455
  let convexHullMesh = SceneManager.FindChildTransformNode(entity, (entity.name + ".Collider"), SearchType.ExactMatch, true);
130859
131456
  if (convexHullMesh != null) {
@@ -133691,7 +134288,7 @@ export class ShurikenParticles extends ScriptComponent {
133691
134288
  }
133692
134289
  canBeginWarmupDraw() {
133693
134290
  const particleSystem = this._prtSystem;
133694
- if (particleSystem == null || particleSystem.isDisposed || this._prtState == null || this._prtProps == null || this._prtProps.enabled === false)
134291
+ if (particleSystem == null || particleSystem.isDisposed || this._prtState == null || this._prtProps == null)
133695
134292
  return false;
133696
134293
  if (this._prtWarming || this._prtPrewarmJob != null)
133697
134294
  return false;
@@ -141523,6 +142120,7 @@ export class TerrainBuilder extends ScriptComponent {
141523
142120
  if (heights == null)
141524
142121
  throw new Error("heights block has no data");
141525
142122
  hf = new TerrainHeightfield(props, heights);
142123
+ hf.setEdgeNeighbors(this.resolveEdgeNeighbors());
141526
142124
  this._heightfield = hf;
141527
142125
  }
141528
142126
  catch (e) {
@@ -141715,6 +142313,45 @@ export class TerrainBuilder extends ScriptComponent {
141715
142313
  this._isBuilt = true;
141716
142314
  this.onBuiltObservable.notifyObservers(this);
141717
142315
  }
142316
+ resolveEdgeNeighbors() {
142317
+ const contract = this._contract;
142318
+ const tiles = TerrainBuilder.GetTerrains(this.scene);
142319
+ const ids = contract.neighbors;
142320
+ const hasIds = ids != null && (ids.left != null || ids.right != null || ids.top != null || ids.bottom != null);
142321
+ const neighbors = {
142322
+ left: this.edgeNeighborFor(tiles, hasIds ? ids.left : null, hasIds, -1, 0),
142323
+ right: this.edgeNeighborFor(tiles, hasIds ? ids.right : null, hasIds, 1, 0),
142324
+ top: this.edgeNeighborFor(tiles, hasIds ? ids.top : null, hasIds, 0, 1),
142325
+ bottom: this.edgeNeighborFor(tiles, hasIds ? ids.bottom : null, hasIds, 0, -1),
142326
+ };
142327
+ const anyNeighbor = neighbors.left != null || neighbors.right != null || neighbors.top != null || neighbors.bottom != null;
142328
+ return anyNeighbor ? neighbors : null;
142329
+ }
142330
+ edgeNeighborFor(tiles, neighborId, useIds, offsetX, offsetZ) {
142331
+ for (const other of tiles) {
142332
+ if (other === this || !this.canShareEdgeWith(other))
142333
+ continue;
142334
+ const isNeighbor = useIds ? other.transform.id === neighborId : this.isAdjacentTile(other, offsetX, offsetZ);
142335
+ if (!isNeighbor)
142336
+ continue;
142337
+ const heights = TerrainContract.GetBlock(other.contract, "heights", "u16");
142338
+ return (heights != null) ? { heights: heights, heightScale: other.contract.size[1] } : null;
142339
+ }
142340
+ return null;
142341
+ }
142342
+ canShareEdgeWith(other) {
142343
+ const contract = this._contract, otherContract = other.contract;
142344
+ return otherContract.heightresolution === contract.heightresolution && otherContract.size[0] === contract.size[0] && otherContract.size[2] === contract.size[2];
142345
+ }
142346
+ isAdjacentTile(other, offsetX, offsetZ) {
142347
+ const contract = this._contract, otherContract = other.contract;
142348
+ if (contract.allowautoconnect !== true || otherContract.allowautoconnect !== true || otherContract.groupingid !== contract.groupingid)
142349
+ return false;
142350
+ this.transform.computeWorldMatrix(true);
142351
+ const offset = this.worldToUnityUV(other.transform.getAbsolutePosition());
142352
+ const toleranceU = TERRAIN_EDGE_TOLERANCE_METERS / contract.size[0], toleranceV = TERRAIN_EDGE_TOLERANCE_METERS / contract.size[2];
142353
+ return Math.abs(offset[0] - offsetX) <= toleranceU && Math.abs(offset[1] - offsetZ) <= toleranceV;
142354
+ }
141718
142355
  applyTreesAndFoliageVisibility() {
141719
142356
  if (this._trees != null)
141720
142357
  this._trees.setVisible(this._drawTreesAndFoliage);
@@ -142299,12 +142936,21 @@ export class TerrainBuilder extends ScriptComponent {
142299
142936
  worldToUnityUV(worldPosition) {
142300
142937
  if (this._contract == null || worldPosition == null)
142301
142938
  return null;
142302
- const inv = Matrix.Invert(this.transform.getWorldMatrix());
142303
- const local = Vector3.TransformCoordinates(worldPosition, inv);
142939
+ this.transform.getWorldMatrix().invertToRef(TerrainBuilder.ScratchInverse);
142940
+ const local = TerrainBuilder.ScratchLocal;
142941
+ Vector3.TransformCoordinatesToRef(worldPosition, TerrainBuilder.ScratchInverse, local);
142304
142942
  const size = this._contract.size;
142305
142943
  const ux = TerrainMath.UnityToLocalX(local.x, this._contract.handedness);
142306
142944
  return [ux / size[0], local.z / size[2]];
142307
142945
  }
142946
+ containsWorldPosition(worldPosition, tolerance) {
142947
+ const uv = this.worldToUnityUV(worldPosition);
142948
+ if (uv == null)
142949
+ return false;
142950
+ const size = this._contract.size;
142951
+ const slackU = tolerance / size[0], slackV = tolerance / size[2];
142952
+ return uv[0] >= -slackU && uv[0] <= 1 + slackU && uv[1] >= -slackV && uv[1] <= 1 + slackV;
142953
+ }
142308
142954
  SampleHeight(worldPosition) {
142309
142955
  if (this._heightfield == null)
142310
142956
  return 0;
@@ -142373,6 +143019,10 @@ export class TerrainBuilder extends ScriptComponent {
142373
143019
  if (list[i]._heightfield != null && list[i].WorldToTerrainUV(worldPosition) != null)
142374
143020
  return list[i];
142375
143021
  }
143022
+ for (let i = list.length - 1; i >= 0; i--) {
143023
+ if (list[i]._heightfield != null && list[i].containsWorldPosition(worldPosition, TERRAIN_EDGE_TOLERANCE_METERS))
143024
+ return list[i];
143025
+ }
142376
143026
  return null;
142377
143027
  }
142378
143028
  static GetWorldHeightAt(scene, worldPosition) {
@@ -142390,6 +143040,8 @@ export class TerrainBuilder extends ScriptComponent {
142390
143040
  }
142391
143041
  }
142392
143042
  TerrainBuilder._registry = new Map();
143043
+ TerrainBuilder.ScratchInverse = new Matrix();
143044
+ TerrainBuilder.ScratchLocal = new Vector3();
142393
143045
  TerrainBuilder.OnLoadProgressObservable = new Observable();
142394
143046
  TerrainBuilder.PRELOAD_TIMEOUT_MS = 120000;
142395
143047
  TerrainBuilder.SETTLE_MS = 30000;
@@ -142810,7 +143462,7 @@ export class TerrainDetails {
142810
143462
  }
142811
143463
  const F = TerrainFoliagePlugin;
142812
143464
  const layer = rt.data;
142813
- const sway = layer.sway;
143465
+ const sway = F.GraphOwnsMotion(source) ? null : layer.sway;
142814
143466
  const urp = ((this._owner.contract != null && this._owner.contract.flavour) || "urp") === "urp";
142815
143467
  const modes = F.MODE_FADE | ((sway != null) ? F.MODE_SWAY : 0) | ((layer.rendermode === "Grass") ? F.MODE_GRASSWAVE : 0) | (urp ? F.MODE_URPSPECULAR : 0);
142816
143468
  const swayKey = (sway != null) ? (sway.amount + "," + sway.speed + "," + sway.wavelength) : "-";
@@ -143087,19 +143739,12 @@ export class TerrainDetails {
143087
143739
  rt.mainMatrices = matrices;
143088
143740
  rt.mainColors = colors;
143089
143741
  rt.main.thinInstanceSetBuffer("matrix", matrices, 16, false);
143090
- TerrainDetails.ResizePreviousMatrices(rt.main, matrices);
143742
+ TerrainTrees.ResizePreviousMatrices(rt.main, matrices);
143091
143743
  if (colors != null)
143092
143744
  rt.main.thinInstanceSetBuffer(TerrainDetails.TintBuffer(rt), colors, 4, false);
143093
143745
  if (ShaderGraphRuntime != null)
143094
143746
  ShaderGraphRuntime.EnsureThinInstanceBuffers(rt.main);
143095
143747
  }
143096
- static ResizePreviousMatrices(mesh, matrices) {
143097
- const storage = mesh._thinInstanceDataStorage;
143098
- if (storage == null || storage.previousMatrixBuffer == null || storage.previousMatrixData != null)
143099
- return;
143100
- storage.previousMatrixBuffer.dispose();
143101
- storage.previousMatrixBuffer = mesh._thinInstanceCreateMatrixBuffer("previousWorld", matrices, false);
143102
- }
143103
143748
  static TintBuffer(rt) { return (rt.grass != null) ? "tkGrassTint" : "color"; }
143104
143749
  chunkDistance(c, x, z, size) {
143105
143750
  const x0 = c.cx * this._cw, x1 = Math.min((c.cx + 1) * this._cw, size[0]);
@@ -143693,6 +144338,9 @@ export class TerrainFoliagePlugin extends MaterialPluginBase {
143693
144338
  const shader = m.getShaderName();
143694
144339
  return shader != null && shader !== "UniversalShaderMaterial" && shader !== "CustomShaderMaterial";
143695
144340
  }
144341
+ static GraphOwnsMotion(material) {
144342
+ return TerrainFoliagePlugin.IsGraphMaterial(material) && material.__speedTreeWind == null;
144343
+ }
143696
144344
  getClassName() {
143697
144345
  return "TerrainFoliagePlugin";
143698
144346
  }
@@ -144865,6 +145513,7 @@ export class TerrainHeightfield {
144865
145513
  this._errors = null;
144866
145514
  this._minH = null;
144867
145515
  this._maxH = null;
145516
+ this.edgeNeighbors = null;
144868
145517
  this.R = contract.heightresolution;
144869
145518
  this.size = [contract.size[0], contract.size[1], contract.size[2]];
144870
145519
  this.handedness = (contract.handedness != null) ? contract.handedness : 1;
@@ -144893,9 +145542,12 @@ export class TerrainHeightfield {
144893
145542
  return this.heights[z * this.R + x] / 65535 * this.size[1];
144894
145543
  }
144895
145544
  normalAt(u, v, out) {
144896
- TerrainMath.SampleNormal(this.heights, this.R, this.size, u, v, out);
145545
+ TerrainMath.SampleNormal(this.heights, this.R, this.size, u, v, out, this.edgeNeighbors);
144897
145546
  out[0] *= (this.handedness === 0 ? -1 : 1);
144898
145547
  }
145548
+ setEdgeNeighbors(neighbors) {
145549
+ this.edgeNeighbors = neighbors;
145550
+ }
144899
145551
  setHoles(pixels, width) {
144900
145552
  const n = this.R - 1;
144901
145553
  const height = (width > 0) ? Math.floor(pixels.length / 4 / width) : 0;
@@ -145174,15 +145826,31 @@ export class TerrainMath {
145174
145826
  const b = h01 + (h11 - h01) * tx;
145175
145827
  return TerrainMath.DecodeHeight(a + (b - a) * tz, sizeY);
145176
145828
  }
145177
- static SampleNormal(h, R, size, u, v, out) {
145829
+ static SampleNormal(h, R, size, u, v, out, neighbors = null) {
145178
145830
  const i = Math.round(TerrainMath.Clamp01(u) * (R - 1));
145179
145831
  const k = Math.round(TerrainMath.Clamp01(v) * (R - 1));
145180
145832
  const iL = Math.max(i - 1, 0), iR = Math.min(i + 1, R - 1);
145181
145833
  const kD = Math.max(k - 1, 0), kU = Math.min(k + 1, R - 1);
145182
145834
  const cellX = size[0] / (R - 1), cellZ = size[2] / (R - 1);
145183
145835
  const sy = size[1];
145184
- const dhdx = (iR > iL) ? (TerrainMath.DecodeHeight(h[k * R + iR], sy) - TerrainMath.DecodeHeight(h[k * R + iL], sy)) / ((iR - iL) * cellX) : 0;
145185
- const dhdz = (kU > kD) ? (TerrainMath.DecodeHeight(h[kU * R + i], sy) - TerrainMath.DecodeHeight(h[kD * R + i], sy)) / ((kU - kD) * cellZ) : 0;
145836
+ let dhdx = (iR > iL) ? (TerrainMath.DecodeHeight(h[k * R + iR], sy) - TerrainMath.DecodeHeight(h[k * R + iL], sy)) / ((iR - iL) * cellX) : 0;
145837
+ let dhdz = (kU > kD) ? (TerrainMath.DecodeHeight(h[kU * R + i], sy) - TerrainMath.DecodeHeight(h[kD * R + i], sy)) / ((kU - kD) * cellZ) : 0;
145838
+ if (i === 0 && neighbors?.left != null) {
145839
+ const outsideHeight = TerrainMath.DecodeHeight(neighbors.left.heights[k * R + (R - 2)], neighbors.left.heightScale);
145840
+ dhdx = (TerrainMath.DecodeHeight(h[k * R + 1], sy) - outsideHeight) / (2 * cellX);
145841
+ }
145842
+ else if (i === R - 1 && neighbors?.right != null) {
145843
+ const outsideHeight = TerrainMath.DecodeHeight(neighbors.right.heights[k * R + 1], neighbors.right.heightScale);
145844
+ dhdx = (outsideHeight - TerrainMath.DecodeHeight(h[k * R + (R - 2)], sy)) / (2 * cellX);
145845
+ }
145846
+ if (k === 0 && neighbors?.bottom != null) {
145847
+ const outsideHeight = TerrainMath.DecodeHeight(neighbors.bottom.heights[(R - 2) * R + i], neighbors.bottom.heightScale);
145848
+ dhdz = (TerrainMath.DecodeHeight(h[1 * R + i], sy) - outsideHeight) / (2 * cellZ);
145849
+ }
145850
+ else if (k === R - 1 && neighbors?.top != null) {
145851
+ const outsideHeight = TerrainMath.DecodeHeight(neighbors.top.heights[1 * R + i], neighbors.top.heightScale);
145852
+ dhdz = (outsideHeight - TerrainMath.DecodeHeight(h[(R - 2) * R + i], sy)) / (2 * cellZ);
145853
+ }
145186
145854
  const nx = -dhdx, ny = 1, nz = -dhdz;
145187
145855
  const len = Math.sqrt(nx * nx + ny * ny + nz * nz);
145188
145856
  out[0] = nx / len;
@@ -146441,6 +147109,7 @@ export class TerrainTrees {
146441
147109
  this._colliderShapes = [];
146442
147110
  this._distance = 2000;
146443
147111
  this._built = false;
147112
+ this._warnedUniqueHost = false;
146444
147113
  this._n = 0;
146445
147114
  this._proto = null;
146446
147115
  this._local = null;
@@ -146533,13 +147202,21 @@ export class TerrainTrees {
146533
147202
  return casters;
146534
147203
  }
146535
147204
  getStats() {
146536
- let drawn = 0, visibleHosts = 0;
147205
+ let drawn = 0, visibleHosts = 0, capacity = 0;
146537
147206
  for (let i = 0; i < this._hosts.length; i++) {
146538
- drawn += this._hosts[i].count;
146539
- if (this._hosts[i].count > 0)
147207
+ const host = this._hosts[i];
147208
+ drawn += host.count;
147209
+ if (host.count > 0)
146540
147210
  visibleHosts++;
147211
+ capacity += host.matrices.length / 16;
147212
+ for (let slotIndex = 0; slotIndex < host.slots.length; slotIndex++)
147213
+ capacity += host.slots[slotIndex].matrices.length / 16;
146541
147214
  }
146542
- return { hosts: this._hosts.length, instances: this._n, drawn: drawn, visibleHosts: visibleHosts, colliders: this._colliderBody != null };
147215
+ return { hosts: this._hosts.length, instances: this._n, drawn: drawn, visibleHosts: visibleHosts, colliders: this._colliderBody != null, capacity: capacity };
147216
+ }
147217
+ static PreparedHostCount(scene) {
147218
+ const prepared = TerrainTrees.PreparedHosts.get(scene.uniqueId);
147219
+ return (prepared != null) ? prepared.size : 0;
146543
147220
  }
146544
147221
  build() {
146545
147222
  const owner = this._owner;
@@ -146597,43 +147274,44 @@ export class TerrainTrees {
146597
147274
  this._built = true;
146598
147275
  this._dirty = true;
146599
147276
  }
146600
- buildPrototype(p, tpl, cap) {
146601
- const lods = Array.isArray(p.lods) ? p.lods : [];
146602
- const runtime = { data: p, heights: lods.map((l) => (l != null && l.height != null) ? l.height : 0), lodHosts: [], fade: (p.fademode > 0 && p.animatecrossfade === true) };
146603
- const tplWorldInv = Matrix.Invert(tpl.computeWorldMatrix(true));
146604
- let any = false;
146605
- for (let l = 0; l < lods.length; l++) {
146606
- const list = [];
146607
- runtime.lodHosts.push(list);
146608
- const renderers = (lods[l] != null && Array.isArray(lods[l].renderers)) ? lods[l].renderers : [];
146609
- for (let r = 0; r < renderers.length; r++) {
146610
- const node = this._scene.getNodeById(renderers[r]);
147277
+ buildPrototype(prototype, template, instanceCount) {
147278
+ const prototypeLods = Array.isArray(prototype.lods) ? prototype.lods : [];
147279
+ const runtime = { data: prototype, heights: prototypeLods.map((prototypeLod) => (prototypeLod != null && prototypeLod.height != null) ? prototypeLod.height : 0), lodHosts: [], fade: (prototype.fademode > 0 && prototype.animatecrossfade === true) };
147280
+ const templateWorldInverse = Matrix.Invert(template.computeWorldMatrix(true));
147281
+ const initialCapacity = Math.min(instanceCount, TREE_INITIAL_CAPACITY);
147282
+ let hasHost = false;
147283
+ for (let lodIndex = 0; lodIndex < prototypeLods.length; lodIndex++) {
147284
+ const lodHostIndices = [];
147285
+ runtime.lodHosts.push(lodHostIndices);
147286
+ const renderers = (prototypeLods[lodIndex] != null && Array.isArray(prototypeLods[lodIndex].renderers)) ? prototypeLods[lodIndex].renderers : [];
147287
+ for (let rendererIndex = 0; rendererIndex < renderers.length; rendererIndex++) {
147288
+ const node = this._scene.getNodeById(renderers[rendererIndex]);
146611
147289
  if (node == null)
146612
147290
  continue;
146613
147291
  let sources = [];
146614
147292
  if (node instanceof AbstractMesh && TerrainTrees.HasGeometry(node))
146615
147293
  sources = [node];
146616
147294
  else
146617
- sources = node.getChildMeshes(true).filter((m) => TerrainTrees.HasGeometry(m));
146618
- const flags = (lods[l] != null) ? lods[l].lightprobes : null;
146619
- const probeLit = TerrainTrees.RendererUsesProbes(flags, r) && TerrainProbePlugin != null;
146620
- for (let s = 0; s < sources.length; s++) {
146621
- const matrices = new Float32Array(16 * cap), fades = new Float32Array(cap);
146622
- const cast = !(lods[l] != null && lods[l].billboard === true) && TerrainTrees.RendererCastsShadows(sources[s], node);
146623
- const host = this.createHost(p, l, sources[s], tplWorldInv, matrices, fades);
147295
+ sources = node.getChildMeshes(true).filter((mesh) => TerrainTrees.HasGeometry(mesh));
147296
+ const flags = (prototypeLods[lodIndex] != null) ? prototypeLods[lodIndex].lightprobes : null;
147297
+ const probeLit = TerrainTrees.RendererUsesProbes(flags, rendererIndex) && TerrainProbePlugin != null;
147298
+ for (let sourceIndex = 0; sourceIndex < sources.length; sourceIndex++) {
147299
+ const matrices = new Float32Array(16 * initialCapacity), fades = new Float32Array(initialCapacity);
147300
+ const cast = !(prototypeLods[lodIndex] != null && prototypeLods[lodIndex].billboard === true) && TerrainTrees.RendererCastsShadows(sources[sourceIndex], node);
147301
+ const host = this.createHost(prototype, lodIndex, sources[sourceIndex], templateWorldInverse, matrices, fades);
146624
147302
  if (host == null)
146625
147303
  continue;
146626
147304
  if (probeLit)
146627
147305
  this._probeHosts++;
146628
- list.push(this._hosts.length);
146629
- this._hosts.push({ mesh: host, matrices: matrices, fades: fades, count: 0, lastCount: -1, cast: cast, box: new Float32Array(6), boxMin: new Vector3(), boxMax: new Vector3(), probes: null, probeBuffer: null, probeLit: probeLit, slots: [] });
147306
+ lodHostIndices.push(this._hosts.length);
147307
+ this._hosts.push({ mesh: host, matrices: matrices, fades: fades, count: 0, lastCount: -1, cast: cast, box: new Float32Array(6), boxMin: new Vector3(), boxMax: new Vector3(), probes: null, probeBuffer: null, probeLit: probeLit, slots: [], capacityCeiling: instanceCount, grown: false });
146630
147308
  if (cast)
146631
- this.createCasterSlots(this._hosts[this._hosts.length - 1], cap);
146632
- any = true;
147309
+ this.createCasterSlots(this._hosts[this._hosts.length - 1], initialCapacity);
147310
+ hasHost = true;
146633
147311
  }
146634
147312
  }
146635
147313
  }
146636
- return any ? runtime : null;
147314
+ return hasHost ? runtime : null;
146637
147315
  }
146638
147316
  static RendererUsesProbes(flags, index) {
146639
147317
  if (!Array.isArray(flags) || index < 0 || index >= flags.length)
@@ -146641,20 +147319,32 @@ export class TerrainTrees {
146641
147319
  return flags[index] !== false;
146642
147320
  }
146643
147321
  static AttachProbeBuffer(host, probes) {
146644
- const P = TerrainProbePlugin;
146645
- for (let o = 0; o < probes.length; o += P.STRIDE)
146646
- probes[o] = -1;
146647
- const buffer = new Buffer(host.getEngine(), probes, true, P.STRIDE, false, true);
146648
- for (let k = 0; k < 3; k++)
146649
- host.setVerticesBuffer(buffer.createVertexBuffer(P.ATTRIBUTES[k], 4 * k, 4, P.STRIDE, true));
146650
- host.onDisposeObservable.addOnce(() => { buffer.dispose(); });
147322
+ const probePlugin = TerrainProbePlugin;
147323
+ for (let recordOffset = 0; recordOffset < probes.length; recordOffset += probePlugin.STRIDE)
147324
+ probes[recordOffset] = -1;
147325
+ const buffer = TerrainTrees.CreateProbeBuffer(host, probes);
147326
+ TerrainTrees.ProbeBuffers.set(host, buffer);
147327
+ host.onDisposeObservable.addOnce(() => { TerrainTrees.ProbeBuffers.get(host).dispose(); });
146651
147328
  const material = host.material;
146652
- const subs = (material != null && material.subMaterials != null) ? material.subMaterials : [material];
146653
- for (let i = 0; i < subs.length; i++)
146654
- if (subs[i] != null)
146655
- P.Attach(subs[i]);
147329
+ const subMaterials = (material != null && material.subMaterials != null) ? material.subMaterials : [material];
147330
+ for (let i = 0; i < subMaterials.length; i++)
147331
+ if (subMaterials[i] != null)
147332
+ probePlugin.Attach(subMaterials[i]);
146656
147333
  return buffer;
146657
147334
  }
147335
+ static CreateProbeBuffer(host, probes) {
147336
+ const probePlugin = TerrainProbePlugin;
147337
+ const buffer = new Buffer(host.getEngine(), probes, true, probePlugin.STRIDE, false, true);
147338
+ for (let viewIndex = 0; viewIndex < 3; viewIndex++)
147339
+ host.setVerticesBuffer(buffer.createVertexBuffer(probePlugin.ATTRIBUTES[viewIndex], 4 * viewIndex, 4, probePlugin.STRIDE, true));
147340
+ return buffer;
147341
+ }
147342
+ static ResizeProbeBuffer(host) {
147343
+ const buffer = TerrainTrees.CreateProbeBuffer(host.mesh, host.probes);
147344
+ host.probeBuffer.dispose();
147345
+ host.probeBuffer = buffer;
147346
+ TerrainTrees.ProbeBuffers.set(host.mesh, buffer);
147347
+ }
146658
147348
  sampleProbes(network) {
146659
147349
  if (network == null || typeof network.sampleInto !== "function" || !TerrainTrees.NetworkLightsProbes(network))
146660
147350
  return false;
@@ -146810,31 +147500,85 @@ export class TerrainTrees {
146810
147500
  throw error;
146811
147501
  }
146812
147502
  }
146813
- createHost(p, lod, src, tplWorldInv, matrices, fades) {
146814
- const geo = (src instanceof InstancedMesh) ? src.sourceMesh : src;
146815
- const host = geo.clone(p.name + ".L" + lod + "." + src.name, null, true);
146816
- if (host == null)
147503
+ static BakeHostGeometry(source, templateWorldInverse, name) {
147504
+ const geometrySource = (source instanceof InstancedMesh) ? source.sourceMesh : source;
147505
+ const mesh = geometrySource.clone(name, null, true);
147506
+ if (mesh == null)
147507
+ return null;
147508
+ mesh.metadata = null;
147509
+ mesh.makeGeometryUnique();
147510
+ const rendererToTemplate = source.computeWorldMatrix(true).multiply(templateWorldInverse);
147511
+ mesh.position.setAll(0);
147512
+ mesh.rotationQuaternion = null;
147513
+ mesh.rotation.setAll(0);
147514
+ mesh.scaling.setAll(1);
147515
+ mesh.bakeTransformIntoVertices(rendererToTemplate);
147516
+ TerrainTrees.InterleaveVertexData(mesh);
147517
+ return mesh;
147518
+ }
147519
+ prepareHostGeometry(prototype, lod, source, templateWorldInverse) {
147520
+ const scene = this._scene;
147521
+ const sceneKey = scene.uniqueId;
147522
+ let prepared = TerrainTrees.PreparedHosts.get(sceneKey);
147523
+ if (prepared == null) {
147524
+ const hosts = new Map();
147525
+ prepared = hosts;
147526
+ TerrainTrees.PreparedHosts.set(sceneKey, hosts);
147527
+ scene.onDisposeObservable.addOnce(() => {
147528
+ TerrainTrees.PreparedHosts.delete(sceneKey);
147529
+ hosts.forEach((mesh) => { mesh.dispose(false, false); });
147530
+ });
147531
+ }
147532
+ const key = prototype.templateid + "|" + lod + "|" + source.uniqueId;
147533
+ const existing = prepared.get(key);
147534
+ if (existing != null)
147535
+ return existing;
147536
+ const mesh = TerrainTrees.BakeHostGeometry(source, templateWorldInverse, prototype.name + ".L" + lod + "." + source.name + ".Prepared");
147537
+ if (mesh == null)
146817
147538
  return null;
146818
- host.metadata = null;
146819
- host.makeGeometryUnique();
146820
- const rel = src.computeWorldMatrix(true).multiply(tplWorldInv);
146821
- host.position.setAll(0);
146822
- host.rotationQuaternion = null;
146823
- host.rotation.setAll(0);
146824
- host.scaling.setAll(1);
146825
- host.bakeTransformIntoVertices(rel);
147539
+ mesh.setEnabled(false);
147540
+ mesh.isVisible = false;
147541
+ mesh.isPickable = false;
147542
+ prepared.set(key, mesh);
147543
+ return mesh;
147544
+ }
147545
+ createUniqueHost(source, templateWorldInverse, name, reason) {
147546
+ if (!this._warnedUniqueHost) {
147547
+ this._warnedUniqueHost = true;
147548
+ Tools.Warn("[TerrainBuilder] " + this._owner.transform.name + ": tree hosts cannot share gpu buffers, copying geometry per tile — " + reason);
147549
+ }
147550
+ const host = TerrainTrees.BakeHostGeometry(source, templateWorldInverse, name);
147551
+ if (host != null)
147552
+ host.setEnabled(true);
147553
+ return host;
147554
+ }
147555
+ createHost(prototype, lod, source, templateWorldInverse, matrices, fades) {
147556
+ const prepared = this.prepareHostGeometry(prototype, lod, source, templateWorldInverse);
147557
+ if (prepared == null)
147558
+ return null;
147559
+ const name = prototype.name + ".L" + lod + "." + source.name;
147560
+ let host = null;
147561
+ try {
147562
+ host = TerrainTrees.CreateSharedHost(this._scene, prepared, name);
147563
+ for (const key of TerrainTrees.HOST_DRAW_STATE)
147564
+ host[key] = prepared[key];
147565
+ }
147566
+ catch (error) {
147567
+ host = this.createUniqueHost(source, templateWorldInverse, name, String(error));
147568
+ if (host == null)
147569
+ return null;
147570
+ }
147571
+ const sourceMaterial = prepared.material;
146826
147572
  host.parent = this._root;
146827
- host.setEnabled(true);
146828
147573
  host.isVisible = false;
146829
147574
  host.isPickable = false;
146830
147575
  host.receiveShadows = true;
146831
147576
  host.alwaysSelectAsActiveMesh = true;
146832
147577
  host.doNotSyncBoundingInfo = true;
146833
- TerrainTrees.InterleaveVertexData(host);
146834
147578
  host.thinInstanceSetBuffer("matrix", matrices, 16, false);
146835
147579
  host.thinInstanceSetBuffer("tkLodFade", fades, 1, false);
146836
147580
  host.thinInstanceCount = 0;
146837
- host.material = this.hostMaterial(p, lod, geo.material, TerrainTrees.HasSpeedTreeWindVertexData(host));
147581
+ host.material = this.hostMaterial(prototype, lod, sourceMaterial, TerrainTrees.HasSpeedTreeWindVertexData(prepared));
146838
147582
  if (ShaderGraphRuntime != null)
146839
147583
  ShaderGraphRuntime.EnsureThinInstanceBuffers(host);
146840
147584
  return host;
@@ -146862,7 +147606,7 @@ export class TerrainTrees {
146862
147606
  ShaderGraphRuntime.EnsureThinInstanceBuffers(mesh);
146863
147607
  if (TerrainBuilder != null)
146864
147608
  TerrainBuilder.SetCasterSlot(mesh, slotIndex);
146865
- slots.push({ mesh: mesh, matrices: matrices, fades: fades, probes: null, count: 0, lastCount: -1, box: new Float32Array(6), boxMin: new Vector3(), boxMax: new Vector3() });
147609
+ slots.push({ mesh: mesh, matrices: matrices, fades: fades, probes: null, count: 0, lastCount: -1, box: new Float32Array(6), boxMin: new Vector3(), boxMax: new Vector3(), capacityCeiling: host.capacityCeiling, grown: false });
146866
147610
  }
146867
147611
  host.slots = slots;
146868
147612
  }
@@ -146903,7 +147647,8 @@ export class TerrainTrees {
146903
147647
  const hue = (info != null && Array.isArray(info.hue) && info.hue.length >= 4) ? info.hue : null;
146904
147648
  const F = TerrainFoliagePlugin;
146905
147649
  const urp = (this._owner.contract != null && (this._owner.contract.flavour || "urp") === "urp");
146906
- const bend = ownWind ? 0 : F.MODE_TREE;
147650
+ const graphMoves = p.speedtree !== true && F.GraphOwnsMotion(source);
147651
+ const bend = (ownWind || graphMoves) ? 0 : F.MODE_TREE;
146907
147652
  const plugin = F.Attach(entry.material, bend | F.MODE_CROSSFADE | (billboard ? F.MODE_BILLBOARD : 0) | (hue != null ? F.MODE_HUE : 0) | (urp ? F.MODE_URPSPECULAR : 0));
146908
147653
  if (plugin != null) {
146909
147654
  plugin.params.bendFactor = (p.bendfactor != null) ? p.bendfactor : 0;
@@ -147608,7 +148353,7 @@ export class TerrainTrees {
147608
148353
  }
147609
148354
  appendInstance(target, instanceIndex, fade) {
147610
148355
  const count = target.count;
147611
- if (16 * (count + 1) > target.matrices.length)
148356
+ if (16 * (count + 1) > target.matrices.length && !TerrainTrees.GrowTarget(target, target.capacityCeiling))
147612
148357
  return;
147613
148358
  const sourceOffset = 16 * instanceIndex;
147614
148359
  const targetOffset = 16 * count;
@@ -147646,6 +148391,26 @@ export class TerrainTrees {
147646
148391
  if (z + radius > box[5])
147647
148392
  box[5] = z + radius;
147648
148393
  }
148394
+ static GrowTarget(target, ceiling) {
148395
+ const capacity = target.matrices.length / 16;
148396
+ if (capacity >= ceiling)
148397
+ return false;
148398
+ const grownCapacity = Math.min(ceiling, Math.max(target.count + 1, Math.ceil(capacity * TREE_GROWTH_FACTOR)));
148399
+ const matrices = new Float32Array(16 * grownCapacity);
148400
+ matrices.set(target.matrices.subarray(0, 16 * target.count));
148401
+ target.matrices = matrices;
148402
+ const fades = new Float32Array(grownCapacity);
148403
+ fades.set(target.fades.subarray(0, target.count));
148404
+ target.fades = fades;
148405
+ if (target.probes != null) {
148406
+ const stride = TerrainProbePlugin.STRIDE;
148407
+ const probes = new Float32Array(stride * grownCapacity);
148408
+ probes.set(target.probes.subarray(0, stride * target.count));
148409
+ target.probes = probes;
148410
+ }
148411
+ target.grown = true;
148412
+ return true;
148413
+ }
147649
148414
  static LodCasts(hosts, proto, lod) {
147650
148415
  if (lod < 0 || lod >= proto.lodHosts.length)
147651
148416
  return false;
@@ -147683,16 +148448,39 @@ export class TerrainTrees {
147683
148448
  }
147684
148449
  }
147685
148450
  }
148451
+ static RebindGrownTarget(target) {
148452
+ const mesh = target.mesh;
148453
+ mesh.thinInstanceSetBuffer("matrix", target.matrices, 16, false);
148454
+ TerrainTrees.ResizePreviousMatrices(mesh, target.matrices);
148455
+ mesh.thinInstanceSetBuffer("tkLodFade", target.fades, 1, false);
148456
+ if (target.probes != null)
148457
+ TerrainTrees.ResizeProbeBuffer(target);
148458
+ if (ShaderGraphRuntime != null)
148459
+ ShaderGraphRuntime.EnsureThinInstanceBuffers(mesh);
148460
+ target.grown = false;
148461
+ }
148462
+ static ResizePreviousMatrices(mesh, matrices) {
148463
+ const storage = mesh._thinInstanceDataStorage;
148464
+ if (storage == null || storage.previousMatrixBuffer == null || storage.previousMatrixData != null)
148465
+ return;
148466
+ storage.previousMatrixBuffer.dispose();
148467
+ storage.previousMatrixBuffer = mesh._thinInstanceCreateMatrixBuffer("previousWorld", matrices, false);
148468
+ }
147686
148469
  uploadTarget(target, probeBuffer, isSlot) {
147687
148470
  const mesh = target.mesh;
147688
148471
  if (target.count > 0) {
147689
148472
  if (isSlot && target.lastCount <= 0)
147690
148473
  mesh.setEnabled(true);
148474
+ const grown = target.grown;
148475
+ if (grown)
148476
+ TerrainTrees.RebindGrownTarget(target);
147691
148477
  mesh.thinInstanceCount = target.count;
147692
- mesh.thinInstancePartialBufferUpdate("matrix", target.count, 0);
147693
- mesh.thinInstancePartialBufferUpdate("tkLodFade", target.count, 0);
147694
- if (probeBuffer != null)
147695
- probeBuffer.updateDirectly(target.probes, 0, target.count);
148478
+ if (!grown) {
148479
+ mesh.thinInstancePartialBufferUpdate("matrix", target.count, 0);
148480
+ mesh.thinInstancePartialBufferUpdate("tkLodFade", target.count, 0);
148481
+ if (probeBuffer != null)
148482
+ probeBuffer.updateDirectly(target.probes, 0, target.count);
148483
+ }
147696
148484
  target.boxMin.set(target.box[0], target.box[1], target.box[2]);
147697
148485
  target.boxMax.set(target.box[3], target.box[4], target.box[5]);
147698
148486
  mesh.getBoundingInfo().reConstruct(target.boxMin, target.boxMax, mesh.computeWorldMatrix(isSlot));
@@ -147816,6 +148604,9 @@ TerrainTrees.CASTER_SLOTS = 4;
147816
148604
  TerrainTrees.SLOT_MARGIN = 4;
147817
148605
  TerrainTrees.NO_PROBE_RECORD = -1;
147818
148606
  TerrainTrees.LIGHT_TURN_DOT = 0.99996192;
148607
+ TerrainTrees.HOST_DRAW_STATE = ["sideOrientation", "renderingGroupId", "layerMask", "alphaIndex", "hasVertexAlpha", "useVertexColors", "numBoneInfluencers", "skeleton"];
148608
+ TerrainTrees.PreparedHosts = new Map();
148609
+ TerrainTrees.ProbeBuffers = new WeakMap();
147819
148610
  TerrainTrees._materials = new Map();
147820
148611
  TerrainTrees.ProbeSampleHeight = 0;
147821
148612
  TerrainTrees.SUBSURFACE_FIELDS = ["isTranslucencyEnabled", "isScatteringEnabled", "translucencyIntensity", "legacyTranslucency", "useGltfStyleTextures",
@@ -166744,6 +167535,1531 @@ export class VisualEffectSpawner {
166744
167535
  }
166745
167536
  VisualEffectSpawner.COUNT_EPSILON = 1e-6;
166746
167537
  VisualEffectSpawner.MAX_PHASE_CHANGES_PER_STEP = 64;
167538
+ export class VolumetricCloudsMath {
167539
+ static ShellInterval(rayOrigin, rayDirection, innerRadius, outerRadius, planetRadius) {
167540
+ const radialDistance = Math.hypot(rayOrigin[0], rayOrigin[1], rayOrigin[2]);
167541
+ if (radialDistance <= 0)
167542
+ return null;
167543
+ const cosChi = (rayOrigin[0] * rayDirection[0] + rayOrigin[1] * rayDirection[1] + rayOrigin[2] * rayDirection[2]) / radialDistance;
167544
+ const inner = VolumetricCloudsMath.IntersectSphere(innerRadius, cosChi, radialDistance);
167545
+ const outer = VolumetricCloudsMath.IntersectSphere(outerRadius, cosChi, radialDistance);
167546
+ if (inner[0] < 0 && inner[1] >= 0) {
167547
+ const radius = (typeof planetRadius === "number") ? planetRadius : innerRadius;
167548
+ const sinHorizon = Math.min(1, radius / radialDistance);
167549
+ const horizonCosine = -Math.sqrt(Math.max(0, 1 - sinHorizon * sinHorizon));
167550
+ return (cosChi >= horizonCosine) ? [inner[1], outer[1]] : null;
167551
+ }
167552
+ if (outer[1] < 0)
167553
+ return null;
167554
+ return [Math.max(outer[0], 0), (inner[0] >= 0) ? inner[0] : outer[1]];
167555
+ }
167556
+ static StepSize(shellLength, steps, maximumStep) {
167557
+ return Math.min(shellLength / Math.max(1, steps), maximumStep);
167558
+ }
167559
+ static MaximumStep(altitudeRange, authoredSteps, tierSteps) {
167560
+ const stretch = (typeof authoredSteps === "number" && tierSteps > 0) ? Math.min(Math.max(1, authoredSteps / tierSteps), CLOUD_MAX_STEP_STRETCH) : 1;
167561
+ return altitudeRange / CLOUD_MAX_STEP_DIVISOR * stretch;
167562
+ }
167563
+ static TierSettings(tier, authored) {
167564
+ const block = authored || {};
167565
+ const positiveOr = (value, fallback) => (typeof value === "number" && value > 0) ? value : fallback;
167566
+ const caps = CLOUD_TIER_CAPS[Math.max(0, Math.min(VolumetricCloudsMath.LowestTier, Math.floor(tier)))];
167567
+ return {
167568
+ resolutionScale: Math.min(positiveOr(block.resolutionscale, CLOUD_DEFAULT_SETTINGS.resolutionScale), caps.resolutionScale),
167569
+ primarySteps: Math.min(positiveOr(block.numprimarysteps, CLOUD_DEFAULT_SETTINGS.primarySteps), caps.primarySteps),
167570
+ longRayMultiplier: Math.min(positiveOr(block.longraysamplemultiplier, CLOUD_DEFAULT_SETTINGS.longRayMultiplier), caps.longRayMultiplier),
167571
+ lightSteps: Math.min(positiveOr(block.numlightsteps, CLOUD_DEFAULT_SETTINGS.lightSteps), caps.lightSteps)
167572
+ };
167573
+ }
167574
+ static StartTier(maximumTextureSize, viewportCssWidth) {
167575
+ if (maximumTextureSize < CLOUD_WEAK_GPU_TEXTURE_SIZE || viewportCssWidth < CLOUD_PHONE_WIDTH_CSS_PX)
167576
+ return 1;
167577
+ return 0;
167578
+ }
167579
+ static IsCameraCut(previousPosition, currentPosition) {
167580
+ if (previousPosition == null)
167581
+ return true;
167582
+ const distance = Math.hypot(currentPosition[0] - previousPosition[0], currentPosition[1] - previousPosition[1], currentPosition[2] - previousPosition[2]);
167583
+ return distance > CLOUD_CAMERA_CUT_METERS;
167584
+ }
167585
+ static PackCurves(density, erosion, ambientOcclusion) {
167586
+ const texels = new Float32Array(CLOUD_CURVE_SAMPLES * 4);
167587
+ const hasCurves = Array.isArray(density) && density.length >= CLOUD_CURVE_SAMPLES;
167588
+ const clamp01 = (value) => Math.max(0, Math.min(1, (typeof value === "number") ? value : 0));
167589
+ for (let i = 0; i < CLOUD_CURVE_SAMPLES; i++) {
167590
+ const isEnd = (i === 0 || i === CLOUD_CURVE_SAMPLES - 1);
167591
+ texels[i * 4] = !hasCurves ? 1 : (isEnd ? 0 : clamp01(density[i]));
167592
+ texels[i * 4 + 1] = (!hasCurves || !Array.isArray(erosion)) ? 1 : clamp01(erosion[i]);
167593
+ texels[i * 4 + 2] = (!hasCurves || !Array.isArray(ambientOcclusion)) ? 1 : clamp01(1 - ambientOcclusion[i]);
167594
+ texels[i * 4 + 3] = 1;
167595
+ }
167596
+ return texels;
167597
+ }
167598
+ static IntersectSphere(sphereRadius, cosChi, radialDistance) {
167599
+ const ratio = sphereRadius / radialDistance;
167600
+ const discriminant = ratio * ratio - Math.max(0, Math.min(1, 1 - cosChi * cosChi));
167601
+ if (discriminant < 0)
167602
+ return [discriminant, discriminant];
167603
+ const root = Math.sqrt(discriminant);
167604
+ return [radialDistance * (-cosChi - root), radialDistance * (-cosChi + root)];
167605
+ }
167606
+ }
167607
+ VolumetricCloudsMath.LowestTier = 2;
167608
+ export class VolumetricCloudsTier {
167609
+ constructor(startTier) {
167610
+ this.isSettled = false;
167611
+ this.windowMilliseconds = 0;
167612
+ this.windowFrames = 0;
167613
+ this.meanBeforeStep = 0;
167614
+ this.previousMean = 0;
167615
+ this.tier = Math.max(0, Math.min(VolumetricCloudsMath.LowestTier, startTier));
167616
+ }
167617
+ addFrame(frameMilliseconds) {
167618
+ this.windowMilliseconds += frameMilliseconds;
167619
+ this.windowFrames++;
167620
+ if (this.windowFrames < CLOUD_TIER_WINDOW_FRAMES)
167621
+ return false;
167622
+ const meanMilliseconds = this.windowMilliseconds / this.windowFrames;
167623
+ this.windowMilliseconds = 0;
167624
+ this.windowFrames = 0;
167625
+ if (this.meanBeforeStep > 0) {
167626
+ const savedEnough = meanMilliseconds <= this.meanBeforeStep * (1 - CLOUD_TIER_MINIMUM_SAVING);
167627
+ this.meanBeforeStep = 0;
167628
+ if (!savedEnough) {
167629
+ this.tier--;
167630
+ this.isSettled = true;
167631
+ return true;
167632
+ }
167633
+ }
167634
+ const previousMean = this.previousMean;
167635
+ this.previousMean = meanMilliseconds;
167636
+ const isSteady = previousMean > 0 && meanMilliseconds > previousMean * (1 - CLOUD_TIER_MINIMUM_SAVING);
167637
+ if (this.isSettled || !isSteady || meanMilliseconds <= CLOUD_TIER_BUDGET_MS || this.tier >= VolumetricCloudsMath.LowestTier)
167638
+ return false;
167639
+ this.tier++;
167640
+ this.meanBeforeStep = meanMilliseconds;
167641
+ return true;
167642
+ }
167643
+ }
167644
+ export class VolumetricClouds {
167645
+ static TryCreate(scene, metadata, root) {
167646
+ const block = PhysicalSkyBlock.UrpBlock(metadata);
167647
+ const clouds = (block != null) ? block.clouds : null;
167648
+ if (clouds == null || clouds.state === false || scene == null)
167649
+ return null;
167650
+ if (HdrpPhysicallyBasedSky == null || !HdrpPhysicallyBasedSky.IsSupported(scene.getEngine()))
167651
+ return null;
167652
+ const existing = VolumetricClouds.Get(scene);
167653
+ if (existing != null)
167654
+ return existing;
167655
+ return new VolumetricClouds(scene, clouds, block.sky, (typeof root === "string") ? root : "");
167656
+ }
167657
+ static Get(scene) {
167658
+ if (scene == null)
167659
+ return null;
167660
+ const clouds = scene._tkVolumetricClouds;
167661
+ return (clouds != null && clouds.disposed !== true) ? clouds : null;
167662
+ }
167663
+ constructor(scene, block, sky, root) {
167664
+ this.disposed = false;
167665
+ this.tier = 0;
167666
+ this.hasAerial = false;
167667
+ this.placeholderAerial = null;
167668
+ this.worleyTexture = null;
167669
+ this.erosionTexture = null;
167670
+ this.curveTexture = null;
167671
+ this.placeholderDepth = null;
167672
+ this.marchTarget = null;
167673
+ this.renderer = null;
167674
+ this.marchPass = null;
167675
+ this.temporalPass = null;
167676
+ this.historyTargets = [];
167677
+ this.historyIndex = 0;
167678
+ this.hasHistory = false;
167679
+ this.compositeMesh = null;
167680
+ this.compositeMaterial = null;
167681
+ this.isReady = false;
167682
+ this.isPending = false;
167683
+ this.preExposure = 1;
167684
+ this.preExposureBinding = null;
167685
+ this.frameSeed = 0;
167686
+ this.windOffset = [0, 0];
167687
+ this.verticalShapeOffset = 0;
167688
+ this.verticalErosionOffset = 0;
167689
+ this.sceneDepth = null;
167690
+ this.beforeRenderObserver = null;
167691
+ this.afterRenderObserver = null;
167692
+ this.tierTracker = null;
167693
+ this.targetWidth = 1;
167694
+ this.targetHeight = 1;
167695
+ this.lastCamera = null;
167696
+ this.sunLight = null;
167697
+ this.ambientSource = null;
167698
+ this.ambientCaptureCount = -1;
167699
+ this.previousEye = [0, 0, 0];
167700
+ this.currentEye = [0, 0, 0];
167701
+ this.hasPreviousEye = false;
167702
+ this.beforeTargetsObserver = null;
167703
+ this.afterTargetsObserver = null;
167704
+ this.disposeObserver = null;
167705
+ this.mirror = Matrix.Identity();
167706
+ this.inverseViewProjection = new Matrix();
167707
+ this.previousViewProjection = new Matrix();
167708
+ this.previousViewProjectionUnity = new Matrix();
167709
+ this.sunDirection = new Vector3();
167710
+ this.cameraForward = new Vector3();
167711
+ this.ambientTop = new Color3();
167712
+ this.ambientBottom = new Color3();
167713
+ this.scene = scene;
167714
+ this.block = block;
167715
+ this.sky = sky;
167716
+ this.root = root;
167717
+ scene._tkVolumetricClouds = this;
167718
+ const planet = (sky != null) ? sky.planet : null;
167719
+ this.isLocal = (planet != null && typeof planet.renderingspace === "number") ? planet.renderingspace === 1 : block.localclouds === true;
167720
+ const curvature = (typeof block.earthcurvature === "number") ? block.earthcurvature : 0;
167721
+ this.planetRadius = (planet != null && typeof planet.radius === "number" && planet.radius > 0) ? planet.radius : Scalar.Lerp(1, CLOUD_MINIMUM_CURVATURE_SCALE, curvature) * CLOUD_EARTH_RADIUS_METERS;
167722
+ this.planetCenter = (this.isLocal && planet != null && Array.isArray(planet.center)) ? Vector3.FromArray(planet.center) : new Vector3(0, -this.planetRadius, 0);
167723
+ this.scatteringTint = VolumetricClouds.LinearScatteringTint(block.scatteringtint);
167724
+ if (scene.useRightHandedSystem === true)
167725
+ Matrix.ScalingToRef(-1, 1, 1, this.mirror);
167726
+ this.localForward = new Vector3(0, 0, (scene.useRightHandedSystem === true) ? -1 : 1);
167727
+ this.tier = VolumetricCloudsMath.StartTier(scene.getEngine().getCaps().maxTextureSize, VolumetricClouds.CanvasCssWidth(scene));
167728
+ this.tierTracker = new VolumetricCloudsTier(this.tier);
167729
+ this.settings = VolumetricCloudsMath.TierSettings(this.tier, block);
167730
+ this.reportLoweredSettings();
167731
+ this.isPending = true;
167732
+ scene.addPendingData(this);
167733
+ this.preExposureBinding = HdrpRendering.Bind(scene, 1, 1, (scaled) => { this.preExposure = scaled; });
167734
+ this.disposeObserver = scene.onDisposeObservable.add(() => this.dispose());
167735
+ this.createPasses();
167736
+ this.loadNoise().then((loaded) => {
167737
+ if (this.disposed)
167738
+ return;
167739
+ if (!loaded) {
167740
+ this.releasePending();
167741
+ return;
167742
+ }
167743
+ this.waitUntilReady();
167744
+ });
167745
+ }
167746
+ dispose() {
167747
+ if (this.disposed)
167748
+ return;
167749
+ this.disposed = true;
167750
+ const scene = this.scene;
167751
+ this.releasePending();
167752
+ if (this.beforeRenderObserver != null)
167753
+ scene.onBeforeRenderObservable.remove(this.beforeRenderObserver);
167754
+ if (this.afterRenderObserver != null)
167755
+ scene.onAfterRenderObservable.remove(this.afterRenderObserver);
167756
+ if (this.beforeTargetsObserver != null)
167757
+ scene.onBeforeRenderTargetsRenderObservable.remove(this.beforeTargetsObserver);
167758
+ if (this.afterTargetsObserver != null)
167759
+ scene.onAfterRenderTargetsRenderObservable.remove(this.afterTargetsObserver);
167760
+ if (this.disposeObserver != null)
167761
+ scene.onDisposeObservable.remove(this.disposeObserver);
167762
+ if (this.preExposureBinding != null)
167763
+ HdrpRendering.Unbind(scene, this.preExposureBinding);
167764
+ const disposables = [this.compositeMesh, this.compositeMaterial, this.marchPass, this.temporalPass, this.renderer, this.marchTarget, this.worleyTexture, this.erosionTexture, this.curveTexture, this.placeholderDepth, this.placeholderAerial].concat(this.historyTargets);
167765
+ for (const disposable of disposables) {
167766
+ try {
167767
+ if (disposable != null)
167768
+ disposable.dispose();
167769
+ }
167770
+ catch (error) { }
167771
+ }
167772
+ const sceneWithClouds = scene;
167773
+ if (sceneWithClouds._tkVolumetricClouds === this)
167774
+ sceneWithClouds._tkVolumetricClouds = null;
167775
+ }
167776
+ createPasses() {
167777
+ const scene = this.scene;
167778
+ const engine = scene.getEngine();
167779
+ const isWebGPU = engine.isWebGPU === true;
167780
+ const language = isWebGPU ? ShaderLanguage.WGSL : ShaderLanguage.GLSL;
167781
+ const defines = [];
167782
+ if (this.isLocal)
167783
+ defines.push("#define CLOUD_LOCAL");
167784
+ if (this.block.microerosion === true && this.block.microerosionfactor > 0)
167785
+ defines.push("#define CLOUD_MICRO_EROSION");
167786
+ const liveSky = HdrpPhysicallyBasedSky.Get(scene);
167787
+ this.hasAerial = liveSky != null && HdrpFogMath != null && HdrpFogMath.HasAerialPerspective(liveSky);
167788
+ const marchUniforms = VolumetricCloudsShaders.MarchUniforms.slice();
167789
+ const marchSamplers = VolumetricCloudsShaders.MarchSamplers.slice();
167790
+ if (this.hasAerial) {
167791
+ defines.push("#define CLOUD_AERIAL");
167792
+ marchUniforms.push(VolumetricCloudsShaders.AerialCameraUniform, ...HdrpPbSkyShaders.AtmosphereUniforms);
167793
+ marchSamplers.push(VolumetricCloudsShaders.AerialSampler);
167794
+ this.placeholderAerial = new RawTexture2DArray(new Uint8Array(4), 1, 1, 1, Constants.TEXTUREFORMAT_RGBA, scene, false, false, Texture.NEAREST_SAMPLINGMODE, Constants.TEXTURETYPE_UNSIGNED_BYTE);
167795
+ this.placeholderAerial.name = "Volumetric Clouds Aerial Placeholder";
167796
+ }
167797
+ VolumetricCloudsShaders.Register();
167798
+ this.curveTexture = this.createCurveTexture();
167799
+ this.placeholderDepth = new RawTexture(new Float32Array([0]), 1, 1, Constants.TEXTUREFORMAT_R, scene, false, false, Texture.NEAREST_SAMPLINGMODE, Constants.TEXTURETYPE_FLOAT);
167800
+ this.placeholderDepth.name = "Volumetric Clouds Depth Placeholder";
167801
+ const textureType = (engine.getCaps().textureHalfFloatRender === true) ? Constants.TEXTURETYPE_HALF_FLOAT : Constants.TEXTURETYPE_FLOAT;
167802
+ this.updateTargetSize();
167803
+ this.marchTarget = this.createCloudTarget("Volumetric Clouds Lighting", textureType);
167804
+ this.historyTargets.push(this.createCloudTarget("Volumetric Clouds History A", textureType), this.createCloudTarget("Volumetric Clouds History B", textureType));
167805
+ this.renderer = new EffectRenderer(engine);
167806
+ this.marchPass = new EffectWrapper({
167807
+ engine: engine, name: VolumetricCloudsShaders.MarchName, fragmentShader: VolumetricCloudsShaders.MarchName, useShaderStore: true,
167808
+ uniformNames: marchUniforms, samplerNames: marchSamplers,
167809
+ defines: defines.join("\n"), shaderLanguage: language
167810
+ });
167811
+ this.marchPass.onApplyObservable.add(() => this.bindMarch(this.marchPass.effect));
167812
+ this.temporalPass = new EffectWrapper({
167813
+ engine: engine, name: VolumetricCloudsShaders.TemporalName, fragmentShader: VolumetricCloudsShaders.TemporalName, useShaderStore: true,
167814
+ uniformNames: VolumetricCloudsShaders.TemporalUniforms.slice(), samplerNames: VolumetricCloudsShaders.TemporalSamplers.slice(),
167815
+ defines: defines.join("\n"), shaderLanguage: language
167816
+ });
167817
+ this.temporalPass.onApplyObservable.add(() => this.bindTemporal(this.temporalPass.effect));
167818
+ this.createCompositeMesh(language);
167819
+ }
167820
+ createCloudTarget(name, textureType) {
167821
+ const target = new RenderTargetTexture(name, { width: this.targetWidth, height: this.targetHeight }, this.scene, false, true, textureType, false, Texture.BILINEAR_SAMPLINGMODE, false, false, false, Constants.TEXTUREFORMAT_RGBA);
167822
+ target.wrapU = Texture.CLAMP_ADDRESSMODE;
167823
+ target.wrapV = Texture.CLAMP_ADDRESSMODE;
167824
+ target.anisotropicFilteringLevel = 1;
167825
+ return target;
167826
+ }
167827
+ createCurveTexture() {
167828
+ const curves = this.block.curves || {};
167829
+ const texels = VolumetricCloudsMath.PackCurves(curves.density, curves.erosion, curves.ambientocclusion);
167830
+ const halfTexels = new Uint16Array(texels.length);
167831
+ for (let i = 0; i < texels.length; i++)
167832
+ halfTexels[i] = TextureTools.ToHalfFloat(texels[i]);
167833
+ const texture = new RawTexture(halfTexels, CLOUD_CURVE_SAMPLES, 1, Constants.TEXTUREFORMAT_RGBA, this.scene, false, false, Texture.BILINEAR_SAMPLINGMODE, Constants.TEXTURETYPE_HALF_FLOAT);
167834
+ texture.name = "Volumetric Clouds Curves";
167835
+ texture.wrapU = Texture.WRAP_ADDRESSMODE;
167836
+ texture.wrapV = Texture.CLAMP_ADDRESSMODE;
167837
+ return texture;
167838
+ }
167839
+ createCompositeMesh(language) {
167840
+ const scene = this.scene;
167841
+ const mesh = new Mesh(VolumetricClouds.MeshName, scene);
167842
+ const vertexData = new VertexData();
167843
+ vertexData.positions = [-1, -1, 0, 1, -1, 0, 1, 1, 0, -1, 1, 0];
167844
+ vertexData.indices = [0, 1, 2, 0, 2, 3];
167845
+ vertexData.applyToMesh(mesh, false);
167846
+ const material = new ShaderMaterial("Volumetric Clouds Composite", scene, { vertex: VolumetricCloudsShaders.CompositeName, fragment: VolumetricCloudsShaders.CompositeName }, { attributes: ["position"], uniforms: ["cloudDisplayEncoded"], samplers: ["cloudLighting"], needAlphaBlending: true, shaderLanguage: language });
167847
+ material.alphaMode = Constants.ALPHA_PREMULTIPLIED;
167848
+ material.disableDepthWrite = true;
167849
+ material.depthFunction = Constants.ALWAYS;
167850
+ material.backFaceCulling = false;
167851
+ material.setTexture("cloudLighting", this.marchTarget);
167852
+ material.setFloat("cloudDisplayEncoded", this.isDisplayEncoded() ? 1 : 0);
167853
+ mesh.material = material;
167854
+ mesh.renderingGroupId = Utilities.DefaultRenderGroup();
167855
+ mesh.alphaIndex = CLOUD_ALPHA_INDEX;
167856
+ mesh.alwaysSelectAsActiveMesh = true;
167857
+ mesh.isPickable = false;
167858
+ mesh.doNotSyncBoundingInfo = true;
167859
+ mesh.applyFog = false;
167860
+ mesh.setEnabled(false);
167861
+ this.compositeMesh = mesh;
167862
+ this.compositeMaterial = material;
167863
+ this.beforeTargetsObserver = scene.onBeforeRenderTargetsRenderObservable.add(() => { mesh.isVisible = false; });
167864
+ this.afterTargetsObserver = scene.onAfterRenderTargetsRenderObservable.add(() => { mesh.isVisible = true; });
167865
+ }
167866
+ async loadNoise() {
167867
+ const noise = this.block.noise;
167868
+ if (noise == null || noise.worley == null || noise.erosion == null) {
167869
+ LightingConversions.WarnOnce("urp:clouds:nonoise", "Volumetric clouds: the export carries no cloud noise, so the clouds do not draw");
167870
+ return false;
167871
+ }
167872
+ try {
167873
+ const buffers = await Promise.all([
167874
+ Tools.LoadFileAsync(this.root + noise.worley.url, true),
167875
+ Tools.LoadFileAsync(this.root + noise.erosion.url, true)
167876
+ ]);
167877
+ if (this.disposed)
167878
+ return false;
167879
+ this.worleyTexture = this.createNoiseTexture("Volumetric Clouds Worley Noise", buffers[0], noise.worley.size);
167880
+ this.erosionTexture = this.createNoiseTexture("Volumetric Clouds Erosion Noise", buffers[1], noise.erosion.size);
167881
+ return this.worleyTexture != null && this.erosionTexture != null;
167882
+ }
167883
+ catch (error) {
167884
+ LightingConversions.WarnOnce("urp:clouds:noiseload", "Volumetric clouds: a cloud noise file failed to load, so the clouds do not draw (" + error + ")");
167885
+ return false;
167886
+ }
167887
+ }
167888
+ createNoiseTexture(name, buffer, size) {
167889
+ if (buffer == null || buffer.byteLength !== size * size * size) {
167890
+ LightingConversions.WarnOnce("urp:clouds:noisesize", "Volumetric clouds: " + name + " is not " + size + "³ bytes, so the clouds do not draw");
167891
+ return null;
167892
+ }
167893
+ const texture = new RawTexture3D(new Uint8Array(buffer), size, size, size, Constants.TEXTUREFORMAT_R, this.scene, true, false, Texture.TRILINEAR_SAMPLINGMODE, Constants.TEXTURETYPE_UNSIGNED_BYTE);
167894
+ texture.name = name;
167895
+ texture.wrapU = Texture.WRAP_ADDRESSMODE;
167896
+ texture.wrapV = Texture.WRAP_ADDRESSMODE;
167897
+ texture.wrapR = Texture.WRAP_ADDRESSMODE;
167898
+ return texture;
167899
+ }
167900
+ waitUntilReady() {
167901
+ if (this.disposed)
167902
+ return;
167903
+ const camera = this.scene.activeCamera;
167904
+ if (camera != null && this.sceneDepth == null)
167905
+ this.sceneDepth = CameraDepthCopy.AcquireForSampler(this.scene, camera, this);
167906
+ const noiseReady = this.worleyTexture.isReady() && this.erosionTexture.isReady() && this.curveTexture.isReady();
167907
+ const compositeReady = this.compositeMaterial.isReady(this.compositeMesh);
167908
+ if (!noiseReady || !this.marchPass.isReady() || !this.temporalPass.isReady() || !compositeReady || camera == null) {
167909
+ setTimeout(() => this.waitUntilReady(), CLOUD_READY_POLL_MS);
167910
+ return;
167911
+ }
167912
+ this.isReady = true;
167913
+ this.compositeMesh.setEnabled(true);
167914
+ this.beforeRenderObserver = this.scene.onBeforeRenderObservable.add(() => this.render());
167915
+ this.releasePending();
167916
+ SceneManager.WhenSceneRevealed(this.scene).then((revealed) => {
167917
+ if (!revealed || this.disposed)
167918
+ return;
167919
+ this.afterRenderObserver = this.scene.onAfterRenderObservable.add(() => this.updateTier());
167920
+ });
167921
+ }
167922
+ releasePending() {
167923
+ if (!this.isPending)
167924
+ return;
167925
+ this.isPending = false;
167926
+ this.scene.removePendingData(this);
167927
+ }
167928
+ render() {
167929
+ const camera = this.scene.activeCamera;
167930
+ if (!this.isReady || this.disposed || camera == null)
167931
+ return;
167932
+ this.advanceWind(this.scene.getEngine().getDeltaTime() / 1000);
167933
+ this.resizeTargets();
167934
+ this.frameSeed = (this.frameSeed + 1) % 65536;
167935
+ this.sceneDepth = CameraDepthCopy.AcquireForSampler(this.scene, camera, this);
167936
+ this.trackCamera(camera);
167937
+ this.renderer.render(this.marchPass, this.marchTarget);
167938
+ const accumulation = (typeof this.block.temporalaccumulationfactor === "number") ? this.block.temporalaccumulationfactor : 0;
167939
+ if (accumulation < CLOUD_MINIMUM_ACCUMULATION) {
167940
+ this.compositeMaterial.setTexture("cloudLighting", this.marchTarget);
167941
+ }
167942
+ else {
167943
+ this.historyIndex = 1 - this.historyIndex;
167944
+ this.renderer.render(this.temporalPass, this.historyTargets[this.historyIndex]);
167945
+ this.compositeMaterial.setTexture("cloudLighting", this.historyTargets[this.historyIndex]);
167946
+ this.hasHistory = true;
167947
+ }
167948
+ this.compositeMaterial.setFloat("cloudDisplayEncoded", this.isDisplayEncoded() ? 1 : 0);
167949
+ this.previousViewProjection.copyFrom(camera.getTransformationMatrix());
167950
+ }
167951
+ isDisplayEncoded() {
167952
+ return HdrpRendering.IsParity(this.scene) !== true && this.scene.imageProcessingConfiguration.applyByPostProcess !== true;
167953
+ }
167954
+ trackCamera(camera) {
167955
+ const eye = camera.globalPosition;
167956
+ this.currentEye[0] = eye.x;
167957
+ this.currentEye[1] = eye.y;
167958
+ this.currentEye[2] = eye.z;
167959
+ const isCut = camera !== this.lastCamera || VolumetricCloudsMath.IsCameraCut(this.hasPreviousEye ? this.previousEye : null, this.currentEye);
167960
+ if (isCut)
167961
+ this.hasHistory = false;
167962
+ this.lastCamera = camera;
167963
+ this.previousEye[0] = eye.x;
167964
+ this.previousEye[1] = eye.y;
167965
+ this.previousEye[2] = eye.z;
167966
+ this.hasPreviousEye = true;
167967
+ }
167968
+ updateTier() {
167969
+ if (this.disposed || !this.tierTracker.addFrame(this.scene.getEngine().getDeltaTime()))
167970
+ return;
167971
+ this.tier = this.tierTracker.tier;
167972
+ this.settings = VolumetricCloudsMath.TierSettings(this.tier, this.block);
167973
+ if (this.tierTracker.isSettled) {
167974
+ LightingConversions.WarnOnce("urp:clouds:restored", "Volumetric clouds: a lower tier saved under " + (CLOUD_TIER_MINIMUM_SAVING * 100) + " % of the frame time, so tier " + this.tier + " is kept");
167975
+ return;
167976
+ }
167977
+ LightingConversions.WarnOnce("urp:clouds:tier" + this.tier, "Volumetric clouds: frames ran over " + CLOUD_TIER_BUDGET_MS + " ms, trying tier " + this.tier
167978
+ + " (resolution " + this.settings.resolutionScale + ", " + this.settings.primarySteps + " primary steps, " + this.settings.lightSteps + " light steps)");
167979
+ }
167980
+ reportLoweredSettings() {
167981
+ const block = this.block;
167982
+ const settings = this.settings;
167983
+ if (typeof block.numprimarysteps === "number" && settings.primarySteps < block.numprimarysteps) {
167984
+ LightingConversions.WarnOnce("urp:clouds:primarysteps:" + this.tier, "Volumetric clouds: " + block.numprimarysteps + " primary steps are capped at " + settings.primarySteps + " in the browser (tier " + this.tier + ")");
167985
+ }
167986
+ if (typeof block.longraysamplemultiplier === "number" && settings.longRayMultiplier < block.longraysamplemultiplier) {
167987
+ LightingConversions.WarnOnce("urp:clouds:longray:" + this.tier, "Volumetric clouds: long ray sample multiplier " + block.longraysamplemultiplier + " is capped at " + settings.longRayMultiplier + " in the browser (tier " + this.tier + ")");
167988
+ }
167989
+ if (typeof block.numlightsteps === "number" && settings.lightSteps < block.numlightsteps) {
167990
+ LightingConversions.WarnOnce("urp:clouds:lightsteps:" + this.tier, "Volumetric clouds: " + block.numlightsteps + " light steps are capped at " + settings.lightSteps + " (tier " + this.tier + ")");
167991
+ }
167992
+ if (typeof block.resolutionscale === "number" && settings.resolutionScale < block.resolutionscale) {
167993
+ LightingConversions.WarnOnce("urp:clouds:resolution:" + this.tier, "Volumetric clouds: resolution scale " + block.resolutionscale + " is lowered to " + settings.resolutionScale + " (tier " + this.tier + ")");
167994
+ }
167995
+ }
167996
+ advanceWind(deltaSeconds) {
167997
+ const block = this.block;
167998
+ const signedDelta = Math.min(Math.max(deltaSeconds, 0), 1) * CLOUD_WIND_KMH_TO_MS;
167999
+ const theta = Tools.ToRadians(block.globalorientation || 0);
168000
+ const speed = block.globalspeed || 0;
168001
+ this.windOffset[0] = (this.windOffset[0] + signedDelta * speed * Math.cos(theta)) % CLOUD_WIND_WRAP_METERS;
168002
+ this.windOffset[1] = (this.windOffset[1] + signedDelta * speed * Math.sin(theta)) % CLOUD_WIND_WRAP_METERS;
168003
+ this.verticalShapeOffset = (this.verticalShapeOffset + signedDelta * (block.verticalshapewindspeed || 0)) % CLOUD_WIND_WRAP_METERS;
168004
+ this.verticalErosionOffset = (this.verticalErosionOffset + signedDelta * (block.erosionspeedmultiplier || 0)) % CLOUD_WIND_WRAP_METERS;
168005
+ }
168006
+ resizeTargets() {
168007
+ if (!this.updateTargetSize())
168008
+ return;
168009
+ const size = { width: this.targetWidth, height: this.targetHeight };
168010
+ this.marchTarget.resize(size);
168011
+ for (const history of this.historyTargets)
168012
+ history.resize(size);
168013
+ this.hasHistory = false;
168014
+ }
168015
+ updateTargetSize() {
168016
+ const engine = this.scene.getEngine();
168017
+ const scale = this.settings.resolutionScale;
168018
+ const width = Math.max(1, Math.round(engine.getRenderWidth() * scale));
168019
+ const height = Math.max(1, Math.round(engine.getRenderHeight() * scale));
168020
+ if (width === this.targetWidth && height === this.targetHeight)
168021
+ return false;
168022
+ this.targetWidth = width;
168023
+ this.targetHeight = height;
168024
+ return true;
168025
+ }
168026
+ bindMarch(effect) {
168027
+ const scene = this.scene;
168028
+ const camera = scene.activeCamera;
168029
+ if (camera == null)
168030
+ return;
168031
+ const block = this.block;
168032
+ const settings = this.settings;
168033
+ const mirrorX = this.mirror.m[0];
168034
+ camera.getTransformationMatrix().invertToRef(this.inverseViewProjection);
168035
+ this.inverseViewProjection.multiplyToRef(this.mirror, this.inverseViewProjection);
168036
+ effect.setMatrix("cloudInvViewProj", this.inverseViewProjection);
168037
+ const eye = camera.globalPosition;
168038
+ effect.setFloat4("cloudEye", eye.x * mirrorX, eye.y, eye.z, 0);
168039
+ camera.getDirectionToRef(this.localForward, this.cameraForward);
168040
+ effect.setFloat4("cloudForward", this.cameraForward.x * mirrorX, this.cameraForward.y, this.cameraForward.z, mirrorX);
168041
+ const hasDepth = this.sceneDepth != null;
168042
+ effect.setTexture("sceneDepth", hasDepth ? this.sceneDepth : this.placeholderDepth);
168043
+ effect.setFloat4("cloudCamera", (camera.maxZ > 0) ? camera.maxZ : Number.MAX_VALUE, hasDepth ? 1 : 0, this.frameSeed, this.preExposure);
168044
+ if (this.hasAerial)
168045
+ this.bindAerial(effect);
168046
+ const center = this.planetCenter;
168047
+ const bottomRadius = this.planetRadius + (block.bottomaltitude || 0);
168048
+ const altitudeRange = block.altituderange || 1;
168049
+ effect.setFloat4("cloudPlanet", center.x * mirrorX, center.y, center.z, this.planetRadius);
168050
+ effect.setFloat4("cloudLayer", bottomRadius, bottomRadius + altitudeRange, VolumetricCloudsMath.MaximumStep(altitudeRange, block.numprimarysteps, settings.primarySteps), settings.primarySteps);
168051
+ const isAutomaticFade = block.fadeinmode !== CLOUD_FADE_MODE_MANUAL;
168052
+ const fadeStart = isAutomaticFade ? Math.max(altitudeRange * CLOUD_AUTO_FADE_START_FRACTION, camera.minZ) : Math.max(block.fadeinstart || 0, camera.minZ);
168053
+ const fadeDistance = isAutomaticFade ? altitudeRange * CLOUD_AUTO_FADE_DISTANCE_FRACTION : (block.fadeindistance || 0);
168054
+ effect.setFloat4("cloudSteps", settings.longRayMultiplier, settings.lightSteps, fadeStart, fadeDistance);
168055
+ effect.setFloat4("cloudShape", block.shapescale, block.shapefactor, block.erosionscale, block.erosionfactor);
168056
+ const density = block.densitymultiplier * block.densitymultiplier * CLOUD_DENSITY_SCALE;
168057
+ effect.setFloat4("cloudDetail", block.erosionocclusion, block.microerosionscale, block.microerosionfactor, density);
168058
+ const shapeOffset = Array.isArray(block.shapeoffset) ? block.shapeoffset : [0, 0, 0];
168059
+ effect.setFloat4("cloudShapeOffset", shapeOffset[0], shapeOffset[2], shapeOffset[1], block.altitudedistortion * CLOUD_ALTITUDE_DISTORTION_SCALE);
168060
+ const theta = Tools.ToRadians(block.globalorientation || 0);
168061
+ effect.setFloat4("cloudWind", -Math.cos(theta), -Math.sin(theta), this.windOffset[0], this.windOffset[1]);
168062
+ effect.setFloat4("cloudWindSpeed", block.shapespeedmultiplier, block.erosionspeedmultiplier, this.verticalShapeOffset, this.verticalErosionOffset);
168063
+ const coverageInverseSize = 1 / Math.max(CLOUD_MINIMUM_COVERAGE_PATTERN_METERS, block.coveragepatternsize || CLOUD_DEFAULT_COVERAGE_PATTERN_METERS);
168064
+ const coverageGap = (typeof block.coveragegapamount === "number") ? block.coveragegapamount : CLOUD_DEFAULT_COVERAGE_GAP;
168065
+ effect.setFloat4("cloudCoverage", block.coveragevariation || 0, coverageInverseSize, coverageGap, 0);
168066
+ const coverageOffset = Array.isArray(block.coveragepatternoffset) ? block.coveragepatternoffset : [0, 0];
168067
+ effect.setFloat4("cloudCoverageOffset", coverageOffset[0], coverageOffset[1], 0, 0);
168068
+ effect.setFloat4("cloudScattering", 1 - block.multiscattering * CLOUD_MULTI_SCATTERING_SCALE, block.powdereffectintensity, 0, 0);
168069
+ effect.setFloat4("cloudTint", this.scatteringTint.r, this.scatteringTint.g, this.scatteringTint.b, 0);
168070
+ this.bindLighting(effect, mirrorX);
168071
+ effect.setTexture("worleyNoise", this.worleyTexture);
168072
+ effect.setTexture("erosionNoise", this.erosionTexture);
168073
+ effect.setTexture("cloudCurves", this.curveTexture);
168074
+ }
168075
+ bindLighting(effect, mirrorX) {
168076
+ const block = this.block;
168077
+ const sun = this.resolveSun();
168078
+ const sunScale = (this.sky != null && typeof this.sky.sunintensityscale === "number") ? this.sky.sunintensityscale : 1;
168079
+ const dimmer = (typeof block.sunlightdimmer === "number") ? block.sunlightdimmer : 1;
168080
+ if (sun != null) {
168081
+ const direction = this.sunDirection;
168082
+ const strength = sun.intensity * sunScale * dimmer;
168083
+ effect.setFloat4("cloudSunDirection", -direction.x * mirrorX, -direction.y, -direction.z, 0);
168084
+ effect.setFloat4("cloudSunColor", sun.diffuse.r * strength, sun.diffuse.g * strength, sun.diffuse.b * strength, 0);
168085
+ }
168086
+ else {
168087
+ effect.setFloat4("cloudSunDirection", 0, 1, 0, 0);
168088
+ effect.setFloat4("cloudSunColor", 0, 0, 0, 0);
168089
+ }
168090
+ this.refreshAmbient();
168091
+ const ambientDimmer = (typeof block.ambientlightprobedimmer === "number") ? block.ambientlightprobedimmer : 1;
168092
+ effect.setFloat4("cloudAmbientTop", this.ambientTop.r * ambientDimmer, this.ambientTop.g * ambientDimmer, this.ambientTop.b * ambientDimmer, 0);
168093
+ effect.setFloat4("cloudAmbientBottom", this.ambientBottom.r * ambientDimmer, this.ambientBottom.g * ambientDimmer, this.ambientBottom.b * ambientDimmer, 0);
168094
+ }
168095
+ bindAerial(effect) {
168096
+ const sky = HdrpPhysicallyBasedSky.Get(this.scene);
168097
+ const isBound = sky != null && sky.bindAerialPerspective(effect, VolumetricCloudsShaders.AerialSampler, VolumetricCloudsShaders.AerialCameraUniform);
168098
+ if (isBound)
168099
+ return;
168100
+ effect.setTexture(VolumetricCloudsShaders.AerialSampler, this.placeholderAerial);
168101
+ effect.setFloat4(VolumetricCloudsShaders.AerialCameraUniform, 0, 0, 1, 0);
168102
+ }
168103
+ resolveSun() {
168104
+ if (this.sunLight == null || this.sunLight.isDisposed() || !this.sunLight.isEnabled()) {
168105
+ const lights = HdrpPhysicallyBasedSky.InteractingLights(this.scene.lights);
168106
+ this.sunLight = (lights.length > 0) ? lights[0] : null;
168107
+ }
168108
+ const sun = this.sunLight;
168109
+ if (sun == null)
168110
+ return null;
168111
+ sun.computeTransformedInformation();
168112
+ const transformed = sun.transformedDirection;
168113
+ this.sunDirection.copyFrom((transformed != null && sun.parent != null) ? transformed : sun.direction).normalize();
168114
+ return sun;
168115
+ }
168116
+ refreshAmbient() {
168117
+ const sky = HdrpPhysicallyBasedSky.Get(this.scene);
168118
+ const source = (sky != null) ? sky : this.scene.environmentTexture;
168119
+ const captureCount = (sky != null) ? sky.captureCount : 0;
168120
+ if (source === this.ambientSource && captureCount === this.ambientCaptureCount && source != null)
168121
+ return;
168122
+ this.ambientSource = source;
168123
+ this.ambientCaptureCount = captureCount;
168124
+ if (sky != null) {
168125
+ this.ambientTop.copyFrom(sky.ambientAt(Vector3.UpReadOnly));
168126
+ this.ambientBottom.copyFrom(sky.ambientAt(Vector3.DownReadOnly));
168127
+ return;
168128
+ }
168129
+ const environment = this.scene.environmentTexture;
168130
+ const polynomial = (environment != null) ? environment.sphericalPolynomial : null;
168131
+ if (polynomial != null) {
168132
+ this.ambientTop.copyFrom(HdrpPbSkyMath.EvaluatePolynomial(polynomial, Vector3.UpReadOnly));
168133
+ this.ambientBottom.copyFrom(HdrpPbSkyMath.EvaluatePolynomial(polynomial, Vector3.DownReadOnly));
168134
+ return;
168135
+ }
168136
+ this.ambientTop.copyFrom(this.scene.ambientColor);
168137
+ this.ambientBottom.copyFrom(this.scene.ambientColor);
168138
+ }
168139
+ bindTemporal(effect) {
168140
+ const camera = this.scene.activeCamera;
168141
+ if (camera == null)
168142
+ return;
168143
+ const mirrorX = this.mirror.m[0];
168144
+ this.mirror.multiplyToRef(this.previousViewProjection, this.previousViewProjectionUnity);
168145
+ effect.setMatrix("cloudInvViewProj", this.inverseViewProjection);
168146
+ effect.setMatrix("cloudPrevViewProj", this.previousViewProjectionUnity);
168147
+ const eye = camera.globalPosition;
168148
+ effect.setFloat4("cloudEye", eye.x * mirrorX, eye.y, eye.z, 0);
168149
+ effect.setFloat4("cloudForward", this.cameraForward.x * mirrorX, this.cameraForward.y, this.cameraForward.z, 0);
168150
+ const hasDepth = this.sceneDepth != null;
168151
+ effect.setTexture("sceneDepth", hasDepth ? this.sceneDepth : this.placeholderDepth);
168152
+ const accumulation = this.block.temporalaccumulationfactor;
168153
+ effect.setFloat4("cloudCamera", (camera.maxZ > 0) ? camera.maxZ : Number.MAX_VALUE, hasDepth ? 1 : 0, this.hasHistory ? 1 : 0, accumulation);
168154
+ const center = this.planetCenter;
168155
+ const bottomRadius = this.planetRadius + (this.block.bottomaltitude || 0);
168156
+ const altitudeRange = this.block.altituderange || 1;
168157
+ effect.setFloat4("cloudPlanet", center.x * mirrorX, center.y, center.z, this.planetRadius);
168158
+ effect.setFloat4("cloudLayer", bottomRadius, bottomRadius + altitudeRange, altitudeRange / CLOUD_MAX_STEP_DIVISOR, this.settings.primarySteps);
168159
+ effect.setFloat4("cloudTexel", 1 / this.targetWidth, 1 / this.targetHeight, 0, 0);
168160
+ effect.setTexture("cloudCurrent", this.marchTarget);
168161
+ effect.setTexture("cloudHistory", this.historyTargets[1 - this.historyIndex]);
168162
+ }
168163
+ static LinearScatteringTint(tint) {
168164
+ const source = Array.isArray(tint) ? tint : [0, 0, 0];
168165
+ return new Color3(1 - source[0] * CLOUD_SCATTERING_TINT_SCALE, 1 - source[1] * CLOUD_SCATTERING_TINT_SCALE, 1 - source[2] * CLOUD_SCATTERING_TINT_SCALE).toLinearSpace();
168166
+ }
168167
+ static CanvasCssWidth(scene) {
168168
+ const engine = scene.getEngine();
168169
+ const canvas = engine.getRenderingCanvas();
168170
+ if (canvas != null && typeof canvas.clientWidth === "number" && canvas.clientWidth > 0)
168171
+ return canvas.clientWidth;
168172
+ return engine.getRenderWidth();
168173
+ }
168174
+ }
168175
+ VolumetricClouds.MeshName = "Volumetric Clouds";
168176
+ export class VolumetricCloudsShaders {
168177
+ static Register() {
168178
+ if (VolumetricCloudsShaders.registered === true)
168179
+ return;
168180
+ const glsl = ShaderStore.ShadersStore;
168181
+ const wgsl = ShaderStore.ShadersStoreWGSL;
168182
+ const shaders = VolumetricCloudsShaders;
168183
+ glsl[shaders.MarchName + "FragmentShader"] = shaders.GlslMarch();
168184
+ glsl[shaders.CompositeName + "VertexShader"] = shaders.GlslCompositeVertex();
168185
+ glsl[shaders.CompositeName + "FragmentShader"] = shaders.GlslCompositeFragment();
168186
+ glsl[shaders.TemporalName + "FragmentShader"] = shaders.GlslTemporal();
168187
+ wgsl[shaders.MarchName + "FragmentShader"] = shaders.WgslMarch();
168188
+ wgsl[shaders.CompositeName + "VertexShader"] = shaders.WgslCompositeVertex();
168189
+ wgsl[shaders.CompositeName + "FragmentShader"] = shaders.WgslCompositeFragment();
168190
+ wgsl[shaders.TemporalName + "FragmentShader"] = shaders.WgslTemporal();
168191
+ VolumetricCloudsShaders.registered = true;
168192
+ }
168193
+ static AllSources() {
168194
+ const shaders = VolumetricCloudsShaders;
168195
+ return [
168196
+ shaders.GlslMarch(), shaders.GlslCompositeVertex(), shaders.GlslCompositeFragment(), shaders.GlslTemporal(),
168197
+ shaders.WgslMarch(), shaders.WgslCompositeVertex(), shaders.WgslCompositeFragment(), shaders.WgslTemporal()
168198
+ ];
168199
+ }
168200
+ static GlslMarchUniforms() {
168201
+ return VolumetricCloudsShaders.MarchUniforms.map((name) => (name === "cloudInvViewProj") ? "uniform mat4 " + name + ";" : "uniform vec4 " + name + ";").join("\n") + "\n";
168202
+ }
168203
+ static GlslConstants() {
168204
+ return `const float CLOUD_DENSITY_THRESHOLD = 0.001;
168205
+ const int EMPTY_STEPS_BEFORE_LARGE_STEPS = 8;
168206
+ const float FORWARD_ECCENTRICITY = 0.7;
168207
+ const float BACKWARD_ECCENTRICITY = 0.7;
168208
+ const float MIN_EROSION_DISTANCE = 3000.0;
168209
+ const float MAX_EROSION_DISTANCE = 100000.0;
168210
+ const float NOISE_NORMALIZATION = 100000.0;
168211
+ const float MAX_SKYBOX_DISTANCE = 200000.0;
168212
+ const float LIGHT_STEP_MAXIMAL_SIZE = 1000.0;
168213
+ const float CLOUD_PI = 3.14159265359;
168214
+ const int MAX_PRIMARY_STEPS = 1024;
168215
+ const int MAX_LIGHT_STEPS = 16;
168216
+ `;
168217
+ }
168218
+ static GlslGeometry() {
168219
+ return `vec2 cloudIntersectSphere(float sphereRadius, float cosChi, float radialDistance) {
168220
+ float ratio = sphereRadius / radialDistance;
168221
+ float d = ratio * ratio - clamp(1.0 - cosChi * cosChi, 0.0, 1.0);
168222
+ return (d < 0.0) ? vec2(d) : radialDistance * vec2(-cosChi - sqrt(d), -cosChi + sqrt(d));
168223
+ }
168224
+ float cloudHorizonCosine(float radialDistance) {
168225
+ float sinHorizon = cloudPlanet.w / radialDistance;
168226
+ return -sqrt(clamp(1.0 - sinHorizon * sinHorizon, 0.0, 1.0));
168227
+ }
168228
+ bool cloudIntersectVolume(vec3 originPS, vec3 direction, out float entry, out float exitDistance) {
168229
+ float radialDistance = length(originPS);
168230
+ float cosChi = dot(originPS, direction) / radialDistance;
168231
+ vec2 inner = cloudIntersectSphere(cloudLayer.x, cosChi, radialDistance);
168232
+ vec2 outer = cloudIntersectSphere(cloudLayer.y, cosChi, radialDistance);
168233
+ if (inner.x < 0.0 && inner.y >= 0.0) {
168234
+ entry = inner.y;
168235
+ exitDistance = outer.y;
168236
+ return cosChi >= cloudHorizonCosine(radialDistance);
168237
+ }
168238
+ entry = max(outer.x, 0.0);
168239
+ exitDistance = (inner.x >= 0.0) ? inner.x : outer.y;
168240
+ return outer.y >= 0.0;
168241
+ }
168242
+ int cloudRaySphere(vec3 startWS, vec3 direction, float radius, out vec2 result) {
168243
+ vec3 startPS = startWS + vec3(0.0, cloudPlanet.w, 0.0);
168244
+ float a = dot(direction, direction);
168245
+ float b = 2.0 * dot(direction, startPS);
168246
+ float c = dot(startPS, startPS) - radius * radius;
168247
+ float d = b * b - 4.0 * a * c;
168248
+ result = vec2(0.0);
168249
+ int solutions = 0;
168250
+ if (d >= 0.0) {
168251
+ float q = -0.5 * (b + ((b >= 0.0) ? 1.0 : -1.0) * sqrt(d));
168252
+ result = vec2(c / q, q / a);
168253
+ solutions = 2;
168254
+ if (result.x < 0.0) { solutions--; result.x = result.y; }
168255
+ if (result.y < 0.0) solutions--;
168256
+ }
168257
+ return solutions;
168258
+ }
168259
+ bool cloudRange(vec3 originWS, vec3 direction, out float rangeStart, out float rangeEnd) {
168260
+ #ifdef CLOUD_LOCAL
168261
+ return cloudIntersectVolume(originWS - cloudPlanet.xyz, direction, rangeStart, rangeEnd);
168262
+ #else
168263
+ vec2 inner = vec2(0.0);
168264
+ vec2 outer = vec2(0.0);
168265
+ cloudRaySphere(originWS, direction, cloudLayer.x, inner);
168266
+ cloudRaySphere(originWS, direction, cloudLayer.y, outer);
168267
+ rangeStart = inner.x;
168268
+ rangeEnd = outer.x;
168269
+ return true;
168270
+ #endif
168271
+ }
168272
+ `;
168273
+ }
168274
+ static GlslDensity() {
168275
+ return `struct CloudProperties {
168276
+ float density;
168277
+ float ambientOcclusion;
168278
+ float height;
168279
+ float sigmaT;
168280
+ };
168281
+ float cloudRemap(float x, float a, float b, float c, float d) {
168282
+ return (((x - a) / (b - a)) * (d - c)) + c;
168283
+ }
168284
+ float cloudNormalizedHeight(vec3 positionPS) {
168285
+ return clamp((length(positionPS) - cloudLayer.x) / (cloudLayer.y - cloudLayer.x), 0.0, 1.0);
168286
+ }
168287
+ vec3 cloudAnimateShape(vec3 positionPS) {
168288
+ vec3 position = positionPS;
168289
+ position.y += position.x / 3.0 + position.z / 7.0;
168290
+ return position + vec3(cloudWind.z, 0.0, cloudWind.w) * cloudWindSpeed.x + vec3(0.0, cloudWindSpeed.z, 0.0);
168291
+ }
168292
+ vec3 cloudAnimateErosion(vec3 positionPS) {
168293
+ return positionPS + vec3(cloudWind.z, 0.0, cloudWind.w) * cloudWindSpeed.y + vec3(0.0, cloudWindSpeed.w, 0.0);
168294
+ }
168295
+ float cloudCoverageHash(vec2 cell) {
168296
+ vec3 p = fract(vec3(cell.x, cell.y, cell.x) * 0.1031);
168297
+ p += dot(p, p.yzx + 33.33);
168298
+ return fract((p.x + p.y) * p.z);
168299
+ }
168300
+ float cloudCoverageNoise(vec2 position) {
168301
+ vec2 cell = floor(position);
168302
+ vec2 blend = fract(position);
168303
+ blend = blend * blend * (3.0 - 2.0 * blend);
168304
+ float bottom = mix(cloudCoverageHash(cell), cloudCoverageHash(cell + vec2(1.0, 0.0)), blend.x);
168305
+ float top = mix(cloudCoverageHash(cell + vec2(0.0, 1.0)), cloudCoverageHash(cell + vec2(1.0, 1.0)), blend.x);
168306
+ return mix(bottom, top, blend.y);
168307
+ }
168308
+ vec2 cloudCoverageAt(vec3 positionPS) {
168309
+ float coverage = 0.9;
168310
+ float shapeFill = 0.0;
168311
+ if (cloudCoverage.x != 0.0) {
168312
+ vec2 coordinates = (positionPS.xz + cloudWind.zw * cloudWindSpeed.x + cloudCoverageOffset.xy) * cloudCoverage.y;
168313
+ float coverageNoise = cloudCoverageNoise(coordinates) * 0.7 + cloudCoverageNoise(coordinates * 2.03 + vec2(17.13, 43.71)) * 0.3;
168314
+ float threshold = mix(-0.15, 1.15, cloudCoverage.z);
168315
+ float mask = smoothstep(threshold - 0.12, threshold + 0.12, coverageNoise);
168316
+ if (cloudCoverage.x > 0.0) {
168317
+ coverage *= mix(1.0, mask, cloudCoverage.x);
168318
+ } else {
168319
+ shapeFill = clamp(-cloudCoverage.x, 0.0, 1.0) * (1.0 - mask);
168320
+ coverage = mix(coverage, 1.0, shapeFill);
168321
+ }
168322
+ }
168323
+ return vec2(coverage, shapeFill);
168324
+ }
168325
+ CloudProperties cloudPropertiesAt(vec3 samplePositionPS, float noiseMip, float erosionMip, bool cheapVersion, bool lightSampling) {
168326
+ CloudProperties properties = CloudProperties(0.0, 0.0, 0.0, 0.0);
168327
+ vec3 positionPS = samplePositionPS;
168328
+ #ifndef CLOUD_LOCAL
168329
+ if (positionPS.y < cloudPlanet.w) return properties;
168330
+ #endif
168331
+ properties.ambientOcclusion = 1.0;
168332
+ properties.height = cloudNormalizedHeight(positionPS);
168333
+ #ifndef CLOUD_LOCAL
168334
+ positionPS.xz += cloudEye.xz;
168335
+ #endif
168336
+ vec3 baseCoordinates = (cloudAnimateShape(positionPS).xzy / NOISE_NORMALIZATION) * cloudShape.x - cloudShapeOffset.xyz;
168337
+ baseCoordinates += properties.height * vec3(cloudWind.x, cloudWind.y, 0.0) * cloudShapeOffset.w;
168338
+ float lowFrequencyNoise = textureLod(worleyNoise, baseCoordinates, noiseMip).r;
168339
+ vec2 coverage = cloudCoverageAt(positionPS);
168340
+ if (coverage.x <= CLOUD_DENSITY_THRESHOLD) return properties;
168341
+ vec3 curves = textureLod(cloudCurves, vec2(properties.height, 0.5), 0.0).xyz;
168342
+ float shapeFactor = mix(0.1, 1.0, cloudShape.y * (1.0 - coverage.y)) * curves.y;
168343
+ float erosionFactor = cloudShape.w * curves.y;
168344
+ float microDetailFactor = cloudDetail.z * curves.y;
168345
+ lowFrequencyNoise = mix(1.0, lowFrequencyNoise, shapeFactor);
168346
+ float baseCloud = 1.0 - curves.x * coverage.x * (1.0 - shapeFactor);
168347
+ baseCloud = clamp(cloudRemap(lowFrequencyNoise, baseCloud, 1.0, 0.0, 1.0), 0.0, 1.0) * coverage.x * coverage.x;
168348
+ properties.sigmaT = 0.04;
168349
+ float occlusionBlend = clamp(1.0 - max(erosionFactor, shapeFactor) * 0.5, 0.0, 1.0);
168350
+ properties.ambientOcclusion = mix(1.0, curves.z, occlusionBlend);
168351
+ if (!cheapVersion) {
168352
+ vec3 erosionCoordinates = cloudAnimateErosion(positionPS) / NOISE_NORMALIZATION * cloudShape.z;
168353
+ float erosionValue = 1.0 - textureLod(erosionNoise, erosionCoordinates, erosionMip).r;
168354
+ erosionValue = mix(0.0, erosionValue, erosionFactor * 0.75 * coverage.x);
168355
+ properties.ambientOcclusion = clamp(properties.ambientOcclusion - sqrt(erosionValue * cloudDetail.x), 0.0, 1.0);
168356
+ baseCloud = cloudRemap(baseCloud, erosionValue, 1.0, 0.0, 1.0);
168357
+ #ifdef CLOUD_MICRO_EROSION
168358
+ vec3 fineCoordinates = cloudAnimateErosion(positionPS) / NOISE_NORMALIZATION * cloudDetail.y;
168359
+ float fineValue = 1.0 - textureLod(erosionNoise, fineCoordinates, erosionMip).r;
168360
+ fineValue = mix(0.0, fineValue, microDetailFactor * 0.5 * coverage.x);
168361
+ baseCloud = cloudRemap(baseCloud, fineValue, 1.0, 0.0, 1.0);
168362
+ #endif
168363
+ }
168364
+ if (lightSampling) {
168365
+ baseCloud -= erosionFactor * 0.1;
168366
+ #ifdef CLOUD_MICRO_EROSION
168367
+ baseCloud -= microDetailFactor * 0.15;
168368
+ #endif
168369
+ }
168370
+ properties.density = max(0.0, baseCloud) * cloudDetail.w;
168371
+ return properties;
168372
+ }
168373
+ `;
168374
+ }
168375
+ static GlslLighting() {
168376
+ return `float cloudPhase(float anisotropy, float cosTheta) {
168377
+ float denominator = 1.0 + anisotropy * anisotropy - 2.0 * anisotropy * cosTheta;
168378
+ float falloff = inversesqrt(max(denominator, 1e-6));
168379
+ return (1.0 / (4.0 * CLOUD_PI)) * (1.0 - anisotropy * anisotropy) * falloff * falloff * falloff;
168380
+ }
168381
+ float cloudPowder(float density, float cosAngle, float intensity) {
168382
+ float powder = clamp((1.0 - exp(-density * 4.0)) * 2.0, 0.0, 1.0);
168383
+ return mix(1.0, mix(1.0, powder, smoothstep(0.5, -0.5, cosAngle)), intensity);
168384
+ }
168385
+ vec3 cloudSunTransmittance(vec3 positionPS, vec3 sunDirection, vec2 phase) {
168386
+ float radialDistance = length(positionPS);
168387
+ float cosChi = dot(positionPS, sunDirection) / radialDistance;
168388
+ vec3 transmittance = vec3(0.0);
168389
+ if (cosChi < cloudHorizonCosine(radialDistance)) return transmittance;
168390
+ float lightSteps = cloudSteps.y;
168391
+ float totalLightDistance = clamp(cloudIntersectSphere(cloudLayer.y, cosChi, radialDistance).y, 0.0, lightSteps * LIGHT_STEP_MAXIMAL_SIZE) + 5.0;
168392
+ float intervalSize = totalLightDistance / lightSteps;
168393
+ float opticalDepth = 0.0;
168394
+ for (int j = 0; j < MAX_LIGHT_STEPS; j++) {
168395
+ if (float(j) >= lightSteps) break;
168396
+ float distanceAlongLight = intervalSize * (0.25 + float(j));
168397
+ CloudProperties lightProperties = cloudPropertiesAt(positionPS + sunDirection * distanceAlongLight, 3.0 * float(j) / lightSteps, 0.0, true, true);
168398
+ opticalDepth += lightProperties.density * lightProperties.sigmaT;
168399
+ }
168400
+ vec3 extinction = intervalSize * opticalDepth * cloudTint.rgb;
168401
+ transmittance += exp(-extinction) * phase.x;
168402
+ transmittance += exp(-extinction * cloudScattering.x) * (phase.y * cloudScattering.x);
168403
+ return transmittance;
168404
+ }
168405
+ `;
168406
+ }
168407
+ static GlslAerial() {
168408
+ const skyShaders = HdrpPbSkyShaders;
168409
+ const fogPass = HdrpFogPass;
168410
+ if (skyShaders == null || fogPass == null)
168411
+ return "";
168412
+ return "#ifdef CLOUD_AERIAL\nprecision highp sampler2DArray;\nuniform sampler2DArray apLut;\nuniform vec4 cloudApCamera;\n"
168413
+ + skyShaders.GlslUniforms() + skyShaders.GlslCommon() + fogPass.AerialPerspectiveGlsl() + "#endif\n";
168414
+ }
168415
+ static GlslMarch() {
168416
+ const shaders = VolumetricCloudsShaders;
168417
+ return "precision highp float;\nprecision highp sampler3D;\nvarying vec2 vUV;\nuniform sampler3D worleyNoise;\nuniform sampler3D erosionNoise;\nuniform sampler2D cloudCurves;\nuniform sampler2D sceneDepth;\n"
168418
+ + shaders.GlslMarchUniforms() + shaders.GlslConstants() + shaders.GlslGeometry() + shaders.GlslDensity() + shaders.GlslLighting() + shaders.GlslAerial() + `
168419
+ uint cloudJenkins(uint value) {
168420
+ uint x = value;
168421
+ x += (x << 10u);
168422
+ x ^= (x >> 6u);
168423
+ x += (x << 3u);
168424
+ x ^= (x >> 11u);
168425
+ x += (x << 15u);
168426
+ return x;
168427
+ }
168428
+ float cloudRandom(vec2 pixel, float seed) {
168429
+ uvec3 v = uvec3(uvec2(pixel), uint(seed));
168430
+ uint hash = cloudJenkins(v.x ^ cloudJenkins(v.y ^ cloudJenkins(v.z)));
168431
+ return uintBitsToFloat((hash & 0x007FFFFFu) | 0x3F800000u) - 1.0;
168432
+ }
168433
+ void main(void) {
168434
+ gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0);
168435
+ vec4 farPoint = cloudInvViewProj * vec4(vUV * 2.0 - 1.0, 1.0, 1.0);
168436
+ vec3 direction = normalize(farPoint.xyz / farPoint.w - cloudEye.xyz);
168437
+ float maxRayLength = MAX_SKYBOX_DISTANCE;
168438
+ if (cloudCamera.y > 0.5) {
168439
+ float viewDepth = textureLod(sceneDepth, vUV, 0.0).r;
168440
+ if (viewDepth < cloudCamera.x * 0.999) {
168441
+ #ifdef CLOUD_LOCAL
168442
+ maxRayLength = viewDepth / max(dot(direction, cloudForward.xyz), 0.0001);
168443
+ #else
168444
+ return;
168445
+ #endif
168446
+ }
168447
+ }
168448
+ #ifdef CLOUD_LOCAL
168449
+ vec3 originWS = cloudEye.xyz;
168450
+ #else
168451
+ vec3 originWS = vec3(0.0);
168452
+ #endif
168453
+ float rangeStart = 0.0;
168454
+ float rangeEnd = 0.0;
168455
+ if (!cloudRange(originWS, direction, rangeStart, rangeEnd)) return;
168456
+ if (maxRayLength < rangeStart) return;
168457
+ float integrationNoise = cloudRandom(gl_FragCoord.xy, cloudCamera.z);
168458
+ float primarySteps = cloudLayer.w;
168459
+ float totalDistance = min(rangeEnd, maxRayLength) - rangeStart;
168460
+ float stepSize = min(totalDistance / primarySteps, cloudLayer.z);
168461
+ totalDistance = stepSize * primarySteps;
168462
+ float layerThickness = max(1.0, cloudLayer.y - cloudLayer.x);
168463
+ float sampleMultiplier = min(max(1.0, totalDistance / layerThickness), max(1.0, cloudSteps.x));
168464
+ int primaryStepCount = min(int(ceil(primarySteps * sampleMultiplier)), MAX_PRIMARY_STEPS);
168465
+ stepSize = totalDistance / float(max(1, primaryStepCount));
168466
+ vec3 sunDirection = cloudSunDirection.xyz;
168467
+ float cosAngle = dot(direction, sunDirection);
168468
+ vec2 phase = vec2(
168469
+ cloudPhase(FORWARD_ECCENTRICITY, cosAngle) + cloudPhase(-BACKWARD_ECCENTRICITY, cosAngle),
168470
+ cloudPhase(FORWARD_ECCENTRICITY * cloudScattering.x, cosAngle) + cloudPhase(-BACKWARD_ECCENTRICITY * cloudScattering.x, cosAngle));
168471
+ vec3 scattering = vec3(0.0);
168472
+ float ambient = 0.0;
168473
+ float transmittance = 1.0;
168474
+ float meanDistance = 0.0;
168475
+ float meanDistanceDivider = 0.0;
168476
+ float currentDistance = integrationNoise;
168477
+ vec3 currentPositionWS = originWS + (rangeStart + currentDistance) * direction;
168478
+ bool activeSampling = true;
168479
+ int sequentialEmptySamples = 0;
168480
+ int currentIndex = 0;
168481
+ for (int iteration = 0; iteration < MAX_PRIMARY_STEPS * 2; iteration++) {
168482
+ if (currentIndex >= primaryStepCount || currentDistance >= totalDistance) break;
168483
+ float cameraDistance = rangeStart + currentDistance;
168484
+ float densityFade = clamp((cameraDistance - cloudSteps.z) / (cloudSteps.z + cloudSteps.w), 0.0, 1.0);
168485
+ float erosionMip = mix(0.0, 4.0, clamp((cameraDistance - MIN_EROSION_DISTANCE) / (MAX_EROSION_DISTANCE - MIN_EROSION_DISTANCE), 0.0, 1.0));
168486
+ vec3 currentPositionPS = currentPositionWS - cloudPlanet.xyz;
168487
+ if (activeSampling) {
168488
+ CloudProperties properties = cloudPropertiesAt(currentPositionPS, 0.0, erosionMip, false, false);
168489
+ properties.density *= densityFade;
168490
+ if (properties.density > CLOUD_DENSITY_THRESHOLD) {
168491
+ float transmittanceDensity = transmittance * properties.density;
168492
+ meanDistance += cameraDistance * transmittanceDensity;
168493
+ meanDistanceDivider += transmittanceDensity;
168494
+ float stepTransmittance = exp(-properties.density * properties.sigmaT * stepSize);
168495
+ float powder = cloudPowder(properties.density, cosAngle, cloudScattering.y);
168496
+ vec3 sunLuminance = cloudSunTransmittance(currentPositionPS, sunDirection, phase) * powder;
168497
+ float absorbed = transmittance - transmittance * stepTransmittance;
168498
+ scattering += sunLuminance * absorbed;
168499
+ ambient += properties.ambientOcclusion * absorbed;
168500
+ transmittance *= stepTransmittance;
168501
+ if (transmittance < 0.003) {
168502
+ transmittance = 0.0;
168503
+ break;
168504
+ }
168505
+ sequentialEmptySamples = 0;
168506
+ } else {
168507
+ sequentialEmptySamples++;
168508
+ }
168509
+ if (sequentialEmptySamples == EMPTY_STEPS_BEFORE_LARGE_STEPS) activeSampling = false;
168510
+ float relativeStepSize = mix(integrationNoise, 1.0, clamp(float(currentIndex), 0.0, 1.0));
168511
+ currentPositionWS += direction * stepSize * relativeStepSize;
168512
+ currentDistance += stepSize * relativeStepSize;
168513
+ } else {
168514
+ CloudProperties properties = cloudPropertiesAt(currentPositionPS, 1.0, 0.0, true, false);
168515
+ properties.density *= densityFade;
168516
+ if (properties.density < CLOUD_DENSITY_THRESHOLD) {
168517
+ currentPositionWS += direction * stepSize * 2.0;
168518
+ currentDistance += stepSize * 2.0;
168519
+ } else {
168520
+ currentPositionWS -= direction * stepSize;
168521
+ currentDistance -= stepSize;
168522
+ currentIndex -= 1;
168523
+ activeSampling = true;
168524
+ sequentialEmptySamples = 0;
168525
+ }
168526
+ }
168527
+ currentIndex++;
168528
+ }
168529
+ if (meanDistance == 0.0) return;
168530
+ meanDistance = min(meanDistance / meanDistanceDivider, maxRayLength);
168531
+ float relativeHeight = cloudNormalizedHeight(originWS - cloudPlanet.xyz + meanDistance * direction);
168532
+ vec3 ambientColor = max(vec3(0.0), mix(cloudAmbientBottom.rgb, cloudAmbientTop.rgb, relativeHeight));
168533
+ vec3 color = (cloudSunColor.rgb * scattering + ambientColor * ambient) * cloudCamera.w;
168534
+ #ifdef CLOUD_AERIAL
168535
+ if (cloudApCamera.w > 0.5) {
168536
+ vec3 apDirection = vec3(direction.x * cloudForward.w, direction.y, direction.z);
168537
+ vec3 apColor = vec3(0.0);
168538
+ vec3 apOpacity = vec3(0.0);
168539
+ fogAerialPerspective(vUV, cloudApCamera.xyz, apDirection, meanDistance, apColor, apOpacity);
168540
+ color = apColor * (1.0 - transmittance) + (vec3(1.0) - apOpacity) * color;
168541
+ }
168542
+ #endif
168543
+ gl_FragColor = vec4(color, transmittance);
168544
+ }
168545
+ `;
168546
+ }
168547
+ static GlslCompositeVertex() {
168548
+ return `precision highp float;
168549
+ attribute vec3 position;
168550
+ varying vec2 vUV;
168551
+ void main(void) {
168552
+ vUV = position.xy * 0.5 + 0.5;
168553
+ gl_Position = vec4(position.xy, 0.0, 1.0);
168554
+ }
168555
+ `;
168556
+ }
168557
+ static GlslCompositeFragment() {
168558
+ return `precision highp float;
168559
+ varying vec2 vUV;
168560
+ uniform sampler2D cloudLighting;
168561
+ uniform float cloudDisplayEncoded;
168562
+ const float CLOUD_DISPLAY_GAMMA = 2.2;
168563
+ const vec3 CLOUD_LUMINANCE = vec3(0.2126, 0.7152, 0.0722);
168564
+ void main(void) {
168565
+ vec4 cloud = texture2D(cloudLighting, vUV);
168566
+ vec3 scattering = cloud.rgb;
168567
+ float throughput = cloud.a;
168568
+ if (cloudDisplayEncoded > 0.5) {
168569
+ float encodeExponent = 1.0 / CLOUD_DISPLAY_GAMMA;
168570
+ float scatteringLuminance = clamp(dot(scattering, CLOUD_LUMINANCE), 0.0, 1.0);
168571
+ throughput = pow(min(cloud.a + scatteringLuminance, 1.0), encodeExponent) - pow(scatteringLuminance, encodeExponent);
168572
+ scattering = pow(clamp(scattering, vec3(0.0), vec3(1.0)), vec3(encodeExponent));
168573
+ }
168574
+ gl_FragColor = vec4(scattering, 1.0 - throughput);
168575
+ }
168576
+ `;
168577
+ }
168578
+ static GlslTemporal() {
168579
+ const shaders = VolumetricCloudsShaders;
168580
+ return "precision highp float;\nvarying vec2 vUV;\nuniform sampler2D cloudCurrent;\nuniform sampler2D cloudHistory;\nuniform sampler2D sceneDepth;\n"
168581
+ + shaders.TemporalUniforms.map((name) => (name.indexOf("ViewProj") >= 0) ? "uniform mat4 " + name + ";" : "uniform vec4 " + name + ";").join("\n") + "\n"
168582
+ + shaders.GlslGeometry() + `
168583
+ float cloudReprojectionDistance(vec3 direction) {
168584
+ float distanceToCloud = 0.0;
168585
+ float exitDistance = 0.0;
168586
+ #ifdef CLOUD_LOCAL
168587
+ if (!cloudIntersectVolume(cloudEye.xyz - cloudPlanet.xyz, direction, distanceToCloud, exitDistance)) distanceToCloud = exitDistance;
168588
+ #else
168589
+ cloudRange(vec3(0.0), direction, distanceToCloud, exitDistance);
168590
+ #endif
168591
+ distanceToCloud = max(distanceToCloud, 1.0);
168592
+ if (cloudCamera.y > 0.5) {
168593
+ float viewDepth = textureLod(sceneDepth, vUV, 0.0).r;
168594
+ if (viewDepth < cloudCamera.x * 0.999) distanceToCloud = min(distanceToCloud, viewDepth / max(dot(direction, cloudForward.xyz), 0.0001));
168595
+ }
168596
+ return distanceToCloud;
168597
+ }
168598
+ void main(void) {
168599
+ vec4 current = textureLod(cloudCurrent, vUV, 0.0);
168600
+ gl_FragColor = current;
168601
+ if (cloudCamera.z < 0.5 || current.a >= 1.0) return;
168602
+ vec3 boxMin = current.rgb;
168603
+ vec3 boxMax = current.rgb;
168604
+ vec3 neighbour = textureLod(cloudCurrent, vUV + vec2(0.0, -cloudTexel.y), 0.0).rgb;
168605
+ boxMin = min(boxMin, neighbour);
168606
+ boxMax = max(boxMax, neighbour);
168607
+ neighbour = textureLod(cloudCurrent, vUV + vec2(-cloudTexel.x, 0.0), 0.0).rgb;
168608
+ boxMin = min(boxMin, neighbour);
168609
+ boxMax = max(boxMax, neighbour);
168610
+ neighbour = textureLod(cloudCurrent, vUV + vec2(cloudTexel.x, 0.0), 0.0).rgb;
168611
+ boxMin = min(boxMin, neighbour);
168612
+ boxMax = max(boxMax, neighbour);
168613
+ neighbour = textureLod(cloudCurrent, vUV + vec2(0.0, cloudTexel.y), 0.0).rgb;
168614
+ boxMin = min(boxMin, neighbour);
168615
+ boxMax = max(boxMax, neighbour);
168616
+ vec4 farPoint = cloudInvViewProj * vec4(vUV * 2.0 - 1.0, 1.0, 1.0);
168617
+ vec3 direction = normalize(farPoint.xyz / farPoint.w - cloudEye.xyz);
168618
+ vec3 worldPosition = cloudEye.xyz + direction * cloudReprojectionDistance(direction);
168619
+ vec4 previousClip = cloudPrevViewProj * vec4(worldPosition, 1.0);
168620
+ vec2 previousUV = previousClip.xy / previousClip.w * 0.5 + 0.5;
168621
+ vec2 velocity = previousUV - vUV;
168622
+ if (previousUV.x > 1.0 || previousUV.x < 0.0 || previousUV.y > 1.0 || previousUV.y < 0.0) return;
168623
+ vec3 previous = clamp(textureLod(cloudHistory, previousUV, 0.0).rgb, boxMin, boxMax);
168624
+ float accumulation = cloudCamera.w;
168625
+ float intensity = clamp(min(accumulation - abs(velocity.x) * accumulation, accumulation - abs(velocity.y) * accumulation), 0.0, 1.0);
168626
+ gl_FragColor = vec4(mix(current.rgb, previous, intensity), current.a);
168627
+ }
168628
+ `;
168629
+ }
168630
+ static WgslMarchUniforms() {
168631
+ return VolumetricCloudsShaders.MarchUniforms.map((name) => (name === "cloudInvViewProj") ? "uniform " + name + ": mat4x4f;" : "uniform " + name + ": vec4f;").join("\n") + "\n";
168632
+ }
168633
+ static WgslConstants() {
168634
+ return `const CLOUD_DENSITY_THRESHOLD: f32 = 0.001;
168635
+ const EMPTY_STEPS_BEFORE_LARGE_STEPS: i32 = 8;
168636
+ const FORWARD_ECCENTRICITY: f32 = 0.7;
168637
+ const BACKWARD_ECCENTRICITY: f32 = 0.7;
168638
+ const MIN_EROSION_DISTANCE: f32 = 3000.0;
168639
+ const MAX_EROSION_DISTANCE: f32 = 100000.0;
168640
+ const NOISE_NORMALIZATION: f32 = 100000.0;
168641
+ const MAX_SKYBOX_DISTANCE: f32 = 200000.0;
168642
+ const LIGHT_STEP_MAXIMAL_SIZE: f32 = 1000.0;
168643
+ const CLOUD_PI: f32 = 3.14159265359;
168644
+ const MAX_PRIMARY_STEPS: i32 = 1024;
168645
+ const MAX_LIGHT_STEPS: i32 = 16;
168646
+ `;
168647
+ }
168648
+ static WgslGeometry() {
168649
+ return `struct CloudRange {
168650
+ hit: bool,
168651
+ entryDistance: f32,
168652
+ exitDistance: f32,
168653
+ };
168654
+ fn cloudIntersectSphere(sphereRadius: f32, cosChi: f32, radialDistance: f32) -> vec2f {
168655
+ let ratio: f32 = sphereRadius / radialDistance;
168656
+ let d: f32 = ratio * ratio - clamp(1.0 - cosChi * cosChi, 0.0, 1.0);
168657
+ if (d < 0.0) { return vec2f(d); }
168658
+ return radialDistance * vec2f(-cosChi - sqrt(d), -cosChi + sqrt(d));
168659
+ }
168660
+ fn cloudHorizonCosine(radialDistance: f32) -> f32 {
168661
+ let sinHorizon: f32 = uniforms.cloudPlanet.w / radialDistance;
168662
+ return -sqrt(clamp(1.0 - sinHorizon * sinHorizon, 0.0, 1.0));
168663
+ }
168664
+ fn cloudIntersectVolume(originPS: vec3f, direction: vec3f) -> CloudRange {
168665
+ let radialDistance: f32 = length(originPS);
168666
+ let cosChi: f32 = dot(originPS, direction) / radialDistance;
168667
+ let inner: vec2f = cloudIntersectSphere(uniforms.cloudLayer.x, cosChi, radialDistance);
168668
+ let outer: vec2f = cloudIntersectSphere(uniforms.cloudLayer.y, cosChi, radialDistance);
168669
+ if (inner.x < 0.0 && inner.y >= 0.0) {
168670
+ return CloudRange(cosChi >= cloudHorizonCosine(radialDistance), inner.y, outer.y);
168671
+ }
168672
+ return CloudRange(outer.y >= 0.0, max(outer.x, 0.0), select(outer.y, inner.x, inner.x >= 0.0));
168673
+ }
168674
+ fn cloudRaySphere(startWS: vec3f, direction: vec3f, radius: f32) -> vec2f {
168675
+ let startPS: vec3f = startWS + vec3f(0.0, uniforms.cloudPlanet.w, 0.0);
168676
+ let a: f32 = dot(direction, direction);
168677
+ let b: f32 = 2.0 * dot(direction, startPS);
168678
+ let c: f32 = dot(startPS, startPS) - radius * radius;
168679
+ let d: f32 = b * b - 4.0 * a * c;
168680
+ var result: vec2f = vec2f(0.0);
168681
+ if (d >= 0.0) {
168682
+ let q: f32 = -0.5 * (b + select(-1.0, 1.0, b >= 0.0) * sqrt(d));
168683
+ result = vec2f(c / q, q / a);
168684
+ if (result.x < 0.0) { result.x = result.y; }
168685
+ }
168686
+ return result;
168687
+ }
168688
+ fn cloudRange(originWS: vec3f, direction: vec3f) -> CloudRange {
168689
+ #ifdef CLOUD_LOCAL
168690
+ return cloudIntersectVolume(originWS - uniforms.cloudPlanet.xyz, direction);
168691
+ #else
168692
+ let inner: vec2f = cloudRaySphere(originWS, direction, uniforms.cloudLayer.x);
168693
+ let outer: vec2f = cloudRaySphere(originWS, direction, uniforms.cloudLayer.y);
168694
+ return CloudRange(true, inner.x, outer.x);
168695
+ #endif
168696
+ }
168697
+ `;
168698
+ }
168699
+ static WgslDensity() {
168700
+ return `struct CloudProperties {
168701
+ density: f32,
168702
+ ambientOcclusion: f32,
168703
+ height: f32,
168704
+ sigmaT: f32,
168705
+ };
168706
+ fn cloudRemap(x: f32, a: f32, b: f32, c: f32, d: f32) -> f32 {
168707
+ return (((x - a) / (b - a)) * (d - c)) + c;
168708
+ }
168709
+ fn cloudNormalizedHeight(positionPS: vec3f) -> f32 {
168710
+ return clamp((length(positionPS) - uniforms.cloudLayer.x) / (uniforms.cloudLayer.y - uniforms.cloudLayer.x), 0.0, 1.0);
168711
+ }
168712
+ fn cloudAnimateShape(positionPS: vec3f) -> vec3f {
168713
+ var position: vec3f = positionPS;
168714
+ position.y = position.y + position.x / 3.0 + position.z / 7.0;
168715
+ return position + vec3f(uniforms.cloudWind.z, 0.0, uniforms.cloudWind.w) * uniforms.cloudWindSpeed.x + vec3f(0.0, uniforms.cloudWindSpeed.z, 0.0);
168716
+ }
168717
+ fn cloudAnimateErosion(positionPS: vec3f) -> vec3f {
168718
+ return positionPS + vec3f(uniforms.cloudWind.z, 0.0, uniforms.cloudWind.w) * uniforms.cloudWindSpeed.y + vec3f(0.0, uniforms.cloudWindSpeed.w, 0.0);
168719
+ }
168720
+ fn cloudCoverageHash(cell: vec2f) -> f32 {
168721
+ var p: vec3f = fract(vec3f(cell.x, cell.y, cell.x) * 0.1031);
168722
+ p = p + vec3f(dot(p, p.yzx + vec3f(33.33)));
168723
+ return fract((p.x + p.y) * p.z);
168724
+ }
168725
+ fn cloudCoverageNoise(position: vec2f) -> f32 {
168726
+ let cell: vec2f = floor(position);
168727
+ var blend: vec2f = fract(position);
168728
+ blend = blend * blend * (vec2f(3.0) - 2.0 * blend);
168729
+ let bottom: f32 = mix(cloudCoverageHash(cell), cloudCoverageHash(cell + vec2f(1.0, 0.0)), blend.x);
168730
+ let top: f32 = mix(cloudCoverageHash(cell + vec2f(0.0, 1.0)), cloudCoverageHash(cell + vec2f(1.0, 1.0)), blend.x);
168731
+ return mix(bottom, top, blend.y);
168732
+ }
168733
+ fn cloudCoverageAt(positionPS: vec3f) -> vec2f {
168734
+ var coverage: f32 = 0.9;
168735
+ var shapeFill: f32 = 0.0;
168736
+ if (uniforms.cloudCoverage.x != 0.0) {
168737
+ let coordinates: vec2f = (positionPS.xz + uniforms.cloudWind.zw * uniforms.cloudWindSpeed.x + uniforms.cloudCoverageOffset.xy) * uniforms.cloudCoverage.y;
168738
+ let coverageNoise: f32 = cloudCoverageNoise(coordinates) * 0.7 + cloudCoverageNoise(coordinates * 2.03 + vec2f(17.13, 43.71)) * 0.3;
168739
+ let threshold: f32 = mix(-0.15, 1.15, uniforms.cloudCoverage.z);
168740
+ let mask: f32 = smoothstep(threshold - 0.12, threshold + 0.12, coverageNoise);
168741
+ if (uniforms.cloudCoverage.x > 0.0) {
168742
+ coverage = coverage * mix(1.0, mask, uniforms.cloudCoverage.x);
168743
+ } else {
168744
+ shapeFill = clamp(-uniforms.cloudCoverage.x, 0.0, 1.0) * (1.0 - mask);
168745
+ coverage = mix(coverage, 1.0, shapeFill);
168746
+ }
168747
+ }
168748
+ return vec2f(coverage, shapeFill);
168749
+ }
168750
+ fn cloudPropertiesAt(samplePositionPS: vec3f, noiseMip: f32, erosionMip: f32, cheapVersion: bool, lightSampling: bool) -> CloudProperties {
168751
+ var properties: CloudProperties = CloudProperties(0.0, 0.0, 0.0, 0.0);
168752
+ var positionPS: vec3f = samplePositionPS;
168753
+ #ifndef CLOUD_LOCAL
168754
+ if (positionPS.y < uniforms.cloudPlanet.w) { return properties; }
168755
+ #endif
168756
+ properties.ambientOcclusion = 1.0;
168757
+ properties.height = cloudNormalizedHeight(positionPS);
168758
+ #ifndef CLOUD_LOCAL
168759
+ positionPS = vec3f(positionPS.x + uniforms.cloudEye.x, positionPS.y, positionPS.z + uniforms.cloudEye.z);
168760
+ #endif
168761
+ var baseCoordinates: vec3f = (cloudAnimateShape(positionPS).xzy / NOISE_NORMALIZATION) * uniforms.cloudShape.x - uniforms.cloudShapeOffset.xyz;
168762
+ baseCoordinates = baseCoordinates + properties.height * vec3f(uniforms.cloudWind.x, uniforms.cloudWind.y, 0.0) * uniforms.cloudShapeOffset.w;
168763
+ var lowFrequencyNoise: f32 = textureSampleLevel(worleyNoise, worleyNoiseSampler, baseCoordinates, noiseMip).r;
168764
+ let coverage: vec2f = cloudCoverageAt(positionPS);
168765
+ if (coverage.x <= CLOUD_DENSITY_THRESHOLD) { return properties; }
168766
+ let curves: vec3f = textureSampleLevel(cloudCurves, cloudCurvesSampler, vec2f(properties.height, 0.5), 0.0).xyz;
168767
+ let shapeFactor: f32 = mix(0.1, 1.0, uniforms.cloudShape.y * (1.0 - coverage.y)) * curves.y;
168768
+ let erosionFactor: f32 = uniforms.cloudShape.w * curves.y;
168769
+ let microDetailFactor: f32 = uniforms.cloudDetail.z * curves.y;
168770
+ lowFrequencyNoise = mix(1.0, lowFrequencyNoise, shapeFactor);
168771
+ var baseCloud: f32 = 1.0 - curves.x * coverage.x * (1.0 - shapeFactor);
168772
+ baseCloud = clamp(cloudRemap(lowFrequencyNoise, baseCloud, 1.0, 0.0, 1.0), 0.0, 1.0) * coverage.x * coverage.x;
168773
+ properties.sigmaT = 0.04;
168774
+ let occlusionBlend: f32 = clamp(1.0 - max(erosionFactor, shapeFactor) * 0.5, 0.0, 1.0);
168775
+ properties.ambientOcclusion = mix(1.0, curves.z, occlusionBlend);
168776
+ if (!cheapVersion) {
168777
+ let erosionCoordinates: vec3f = cloudAnimateErosion(positionPS) / NOISE_NORMALIZATION * uniforms.cloudShape.z;
168778
+ var erosionValue: f32 = 1.0 - textureSampleLevel(erosionNoise, erosionNoiseSampler, erosionCoordinates, erosionMip).r;
168779
+ erosionValue = mix(0.0, erosionValue, erosionFactor * 0.75 * coverage.x);
168780
+ properties.ambientOcclusion = clamp(properties.ambientOcclusion - sqrt(erosionValue * uniforms.cloudDetail.x), 0.0, 1.0);
168781
+ baseCloud = cloudRemap(baseCloud, erosionValue, 1.0, 0.0, 1.0);
168782
+ #ifdef CLOUD_MICRO_EROSION
168783
+ let fineCoordinates: vec3f = cloudAnimateErosion(positionPS) / NOISE_NORMALIZATION * uniforms.cloudDetail.y;
168784
+ var fineValue: f32 = 1.0 - textureSampleLevel(erosionNoise, erosionNoiseSampler, fineCoordinates, erosionMip).r;
168785
+ fineValue = mix(0.0, fineValue, microDetailFactor * 0.5 * coverage.x);
168786
+ baseCloud = cloudRemap(baseCloud, fineValue, 1.0, 0.0, 1.0);
168787
+ #endif
168788
+ }
168789
+ if (lightSampling) {
168790
+ baseCloud = baseCloud - erosionFactor * 0.1;
168791
+ #ifdef CLOUD_MICRO_EROSION
168792
+ baseCloud = baseCloud - microDetailFactor * 0.15;
168793
+ #endif
168794
+ }
168795
+ properties.density = max(0.0, baseCloud) * uniforms.cloudDetail.w;
168796
+ return properties;
168797
+ }
168798
+ `;
168799
+ }
168800
+ static WgslLighting() {
168801
+ return `fn cloudPhase(anisotropy: f32, cosTheta: f32) -> f32 {
168802
+ let denominator: f32 = 1.0 + anisotropy * anisotropy - 2.0 * anisotropy * cosTheta;
168803
+ let falloff: f32 = inverseSqrt(max(denominator, 1e-6));
168804
+ return (1.0 / (4.0 * CLOUD_PI)) * (1.0 - anisotropy * anisotropy) * falloff * falloff * falloff;
168805
+ }
168806
+ fn cloudPowder(density: f32, cosAngle: f32, intensity: f32) -> f32 {
168807
+ let powder: f32 = clamp((1.0 - exp(-density * 4.0)) * 2.0, 0.0, 1.0);
168808
+ return mix(1.0, mix(1.0, powder, smoothstep(0.5, -0.5, cosAngle)), intensity);
168809
+ }
168810
+ fn cloudSunTransmittance(positionPS: vec3f, sunDirection: vec3f, phase: vec2f) -> vec3f {
168811
+ let radialDistance: f32 = length(positionPS);
168812
+ let cosChi: f32 = dot(positionPS, sunDirection) / radialDistance;
168813
+ var transmittance: vec3f = vec3f(0.0);
168814
+ if (cosChi < cloudHorizonCosine(radialDistance)) { return transmittance; }
168815
+ let lightSteps: f32 = uniforms.cloudSteps.y;
168816
+ let totalLightDistance: f32 = clamp(cloudIntersectSphere(uniforms.cloudLayer.y, cosChi, radialDistance).y, 0.0, lightSteps * LIGHT_STEP_MAXIMAL_SIZE) + 5.0;
168817
+ let intervalSize: f32 = totalLightDistance / lightSteps;
168818
+ var opticalDepth: f32 = 0.0;
168819
+ for (var j: i32 = 0; j < MAX_LIGHT_STEPS; j++) {
168820
+ if (f32(j) >= lightSteps) { break; }
168821
+ let distanceAlongLight: f32 = intervalSize * (0.25 + f32(j));
168822
+ let lightProperties: CloudProperties = cloudPropertiesAt(positionPS + sunDirection * distanceAlongLight, 3.0 * f32(j) / lightSteps, 0.0, true, true);
168823
+ opticalDepth = opticalDepth + lightProperties.density * lightProperties.sigmaT;
168824
+ }
168825
+ let extinction: vec3f = intervalSize * opticalDepth * uniforms.cloudTint.rgb;
168826
+ transmittance = transmittance + exp(-extinction) * phase.x;
168827
+ transmittance = transmittance + exp(-extinction * uniforms.cloudScattering.x) * (phase.y * uniforms.cloudScattering.x);
168828
+ return transmittance;
168829
+ }
168830
+ `;
168831
+ }
168832
+ static WgslAerial() {
168833
+ const skyShaders = HdrpPbSkyShaders;
168834
+ const fogPass = HdrpFogPass;
168835
+ if (skyShaders == null || fogPass == null)
168836
+ return "";
168837
+ return "#ifdef CLOUD_AERIAL\nvar apLutSampler: sampler;\nvar apLut: texture_2d_array<f32>;\nuniform cloudApCamera: vec4f;\n"
168838
+ + skyShaders.WgslUniforms() + skyShaders.WgslCommon() + fogPass.AerialPerspectiveWgsl() + "#endif\n";
168839
+ }
168840
+ static WgslMarch() {
168841
+ const shaders = VolumetricCloudsShaders;
168842
+ return "varying vUV: vec2f;\nvar worleyNoiseSampler: sampler;\nvar worleyNoise: texture_3d<f32>;\nvar erosionNoiseSampler: sampler;\nvar erosionNoise: texture_3d<f32>;\nvar cloudCurvesSampler: sampler;\nvar cloudCurves: texture_2d<f32>;\nvar sceneDepthSampler: sampler;\nvar sceneDepth: texture_2d<f32>;\n"
168843
+ + shaders.WgslMarchUniforms() + shaders.WgslConstants() + shaders.WgslGeometry() + shaders.WgslDensity() + shaders.WgslLighting() + shaders.WgslAerial() + `
168844
+ fn cloudJenkins(value: u32) -> u32 {
168845
+ var x: u32 = value;
168846
+ x = x + (x << 10u);
168847
+ x = x ^ (x >> 6u);
168848
+ x = x + (x << 3u);
168849
+ x = x ^ (x >> 11u);
168850
+ x = x + (x << 15u);
168851
+ return x;
168852
+ }
168853
+ fn cloudRandom(pixel: vec2f, seed: f32) -> f32 {
168854
+ let v: vec3u = vec3u(u32(pixel.x), u32(pixel.y), u32(seed));
168855
+ let hash: u32 = cloudJenkins(v.x ^ cloudJenkins(v.y ^ cloudJenkins(v.z)));
168856
+ return bitcast<f32>((hash & 0x007FFFFFu) | 0x3F800000u) - 1.0;
168857
+ }
168858
+ @fragment
168859
+ fn main(input: FragmentInputs) -> FragmentOutputs {
168860
+ fragmentOutputs.color = vec4f(0.0, 0.0, 0.0, 1.0);
168861
+ let uv: vec2f = fragmentInputs.vUV;
168862
+ let farPoint: vec4f = uniforms.cloudInvViewProj * vec4f(uv * 2.0 - vec2f(1.0), 1.0, 1.0);
168863
+ let direction: vec3f = normalize(farPoint.xyz / farPoint.w - uniforms.cloudEye.xyz);
168864
+ var maxRayLength: f32 = MAX_SKYBOX_DISTANCE;
168865
+ let viewDepth: f32 = textureSampleLevel(sceneDepth, sceneDepthSampler, uv, 0.0).r;
168866
+ if (uniforms.cloudCamera.y > 0.5 && viewDepth < uniforms.cloudCamera.x * 0.999) {
168867
+ #ifdef CLOUD_LOCAL
168868
+ maxRayLength = viewDepth / max(dot(direction, uniforms.cloudForward.xyz), 0.0001);
168869
+ #else
168870
+ return fragmentOutputs;
168871
+ #endif
168872
+ }
168873
+ #ifdef CLOUD_LOCAL
168874
+ let originWS: vec3f = uniforms.cloudEye.xyz;
168875
+ #else
168876
+ let originWS: vec3f = vec3f(0.0);
168877
+ #endif
168878
+ let marchRange: CloudRange = cloudRange(originWS, direction);
168879
+ if (!marchRange.hit || maxRayLength < marchRange.entryDistance) { return fragmentOutputs; }
168880
+ let rangeStart: f32 = marchRange.entryDistance;
168881
+ let integrationNoise: f32 = cloudRandom(fragmentInputs.position.xy, uniforms.cloudCamera.z);
168882
+ let primarySteps: f32 = uniforms.cloudLayer.w;
168883
+ var totalDistance: f32 = min(marchRange.exitDistance, maxRayLength) - rangeStart;
168884
+ var stepSize: f32 = min(totalDistance / primarySteps, uniforms.cloudLayer.z);
168885
+ totalDistance = stepSize * primarySteps;
168886
+ let layerThickness: f32 = max(1.0, uniforms.cloudLayer.y - uniforms.cloudLayer.x);
168887
+ let sampleMultiplier: f32 = min(max(1.0, totalDistance / layerThickness), max(1.0, uniforms.cloudSteps.x));
168888
+ let primaryStepCount: i32 = min(i32(ceil(primarySteps * sampleMultiplier)), MAX_PRIMARY_STEPS);
168889
+ stepSize = totalDistance / f32(max(1, primaryStepCount));
168890
+ let sunDirection: vec3f = uniforms.cloudSunDirection.xyz;
168891
+ let cosAngle: f32 = dot(direction, sunDirection);
168892
+ let phase: vec2f = vec2f(
168893
+ cloudPhase(FORWARD_ECCENTRICITY, cosAngle) + cloudPhase(-BACKWARD_ECCENTRICITY, cosAngle),
168894
+ cloudPhase(FORWARD_ECCENTRICITY * uniforms.cloudScattering.x, cosAngle) + cloudPhase(-BACKWARD_ECCENTRICITY * uniforms.cloudScattering.x, cosAngle));
168895
+ var scattering: vec3f = vec3f(0.0);
168896
+ var ambient: f32 = 0.0;
168897
+ var transmittance: f32 = 1.0;
168898
+ var meanDistance: f32 = 0.0;
168899
+ var meanDistanceDivider: f32 = 0.0;
168900
+ var currentDistance: f32 = integrationNoise;
168901
+ var currentPositionWS: vec3f = originWS + (rangeStart + currentDistance) * direction;
168902
+ var activeSampling: bool = true;
168903
+ var sequentialEmptySamples: i32 = 0;
168904
+ var currentIndex: i32 = 0;
168905
+ for (var iteration: i32 = 0; iteration < MAX_PRIMARY_STEPS * 2; iteration++) {
168906
+ if (currentIndex >= primaryStepCount || currentDistance >= totalDistance) { break; }
168907
+ let cameraDistance: f32 = rangeStart + currentDistance;
168908
+ let densityFade: f32 = clamp((cameraDistance - uniforms.cloudSteps.z) / (uniforms.cloudSteps.z + uniforms.cloudSteps.w), 0.0, 1.0);
168909
+ let erosionMip: f32 = mix(0.0, 4.0, clamp((cameraDistance - MIN_EROSION_DISTANCE) / (MAX_EROSION_DISTANCE - MIN_EROSION_DISTANCE), 0.0, 1.0));
168910
+ let currentPositionPS: vec3f = currentPositionWS - uniforms.cloudPlanet.xyz;
168911
+ if (activeSampling) {
168912
+ var properties: CloudProperties = cloudPropertiesAt(currentPositionPS, 0.0, erosionMip, false, false);
168913
+ properties.density = properties.density * densityFade;
168914
+ if (properties.density > CLOUD_DENSITY_THRESHOLD) {
168915
+ let transmittanceDensity: f32 = transmittance * properties.density;
168916
+ meanDistance = meanDistance + cameraDistance * transmittanceDensity;
168917
+ meanDistanceDivider = meanDistanceDivider + transmittanceDensity;
168918
+ let stepTransmittance: f32 = exp(-properties.density * properties.sigmaT * stepSize);
168919
+ let powder: f32 = cloudPowder(properties.density, cosAngle, uniforms.cloudScattering.y);
168920
+ let sunLuminance: vec3f = cloudSunTransmittance(currentPositionPS, sunDirection, phase) * powder;
168921
+ let absorbed: f32 = transmittance - transmittance * stepTransmittance;
168922
+ scattering = scattering + sunLuminance * absorbed;
168923
+ ambient = ambient + properties.ambientOcclusion * absorbed;
168924
+ transmittance = transmittance * stepTransmittance;
168925
+ if (transmittance < 0.003) {
168926
+ transmittance = 0.0;
168927
+ break;
168928
+ }
168929
+ sequentialEmptySamples = 0;
168930
+ } else {
168931
+ sequentialEmptySamples = sequentialEmptySamples + 1;
168932
+ }
168933
+ if (sequentialEmptySamples == EMPTY_STEPS_BEFORE_LARGE_STEPS) { activeSampling = false; }
168934
+ let relativeStepSize: f32 = mix(integrationNoise, 1.0, clamp(f32(currentIndex), 0.0, 1.0));
168935
+ currentPositionWS = currentPositionWS + direction * stepSize * relativeStepSize;
168936
+ currentDistance = currentDistance + stepSize * relativeStepSize;
168937
+ } else {
168938
+ var properties: CloudProperties = cloudPropertiesAt(currentPositionPS, 1.0, 0.0, true, false);
168939
+ properties.density = properties.density * densityFade;
168940
+ if (properties.density < CLOUD_DENSITY_THRESHOLD) {
168941
+ currentPositionWS = currentPositionWS + direction * stepSize * 2.0;
168942
+ currentDistance = currentDistance + stepSize * 2.0;
168943
+ } else {
168944
+ currentPositionWS = currentPositionWS - direction * stepSize;
168945
+ currentDistance = currentDistance - stepSize;
168946
+ currentIndex = currentIndex - 1;
168947
+ activeSampling = true;
168948
+ sequentialEmptySamples = 0;
168949
+ }
168950
+ }
168951
+ currentIndex = currentIndex + 1;
168952
+ }
168953
+ if (meanDistance == 0.0) { return fragmentOutputs; }
168954
+ meanDistance = min(meanDistance / meanDistanceDivider, maxRayLength);
168955
+ let relativeHeight: f32 = cloudNormalizedHeight(originWS - uniforms.cloudPlanet.xyz + meanDistance * direction);
168956
+ let ambientColor: vec3f = max(vec3f(0.0), mix(uniforms.cloudAmbientBottom.rgb, uniforms.cloudAmbientTop.rgb, relativeHeight));
168957
+ var color: vec3f = (uniforms.cloudSunColor.rgb * scattering + ambientColor * ambient) * uniforms.cloudCamera.w;
168958
+ #ifdef CLOUD_AERIAL
168959
+ if (uniforms.cloudApCamera.w > 0.5) {
168960
+ let apDirection: vec3f = vec3f(direction.x * uniforms.cloudForward.w, direction.y, direction.z);
168961
+ let aerial: FogAerial = fogAerialPerspective(uv, uniforms.cloudApCamera.xyz, apDirection, meanDistance);
168962
+ color = aerial.color * (1.0 - transmittance) + (vec3f(1.0) - aerial.opacity) * color;
168963
+ }
168964
+ #endif
168965
+ fragmentOutputs.color = vec4f(color, transmittance);
168966
+ }
168967
+ `;
168968
+ }
168969
+ static WgslCompositeVertex() {
168970
+ return `attribute position: vec3f;
168971
+ varying vUV: vec2f;
168972
+ @vertex
168973
+ fn main(input: VertexInputs) -> FragmentInputs {
168974
+ vertexOutputs.vUV = vertexInputs.position.xy * 0.5 + vec2f(0.5);
168975
+ vertexOutputs.position = vec4f(vertexInputs.position.xy, 0.0, 1.0);
168976
+ }
168977
+ `;
168978
+ }
168979
+ static WgslCompositeFragment() {
168980
+ return `varying vUV: vec2f;
168981
+ var cloudLightingSampler: sampler;
168982
+ var cloudLighting: texture_2d<f32>;
168983
+ uniform cloudDisplayEncoded: f32;
168984
+ const CLOUD_DISPLAY_GAMMA: f32 = 2.2;
168985
+ const CLOUD_LUMINANCE: vec3f = vec3f(0.2126, 0.7152, 0.0722);
168986
+ @fragment
168987
+ fn main(input: FragmentInputs) -> FragmentOutputs {
168988
+ let cloud: vec4f = textureSample(cloudLighting, cloudLightingSampler, fragmentInputs.vUV);
168989
+ var scattering: vec3f = cloud.rgb;
168990
+ var throughput: f32 = cloud.a;
168991
+ if (uniforms.cloudDisplayEncoded > 0.5) {
168992
+ let encodeExponent: f32 = 1.0 / CLOUD_DISPLAY_GAMMA;
168993
+ let scatteringLuminance: f32 = clamp(dot(scattering, CLOUD_LUMINANCE), 0.0, 1.0);
168994
+ throughput = pow(min(cloud.a + scatteringLuminance, 1.0), encodeExponent) - pow(scatteringLuminance, encodeExponent);
168995
+ scattering = pow(clamp(scattering, vec3f(0.0), vec3f(1.0)), vec3f(encodeExponent));
168996
+ }
168997
+ fragmentOutputs.color = vec4f(scattering, 1.0 - throughput);
168998
+ }
168999
+ `;
169000
+ }
169001
+ static WgslTemporal() {
169002
+ const shaders = VolumetricCloudsShaders;
169003
+ return "varying vUV: vec2f;\nvar cloudCurrentSampler: sampler;\nvar cloudCurrent: texture_2d<f32>;\nvar cloudHistorySampler: sampler;\nvar cloudHistory: texture_2d<f32>;\nvar sceneDepthSampler: sampler;\nvar sceneDepth: texture_2d<f32>;\n"
169004
+ + shaders.TemporalUniforms.map((name) => (name.indexOf("ViewProj") >= 0) ? "uniform " + name + ": mat4x4f;" : "uniform " + name + ": vec4f;").join("\n") + "\n"
169005
+ + shaders.WgslGeometry() + `
169006
+ fn cloudReprojectionDistance(direction: vec3f, viewDepth: f32) -> f32 {
169007
+ #ifdef CLOUD_LOCAL
169008
+ let layerRange: CloudRange = cloudIntersectVolume(uniforms.cloudEye.xyz - uniforms.cloudPlanet.xyz, direction);
169009
+ var distanceToCloud: f32 = select(layerRange.exitDistance, layerRange.entryDistance, layerRange.hit);
169010
+ #else
169011
+ let layerRange: CloudRange = cloudRange(vec3f(0.0), direction);
169012
+ var distanceToCloud: f32 = layerRange.entryDistance;
169013
+ #endif
169014
+ distanceToCloud = max(distanceToCloud, 1.0);
169015
+ if (uniforms.cloudCamera.y > 0.5 && viewDepth < uniforms.cloudCamera.x * 0.999) {
169016
+ distanceToCloud = min(distanceToCloud, viewDepth / max(dot(direction, uniforms.cloudForward.xyz), 0.0001));
169017
+ }
169018
+ return distanceToCloud;
169019
+ }
169020
+ @fragment
169021
+ fn main(input: FragmentInputs) -> FragmentOutputs {
169022
+ let uv: vec2f = fragmentInputs.vUV;
169023
+ let current: vec4f = textureSampleLevel(cloudCurrent, cloudCurrentSampler, uv, 0.0);
169024
+ let viewDepth: f32 = textureSampleLevel(sceneDepth, sceneDepthSampler, uv, 0.0).r;
169025
+ var north: vec3f = textureSampleLevel(cloudCurrent, cloudCurrentSampler, uv + vec2f(0.0, -uniforms.cloudTexel.y), 0.0).rgb;
169026
+ var west: vec3f = textureSampleLevel(cloudCurrent, cloudCurrentSampler, uv + vec2f(-uniforms.cloudTexel.x, 0.0), 0.0).rgb;
169027
+ var east: vec3f = textureSampleLevel(cloudCurrent, cloudCurrentSampler, uv + vec2f(uniforms.cloudTexel.x, 0.0), 0.0).rgb;
169028
+ var south: vec3f = textureSampleLevel(cloudCurrent, cloudCurrentSampler, uv + vec2f(0.0, uniforms.cloudTexel.y), 0.0).rgb;
169029
+ fragmentOutputs.color = current;
169030
+ if (uniforms.cloudCamera.z < 0.5 || current.a >= 1.0) { return fragmentOutputs; }
169031
+ let boxMin: vec3f = min(min(min(current.rgb, north), min(west, east)), south);
169032
+ let boxMax: vec3f = max(max(max(current.rgb, north), max(west, east)), south);
169033
+ let farPoint: vec4f = uniforms.cloudInvViewProj * vec4f(uv * 2.0 - vec2f(1.0), 1.0, 1.0);
169034
+ let direction: vec3f = normalize(farPoint.xyz / farPoint.w - uniforms.cloudEye.xyz);
169035
+ let worldPosition: vec3f = uniforms.cloudEye.xyz + direction * cloudReprojectionDistance(direction, viewDepth);
169036
+ let previousClip: vec4f = uniforms.cloudPrevViewProj * vec4f(worldPosition, 1.0);
169037
+ let previousUV: vec2f = previousClip.xy / previousClip.w * 0.5 + vec2f(0.5);
169038
+ let velocity: vec2f = previousUV - uv;
169039
+ let history: vec3f = textureSampleLevel(cloudHistory, cloudHistorySampler, clamp(previousUV, vec2f(0.0), vec2f(1.0)), 0.0).rgb;
169040
+ if (previousUV.x > 1.0 || previousUV.x < 0.0 || previousUV.y > 1.0 || previousUV.y < 0.0) { return fragmentOutputs; }
169041
+ let previous: vec3f = clamp(history, boxMin, boxMax);
169042
+ let accumulation: f32 = uniforms.cloudCamera.w;
169043
+ let intensity: f32 = clamp(min(accumulation - abs(velocity.x) * accumulation, accumulation - abs(velocity.y) * accumulation), 0.0, 1.0);
169044
+ fragmentOutputs.color = vec4f(mix(current.rgb, previous, intensity), current.a);
169045
+ }
169046
+ `;
169047
+ }
169048
+ }
169049
+ VolumetricCloudsShaders.MarchName = "tkCloudMarch";
169050
+ VolumetricCloudsShaders.CompositeName = "tkCloudComposite";
169051
+ VolumetricCloudsShaders.TemporalName = "tkCloudTemporal";
169052
+ VolumetricCloudsShaders.TemporalUniforms = ["cloudInvViewProj", "cloudPrevViewProj", "cloudEye", "cloudForward", "cloudCamera", "cloudPlanet", "cloudLayer", "cloudTexel"];
169053
+ VolumetricCloudsShaders.TemporalSamplers = ["cloudCurrent", "cloudHistory", "sceneDepth"];
169054
+ VolumetricCloudsShaders.MarchUniforms = [
169055
+ "cloudInvViewProj", "cloudEye", "cloudForward", "cloudCamera", "cloudPlanet", "cloudLayer", "cloudSteps", "cloudShape",
169056
+ "cloudDetail", "cloudShapeOffset", "cloudWind", "cloudWindSpeed", "cloudCoverage", "cloudCoverageOffset", "cloudScattering",
169057
+ "cloudTint", "cloudSunDirection", "cloudSunColor", "cloudAmbientTop", "cloudAmbientBottom"
169058
+ ];
169059
+ VolumetricCloudsShaders.MarchSamplers = ["worleyNoise", "erosionNoise", "cloudCurves", "sceneDepth"];
169060
+ VolumetricCloudsShaders.AerialSampler = "apLut";
169061
+ VolumetricCloudsShaders.AerialCameraUniform = "cloudApCamera";
169062
+ VolumetricCloudsShaders.registered = false;
166747
169063
  export class WebVideoPlayer extends ScriptComponent {
166748
169064
  getVideoMaterial() { return this.m_videoMaterial; }
166749
169065
  getVideoTexture() { return this.m_videoTexture; }
@@ -167098,6 +169414,7 @@ SceneManager.RegisterClass("TOOLKIT.HdrpWaterDecalSurface", HdrpWaterDecalSurfac
167098
169414
  SceneManager.RegisterClass("TOOLKIT.LensFlareComponentSRP", LensFlareComponentSRP);
167099
169415
  SceneManager.RegisterClass("TOOLKIT.LegacyLensFlare", LegacyLensFlare);
167100
169416
  SceneManager.RegisterClass("TOOLKIT.LightProbeNetwork", LightProbeNetwork);
169417
+ SceneManager.RegisterClass("TOOLKIT.MeshWaterSurface", MeshWaterSurface);
167101
169418
  SceneManager.RegisterClass("TOOLKIT.NavigationAgent", NavigationAgent);
167102
169419
  Object.defineProperty(PlayerRef, "None", { value: new PlayerRef(0), writable: false });
167103
169420
  Object.defineProperty(PlayerRef, "MasterClient", { value: new PlayerRef(-1), writable: false });
@@ -167129,11 +169446,14 @@ SceneManager.RegisterClass("TOOLKIT.ShurikenParticles", ShurikenParticles);
167129
169446
  SceneManager.RegisterClass("TOOLKIT.SkinTextureArray", SkinTextureArray);
167130
169447
  SceneManager.RegisterClass(SplineAnimate.ClassName, SplineAnimate);
167131
169448
  SceneManager.RegisterClass(SplineContainer.ClassName, SplineContainer);
169449
+ const TERRAIN_EDGE_TOLERANCE_METERS = 0.01;
167132
169450
  SceneManager.RegisterClass("TOOLKIT.TerrainBuilder", TerrainBuilder);
167133
169451
  SceneManager.RegisterClass("TOOLKIT.TreeBranchMaterial", TreeBranchMaterial);
167134
169452
  SceneManager.RegisterClass("TOOLKIT.GrassStandardMaterial", GrassStandardMaterial);
167135
169453
  SceneManager.RegisterClass("TOOLKIT.GrassBillboardMaterial", GrassBillboardMaterial);
167136
169454
  SceneManager.RegisterClass("TOOLKIT.TerrainGrassPlugin", TerrainGrassPlugin);
169455
+ const TREE_INITIAL_CAPACITY = 64;
169456
+ const TREE_GROWTH_FACTOR = 1.5;
167137
169457
  SceneManager.RegisterClass("TOOLKIT.TextureAtlasSkin", TextureAtlasSkin);
167138
169458
  SceneManager.RegisterClass(ThirdPersonController.ClassName, ThirdPersonController);
167139
169459
  SceneManager.RegisterClass("BABYLON.GUI.UnitySlider", UnitySlider);
@@ -167143,4 +169463,36 @@ SceneManager.RegisterClass("TOOLKIT.UserInterface", UserInterface);
167143
169463
  SceneManager.RegisterClass("TOOLKIT.VisualEffect", VisualEffect);
167144
169464
  const VFX_EPSILON = 1e-5;
167145
169465
  const VFX_FLT_MIN = 1.175494351e-38;
169466
+ const CLOUD_ALPHA_INDEX = -1000000;
169467
+ const CLOUD_TIER_WINDOW_FRAMES = 120;
169468
+ const CLOUD_TIER_BUDGET_MS = 33;
169469
+ const CLOUD_TIER_MINIMUM_SAVING = 0.1;
169470
+ const CLOUD_MAX_STEP_STRETCH = 4;
169471
+ const CLOUD_CAMERA_CUT_METERS = 50;
169472
+ const CLOUD_CURVE_SAMPLES = 64;
169473
+ const CLOUD_EARTH_RADIUS_METERS = 6378100;
169474
+ const CLOUD_MINIMUM_CURVATURE_SCALE = 0.025;
169475
+ const CLOUD_WIND_KMH_TO_MS = -0.277778;
169476
+ const CLOUD_WIND_WRAP_METERS = 100000;
169477
+ const CLOUD_MAX_STEP_DIVISOR = 8;
169478
+ const CLOUD_PHONE_WIDTH_CSS_PX = 800;
169479
+ const CLOUD_WEAK_GPU_TEXTURE_SIZE = 8192;
169480
+ const CLOUD_READY_POLL_MS = 16;
169481
+ const CLOUD_MINIMUM_ACCUMULATION = 0.01;
169482
+ const CLOUD_AUTO_FADE_START_FRACTION = 0.2;
169483
+ const CLOUD_AUTO_FADE_DISTANCE_FRACTION = 0.3;
169484
+ const CLOUD_FADE_MODE_MANUAL = 1;
169485
+ const CLOUD_DENSITY_SCALE = 2;
169486
+ const CLOUD_ALTITUDE_DISTORTION_SCALE = 0.25;
169487
+ const CLOUD_MULTI_SCATTERING_SCALE = 0.95;
169488
+ const CLOUD_SCATTERING_TINT_SCALE = 0.75;
169489
+ const CLOUD_MINIMUM_COVERAGE_PATTERN_METERS = 100;
169490
+ const CLOUD_DEFAULT_COVERAGE_PATTERN_METERS = 10000;
169491
+ const CLOUD_DEFAULT_COVERAGE_GAP = 0.45;
169492
+ const CLOUD_DEFAULT_SETTINGS = { resolutionScale: 0.5, primarySteps: 32, longRayMultiplier: 1, lightSteps: 2 };
169493
+ const CLOUD_TIER_CAPS = [
169494
+ { resolutionScale: Infinity, primarySteps: 64, longRayMultiplier: 2, lightSteps: Infinity },
169495
+ { resolutionScale: 0.35, primarySteps: 64, longRayMultiplier: 1, lightSteps: 2 },
169496
+ { resolutionScale: 0.25, primarySteps: 64, longRayMultiplier: 1, lightSteps: 1 }
169497
+ ];
167146
169498
  SceneManager.RegisterClass("TOOLKIT.WebVideoPlayer", WebVideoPlayer);