CARLA
Loading...
Searching...
No Matches
Weather.cpp
Go to the documentation of this file.
1// Copyright (c) 2026 Computer Vision Center (CVC) at the Universitat Autonoma
2// de Barcelona (UAB).
3//
4// This work is licensed under the terms of the MIT license.
5// For a copy, see <https://opensource.org/licenses/MIT>.
6
8#include "Carla.h"
15#include "Carla/Weather/Sky.h"
16
17#include <util/ue-header-guard-begin.h>
18#include "Camera/PlayerCameraManager.h"
19#include "Particles/ParticleSystem.h"
20#include "Particles/ParticleSystemComponent.h"
21#include "EngineUtils.h"
22#include "Components/ExponentialHeightFogComponent.h"
23#include "Components/ChildActorComponent.h"
24#include "Components/MeshComponent.h"
25#include "Components/DirectionalLightComponent.h"
26#include "Components/SkyAtmosphereComponent.h"
27#include "Components/LightComponent.h"
28#include "Components/PostProcessComponent.h"
29#include "Components/SceneCaptureComponent2D.h"
30#include "Components/SkyLightComponent.h"
31#include "Components/StaticMeshComponent.h"
32#include "Components/VolumetricCloudComponent.h"
33#include "Curves/CurveFloat.h"
34#include "Engine/StaticMesh.h"
35#include "Engine/TextureCube.h"
36#include "Engine/World.h"
37#include "HAL/IConsoleManager.h"
38#include "Kismet/GameplayStatics.h"
39#include "Kismet/KismetMaterialLibrary.h"
40#include "Materials/MaterialInstanceDynamic.h"
41#include "Materials/MaterialParameterCollection.h"
42#include "Rendering/SkyAtmosphereCommonData.h"
43#include "UObject/ConstructorHelpers.h"
44#include "UObject/UnrealType.h"
45#include <util/ue-header-guard-end.h>
46
47// Volumetric clouds are lit only by atmosphere sun lights. Index 1 is the
48// engine's second slot, so the moon does not take the sun's.
49static TAutoConsoleVariable<float> CVarCarlaWeatherSunAltitudeFalloff(
50 TEXT("carla.Weather.SunAltitudeFalloff"),
51 1.0f,
52 TEXT("How much of the physical sin(elevation) falloff to apply to the sun intensity ")
53 TEXT("curve. 1 is fully physical, 0 leaves the rig's curve alone."),
54 ECVF_Default);
55
56// The renderer's N.L already applies sin(elevation) on the ground, so the
57// plain sine counted it twice: 0.3% of midday at +3 degrees.
58static TAutoConsoleVariable<float> CVarCarlaWeatherSunFalloffExponent(
59 TEXT("carla.Weather.SunFalloffExponent"),
60 0.01f,
61 TEXT("Power the sin(elevation) sun falloff is raised to. 1 is the plain sine, lower ")
62 TEXT("keeps a low sun brighter, 0 disables the falloff."),
63 ECVF_Default);
64
65static TAutoConsoleVariable<float> CVarCarlaWeatherSunHorizonFloor(
66 TEXT("carla.Weather.SunHorizonFloor"),
67 0.05f,
68 TEXT("Lower bound on the sin(elevation) falloff factor, as a fraction of the zenith ")
69 TEXT("value. 0.004 is a real horizon sun's ~400 lux against ~100000 overhead."),
70 ECVF_Default);
71
72static TAutoConsoleVariable<float> CVarCarlaWeatherTransmittanceReleaseDeg(
73 TEXT("carla.Weather.TransmittanceReleaseDeg"),
74 -6.0f,
75 TEXT("Sun altitude, in degrees, by which the transmittance clamp has fully released ")
76 TEXT("back to the engine's -90. Between 0 and this it eases off."),
77 ECVF_Default);
78
79// The project ships r.DefaultFeature.Bloom=False and only the GoPro profile
80// sets bloom, so the viewport sun had no glow. Filled in only when the profile
81// does not set it; RGB sensors write their own bloom_intensity.
82static TAutoConsoleVariable<float> CVarCarlaWeatherBloomIntensity(
83 TEXT("carla.Weather.BloomIntensity"),
84 1.0f,
85 TEXT("Bloom intensity filled into the sky rig's post-process when the loaded profile ")
86 TEXT("does not set one, so the sun glows in the viewport. Negative leaves it alone."),
87 ECVF_Default);
88
89// Off by default: the viewport's camera profile (carla.PostProcess.
90// ViewportProfile) owns its exposure. Set it to force a bias on the viewport
91// only.
92static TAutoConsoleVariable<float> CVarCarlaWeatherExposureBias(
93 TEXT("carla.Weather.ExposureBias"),
94 -1.0f,
95 TEXT("Auto exposure compensation, in EV, forced on the sky rig's post process. ")
96 TEXT("Negative leaves the rig's authored value."),
97 ECVF_Default);
98
99static TAutoConsoleVariable<float> CVarCarlaWeatherExposureMaxEV(
100 TEXT("carla.Weather.ExposureMaxEV"),
101 -1.0f,
102 TEXT("Upper EV100 bound of the auto exposure range. Negative leaves the rig's value. ")
103 TEXT("Above 13 nothing measurably changes."),
104 ECVF_Default);
105
106// The rigs saved non-physical GI multipliers (BP_Carla_Sky: sun 0.3 / sky
107// light 5; Town10's instance overrides the sun to 3), which flatten shadows
108// and differ per map. Physical light is 1. Negative leaves the rig's value.
109static TAutoConsoleVariable<float> CVarCarlaWeatherSunIndirectIntensity(
110 TEXT("carla.Weather.SunIndirectIntensity"),
111 1.0f,
112 TEXT("IndirectLightingIntensity forced on the sky rig's sun. Negative leaves the rig's value."),
113 ECVF_Default);
114
115static TAutoConsoleVariable<float> CVarCarlaWeatherSkyLightIndirectIntensity(
116 TEXT("carla.Weather.SkyLightIndirectIntensity"),
117 1.0f,
118 TEXT("IndirectLightingIntensity forced on the sky rig's sky light. Negative leaves the rig's value."),
119 ECVF_Default);
120
121static TAutoConsoleVariable<float> CVarCarlaWeatherTransmittanceMinElevationDeg(
122 TEXT("carla.Weather.TransmittanceMinElevationDeg"),
123 0.0f,
124 TEXT("Minimum sun elevation, in degrees, used to evaluate the atmospheric tint applied ")
125 TEXT("to lit surfaces. The engine default of -90 makes a sunset tint the whole city red. ")
126 TEXT("Values above ~5 light the city as if it were day under a night sky."),
127 ECVF_Default);
128
129static TAutoConsoleVariable<float> CVarCarlaWeatherDayNightBlendDeg(
130 TEXT("carla.Weather.DayNightBlendDeg"),
131 8.0f,
132 TEXT("Half-width, in degrees of sun altitude, of the band the sun/moon handover is ")
133 TEXT("spread over. 0 gives a hard switch at the horizon."),
134 ECVF_Default);
135
136// The anti-solar moon would shine from below the horizon across dusk. Held at
137// least this high, it acts as a fill light from the dark side of the sky.
138static TAutoConsoleVariable<float> CVarCarlaWeatherMoonMinAltitudeDeg(
139 TEXT("carla.Weather.MoonMinAltitudeDeg"),
140 20.0f,
141 TEXT("Lowest altitude, in degrees, the moon is aimed from. Keeps it lighting the city ")
142 TEXT("across dusk instead of from below the horizon. 0 is the plain anti-solar point."),
143 ECVF_Default);
144
145// The moon's own fade-in, in degrees of sun altitude. Separate from
146// DayNightBlendDeg, which also drives NightFactor and the atmosphere flag.
147static TAutoConsoleVariable<float> CVarCarlaWeatherMoonFadeStartDeg(
148 TEXT("carla.Weather.MoonFadeStartDeg"),
149 10.0f,
150 TEXT("Sun altitude, in degrees, at which the moon starts to fade in."),
151 ECVF_Default);
152
153static TAutoConsoleVariable<float> CVarCarlaWeatherMoonFullAltitudeDeg(
154 TEXT("carla.Weather.MoonFullAltitudeDeg"),
155 0.0f,
156 TEXT("Sun altitude, in degrees, at which the moon reaches full MoonIntensity. ")
157 TEXT("Must be below MoonFadeStartDeg; otherwise the moon switches on hard there."),
158 ECVF_Default);
159
160// As an atmosphere light the moon gets SkyAtmosphere's flat white disc, which
161// read as a second sun at dusk. The textured disc below replaces it.
162static TAutoConsoleVariable<bool> CVarCarlaWeatherMoonAtmosphereDisk(
163 TEXT("carla.Weather.MoonAtmosphereDisk"),
164 false,
165 TEXT("Let SkyAtmosphere paint its flat disc for the moon. False hides it; the ")
166 TEXT("textured moon disc (carla.Weather.MoonDisc) replaces it."),
167 ECVF_Default);
168
169// A plane along the moon light's direction, facing the sky sphere's centre and
170// kept inside it. Its materials must NOT be IsSky: the engine would take it for
171// the sky dome and paint its "does not cover the screen" debug pattern.
172static TAutoConsoleVariable<bool> CVarCarlaWeatherMoonDisc(
173 TEXT("carla.Weather.MoonDisc"),
174 true,
175 TEXT("Draw the textured moon disc along the moon light's direction."),
176 ECVF_Default);
177
178// Opaque hides the day sky behind the disc; additive blends with it, but the
179// clouds only cover it through the material's approximate cloud fogging.
180static TAutoConsoleVariable<bool> CVarCarlaWeatherMoonDiscAdditive(
181 TEXT("carla.Weather.MoonDiscAdditive"),
182 true,
183 TEXT("Draw the moon disc additively over the sky (M_Moon_Additive) instead of as an ")
184 TEXT("opaque disc (M_Moon). Additive blends with the day sky; opaque is covered by ")
185 TEXT("clouds more exactly."),
186 ECVF_Default);
187
188static TAutoConsoleVariable<float> CVarCarlaWeatherMoonDiscContrast(
189 TEXT("carla.Weather.MoonDiscContrast"),
190 0.7f,
191 TEXT("Contrast of the moon texture: 1 as authored, 0 flat grey."),
192 ECVF_Default);
193
194static TAutoConsoleVariable<float> CVarCarlaWeatherMoonDiscAngleDeg(
195 TEXT("carla.Weather.MoonDiscAngleDeg"),
196 2.0f,
197 TEXT("Apparent diameter of the moon disc in degrees. The real moon is 0.52; ")
198 TEXT("larger reads better on screen."),
199 ECVF_Default);
200
201static TAutoConsoleVariable<float> CVarCarlaWeatherMoonDiscDistanceKm(
202 TEXT("carla.Weather.MoonDiscDistanceKm"),
203 100.0f,
204 TEXT("Distance of the moon disc from the sky sphere's centre, in km. Must be beyond ")
205 TEXT("the clouds so they cover it. Clamped to MoonDiscSphereFraction of the sphere."),
206 ECVF_Default);
207
208static TAutoConsoleVariable<float> CVarCarlaWeatherMoonDiscSphereFraction(
209 TEXT("carla.Weather.MoonDiscSphereFraction"),
210 0.9f,
211 TEXT("Largest distance of the moon disc as a fraction of the sky sphere's radius, ")
212 TEXT("so the sphere never draws in front of it."),
213 ECVF_Default);
214
215// Narrow and near the horizon: that is where the rig's sun curve collapses and
216// exposure opens up, and a disc still at its dusk value blows out.
217static TAutoConsoleVariable<float> CVarCarlaWeatherMoonDiscBrightnessStartDeg(
218 TEXT("carla.Weather.MoonDiscBrightnessStartDeg"),
219 2.0f,
220 TEXT("Sun altitude at which the moon disc starts dimming from its dusk brightness."),
221 ECVF_Default);
222
223static TAutoConsoleVariable<float> CVarCarlaWeatherMoonDiscBrightnessEndDeg(
224 TEXT("carla.Weather.MoonDiscBrightnessEndDeg"),
225 -4.0f,
226 TEXT("Sun altitude at which the moon disc reaches its night brightness."),
227 ECVF_Default);
228
229static TAutoConsoleVariable<float> CVarCarlaWeatherMoonDiscNightBrightness(
230 TEXT("carla.Weather.MoonDiscNightBrightness"),
231 40.0f,
232 TEXT("Emissive brightness of the moon disc at full night. Lower also means less bloom ")
233 TEXT("around it, the only per-object bloom control there is."),
234 ECVF_Default);
235
236static TAutoConsoleVariable<float> CVarCarlaWeatherMoonDiscDuskBrightness(
237 TEXT("carla.Weather.MoonDiscDuskBrightness"),
238 3000.0f,
239 TEXT("Emissive brightness of the moon disc while the sun is still up, blended to the ")
240 TEXT("night value between MoonDiscBrightnessStartDeg and MoonDiscBrightnessEndDeg."),
241 ECVF_Default);
242
243// The moon's glow is SkyAtmosphere Mie scattering, not bloom. Mie is shared
244// with the sun, so it is only changed from MoonHaloStartDeg down, which also
245// dims the sun's own glow just above the horizon. Anisotropy near 1 turns the
246// halo blocky: the sky-view LUT cannot resolve that sharp a peak.
247static TAutoConsoleVariable<float> CVarCarlaWeatherMoonHaloMieScale(
248 TEXT("carla.Weather.MoonHaloMieScale"),
249 0.1f,
250 TEXT("Factor on the atmosphere's Mie scattering at night, dimming the moon's halo. ")
251 TEXT("1 leaves it as the rig sets it."),
252 ECVF_Default);
253
254static TAutoConsoleVariable<float> CVarCarlaWeatherMoonHaloMieAnisotropy(
255 TEXT("carla.Weather.MoonHaloMieAnisotropy"),
256 -1.0f,
257 TEXT("Mie anisotropy at night: closer to 1 makes the moon's halo smaller. Negative ")
258 TEXT("leaves it as the rig sets it."),
259 ECVF_Default);
260
261static TAutoConsoleVariable<float> CVarCarlaWeatherMoonHaloStartDeg(
262 TEXT("carla.Weather.MoonHaloStartDeg"),
263 3.0f,
264 TEXT("Sun altitude at which the night Mie settings (MoonHaloMieScale, ")
265 TEXT("MoonHaloMieAnisotropy) start blending in."),
266 ECVF_Default);
267
268static TAutoConsoleVariable<float> CVarCarlaWeatherMoonHaloEndDeg(
269 TEXT("carla.Weather.MoonHaloEndDeg"),
270 0.0f,
271 TEXT("Sun altitude at which the night Mie settings are fully applied."),
272 ECVF_Default);
273
274// A full moon gives 0.1-0.3 lux. 200 lit a moonlit street ten times brighter
275// than one under street lamps. The night clouds come from CityGlow instead.
276static TAutoConsoleVariable<float> CVarCarlaWeatherMoonIntensity(
277 TEXT("carla.Weather.MoonIntensity"),
278 0.5f,
279 TEXT("Minimum DirectionalLightComponentMoon intensity (lux) enforced on the sky rig, ")
280 TEXT("faded in between MoonFadeStartDeg and MoonFullAltitudeDeg of sun altitude. Set 0 to ")
281 TEXT("leave the rig's authored/curve-driven moon intensity untouched."),
282 ECVF_Default);
283
284static TAutoConsoleVariable<float> CVarCarlaWeatherNightSkylightIntensity(
285 TEXT("carla.Weather.NightSkylightIntensity"),
286 10.0f,
287 TEXT("Minimum SkyLightComponent intensity enforced on the sky rig whenever ")
288 TEXT("SunAltitudeAngle < 0 (same units as the rig's SkyIntensity_Curve). Set 0 to ")
289 TEXT("leave the rig's curve-driven skylight intensity untouched."),
290 ECVF_Default);
291
292// The rig's sun curve drops from 100000 lux at +1 degree to 5 at -0.5, and the
293// sky light curve from 1 to 0 with it, so dusk was a cut to night that auto
294// exposure then had to catch up with. Below SunTwilightStartDeg the sun now
295// decays log-linearly to SunTwilightEndFraction of its value there, reached at
296// SunTwilightEndDeg, and the sky light blends to its night floor over the same
297// band. The sky keeps its twilight glow; surfaces stop being lit by the sun
298// once TransmittanceReleaseDeg has released. 0 or above restores the cut.
299// -10, not the astronomical -18: on camera a city night is dark by the end of
300// nautical twilight, and -18 kept most of a night cycle looking like dusk.
301static TAutoConsoleVariable<float> CVarCarlaWeatherSunTwilightEndDeg(
302 TEXT("carla.Weather.SunTwilightEndDeg"),
303 -10.0f,
304 TEXT("Sun altitude at which the sun light has faded out below the horizon. ")
305 TEXT("0 or above leaves the rig's hard cut at the horizon."),
306 ECVF_Default);
307
308static TAutoConsoleVariable<float> CVarCarlaWeatherSunTwilightStartDeg(
309 TEXT("carla.Weather.SunTwilightStartDeg"),
310 1.0f,
311 TEXT("Sun altitude at which the twilight fade starts, from the sun's value there."),
312 ECVF_Default);
313
314static TAutoConsoleVariable<float> CVarCarlaWeatherSunTwilightEndFraction(
315 TEXT("carla.Weather.SunTwilightEndFraction"),
316 0.0001f,
317 TEXT("Fraction of the sun's intensity at SunTwilightStartDeg left at SunTwilightEndDeg; ")
318 TEXT("the fade is log-linear in between."),
319 ECVF_Default);
320
321// The night sky sphere occludes the atmosphere, so showing it at the horizon
322// also cut off the twilight glow.
323static TAutoConsoleVariable<float> CVarCarlaWeatherSkySphereShowDeg(
324 TEXT("carla.Weather.SkySphereShowDeg"),
325 -12.0f,
326 TEXT("Sun altitude below which the night sky sphere (stars) is shown in front of the ")
327 TEXT("atmosphere."),
328 ECVF_Default);
329
330// At 16 km the sphere cut off the far volumetric clouds the moment it
331// appeared, and its own painted clouds replaced them: the sky visibly jumped.
332// Scaled up it sits behind the clouds, and with its painted clouds off the
333// night keeps the same volumetric clouds, lit by the moon.
334static TAutoConsoleVariable<float> CVarCarlaWeatherSkySphereScale(
335 TEXT("carla.Weather.SkySphereScale"),
336 10.0f,
337 TEXT("Factor on the night sky sphere's size, so it draws behind the volumetric clouds. ")
338 TEXT("1 leaves it as authored."),
339 ECVF_Default);
340
341static TAutoConsoleVariable<float> CVarCarlaWeatherSkySphereCloudOpacity(
342 TEXT("carla.Weather.SkySphereCloudOpacity"),
343 0.0f,
344 TEXT("\"Cloud opacity\" of the night sky sphere's painted clouds. Negative leaves the ")
345 TEXT("asset's value."),
346 ECVF_Default);
347
348// With "Colors Determined By Sun Position" on, the sphere's own colour curves
349// gave it a dusk sky all night (orange horizon, brown painted clouds), and it
350// is switched on at SkySphereShowDeg: the sky jumped from black to dusk there.
351// Forced near-black, it matches the atmosphere it hides and only the stars show.
352static TAutoConsoleVariable<FString> CVarCarlaWeatherSkySphereNightColor(
353 TEXT("carla.Weather.SkySphereNightColor"),
354 TEXT("0.002 0.002 0.0025"),
355 TEXT("Horizon, zenith and cloud colour of the night sky sphere, \"R G B\". Anything ")
356 TEXT("else (e.g. \"off\") leaves the sphere's sun-driven colours."),
357 ECVF_Default);
358
359static TAutoConsoleVariable<float> CVarCarlaWeatherStreetLightsOnDeg(
360 TEXT("carla.Weather.StreetLightsOnDeg"),
361 0.8f,
362 TEXT("Sun altitude at or below which street lights are switched on."),
363 ECVF_Default);
364
365// The night sky is pure black on every map (verified content-side, not a
366// generated-map defect): BP_Carla_Sky's SetSkySphere respawns a child actor
367// of the stock engine /Engine/EngineSky/BP_Sky_Sphere on every weather push
368// and stores it in its own "SkySphere" object variable (see PushWeatherToSky
369// below). That sphere's material, M_Sky_Panning_Clouds2, already ships star
370// rendering -- confirmed by dumping strings from the .uasset directly: a
371// MaterialExpressionScalarParameter named "Stars brightness" and a
372// TextureSample referencing /Engine/EngineSky/T_Sky_Stars. The sphere
373// blueprint exposes it through two of its own variables (also confirmed via
374// strings, including their tooltips baked into the asset): "Colors
375// Determined By Sun Position" (bool -- "If enabled, sky colors will change
376// according to the sun's position"; when off, the sphere's "Sun Height"
377// value -- whose own tooltip is "If no directional light is assigned, this
378// value determines the height of the sun" -- stays frozen at its authored
379// fallback instead of tracking the Directional Light Actor BP_Carla_Sky
380// already assigns every push, which would permanently suppress the
381// sun-height-gated star blend regardless of the actual time of day) and
382// "Stars Brightness" (float -- "Multiplier for the brightness of the stars
383// when the sun is below the horizon"). CARLA's own BP_Carla_Sky/BP_
384// CarlaWeather carry a dead "Stars Intensity Over Sun Altitude" comment/graph
385// fragment (same severed-exec-chain pattern documented throughout this file)
386// but, unlike SunIntensity_Curve/SkyIntensity_Curve, there is no backing
387// UCurveFloat asset anywhere in the content for it -- confirmed by searching
388// the plugin Content tree -- so there is nothing to re-wire; drive the two
389// stock engine variables directly instead, the same way this file already
390// routes around every other severed exec chain in the rig.
391// Both are held gated to SunAltitudeAngle < 0, deliberately not applied
392// during the day even though a fresh sphere actor makes it safe either way
393// (SetSkySphere respawns it every push, so nothing here can leak across
394// pushes): "Colors Determined By Sun Position" may also drive the sphere's
395// OWN internal Horizon/Zenith/Cloud color curves, which would compete with
396// CARLA's curve-driven day colors already pushed by UpdateSkySphereColor
397// earlier in the same push -- unverifiable without an editor, so day is left
398// completely untouched to avoid any color-grading regression risk.
399// StarsBrightness is a material-scalar knob, not a physical unit. The
400// white-texel estimate (~100, from the EV100=10 exposure multiplier above)
401// rendered stars at max pixel ~7: the star texture's lit texels sample far
402// below white. 5000 was calibrated visually on a generated Town03 world at
403// sun -30 -- a dense readable star field (sky-crop max ~145) with no bloom
404// halos or day-side effect (the whole block is gated to SunAltitudeAngle<0).
405// City light pollution scattered by the clouds: emission of the volumetric
406// cloud material (WeatherMaterialParameters.CityGlow, M_BasicClouds Emissive)
407// times NightFactor. Kept very low and near-grey: on camera, real overcast city
408// nights show the clouds as barely visible dark grey (sky L* ~2), not lit
409// orange. Per unit of cloud density, weighted to the cloud base in the material
410// ((1 - NormAltitudeInLayer)^4): the city lights it from below.
411static TAutoConsoleVariable<float> CVarCarlaWeatherCityGlow(
412 TEXT("carla.Weather.CityGlow"),
413 0.000002f,
414 TEXT("Night emission of the clouds from city lights (0 off)."),
415 ECVF_Default);
416
417static TAutoConsoleVariable<FString> CVarCarlaWeatherCityGlowColor(
418 TEXT("carla.Weather.CityGlowColor"),
419 TEXT("1.0 0.85 0.7"),
420 TEXT("Colour of the city glow on the clouds, \"R G B\"."),
421 ECVF_Default);
422
423static TAutoConsoleVariable<float> CVarCarlaWeatherStarsBrightness(
424 TEXT("carla.Weather.StarsBrightness"),
425 4.0f,
426 TEXT("Value forced into the night sky sphere's \"Stars Brightness\" variable ")
427 TEXT("(BP_Sky_Sphere, engine content) whenever SunAltitudeAngle < 0. Set 0 to leave the ")
428 TEXT("sphere's authored value alone; any other value replaces it on every push, which is ")
429 TEXT("the only way to make the stars FAINTER than the 100 the asset ships."),
430 ECVF_Default);
431
432// Reverse-engineered from BP_GeneralSceneSettings.UpdateClouds (before it was
433// ported here natively): below this Cloudiness value the rig's
434// VolumetricCloudComponent gets a fresh MID off the plain MI_Clouds master
435// (no density override -- the sphere's 2D texture is doing the actual cloud
436// look at low cloudiness); at or above it, a fresh MID off the "Billowy"
437// overcast master (M_VolumetricCloud_03_Profiles_Billowy_Inst) with its
438// "Cloud Density" scalar driven by C_BillowyDensity.GetFloatValue(Cloudiness)
439// -- confirmed by probing the live material param through the same threshold
440// BP_GeneralSceneSettings used (90): 90->10, 95->38, 100->247.
441static TAutoConsoleVariable<float> CVarCarlaWeatherOvercastThreshold(
442 TEXT("carla.Weather.OvercastThreshold"),
443 90.0f,
444 TEXT("Cloudiness value at/above which the sky rig's VolumetricCloudComponent switches ")
445 TEXT("from the plain cloud master material to the Billowy overcast one."),
446 ECVF_Default);
447
448// Off: the hard swap to the Billowy material at the threshold is a visible
449// break (two different rendering techniques), and its thick decks render as
450// broken dark cumulus. The plain MI_Clouds master closes into the overcast
451// deck continuously instead (see CVarCarlaWeatherDeckStartCloudiness).
452static TAutoConsoleVariable<bool> CVarCarlaWeatherEnableOvercastClouds(
453 TEXT("carla.Weather.EnableOvercastClouds"),
454 false,
455 TEXT("Whether Cloudiness at/above OvercastThreshold switches the cloud material to the ")
456 TEXT("Billowy overcast one. Set false to always use the plain master instead."),
457 ECVF_Default);
458
459// False stops the weather push re-asserting the cloud material.
460static TAutoConsoleVariable<float> CVarCarlaWeatherOvercastSunIntensity(
461 TEXT("carla.Weather.OvercastSunIntensity"),
462 1200.0f,
463 TEXT("Sun intensity at Cloudiness 100, blended in from SunIntensity_Curve by Cloudiness ")
464 TEXT("and clamped so it can only darken. Negative disables."),
465 ECVF_Default);
466
467static TAutoConsoleVariable<float> CVarCarlaWeatherOvercastSkyLightIntensity(
468 TEXT("carla.Weather.OvercastSkyLightIntensity"),
469 10.0f,
470 TEXT("Sky light intensity at Cloudiness 100, blended in from SkyIntensity_Curve by ")
471 TEXT("Cloudiness and clamped so it can only brighten. Negative disables."),
472 ECVF_Default);
473
474// OvercastSkyLightIntensity multiplies a red sunset capture up to 10x, which
475// tints the whole city red, but under a thick deck the ambient is grey. The
476// tint of the sun's transmittance relative to the zenith is cancelled, scaled
477// by Cloudiness so a clear sunset keeps its red.
478static TAutoConsoleVariable<float> CVarCarlaWeatherOvercastAmbientNeutralize(
479 TEXT("carla.Weather.OvercastAmbientNeutralize"),
480 0.4f,
481 TEXT("How much of the sun's atmospheric tint to cancel from the sky light at ")
482 TEXT("Cloudiness 100 (scaled by Cloudiness). 0 leaves the sky light white."),
483 ECVF_Default);
484
485// The same on the sun itself, so an overcast deck does not glow red.
486static TAutoConsoleVariable<float> CVarCarlaWeatherOvercastSunNeutralize(
487 TEXT("carla.Weather.OvercastSunNeutralize"),
488 0.6f,
489 TEXT("How much of the sun's atmospheric tint to cancel from the sun light itself at ")
490 TEXT("Cloudiness 100 (scaled by Cloudiness), taking the red out of an overcast deck ")
491 TEXT("at sunset. 0 leaves the sun white."),
492 ECVF_Default);
493
494static TAutoConsoleVariable<float> CVarCarlaWeatherOvercastAmbientMaxGain(
495 TEXT("carla.Weather.OvercastAmbientMaxGain"),
496 3.0f,
497 TEXT("Largest per-channel gain the sky light neutralisation may apply, so a sun on ")
498 TEXT("the horizon cannot turn the ambient blue."),
499 ECVF_Default);
500
501// Overcast deck. Real heavy overcast is a continuous grey-white layer, the
502// brightest thing in the frame (sky L* ~90 in rain footage, facades and wet
503// asphalt well below it). The volumetric clouds alone render it as broken,
504// dark, blue-tinted cumulus: the single-scattering + approximate multiple
505// scattering model loses most of the light a thick deck diffuses downwards.
506// From DeckStartCloudiness to 100 (smoothstep) the plain cloud material closes
507// into a continuous layer (BaseNoiseExp -> DeckNoiseExp), gets a softer base
508// (ExtinctionScale, domain warp; the authored warp draws one big swirl across
509// a closed deck), emits the missing diffuse light (DeckGlow, scaled by the
510// sun's intensity) and the sky light boost drops (DeckSkyLightIntensity),
511// since the deck itself now feeds the real-time sky light capture.
512static TAutoConsoleVariable<float> CVarCarlaWeatherDeckStartCloudiness(
513 TEXT("carla.Weather.DeckStartCloudiness"),
514 80.0f,
515 TEXT("Cloudiness at which the clouds start closing into the overcast deck (full at 100)."),
516 ECVF_Default);
517
518// The deck's light (DeckGlow, DeckSkyLightIntensity) ramps in faster than its
519// coverage: a deck that has closed but does not glow yet reads as a dark grey
520// stage that real overcast does not go through.
521static TAutoConsoleVariable<float> CVarCarlaWeatherDeckLightFullCloudiness(
522 TEXT("carla.Weather.DeckLightFullCloudiness"),
523 84.0f,
524 TEXT("Cloudiness at which the deck's emission and sky light reach their full value."),
525 ECVF_Default);
526
527// M_BasicClouds' domain warp loop blends its last iteration by the count's
528// fraction, so the warp count blends continuously with the deck factor (-1).
529// The stock loop truncated the count to an integer (1.99 rendered as 1): a
530// value in [0, 1] flips it once at that deck factor instead, for that material.
531static TAutoConsoleVariable<float> CVarCarlaWeatherDeckWarpSwitch(
532 TEXT("carla.Weather.DeckWarpSwitch"),
533 -1.0f,
534 TEXT("-1: blend the warp count with the deck factor. 0-1: flip it to DeckWarpCount at that deck factor."),
535 ECVF_Default);
536
537// BaseNoiseExp closes the coverage fast at first (a log blend, and the deck's
538// glow makes thin cloud read as cloud): the coverage ramp is the deck factor
539// to this power, so the gaps close gradually over the upper half of the range.
540static TAutoConsoleVariable<float> CVarCarlaWeatherDeckCoverageExponent(
541 TEXT("carla.Weather.DeckCoverageExponent"),
542 2.0f,
543 TEXT("Power applied to the deck factor for the cloud coverage (BaseNoiseExp) blend."),
544 ECVF_Default);
545
546static TAutoConsoleVariable<float> CVarCarlaWeatherDeckNoiseExp(
547 TEXT("carla.Weather.DeckNoiseExp"),
548 1.0f,
549 TEXT("Cloud material BaseNoiseExp at Cloudiness 100 (lower = more coverage)."),
550 ECVF_Default);
551
552static TAutoConsoleVariable<float> CVarCarlaWeatherDeckExtinctionScale(
553 TEXT("carla.Weather.DeckExtinctionScale"),
554 0.005f,
555 TEXT("Cloud material ExtinctionScale at Cloudiness 100 (softer deck base). Negative leaves the material's."),
556 ECVF_Default);
557
558static TAutoConsoleVariable<float> CVarCarlaWeatherDeckWarpCount(
559 TEXT("carla.Weather.DeckWarpCount"),
560 1.0f,
561 TEXT("Cloud material domain warp count at Cloudiness 100. Negative leaves the material's."),
562 ECVF_Default);
563
564static TAutoConsoleVariable<float> CVarCarlaWeatherDeckGlow(
565 TEXT("carla.Weather.DeckGlow"),
566 2.0f,
567 TEXT("Cloud emission at Cloudiness 100 with the sun at its peak (0 off)."),
568 ECVF_Default);
569
570static TAutoConsoleVariable<FString> CVarCarlaWeatherDeckGlowColor(
571 TEXT("carla.Weather.DeckGlowColor"),
572 TEXT("1.0 1.0 1.0"),
573 TEXT("Colour of the overcast deck emission, \"R G B\"."),
574 ECVF_Default);
575
576static TAutoConsoleVariable<float> CVarCarlaWeatherDeckSkyLightIntensity(
577 TEXT("carla.Weather.DeckSkyLightIntensity"),
578 1.0f,
579 TEXT("Replaces OvercastSkyLightIntensity at Cloudiness 100 (blended by the deck factor)."),
580 ECVF_Default);
581
582// A raining deck is thicker and darker than a dry overcast one: in rain
583// footage the sky is mid grey (L* ~70-85), not the white of a bright overcast.
584// With Precipitation (linearly up to 100) the deck's emission is scaled down
585// and its sky light up, so the sky darkens while the ground keeps its light.
586static TAutoConsoleVariable<float> CVarCarlaWeatherRainDeckGlowScale(
587 TEXT("carla.Weather.RainDeckGlowScale"),
588 0.25f,
589 TEXT("DeckGlow multiplier at Precipitation 100 (1 = rain does not change the deck)."),
590 ECVF_Default);
591
592static TAutoConsoleVariable<float> CVarCarlaWeatherRainDeckSkyLightScale(
593 TEXT("carla.Weather.RainDeckSkyLightScale"),
594 3.0f,
595 TEXT("DeckSkyLightIntensity multiplier at Precipitation 100 (1 = rain does not change it)."),
596 ECVF_Default);
597
598static float RainDeckScale(const TAutoConsoleVariable<float>& CVar, float Precipitation)
599{
600 return FMath::Lerp(1.0f, FMath::Max(CVar.GetValueOnGameThread(), 0.0f),
601 FMath::Clamp(Precipitation / 100.0f, 0.0f, 1.0f));
602}
603
604// 0 below DeckStartCloudiness, 1 at Cloudiness 100, smoothstep in between.
605static float ComputeCloudDeckFactor(float Cloudiness)
606{
607 const float Start = FMath::Clamp(CVarCarlaWeatherDeckStartCloudiness.GetValueOnGameThread(), 0.0f, 99.0f);
608 const float T = FMath::Clamp((Cloudiness - Start) / (100.0f - Start), 0.0f, 1.0f);
609 return T * T * (3.0f - 2.0f * T);
610}
611
612// The same from DeckStartCloudiness to DeckLightFullCloudiness.
613static float ComputeCloudDeckLightFactor(float Cloudiness)
614{
615 const float Start = FMath::Clamp(CVarCarlaWeatherDeckStartCloudiness.GetValueOnGameThread(), 0.0f, 99.0f);
616 const float Full = FMath::Clamp(CVarCarlaWeatherDeckLightFullCloudiness.GetValueOnGameThread(), Start + 1.0f, 100.0f);
617 const float T = FMath::Clamp((Cloudiness - Start) / (Full - Start), 0.0f, 1.0f);
618 return T * T * (3.0f - 2.0f * T);
619}
620
621// OvercastSkyLightIntensity, giving way to DeckSkyLightIntensity as the deck closes.
622static float GetOvercastSkyLightTarget(float Cloudiness, float Precipitation)
623{
624 const float Overcast = CVarCarlaWeatherOvercastSkyLightIntensity.GetValueOnGameThread();
625 const float DeckRaw = CVarCarlaWeatherDeckSkyLightIntensity.GetValueOnGameThread();
626 // Test the sentinel before the rain scale: a scale of 0 turns -1 into -0, which is not < 0.
627 if (Overcast < 0.0f || DeckRaw < 0.0f)
628 return Overcast;
629 const float Deck = DeckRaw * RainDeckScale(CVarCarlaWeatherRainDeckSkyLightScale, Precipitation);
630 return FMath::Lerp(Overcast, Deck, ComputeCloudDeckLightFactor(Cloudiness));
631}
632
633static FLinearColor ParseColorCVar(const TAutoConsoleVariable<FString>& CVar, const FLinearColor& Fallback)
634{
635 TArray<FString> Channels;
636 CVar.GetValueOnGameThread().ParseIntoArrayWS(Channels);
637 if (Channels.Num() != 3)
638 return Fallback;
639 return FLinearColor(FCString::Atof(*Channels[0]), FCString::Atof(*Channels[1]), FCString::Atof(*Channels[2]));
640}
641
642// Sun light scattered by the volumetric clouds near the horizon. Below ~2 deg
643// the atmosphere leaves the sun almost pure red, and the clouds it lights read
644// saturated salmon on camera. Scaling only the cloud scattering keeps the
645// afterglow (the ground, the sky and the sun disc are untouched) but dimmer
646// and less saturated. Full at or below TwilightCloudFullDeg, fading to white
647// (no effect) at TwilightCloudOffDeg.
648static TAutoConsoleVariable<FString> CVarCarlaWeatherTwilightCloudScale(
649 TEXT("carla.Weather.TwilightCloudScale"),
650 TEXT("0.4 0.55 0.8"),
651 TEXT("Sun CloudScatteredLuminanceScale at the horizon, \"R G B\" (\"1 1 1\" off)."),
652 ECVF_Default);
653
654static TAutoConsoleVariable<float> CVarCarlaWeatherTwilightCloudFullDeg(
655 TEXT("carla.Weather.TwilightCloudFullDeg"),
656 2.0f,
657 TEXT("Sun altitude at or below which carla.Weather.TwilightCloudScale applies in full."),
658 ECVF_Default);
659
660static TAutoConsoleVariable<float> CVarCarlaWeatherTwilightCloudOffDeg(
661 TEXT("carla.Weather.TwilightCloudOffDeg"),
662 12.0f,
663 TEXT("Sun altitude at or above which the clouds get the sun's full scattering again."),
664 ECVF_Default);
665
666static FLinearColor ComputeTwilightCloudScale(float SunAltitudeAngle)
667{
668 const float Full = CVarCarlaWeatherTwilightCloudFullDeg.GetValueOnGameThread();
669 const float Off = FMath::Max(CVarCarlaWeatherTwilightCloudOffDeg.GetValueOnGameThread(), Full + 0.1f);
670 const float T = FMath::SmoothStep(0.0f, 1.0f, FMath::Clamp((SunAltitudeAngle - Full) / (Off - Full), 0.0f, 1.0f));
671 FLinearColor Scale = FMath::Lerp(ParseColorCVar(CVarCarlaWeatherTwilightCloudScale, FLinearColor::White),
672 FLinearColor::White, T);
673 Scale.A = 1.0f;
674 return Scale;
675}
676
677// bOnlyDarken picks the clamp direction: a ceiling for the sun, a floor for
678// the sky light.
679static float ComputeNightBlend(float SunAltitudeAngle)
680{
681 const float Band = CVarCarlaWeatherDayNightBlendDeg.GetValueOnGameThread();
682 if (Band <= 0.0f)
683 return SunAltitudeAngle < 0.0f ? 1.0f : 0.0f;
684 const float T = FMath::Clamp(0.5f - SunAltitudeAngle / (2.0f * Band), 0.0f, 1.0f);
685 return T * T * (3.0f - 2.0f * T); // smoothstep
686}
687
688// 0 no moon, 1 full MoonIntensity.
689static float ComputeMoonBlend(float SunAltitudeAngle)
690{
691 const float Start = CVarCarlaWeatherMoonFadeStartDeg.GetValueOnGameThread();
692 const float Full = CVarCarlaWeatherMoonFullAltitudeDeg.GetValueOnGameThread();
693 if (Start <= Full)
694 return SunAltitudeAngle < Full ? 1.0f : 0.0f;
695 const float T = FMath::Clamp((Start - SunAltitudeAngle) / (Start - Full), 0.0f, 1.0f);
696 return T * T * (3.0f - 2.0f * T); // smoothstep
697}
698
699// 0 day Mie, 1 night Mie.
700static float ComputeMoonHaloBlend(float SunAltitudeAngle)
701{
702 const float Start = CVarCarlaWeatherMoonHaloStartDeg.GetValueOnGameThread();
703 const float End = CVarCarlaWeatherMoonHaloEndDeg.GetValueOnGameThread();
704 if (Start <= End)
705 return SunAltitudeAngle < End ? 1.0f : 0.0f;
706 const float T = FMath::Clamp((Start - SunAltitudeAngle) / (Start - End), 0.0f, 1.0f);
707 return T * T * (3.0f - 2.0f * T); // smoothstep
708}
709
710// The directional light's VSM cache is keyed on the exact light direction. A
711// sun that turns once per tick, while each tick renders several views (the
712// viewport and every camera sensor), keeps swapping the cache entry between
713// cached and uncached. With a low sun the stale pages show: direct sunlight
714// switching on and off in single frames (Town15, sun at +1..+5 degrees, 12 of
715// 120 frames; 0 with this). The engine's advice for a light that invalidates
716// often but not on every render (VirtualShadowMapCacheManager.cpp) is
717// r.Shadow.Virtual.Cache.ForceInvalidateDirectional. It is held on while the
718// sun moves and released once the sun has been still for
719// VSMSunStillSeconds. It is set by code, so a value set in the console wins.
720static TAutoConsoleVariable<bool> CVarCarlaWeatherVSMInvalidateWhileSunMoves(
721 TEXT("carla.Weather.VSMInvalidateWhileSunMoves"),
722 true,
723 TEXT("Hold r.Shadow.Virtual.Cache.ForceInvalidateDirectional on while the sun direction ")
724 TEXT("changes. 0 leaves it alone."),
725 ECVF_Default);
726
727static TAutoConsoleVariable<float> CVarCarlaWeatherVSMSunStillSeconds(
728 TEXT("carla.Weather.VSMSunStillSeconds"),
729 1.0f,
730 TEXT("World seconds without a sun direction change before the directional VSM cache is ")
731 TEXT("used again. World time, not engine frames: in synchronous mode the editor keeps ")
732 TEXT("rendering between client ticks, and a frame count released the cvar between two ")
733 TEXT("pushes of a moving sun, which brought the flicker back."),
734 ECVF_Default);
735
736static FRotator GLastSunRotation(ForceInitToZero);
737static double GLastSunMoveWorldSeconds = 0.0;
739
741{
743 return;
744 if (IConsoleVariable* CVar = IConsoleManager::Get().FindConsoleVariable(
745 TEXT("r.Shadow.Virtual.Cache.ForceInvalidateDirectional")))
746 {
747 CVar->Set(bOn ? 1 : 0, ECVF_SetByCode);
749 UE_LOG(LogCarla, Log, TEXT("AWeather: directional VSM force-invalidate %s"), bOn ? TEXT("on") : TEXT("off"));
750 }
751}
752
753static void NoteSunRotation(const UWorld* World, const FRotator& Rotation)
754{
755 if (!CVarCarlaWeatherVSMInvalidateWhileSunMoves.GetValueOnGameThread())
756 {
758 return;
759 }
760 if (!Rotation.Equals(GLastSunRotation, 1e-4f))
761 {
762 GLastSunRotation = Rotation;
763 GLastSunMoveWorldSeconds = World->GetTimeSeconds();
765 }
766}
767
768static void ReleaseDirectionalVSMInvalidateIfSunStill(const UWorld* World)
769{
770 if (GForcingDirectionalVSMInvalidate && World != nullptr
771 && World->GetTimeSeconds() - GLastSunMoveWorldSeconds
772 > FMath::Max(CVarCarlaWeatherVSMSunStillSeconds.GetValueOnGameThread(), 0.0f))
774}
775
776// Lumen caches its lighting (radiance cache probes, surface cache) and updates
777// it over many frames, nearest first; the sky light's real-time capture is
778// time-sliced too. After a jump from day to night the city stayed lit like day
779// for 20+ s (Town10, fixed exposure: 24 s, nearest street first, far facades
780// last). Held for LightingJumpSettleSeconds of world time after a weather push
781// that moves the sun or the clouds by more than the thresholds, the three
782// cvars below bring that to ~6 s. Gradual changes (a timeline, a client easing
783// the sun) never trigger it. Set by code, so a value set in the console wins.
784static TAutoConsoleVariable<float> CVarCarlaWeatherLightingJumpSunDeg(
785 TEXT("carla.Weather.LightingJumpSunDeg"),
786 5.0f,
787 TEXT("Sun altitude change, in degrees, between two weather pushes that counts as a lighting ")
788 TEXT("jump and forces Lumen and the sky light capture to update in full. 0 disables it."),
789 ECVF_Default);
790
791static TAutoConsoleVariable<float> CVarCarlaWeatherLightingJumpCloudiness(
792 TEXT("carla.Weather.LightingJumpCloudiness"),
793 30.0f,
794 TEXT("Cloudiness change between two weather pushes that counts as a lighting jump. 0 disables it."),
795 ECVF_Default);
796
797static TAutoConsoleVariable<float> CVarCarlaWeatherLightingJumpSettleSeconds(
798 TEXT("carla.Weather.LightingJumpSettleSeconds"),
799 8.0f,
800 TEXT("World seconds the full Lumen / sky light updates are held after a lighting jump."),
801 ECVF_Default);
802
803static const TCHAR* GLightingJumpCVars[] = {
804 TEXT("r.Lumen.RadianceCache.ForceFullUpdate"),
805 TEXT("r.LumenScene.Lighting.ForceLightingUpdate"),
806 TEXT("r.SkyLight.RealTimeReflectionCapture.TimeSlice")};
807static const int32 GLightingJumpValues[] = {1, 1, 0};
808static int32 GLightingJumpRestore[UE_ARRAY_COUNT(GLightingJumpCVars)] = {};
809static bool GForcingLightingUpdate = false;
810static double GLightingJumpWorldSeconds = 0.0;
811static float GLastPushedSunAltitude = TNumericLimits<float>::Max();
812static float GLastPushedCloudiness = TNumericLimits<float>::Max();
813
814static void SetForceLightingUpdate(bool bOn)
815{
816 if (GForcingLightingUpdate == bOn)
817 return;
818 for (int32 i = 0; i < UE_ARRAY_COUNT(GLightingJumpCVars); ++i)
819 {
820 IConsoleVariable* CVar = IConsoleManager::Get().FindConsoleVariable(GLightingJumpCVars[i]);
821 if (CVar == nullptr)
822 continue;
823 if (bOn)
824 GLightingJumpRestore[i] = CVar->GetInt();
825 CVar->Set(bOn ? GLightingJumpValues[i] : GLightingJumpRestore[i], ECVF_SetByCode);
826 }
828 UE_LOG(LogCarla, Log, TEXT("AWeather: full Lumen / sky light update %s"), bOn ? TEXT("on") : TEXT("off"));
829}
830
831static void NoteLightingChange(const UWorld* World, float SunAltitude, float Cloudiness)
832{
833 const float SunDeg = CVarCarlaWeatherLightingJumpSunDeg.GetValueOnGameThread();
834 const float CloudJump = CVarCarlaWeatherLightingJumpCloudiness.GetValueOnGameThread();
835 const bool bFirst = GLastPushedSunAltitude == TNumericLimits<float>::Max();
836 const bool bJump = !bFirst && (
837 (SunDeg > 0.0f && FMath::Abs(SunAltitude - GLastPushedSunAltitude) > SunDeg) ||
838 (CloudJump > 0.0f && FMath::Abs(Cloudiness - GLastPushedCloudiness) > CloudJump));
839 GLastPushedSunAltitude = SunAltitude;
840 GLastPushedCloudiness = Cloudiness;
841 if (bJump)
842 {
843 GLightingJumpWorldSeconds = World->GetTimeSeconds();
845 }
846}
847
848static void ReleaseLightingUpdateIfSettled(const UWorld* World)
849{
850 if (GForcingLightingUpdate && World != nullptr
851 && World->GetTimeSeconds() - GLightingJumpWorldSeconds
852 > FMath::Max(CVarCarlaWeatherLightingJumpSettleSeconds.GetValueOnGameThread(), 0.0f))
854}
855
856// 1 at SunTwilightStartDeg and above, decaying to 0 at SunTwilightEndDeg.
857// Negative when the twilight fade is disabled.
858static float ComputeSunTwilightFactor(float SunAltitudeAngle)
859{
860 const float End = CVarCarlaWeatherSunTwilightEndDeg.GetValueOnGameThread();
861 const float Start = CVarCarlaWeatherSunTwilightStartDeg.GetValueOnGameThread();
862 if (End >= 0.0f || Start <= End)
863 return -1.0f;
864 if (SunAltitudeAngle >= Start)
865 return 1.0f;
866 if (SunAltitudeAngle <= End)
867 return 0.0f;
868 const float T = (Start - SunAltitudeAngle) / (Start - End);
869 const float EndFraction = FMath::Clamp(
870 CVarCarlaWeatherSunTwilightEndFraction.GetValueOnGameThread(), 1e-8f, 1.0f);
871 // Eased to zero over the last 3 degrees, so the end fraction does not pop off.
872 const float Tail = FMath::SmoothStep(0.0f, 1.0f, FMath::Clamp((SunAltitudeAngle - End) / 3.0f, 0.0f, 1.0f));
873 return FMath::Pow(EndFraction, T) * Tail;
874}
875
876// Applied on top of what the rig wrote. Tracks its own last write, so a value
877// the rig did not rewrite since is not scaled twice.
878static void UpdateMoonHalo(AActor* SkyActor, float HaloBlend)
879{
880 FObjectProperty* AtmosphereProperty = CastField<FObjectProperty>(
881 SkyActor->GetClass()->FindPropertyByName(TEXT("SkyAtmosphereComponent")));
882 USkyAtmosphereComponent* Atmosphere = AtmosphereProperty != nullptr
883 ? Cast<USkyAtmosphereComponent>(AtmosphereProperty->GetObjectPropertyValue_InContainer(SkyActor))
884 : nullptr;
885 if (Atmosphere == nullptr)
886 return;
887
888 static TWeakObjectPtr<USkyAtmosphereComponent> Tracked;
889 static float BaseScale = 0.0f, WrittenScale = -1.0f;
890 static float BaseAnisotropy = 0.0f, WrittenAnisotropy = -2.0f;
891 if (Tracked.Get() != Atmosphere)
892 {
893 Tracked = Atmosphere;
894 WrittenScale = -1.0f;
895 WrittenAnisotropy = -2.0f;
896 }
897 if (Atmosphere->MieScatteringScale != WrittenScale)
898 BaseScale = Atmosphere->MieScatteringScale;
899 if (Atmosphere->MieAnisotropy != WrittenAnisotropy)
900 BaseAnisotropy = Atmosphere->MieAnisotropy;
901
902 const float ScaleFactor = FMath::Lerp(1.0f,
903 FMath::Max(CVarCarlaWeatherMoonHaloMieScale.GetValueOnGameThread(), 0.0f), HaloBlend);
904 const float NightAnisotropy = CVarCarlaWeatherMoonHaloMieAnisotropy.GetValueOnGameThread();
905 const float Anisotropy = NightAnisotropy >= 0.0f
906 ? FMath::Lerp(BaseAnisotropy, FMath::Min(NightAnisotropy, 0.999f), HaloBlend)
907 : BaseAnisotropy;
908
909 WrittenScale = BaseScale * ScaleFactor;
910 WrittenAnisotropy = Anisotropy;
911 if (Atmosphere->MieScatteringScale != WrittenScale)
912 Atmosphere->SetMieScatteringScale(WrittenScale);
913 if (Atmosphere->MieAnisotropy != WrittenAnisotropy)
914 Atmosphere->SetMieAnisotropy(WrittenAnisotropy);
915}
916
917// A transient component on the sky rig, looked up by name every push and
918// recreated if a re-instancing of the rig dropped it.
919static void UpdateMoonDisc(AActor* SkyActor, const USceneComponent* MoonLight,
920 float MoonBlend, float SunAltitudeAngle)
921{
922 static const FName DiscName(TEXT("CarlaMoonDisc"));
923 UStaticMeshComponent* Disc = FindObjectFast<UStaticMeshComponent>(SkyActor, DiscName);
924 if (!CVarCarlaWeatherMoonDisc.GetValueOnGameThread() || MoonLight == nullptr || MoonBlend <= 0.0f)
925 {
926 if (Disc != nullptr && Disc->IsVisible())
927 Disc->SetVisibility(false);
928 return;
929 }
930
931 const TCHAR* MaterialPath = CVarCarlaWeatherMoonDiscAdditive.GetValueOnGameThread()
932 ? TEXT("/Game/Carla/Blueprints/Weather/Materials/M_Moon_Additive.M_Moon_Additive")
933 : TEXT("/Game/Carla/Blueprints/Weather/Materials/M_Moon.M_Moon");
934 UMaterialInterface* Material = LoadObject<UMaterialInterface>(nullptr, MaterialPath);
935 if (Material == nullptr)
936 {
937 static bool bWarned = false;
938 if (!bWarned)
939 UE_LOG(LogCarla, Warning, TEXT("AWeather: moon disc disabled, missing %s"), MaterialPath);
940 bWarned = true;
941 if (Disc != nullptr && Disc->IsVisible())
942 Disc->SetVisibility(false);
943 return;
944 }
945
946 if (Disc == nullptr)
947 {
948 UStaticMesh* Plane = LoadObject<UStaticMesh>(nullptr, TEXT("/Engine/BasicShapes/Plane.Plane"));
949 if (Plane == nullptr)
950 {
951 static bool bWarned = false;
952 if (!bWarned)
953 UE_LOG(LogCarla, Warning, TEXT("AWeather: moon disc disabled, missing /Engine/BasicShapes/Plane"));
954 bWarned = true;
955 return;
956 }
957 Disc = NewObject<UStaticMeshComponent>(SkyActor, DiscName, RF_Transient);
958 Disc->SetStaticMesh(Plane);
959 Disc->SetMobility(EComponentMobility::Movable);
960 Disc->SetUsingAbsoluteLocation(true);
961 Disc->SetUsingAbsoluteRotation(true);
962 Disc->SetUsingAbsoluteScale(true);
963 Disc->SetCollisionEnabled(ECollisionEnabled::NoCollision);
964 Disc->SetCastShadow(false);
965 Disc->bAffectDistanceFieldLighting = false;
966 Disc->bVisibleInReflectionCaptures = false;
967 Disc->bVisibleInRealTimeSkyCaptures = false;
968 Disc->bVisibleInRayTracing = false;
969 Disc->SetupAttachment(SkyActor->GetRootComponent());
970 Disc->RegisterComponent();
971 }
972 else if (!Disc->IsRegistered())
973 {
974 Disc->RegisterComponent();
975 }
976
977 // The light shines along its forward vector, so the moon sits opposite.
978 const FVector ToMoon = -MoonLight->GetForwardVector();
979
980 // The sky sphere is the biggest mesh among the rig's child actors.
981 FVector Centre = FVector::ZeroVector;
982 float SphereRadius = 0.0f;
983 TInlineComponentArray<UChildActorComponent*> RigChildActors;
984 SkyActor->GetComponents(RigChildActors);
985 for (UChildActorComponent* RigChild : RigChildActors)
986 {
987 if (RigChild == nullptr || RigChild->GetChildActor() == nullptr)
988 continue;
989 TInlineComponentArray<UMeshComponent*> ChildMeshes;
990 RigChild->GetChildActor()->GetComponents(ChildMeshes);
991 for (UMeshComponent* ChildMesh : ChildMeshes)
992 {
993 if (ChildMesh != nullptr && ChildMesh->Bounds.SphereRadius > SphereRadius)
994 {
995 SphereRadius = ChildMesh->Bounds.SphereRadius;
996 Centre = ChildMesh->Bounds.Origin;
997 }
998 }
999 }
1000 float Distance = FMath::Max(CVarCarlaWeatherMoonDiscDistanceKm.GetValueOnGameThread(), 0.01f) * 100000.0f;
1001 if (SphereRadius > 0.0f)
1002 Distance = FMath::Min(Distance,
1003 SphereRadius * FMath::Clamp(CVarCarlaWeatherMoonDiscSphereFraction.GetValueOnGameThread(), 0.01f, 1.0f));
1004 static float LastLoggedDistance = -1.0f;
1005 if (Distance != LastLoggedDistance)
1006 {
1007 UE_LOG(LogCarla, Log, TEXT("AWeather moon disc: sky sphere radius %.0f cm, disc distance %.0f cm"),
1008 SphereRadius, Distance);
1009 LastLoggedDistance = Distance;
1010 }
1011 const float Angle = FMath::Clamp(CVarCarlaWeatherMoonDiscAngleDeg.GetValueOnGameThread(), 0.01f, 45.0f);
1012 // BasicShapes/Plane is 100 units across and faces +Z.
1013 const float Scale = 2.0f * Distance * FMath::Tan(FMath::DegreesToRadians(Angle) * 0.5f) / 100.0f;
1014 Disc->SetWorldLocationAndRotation(
1015 Centre + ToMoon * Distance, FRotationMatrix::MakeFromZX(-ToMoon, FVector::UpVector).Rotator());
1016 Disc->SetWorldScale3D(FVector(Scale));
1017
1018 // Swapped in place when MoonDiscAdditive changes.
1019 UMaterialInstanceDynamic* MoonMaterial = Cast<UMaterialInstanceDynamic>(Disc->GetMaterial(0));
1020 if (MoonMaterial == nullptr || MoonMaterial->Parent != Material)
1021 {
1022 MoonMaterial = UMaterialInstanceDynamic::Create(Material, Disc);
1023 Disc->SetMaterial(0, MoonMaterial);
1024 }
1025 if (MoonMaterial != nullptr)
1026 {
1027 MoonMaterial->SetScalarParameterValue(TEXT("Contrast"),
1028 FMath::Clamp(CVarCarlaWeatherMoonDiscContrast.GetValueOnGameThread(), 0.0f, 2.0f));
1029 const float Start = CVarCarlaWeatherMoonDiscBrightnessStartDeg.GetValueOnGameThread();
1030 const float End = CVarCarlaWeatherMoonDiscBrightnessEndDeg.GetValueOnGameThread();
1031 const float T = Start > End
1032 ? FMath::SmoothStep(0.0f, 1.0f, FMath::Clamp((Start - SunAltitudeAngle) / (Start - End), 0.0f, 1.0f))
1033 : (SunAltitudeAngle < End ? 1.0f : 0.0f);
1034 MoonMaterial->SetScalarParameterValue(TEXT("Brightness"), FMath::Lerp(
1035 CVarCarlaWeatherMoonDiscDuskBrightness.GetValueOnGameThread(),
1036 CVarCarlaWeatherMoonDiscNightBrightness.GetValueOnGameThread(),
1037 T));
1038 }
1039 if (!Disc->IsVisible())
1040 Disc->SetVisibility(true);
1041}
1042
1043// Applied before the overcast blend, so its peak stays the curve's own.
1044static float ApplySunAltitudeFalloff(float CurveValue, float CurvePeak, float SunAltitudeAngle)
1045{
1046 const float Strength = FMath::Clamp(
1047 CVarCarlaWeatherSunAltitudeFalloff.GetValueOnGameThread(), 0.0f, 1.0f);
1048 if (Strength <= 0.0f || CurvePeak <= UE_KINDA_SMALL_NUMBER)
1049 return CurveValue;
1050
1051 // Both fractions are of the curve's peak, not of its value at this altitude.
1052 const float SinElevation =
1053 FMath::Sin(FMath::DegreesToRadians(FMath::Max(SunAltitudeAngle, 0.0f)));
1054 const float Exponent = FMath::Max(CVarCarlaWeatherSunFalloffExponent.GetValueOnGameThread(), 0.0f);
1055 const float Physical = CurvePeak * FMath::Max(
1056 FMath::Pow(SinElevation, Exponent), CVarCarlaWeatherSunHorizonFloor.GetValueOnGameThread());
1057
1058 // Min so it only darkens; below the horizon the curve governs.
1059 return FMath::Min(CurveValue, FMath::Lerp(CurveValue, Physical, Strength));
1060}
1061
1062// The curve's peak above the horizon; the overcast targets are quoted at it.
1063static float SampleCurvePeakAboveHorizon(UCurveFloat* Curve)
1064{
1065 float Peak = 0.0f;
1066 if (Curve != nullptr)
1067 for (float Altitude = 0.0f; Altitude <= 90.0f; Altitude += 5.0f)
1068 Peak = FMath::Max(Peak, Curve->GetFloatValue(Altitude));
1069 return Peak;
1070}
1071
1072// CurvePeak > 0 treats OvercastValue as a factor on the curve, 0 as an absolute.
1073static float ApplyOvercastBlend(float CurveValue, float OvercastValue,
1074 float Cloudiness, bool bOnlyDarken, float CurvePeak)
1075{
1076 if (OvercastValue < 0.0f)
1077 return CurveValue;
1078 const float Overcast = FMath::Clamp(Cloudiness / 100.0f, 0.0f, 1.0f);
1079 const float Blended = CurvePeak > UE_KINDA_SMALL_NUMBER
1080 ? CurveValue * FMath::Lerp(1.0f, OvercastValue / CurvePeak, Overcast)
1081 : FMath::Lerp(CurveValue, OvercastValue, Overcast);
1082 return bOnlyDarken ? FMath::Min(CurveValue, Blended) : FMath::Max(CurveValue, Blended);
1083}
1084
1085// White when there is nothing to cancel, so writing it every push also undoes
1086// an earlier tint. The sky light stores an 8-bit colour, so the tint comes
1087// back scaled to a peak of 1 and OutIntensityScale carries the difference.
1088static FLinearColor ComputeOvercastNeutralTint(const USkyAtmosphereComponent* Atmosphere,
1089 float SunAltitudeAngle, float Cloudiness,
1090 float NeutralizeAmount, float& OutIntensityScale)
1091{
1092 OutIntensityScale = 1.0f;
1093 const float Release = CVarCarlaWeatherTransmittanceReleaseDeg.GetValueOnGameThread();
1094 const float DayFade = Release < 0.0f
1095 ? 1.0f - FMath::SmoothStep(0.0f, 1.0f, FMath::Clamp(SunAltitudeAngle / Release, 0.0f, 1.0f))
1096 : (SunAltitudeAngle >= 0.0f ? 1.0f : 0.0f);
1097 const float Strength = FMath::Clamp(NeutralizeAmount, 0.0f, 1.0f)
1098 * FMath::Clamp(Cloudiness / 100.0f, 0.0f, 1.0f) * DayFade;
1099 if (Atmosphere == nullptr || Strength <= 0.0f)
1100 return FLinearColor::White;
1101
1102 // Held at the horizon or above: a ray through the ground comes back black.
1103 FAtmosphereSetup Setup(*Atmosphere);
1104 Setup.TransmittanceMinLightElevationAngle = 0.0f;
1105 const float Elevation = FMath::DegreesToRadians(FMath::Max(SunAltitudeAngle, 0.0f));
1106 const FLinearColor TSun = Setup.GetTransmittanceAtGroundLevel(
1107 FVector(FMath::Cos(Elevation), 0.0f, FMath::Sin(Elevation)));
1108 // Relative to the zenith, or every overcast midday would turn blue.
1109 const FLinearColor TZenith = Setup.GetTransmittanceAtGroundLevel(FVector::UpVector);
1110 const FLinearColor T(
1111 TSun.R / FMath::Max(TZenith.R, UE_KINDA_SMALL_NUMBER),
1112 TSun.G / FMath::Max(TZenith.G, UE_KINDA_SMALL_NUMBER),
1113 TSun.B / FMath::Max(TZenith.B, UE_KINDA_SMALL_NUMBER));
1114 const float Luminance = T.GetLuminance();
1115 if (Luminance <= UE_KINDA_SMALL_NUMBER)
1116 return FLinearColor::White;
1117
1118 const float MaxGain = FMath::Max(CVarCarlaWeatherOvercastAmbientMaxGain.GetValueOnGameThread(), 1.0f);
1119 FLinearColor Gain(
1120 FMath::Min(Luminance / FMath::Max(T.R, UE_KINDA_SMALL_NUMBER), MaxGain),
1121 FMath::Min(Luminance / FMath::Max(T.G, UE_KINDA_SMALL_NUMBER), MaxGain),
1122 FMath::Min(Luminance / FMath::Max(T.B, UE_KINDA_SMALL_NUMBER), MaxGain),
1123 1.0f);
1124 const float GainLuminance = Gain.GetLuminance();
1125 if (GainLuminance > UE_KINDA_SMALL_NUMBER)
1126 Gain = Gain * (1.0f / GainLuminance);
1127 Gain.A = 1.0f;
1128 FLinearColor Tint = FMath::Lerp(FLinearColor::White, Gain, Strength);
1129 const float Peak = FMath::Max3(Tint.R, Tint.G, Tint.B);
1130 if (Peak > 1.0f)
1131 {
1132 Tint = Tint * (1.0f / Peak);
1133 OutIntensityScale = Peak;
1134 }
1135 Tint.A = 1.0f;
1136 return Tint;
1137}
1138
1139static TAutoConsoleVariable<float> CVarCarlaWeatherFogDensityScale(
1140 TEXT("carla.Weather.FogDensityScale"),
1141 0.001f,
1142 TEXT("Multiplier taking CARLA's 0-100 FogDensity to the height fog component's density. ")
1143 TEXT("The shipped 0.001 caps the engine at 0.1, too thin for a heavy-rain veil."),
1144 ECVF_Default);
1145
1146static TAutoConsoleVariable<float> CVarCarlaWeatherCloudShadowExtentKm(
1147 TEXT("carla.Weather.CloudShadowExtentKm"),
1148 10.0f,
1149 TEXT("Radius in km covered by the volumetric cloud shadow map, set on the sky rig's sun ")
1150 TEXT("light. Smaller means finer shadow map texels and less stepping as the sun rotates, ")
1151 TEXT("at the cost of clouds no longer shadowing anything further away. 0 or less leaves ")
1152 TEXT("whatever the rig authored."),
1153 ECVF_Default);
1154
1155AWeather::AWeather(const FObjectInitializer& ObjectInitializer)
1156 : Super(ObjectInitializer)
1157{
1158 PrecipitationPostProcessMaterial = ConstructorHelpers::FObjectFinder<UMaterial>(
1159 TEXT("Material'/Game/Carla/Static/GenericMaterials/FX/ScreenDust/M_LensRain.M_LensRain'")).Object;
1160
1161 DustStormPostProcessMaterial = ConstructorHelpers::FObjectFinder<UMaterial>(
1162 TEXT("Material'/Game/Carla/Static/GenericMaterials/FX/ScreenDust/M_screenDust_wind.M_screenDust_wind'")).Object;
1163
1164 RainParameters = ConstructorHelpers::FObjectFinder<UMaterialParameterCollection>(
1165 TEXT("/Game/Carla/Blueprints/Weather/Materials/WeatherMaterialParameters.WeatherMaterialParameters")).Object;
1166 RainTemplate = ConstructorHelpers::FObjectFinder<UParticleSystem>(
1167 TEXT("/Game/Carla/Static/FX/Particles/Rain/PS_Rain.PS_Rain")).Object;
1168 PrimaryActorTick.bCanEverTick = true;
1169 PrimaryActorTick.TickGroup = TG_PostPhysics;
1170 RootComponent = ObjectInitializer.CreateDefaultSubobject<USceneComponent>(this, TEXT("RootComponent"));
1171}
1172
1174 float& BlendWeight, FPostProcessSettings& Settings)
1175{
1176 if (RainMaterial && RainWeight > 0.0f)
1177 {
1178 Settings.AddBlendable(RainMaterial, RainWeight);
1179 BlendWeight = 1.0f;
1180 }
1181}
1182
1183UParticleSystemComponent* AWeather::CreateRainEmitter(AActor* Owner, bool bSensor)
1184{
1185 if (!RainTemplate)
1186 return nullptr;
1187 UParticleSystemComponent* Emitter = nullptr;
1188 if (bSensor)
1189 {
1190 // RGB blueprints already attach PS_Rain. Adopt it, including its BP
1191 // references, rather than creating duplicate rain or invalidating them.
1192 TInlineComponentArray<UParticleSystemComponent*> Components(Owner);
1193 for (auto* Component : Components)
1194 if (Component->Template == RainTemplate)
1195 {
1196 Emitter = Component;
1197 break;
1198 }
1199 }
1200 const bool bNewComponent = Emitter == nullptr;
1201 if (bNewComponent)
1202 Emitter = NewObject<UParticleSystemComponent>(Owner);
1203 Emitter->DetachFromComponent(FDetachmentTransformRules::KeepWorldTransform);
1204 Emitter->bAutoActivate = false;
1205 Emitter->bAutoDestroy = false;
1206 Emitter->SetTemplate(RainTemplate);
1207 // Each capture sees only its own volume. The spectator volume is excluded
1208 // from captures, so adding cameras never increases another view's density.
1209 Emitter->SetOnlyOwnerSee(bSensor);
1210 Emitter->SetVisibleInSceneCaptureOnly(bSensor);
1211 Emitter->SetHiddenInSceneCapture(!bSensor);
1212 Emitter->SetCastShadow(false);
1213 if (bNewComponent)
1214 {
1215 Owner->AddInstanceComponent(Emitter);
1216 Emitter->RegisterComponent();
1217 }
1218 Emitter->SetWorldScale3D(FVector(2.0f));
1219 if (auto* Material = Emitter->CreateDynamicMaterialInstance(0))
1220 {
1221 Material->SetScalarParameterValue(TEXT("StreakBrightness"), 1.2f);
1222 Material->SetScalarParameterValue(TEXT("StreakOpacity"), 0.65f);
1223 }
1224 return Emitter;
1225}
1226
1228{
1229 if (!GetWorld() || !GetWorld()->IsGameWorld())
1230 return;
1231
1232 const float Rain = FMath::Clamp(Weather.Precipitation / 100.0f, 0.0f, 1.0f);
1233 // Explicitly drive the lens collection without depending on a town's sky BP.
1234 if (RainParameters)
1235 UKismetMaterialLibrary::SetScalarParameterValue(GetWorld(), RainParameters, TEXT("Precipitation"), Rain);
1236
1237 auto UpdateEmitter = [this, Rain](UParticleSystemComponent* Emitter, const FVector& Location, float Yaw)
1238 {
1239 if (!Emitter) return;
1240 // PS_Rain's scaled spawn box is X[-1000,4000], Y[-2000,2000] cm. Turned with
1241 // the view so it reaches 40 m ahead of it: left axis-aligned it rained on
1242 // one side of the camera only, and the drops looked parked in one place.
1243 // World-space, vertically falling drops.
1244 const FRotator Heading(0.0f, Yaw, 0.0f);
1245 const FVector Origin = Location + FVector(0.0f, 0.0f, 400.0f);
1246 Emitter->SetWorldRotation(Heading);
1247 if (FVector::DistSquared(Emitter->GetComponentLocation(), Origin) > FMath::Square(2000.0f))
1248 Emitter->DeactivateImmediate(); // Do not leave a trail after teleporting.
1249 Emitter->SetWorldLocation(Origin);
1250 Emitter->SetFloatParameter(TEXT("RainDensity"), Rain * (4000.0f / 0.85f));
1251 // Legacy distribution maps Z [0,1] to [0,-1000] cm/s. Negative input clamps to zero.
1252 Emitter->SetVectorParameter(TEXT("RainVelocity"),
1253 FVector(FMath::Clamp(Weather.WindIntensity, 0.0f, 100.0f) * 3.0f, 0.0f, 1.0f));
1254 if (Rain <= 0.0f)
1255 Emitter->DeactivateImmediate();
1256 else if (!Emitter->IsActive())
1257 Emitter->ActivateSystem(true);
1258 };
1259
1260 if (auto* Camera = UGameplayStatics::GetPlayerCameraManager(GetWorld(), 0))
1261 {
1262 if (!IsValid(RainCameraModifier) || RainCameraModifier->GetOuter() != Camera)
1263 {
1265 if (auto* PreviousCamera = Cast<APlayerCameraManager>(RainCameraModifier->GetOuter()))
1266 PreviousCamera->RemoveCameraModifier(RainCameraModifier);
1267 RainCameraModifier = Cast<UCarlaRainCameraModifier>(
1268 Camera->AddNewCameraModifier(UCarlaRainCameraModifier::StaticClass()));
1269 }
1271 {
1274 }
1275 if (!ViewportRain && Rain > 0.0f)
1276 ViewportRain = CreateRainEmitter(this, false);
1277 UpdateEmitter(ViewportRain, Camera->GetCameraLocation(), Camera->GetCameraRotation().Yaw);
1278 }
1279 else if (ViewportRain)
1280 ViewportRain->DeactivateImmediate();
1281
1282 for (auto It = SensorRain.CreateIterator(); It; ++It)
1283 if (!It.Key().IsValid() || !It.Value().IsValid())
1284 {
1285 if (It.Value().IsValid()) It.Value()->DestroyComponent();
1286 It.RemoveCurrent();
1287 }
1288 for (TActorIterator<ASceneCaptureCamera> It(GetWorld()); It; ++It)
1289 {
1290 auto* Sensor = *It;
1291 auto* Emitter = SensorRain.FindRef(Sensor).Get();
1292 if (!Emitter && Rain > 0.0f)
1293 {
1294 Emitter = CreateRainEmitter(Sensor, true);
1295 SensorRain.Add(Sensor, Emitter);
1296 }
1297 UpdateEmitter(Emitter, Sensor->GetCaptureComponent2D()->GetComponentLocation(),
1298 Sensor->GetCaptureComponent2D()->GetComponentRotation().Yaw);
1299 }
1300}
1301
1302void AWeather::Tick(float DeltaSeconds)
1303{
1304 Super::Tick(DeltaSeconds);
1305 UpdateRain();
1308}
1309
1311{
1312 Super::BeginPlay();
1313 // BP_CarlaWeather is saved with "Start with Tick Enabled" off, which left
1314 // UpdateRain dead: no rain volume followed the spectator or the sensors.
1315 SetActorTickEnabled(true);
1316}
1317
1318void AWeather::EndPlay(const EEndPlayReason::Type EndPlayReason)
1319{
1322 GLastPushedSunAltitude = TNumericLimits<float>::Max();
1323 GLastPushedCloudiness = TNumericLimits<float>::Max();
1324 for (auto& Entry : SensorRain)
1325 if (Entry.Value.IsValid()) Entry.Value->DestroyComponent();
1326 SensorRain.Empty();
1328 if (auto* Camera = Cast<APlayerCameraManager>(RainCameraModifier->GetOuter()))
1329 Camera->RemoveCameraModifier(RainCameraModifier);
1330 RainCameraModifier = nullptr;
1331 Super::EndPlay(EndPlayReason);
1332}
1333
1335{
1336 if (Weather.Precipitation > 0.0f)
1338 else
1340
1341 if (Weather.DustStorm > 0.0f)
1343 else
1345
1346 TArray<AActor*> SensorActors;
1347 UGameplayStatics::GetAllActorsOfClass(GetWorld(), ASceneCaptureCamera::StaticClass(), SensorActors);
1348 for (AActor* SensorActor : SensorActors)
1349 {
1350 ASceneCaptureCamera* Sensor = Cast<ASceneCaptureCamera>(SensorActor);
1351 // Removing a material from ActiveBlendables does not remove it from
1352 // an existing camera. Clear stale weather passes when rain/dust stops;
1353 // leave unrelated camera post-process materials intact.
1355 Sensor->GetCaptureComponent2D()->PostProcessSettings.RemoveBlendable(PrecipitationPostProcessMaterial);
1357 Sensor->GetCaptureComponent2D()->PostProcessSettings.RemoveBlendable(DustStormPostProcessMaterial);
1358 for (auto& ActiveBlendable : ActiveBlendables)
1359 Sensor->GetCaptureComponent2D()->PostProcessSettings.AddBlendable(ActiveBlendable.Key, ActiveBlendable.Value);
1360 }
1361}
1362
1363// Environment-map override for the sky light (set_sky_light_map RPC). The
1364// cubemap lives in the transient package and is rooted while set, but the
1365// override is scoped to the world it was set on: it is re-applied to that
1366// world's sky rig on every weather push, and dropped when that world is
1367// cleaned up (load_world / reload_world), so a freshly loaded level always
1368// starts on its own real-time atmosphere capture -- a client that loaded a
1369// new map never asked for the previous map's lighting.
1370namespace
1371{
1372 struct FSkyLightMapOverride
1373 {
1374 UTextureCube* Cubemap = nullptr;
1375 float Intensity = 1.0f;
1376 TWeakObjectPtr<UWorld> World;
1377 };
1378 FSkyLightMapOverride GSkyLightMapOverride;
1379 FDelegateHandle GSkyLightMapWorldCleanupHandle;
1380
1381 bool SkyLightMapAppliesTo(const UWorld* World)
1382 {
1383 return GSkyLightMapOverride.Cubemap != nullptr && World != nullptr &&
1384 GSkyLightMapOverride.World.Get() == World;
1385 }
1386
1387 // What the override rewrites on a sky light, saved the first time it is
1388 // applied to that component and put back by ClearSkyLightMap. The
1389 // real-time capture honours bLowerHemisphereIsBlack too, so leaving it
1390 // cleared would change the rig's ambient after the map is removed.
1391 struct FSkyLightSavedState
1392 {
1393 TEnumAsByte<ESkyLightSourceType> SourceType;
1394 bool bLowerHemisphereIsBlack;
1395 };
1396 TMap<TWeakObjectPtr<USkyLightComponent>, FSkyLightSavedState> GSkyLightSavedStates;
1397
1398 void ApplySkyLightMapToComponent(USkyLightComponent* SkyLightComponent)
1399 {
1400 if (!GSkyLightSavedStates.Contains(SkyLightComponent))
1401 {
1402 GSkyLightSavedStates.Add(SkyLightComponent,
1403 {SkyLightComponent->SourceType, SkyLightComponent->bLowerHemisphereIsBlack});
1404 }
1405 if (SkyLightComponent->bRealTimeCapture)
1406 SkyLightComponent->SetRealTimeCaptureEnabled(false);
1407 if (SkyLightComponent->SourceType != SLS_SpecifiedCubemap)
1408 {
1409 SkyLightComponent->SourceType = SLS_SpecifiedCubemap;
1410 SkyLightComponent->MarkRenderStateDirty();
1411 SkyLightComponent->SetCaptureIsDirty();
1412 }
1413 if (SkyLightComponent->bLowerHemisphereIsBlack)
1414 {
1415 SkyLightComponent->bLowerHemisphereIsBlack = false;
1416 SkyLightComponent->MarkRenderStateDirty();
1417 }
1418 SkyLightComponent->SetSourceCubemapAngle(0.0f);
1419 SkyLightComponent->SetCubemap(GSkyLightMapOverride.Cubemap);
1420 if (SkyLightComponent->Cubemap != GSkyLightMapOverride.Cubemap)
1421 {
1422 // SetCubemap is a no-op on a Static-mobility light; the rig's
1423 // skylight is meant to be Stationary, but do not depend on it.
1424 SkyLightComponent->Cubemap = GSkyLightMapOverride.Cubemap;
1425 }
1426 // Always re-process: the cubemap contents may be new even when the
1427 // pointer is not (see SkyLightMap.cpp on object naming).
1428 SkyLightComponent->MarkRenderStateDirty();
1429 SkyLightComponent->SetCaptureIsDirty();
1430 SkyLightComponent->SetLightColor(FLinearColor::White);
1431 SkyLightComponent->SetIntensity(GSkyLightMapOverride.Intensity);
1432 if (!SkyLightComponent->IsActive())
1433 SkyLightComponent->SetActive(true);
1434 }
1435
1436 // Forget the override: unroot the cubemap (the transient object is then
1437 // collected with the next GC pass) and drop the saved component states,
1438 // whose weak keys are dead once the owning world has gone anyway.
1439 void DropSkyLightMapOverride()
1440 {
1441 if (GSkyLightMapOverride.Cubemap != nullptr)
1442 GSkyLightMapOverride.Cubemap->RemoveFromRoot();
1443 GSkyLightMapOverride = FSkyLightMapOverride{};
1444 GSkyLightSavedStates.Empty();
1445 if (GSkyLightMapWorldCleanupHandle.IsValid())
1446 {
1447 FWorldDelegates::OnWorldCleanup.Remove(GSkyLightMapWorldCleanupHandle);
1448 GSkyLightMapWorldCleanupHandle.Reset();
1449 }
1450 }
1451
1452 // load_world / reload_world tear the current world down through
1453 // UWorld::CleanupWorld before the next level is brought up; that is the
1454 // end of the override's scope. Other worlds (the editor world, a level
1455 // instance) are not ours and are left alone.
1456 void OnSkyLightMapWorldCleanup(UWorld* World, bool /*bSessionEnded*/, bool /*bCleanupResources*/)
1457 {
1458 if (World == nullptr || GSkyLightMapOverride.World.Get() != World)
1459 return;
1460 DropSkyLightMapOverride();
1461 UE_LOG(LogCarla, Log, TEXT("AWeather: sky light map dropped with world %s"), *World->GetName());
1462 }
1463}
1464
1465bool AWeather::SetSkyLightMap(UWorld* World, UTextureCube* Cubemap, float Intensity)
1466{
1467 if (Cubemap == nullptr || World == nullptr)
1468 return false;
1469 if (GSkyLightMapOverride.Cubemap != nullptr && GSkyLightMapOverride.Cubemap != Cubemap)
1470 GSkyLightMapOverride.Cubemap->RemoveFromRoot();
1471 Cubemap->AddToRoot();
1472 if (GSkyLightMapOverride.World.Get() != World)
1473 {
1474 // Setting on a different world than the previous override: whatever
1475 // that world's rigs remembered is gone with it.
1476 GSkyLightSavedStates.Empty();
1477 }
1478 GSkyLightMapOverride.Cubemap = Cubemap;
1479 GSkyLightMapOverride.Intensity = Intensity;
1480 GSkyLightMapOverride.World = World;
1481 if (!GSkyLightMapWorldCleanupHandle.IsValid())
1482 GSkyLightMapWorldCleanupHandle = FWorldDelegates::OnWorldCleanup.AddStatic(&OnSkyLightMapWorldCleanup);
1483
1484 int32 Applied = 0;
1485 TArray<AActor*> SkyActors;
1486 UGameplayStatics::GetAllActorsOfClass(World, ASkyBase::StaticClass(), SkyActors);
1487 for (AActor* SkyActor : SkyActors)
1488 {
1489 ASkyBase* Sky = Cast<ASkyBase>(SkyActor);
1490 if (USkyLightComponent* SkyLightComponent = Sky != nullptr ? Sky->GetSkyLightComponent() : nullptr)
1491 {
1492 ApplySkyLightMapToComponent(SkyLightComponent);
1493 ++Applied;
1494 }
1495 }
1496 UE_LOG(LogCarla, Log, TEXT("AWeather: sky light map set (intensity %.3f) on %d sky rig(s)"), Intensity, Applied);
1497 return Applied > 0;
1498}
1499
1501{
1502 // Restore the rigs first (the saved states go away with the override).
1503 TArray<AActor*> SkyActors;
1504 UGameplayStatics::GetAllActorsOfClass(World, ASkyBase::StaticClass(), SkyActors);
1505 for (AActor* SkyActor : SkyActors)
1506 {
1507 ASkyBase* Sky = Cast<ASkyBase>(SkyActor);
1508 if (USkyLightComponent* SkyLightComponent = Sky != nullptr ? Sky->GetSkyLightComponent() : nullptr)
1509 {
1510 SkyLightComponent->SetCubemap(nullptr);
1511 if (const FSkyLightSavedState* Saved = GSkyLightSavedStates.Find(SkyLightComponent))
1512 {
1513 SkyLightComponent->SourceType = Saved->SourceType;
1514 SkyLightComponent->bLowerHemisphereIsBlack = Saved->bLowerHemisphereIsBlack;
1515 SkyLightComponent->MarkRenderStateDirty();
1516 SkyLightComponent->SetCaptureIsDirty();
1517 }
1518 SkyLightComponent->SetRealTimeCaptureEnabled(true);
1519 }
1520 }
1521 DropSkyLightMapOverride();
1522 // Restores the curve intensity and the real-time capture through the
1523 // normal weather push.
1524 if (UCarlaEpisode* Episode = UCarlaStatics::GetCurrentEpisode(World))
1525 if (AWeather* Weather = Episode->GetWeather())
1526 Weather->PushWeatherToSky();
1527 UE_LOG(LogCarla, Log, TEXT("AWeather: sky light map cleared"));
1528}
1529
1531{
1532 return GSkyLightMapOverride.Cubemap != nullptr;
1533}
1534
1536{
1537 TArray<AActor*> SkyActors;
1538 UGameplayStatics::GetAllActorsOfClass(GetWorld(), ASkyBase::StaticClass(), SkyActors);
1539 for (AActor* SkyActor : SkyActors)
1541}
1542
1544{
1545 if (SkyActor == nullptr)
1546 return;
1547
1548 {
1549 // Blueprint member variables get decorated FNames in their generated
1550 // class (exact FindPropertyByName misses them), so match on the
1551 // authored name instead -- same lesson as the vehicle "Beam Lights"
1552 // variable in CarlaWheeledVehicle.cpp. Shared by every reflection
1553 // lookup in this function.
1554 const auto FindPropertyByAuthoredName =
1555 [](const UClass* Class, const TCHAR* AuthoredName) -> FProperty*
1556 {
1557 for (TFieldIterator<FProperty> It(Class); It; ++It)
1558 {
1559 if ((*It)->GetAuthoredName() == AuthoredName || (*It)->GetName() == AuthoredName)
1560 return *It;
1561 }
1562 return nullptr;
1563 };
1564
1565 // The rig stores the parameters it renders from in a blueprint variable
1566 // and refreshes every component from its blueprint Update function.
1567 // Matched by type via ASkyBase::FindWeatherParameters, not by name --
1568 // a name-based lookup here silently broke (no warning triggered,
1569 // because "SkyParameters"/"Sky Parameters" both still resolved to
1570 // *something* is wrong; it just returned null and this whole write
1571 // no-opped) the moment that Blueprint variable got renamed to
1572 // "WeatherParameters". Precipitation/Wetness visuals -- driven by the
1573 // legacy UpdateAtmosphereAndPrecipitation Blueprint call below, which
1574 // reads this struct rather than taking Weather as a parameter -- are
1575 // what silently went stale; native pushes further down (sun, clouds,
1576 // fog, exposure) read straight from the Weather parameter and were
1577 // never affected.
1578 if (FWeatherParameters* SkyParameters = ASkyBase::FindWeatherParameters(SkyActor))
1579 *SkyParameters = Weather;
1580 else
1581 UE_LOG(LogCarla, Warning, TEXT("AWeather: no WeatherParameters property on %s"),
1582 *SkyActor->GetName());
1583
1584 // The rig's blueprint 'Update' only reaches UpdateClouds (the rest of
1585 // its exec chain was never wired in the ue5-dev content rework), so
1586 // run the individual refresh functions in dependency order instead.
1587 // NOTE: the blueprint also has an UpdateNight function, deliberately
1588 // NOT in this list: it multiplies the sky sphere material's current
1589 // "Horizon color" by ~0.004 in place, i.e. it was authored to run
1590 // exactly once at a day->night transition. Running it on every
1591 // weather push collapses the sphere to pure black (no stars, no
1592 // horizon glow) after a single call. The sphere is respawned fresh
1593 // by SetSkySphere each push, and UpdateSkySphereColor plus the
1594 // sphere's own RefreshMaterial already produce the day/night look
1595 // from absolute curve values.
1596 static const TCHAR* UpdateFunctionNames[] = {
1597 TEXT("SetSunActorReference"),
1598 TEXT("SetSkySphere"),
1599 TEXT("UpdateSun"),
1600 TEXT("UpdateClouds"),
1601 TEXT("UpdateFog"),
1602 TEXT("UpdateMoon"),
1603 TEXT("UpdateAtmosphereAndPrecipitation"),
1604 TEXT("UpdateSkySphere"),
1605 TEXT("UpdateSkySphereColor")};
1606 for (const TCHAR* FunctionName : UpdateFunctionNames)
1607 {
1608 UFunction* Function = SkyActor->FindFunction(FunctionName);
1609 if (Function != nullptr && Function->ParmsSize == 0)
1610 SkyActor->ProcessEvent(Function, nullptr);
1611 else
1612 UE_LOG(LogCarla, Warning, TEXT("AWeather: skipping '%s' on %s (missing or has parameters)"),
1613 FunctionName, *SkyActor->GetName());
1614 }
1615
1616 // SetSkySphere (just called above) spawns a fresh stock-engine
1617 // BP_Sky_Sphere and tries to attach it to this rig's root
1618 // (PostProcessComponent), but that root's Mobility is Movable while
1619 // the stock sphere's root is Static -- UE refuses to attach a Static
1620 // child to a non-Static parent ("AttachTo: ... is not static ...
1621 // Aborting", logged every push). The spawn itself still succeeds and
1622 // "SkySphere" still gets assigned, so this is silent rather than
1623 // fatal: the sphere just renders unattached at wherever it spawned
1624 // instead of following the rig. Finish the attach here natively,
1625 // forcing the spawned instance's root to Movable first (a fresh
1626 // stock actor with no other purpose -- safe to change).
1627 {
1628 FObjectProperty* SphereProperty = CastField<FObjectProperty>(
1629 FindPropertyByAuthoredName(SkyActor->GetClass(), TEXT("SkySphere")));
1630 if (SphereProperty == nullptr)
1631 SphereProperty = CastField<FObjectProperty>(
1632 FindPropertyByAuthoredName(SkyActor->GetClass(), TEXT("Sky Sphere")));
1633 AActor* SphereActor = SphereProperty != nullptr
1634 ? Cast<AActor>(SphereProperty->GetObjectPropertyValue_InContainer(SkyActor))
1635 : nullptr;
1636 USceneComponent* SphereRoot = SphereActor != nullptr ? SphereActor->GetRootComponent() : nullptr;
1637 if (SphereRoot != nullptr && SphereRoot->GetAttachParent() == nullptr)
1638 {
1639 if (SphereRoot->Mobility == EComponentMobility::Static)
1640 SphereRoot->SetMobility(EComponentMobility::Movable);
1641 SphereActor->AttachToActor(SkyActor, FAttachmentTransformRules::KeepWorldTransform);
1642 }
1643
1644 // The sphere's painted sun follows its "Directional Light Actor", which is
1645 // unset here, so the parameters are written directly. The sphere is reached
1646 // through the rig's child actors.
1647 {
1648 const FVector SunDirection =
1649 -FRotator(-Weather.SunAltitudeAngle, Weather.SunAzimuthAngle, 0.0f).Vector();
1650
1651 TArray<AActor*> SkyCandidates;
1652 if (SphereActor != nullptr)
1653 SkyCandidates.Add(SphereActor);
1654 TInlineComponentArray<UChildActorComponent*> RigChildActors;
1655 SkyActor->GetComponents(RigChildActors);
1656 for (UChildActorComponent* RigChild : RigChildActors)
1657 if (RigChild != nullptr && RigChild->GetChildActor() != nullptr)
1658 SkyCandidates.AddUnique(RigChild->GetChildActor());
1659
1660 for (AActor* Candidate : SkyCandidates)
1661 {
1662 // Written on the blueprint variable as well as the material, since
1663 // RefreshMaterial pushes the variable into the material.
1664 if (FNumericProperty* SunHeightProperty = CastField<FNumericProperty>(
1665 FindPropertyByAuthoredName(Candidate->GetClass(), TEXT("Sun Height"))))
1666 {
1667 SunHeightProperty->SetFloatingPointPropertyValue(
1668 SunHeightProperty->ContainerPtrToValuePtr<void>(Candidate),
1669 SunDirection.Z);
1670 }
1671
1672 TInlineComponentArray<UMeshComponent*> SphereMeshes;
1673 Candidate->GetComponents(SphereMeshes);
1674 for (UMeshComponent* SphereMesh : SphereMeshes)
1675 {
1676 if (SphereMesh == nullptr)
1677 continue;
1678
1679 // The sphere occludes the atmosphere, so it is only shown at night.
1680 const float ShowBelow = FMath::Min(
1681 CVarCarlaWeatherSkySphereShowDeg.GetValueOnGameThread(), 0.0f);
1682 const bool bShouldBeVisible = Weather.SunAltitudeAngle < ShowBelow;
1683 if (SphereMesh->IsVisible() != bShouldBeVisible)
1684 SphereMesh->SetVisibility(bShouldBeVisible);
1685
1686 // Tagged once scaled: a push that did not respawn the sphere
1687 // must not compound the scale.
1688 static const FName ScaledTag(TEXT("CarlaSkySphereScaled"));
1689 const float SphereScale = CVarCarlaWeatherSkySphereScale.GetValueOnGameThread();
1690 if (SphereScale > 0.0f && SphereScale != 1.0f && !SphereMesh->ComponentHasTag(ScaledTag))
1691 {
1692 SphereMesh->SetRelativeScale3D(SphereMesh->GetRelativeScale3D() * SphereScale);
1693 SphereMesh->ComponentTags.Add(ScaledTag);
1694 }
1695
1696 // Reuses the existing dynamic instance; unknown parameters are a no-op.
1697 if (UMaterialInstanceDynamic* SphereMID =
1698 SphereMesh->CreateAndSetMaterialInstanceDynamic(0))
1699 {
1700 SphereMID->SetVectorParameterValue(
1701 TEXT("Light direction"), FLinearColor(SunDirection));
1702 SphereMID->SetScalarParameterValue(TEXT("Sun height"), SunDirection.Z);
1703 const float CloudOpacity = CVarCarlaWeatherSkySphereCloudOpacity.GetValueOnGameThread();
1704 if (CloudOpacity >= 0.0f)
1705 SphereMID->SetScalarParameterValue(TEXT("Cloud opacity"), CloudOpacity);
1706 }
1707 }
1708 }
1709 }
1710
1711 // PIE start reliably left duplicate attached actors -- not just
1712 // the sphere (SetSkySphere's respawn: destroy-old, spawn-new,
1713 // doesn't reliably find/destroy the previous one across
1714 // PIE's construction timing), but independently a duplicate
1715 // DirectionalLight too (SetSunActorReference, called earlier in
1716 // the UpdateFunctionNames loop above, manages its own actor
1717 // reference the same lossy way) -- tripping the "multiple
1718 // directional lights competing" render warning. Rather than
1719 // chase the exact PIE timing for each function separately, make
1720 // this self-healing and general: whatever ends up attached to
1721 // this rig, keep at most one instance per class.
1722 {
1723 TMap<UClass*, TArray<AActor*>> AttachedByClass;
1724 TArray<AActor*> AttachedToSky;
1725 SkyActor->GetAttachedActors(AttachedToSky);
1726 for (AActor* AttachedActor : AttachedToSky)
1727 if (AttachedActor != nullptr)
1728 AttachedByClass.FindOrAdd(AttachedActor->GetClass()).Add(AttachedActor);
1729
1730 for (const TPair<UClass*, TArray<AActor*>>& Pair : AttachedByClass)
1731 {
1732 const TArray<AActor*>& Instances = Pair.Value;
1733 // Keep the one "SkySphere" actually points to when this
1734 // is its class; otherwise keep whichever is last (order
1735 // is not meaningful here, just needs to be consistent).
1736 AActor* ToKeep = (SphereActor != nullptr && Instances.Contains(SphereActor))
1737 ? SphereActor : Instances.Last();
1738 for (AActor* Instance : Instances)
1739 {
1740 if (Instance != ToKeep)
1741 {
1742 TArray<AActor*> OrphanChildren;
1743 Instance->GetAttachedActors(OrphanChildren);
1744 for (AActor* OrphanChild : OrphanChildren)
1745 if (OrphanChild != nullptr)
1746 OrphanChild->Destroy();
1747 Instance->Destroy();
1748 }
1749 }
1750
1751 // SetSunActorReference (called earlier in the
1752 // UpdateFunctionNames loop) links a stock-engine
1753 // ADirectionalLight onto this rig, on top of our own
1754 // Sun/Moon components -- a THIRD directional light,
1755 // confirmed in the outliner ("DirectionalLight0") and over
1756 // the render warning ("Multiple directional lights are
1757 // competing..."). Neutralize the survivor the same way
1758 // Sky.cpp's constructor already does for the Moon: below
1759 // the Sun's ForwardShadingPriority, and out of the running
1760 // for SkyAtmosphere's single sun-light slot (every
1761 // DirectionalLightComponent defaults bAtmosphereSunLight
1762 // true -- left alone, this stray light could win that slot
1763 // over our real Sun, which is what actually broke
1764 // SkyAtmosphere/rendered a black sky once the sun rose,
1765 // independent of the render warning). Cheap and
1766 // idempotent, run every push like the dedup above.
1767 if (UDirectionalLightComponent* StrayLight =
1768 ToKeep != nullptr ? ToKeep->FindComponentByClass<UDirectionalLightComponent>() : nullptr)
1769 {
1770 if (StrayLight->ForwardShadingPriority != -1)
1771 StrayLight->SetForwardShadingPriority(-1);
1772 if (StrayLight->IsUsedAsAtmosphereSunLight())
1773 StrayLight->SetAtmosphereSunLight(false);
1774 }
1775 }
1776 }
1777
1778 // Their owning components are re-added on every push and recreate the actors,
1779 // so the components go too.
1780 {
1781 TInlineComponentArray<UChildActorComponent*> ChildActorComponents;
1782 SkyActor->GetComponents(ChildActorComponents);
1783 TMap<UClass*, TArray<UChildActorComponent*>> ComponentsByChildClass;
1784 for (UChildActorComponent* ChildComponent : ChildActorComponents)
1785 if (ChildComponent != nullptr && ChildComponent->GetChildActorClass() != nullptr)
1786 ComponentsByChildClass.FindOrAdd(ChildComponent->GetChildActorClass()).Add(ChildComponent);
1787
1788 for (const TPair<UClass*, TArray<UChildActorComponent*>>& Pair : ComponentsByChildClass)
1789 {
1790 const TArray<UChildActorComponent*>& Components = Pair.Value;
1791 if (Components.Num() < 2)
1792 continue;
1793 UChildActorComponent* ComponentToKeep = Components.Last();
1794 for (UChildActorComponent* ChildComponent : Components)
1795 {
1796 if (ChildComponent->GetChildActor() != nullptr
1797 && ChildComponent->GetChildActor() == SphereActor)
1798 {
1799 ComponentToKeep = ChildComponent;
1800 break;
1801 }
1802 }
1803 for (UChildActorComponent* ChildComponent : Components)
1804 if (ChildComponent != ComponentToKeep)
1805 ChildComponent->DestroyComponent();
1806 }
1807 }
1808 }
1809
1810 // The blueprint's UpdateFog runs, but the values it leaves on the
1811 // height fog component were saved for the old small towns and break
1812 // on large maps: a 1 km FogCutoffDistance erases fog on everything
1813 // beyond it (distant buildings render crisp while the mid ground is
1814 // fogged), the CARLA FogDistance parameter (meters) is applied raw as
1815 // centimeters so fog starts at the camera, and FogDensity/100 is 5x
1816 // Unreal's already-hazy default. Re-map the parameters here with the
1817 // content left untouched: meters to centimeters for the start
1818 // distance, no hard cutoff, and a density scale whose extremes keep
1819 // CARLA semantics (0 = clear, 100 = dense fog).
1820 FObjectProperty* FogProperty = CastField<FObjectProperty>(
1821 SkyActor->GetClass()->FindPropertyByName(TEXT("ExponentialHeightFogComponent")));
1822 UExponentialHeightFogComponent* FogComponent = FogProperty != nullptr
1823 ? Cast<UExponentialHeightFogComponent>(FogProperty->GetObjectPropertyValue_InContainer(SkyActor))
1824 : nullptr;
1825 if (FogComponent != nullptr)
1826 {
1827 FogComponent->SetFogDensity(
1828 Weather.FogDensity * CVarCarlaWeatherFogDensityScale.GetValueOnGameThread());
1829 FogComponent->SetStartDistance(Weather.FogDistance * 100.0f);
1830 FogComponent->SetFogCutoffDistance(0.0f);
1831 }
1832
1833 // The blueprint's ControlSunIntensity has a severed exec chain (its
1834 // entry sets SunTrayectory and dead-ends), so the curve-driven light
1835 // intensities never apply. Evaluate the curves directly and drive the
1836 // rig components; the curve assets remain the content-side tuning
1837 // source. Loaded by fixed path (BP_CarlaWeather's own default values
1838 // for these two variables) rather than read off an AWeather instance,
1839 // so this function works with no AWeather actor placed in the level
1840 // at all -- see ASkyBase::RefreshWeather/LoadPreset.
1841 // Loaded per call, never cached in function-local statics: a raw
1842 // static UObject pointer is invisible to the garbage collector, and
1843 // these assets are otherwise unreferenced once the world that first
1844 // loaded them is purged. With the old static cache, the second
1845 // episode of a session (e.g. any generate_opendrive_world load)
1846 // evaluated a freed UCurveFloat and crashed inside FRichCurve::Eval
1847 // (SIGSEGV in ApplyWeatherToSkyActor <- GameMode BeginPlay). While
1848 // the asset is alive LoadObject is a FindObject hit, so per-call
1849 // loading costs nothing measurable at weather-push frequency.
1850 auto FindCurve = [](const TCHAR* PropertyName) -> UCurveFloat*
1851 {
1852 if (FCString::Strcmp(PropertyName, TEXT("SunIntensity_Curve")) == 0)
1853 return LoadObject<UCurveFloat>(nullptr,
1854 TEXT("/Game/Carla/Blueprints/Weather/Weather2_Curves/SunIntensity_2.SunIntensity_2"));
1855 if (FCString::Strcmp(PropertyName, TEXT("SkyIntensity_Curve")) == 0)
1856 return LoadObject<UCurveFloat>(nullptr,
1857 TEXT("/Game/Carla/Blueprints/Weather/Weather2_Curves/SkylightIntensity_2.SkylightIntensity_2"));
1858 return nullptr;
1859 };
1860 auto FindComponent = [SkyActor](const TCHAR* PropertyName) -> ULightComponent*
1861 {
1862 FObjectProperty* ComponentProperty =
1863 CastField<FObjectProperty>(SkyActor->GetClass()->FindPropertyByName(PropertyName));
1864 return ComponentProperty != nullptr
1865 ? Cast<ULightComponent>(ComponentProperty->GetObjectPropertyValue_InContainer(SkyActor))
1866 : nullptr;
1867 };
1868 // USkyLightComponent is a sibling of ULightComponent (both derive from
1869 // ULightComponentBase, not from each other), and SetIntensity is
1870 // declared separately on each subclass -- it needs its own,
1871 // correctly-typed finder. The old FindComponent-based lookup below
1872 // always Cast<ULightComponent>'d the sky light to nullptr, so
1873 // SkyIntensity_Curve silently never reached the component; fixed here.
1874 auto FindSkyLightComponent = [SkyActor](const TCHAR* PropertyName) -> USkyLightComponent*
1875 {
1876 FObjectProperty* ComponentProperty =
1877 CastField<FObjectProperty>(SkyActor->GetClass()->FindPropertyByName(PropertyName));
1878 return ComponentProperty != nullptr
1879 ? Cast<USkyLightComponent>(ComponentProperty->GetObjectPropertyValue_InContainer(SkyActor))
1880 : nullptr;
1881 };
1882
1883 {
1884 const float SunIndirect = CVarCarlaWeatherSunIndirectIntensity.GetValueOnGameThread();
1885 ULightComponent* Sun = FindComponent(TEXT("DirectionalLightComponentSun"));
1886 if (Sun != nullptr && SunIndirect >= 0.0f && Sun->IndirectLightingIntensity != SunIndirect)
1887 Sun->SetIndirectLightingIntensity(SunIndirect);
1888 const float SkyIndirect = CVarCarlaWeatherSkyLightIndirectIntensity.GetValueOnGameThread();
1889 USkyLightComponent* SkyLight = FindSkyLightComponent(TEXT("SkyLightComponent"));
1890 if (SkyLight != nullptr && SkyIndirect >= 0.0f && SkyLight->IndirectLightingIntensity != SkyIndirect)
1891 SkyLight->SetIndirectLightingIntensity(SkyIndirect);
1892 }
1893
1894 if (UCurveFloat* SunIntensityCurve = FindCurve(TEXT("SunIntensity_Curve")))
1895 {
1896 if (ULightComponent* SunLightComponent = FindComponent(TEXT("DirectionalLightComponentSun")))
1897 {
1898 SunLightComponent->SetIntensity(ApplyOvercastBlend(
1900 SunIntensityCurve->GetFloatValue(Weather.SunAltitudeAngle),
1901 SampleCurvePeakAboveHorizon(SunIntensityCurve),
1903 CVarCarlaWeatherOvercastSunIntensity.GetValueOnGameThread(),
1904 Weather.Cloudiness, /*bOnlyDarken=*/true,
1905 SampleCurvePeakAboveHorizon(SunIntensityCurve)));
1906 // See the comment on CVarCarlaWeatherSunTwilightEndDeg.
1907 const float Twilight = ComputeSunTwilightFactor(Weather.SunAltitudeAngle);
1908 if (Twilight >= 0.0f && Twilight < 1.0f)
1909 {
1910 const float StartAltitude = CVarCarlaWeatherSunTwilightStartDeg.GetValueOnGameThread();
1911 const float AtStart = ApplyOvercastBlend(
1913 SunIntensityCurve->GetFloatValue(StartAltitude),
1914 SampleCurvePeakAboveHorizon(SunIntensityCurve),
1915 StartAltitude),
1916 CVarCarlaWeatherOvercastSunIntensity.GetValueOnGameThread(),
1917 Weather.Cloudiness, /*bOnlyDarken=*/true,
1918 SampleCurvePeakAboveHorizon(SunIntensityCurve));
1919 SunLightComponent->SetIntensity(FMath::Max(SunLightComponent->Intensity, AtStart * Twilight));
1920 // The rig's UpdateSun hides the sun below the horizon.
1921 if (Twilight > 0.0f && !SunLightComponent->IsVisible())
1922 SunLightComponent->SetVisibility(true);
1923 }
1924 // Rigs saved with a black light color render no sunlight at any
1925 // intensity; the physical tint comes from the color temperature.
1926 // White unless the overcast neutralisation has something to cancel.
1927 FObjectProperty* SunAtmosphereProperty = CastField<FObjectProperty>(
1928 SkyActor->GetClass()->FindPropertyByName(TEXT("SkyAtmosphereComponent")));
1929 const USkyAtmosphereComponent* SunAtmosphere = SunAtmosphereProperty != nullptr
1930 ? Cast<USkyAtmosphereComponent>(SunAtmosphereProperty->GetObjectPropertyValue_InContainer(SkyActor))
1931 : nullptr;
1932 float SunIntensityScale = 1.0f;
1933 SunLightComponent->SetLightColor(ComputeOvercastNeutralTint(
1935 CVarCarlaWeatherOvercastSunNeutralize.GetValueOnGameThread(), SunIntensityScale));
1936 if (SunIntensityScale != 1.0f)
1937 SunLightComponent->SetIntensity(SunLightComponent->Intensity * SunIntensityScale);
1938 }
1939 }
1940 // UpdateSun never applies a rotation. Pitch is negated because a
1941 // directional light shines along its forward vector.
1942 if (ULightComponent* SunLightComponent = FindComponent(TEXT("DirectionalLightComponentSun")))
1943 {
1944 const FRotator SunRotation(-Weather.SunAltitudeAngle, Weather.SunAzimuthAngle, 0.0f);
1945 SunLightComponent->SetWorldRotation(SunRotation);
1946 if (UDirectionalLightComponent* SunDirectional = Cast<UDirectionalLightComponent>(SunLightComponent))
1947 {
1948 const FLinearColor CloudScale = ComputeTwilightCloudScale(Weather.SunAltitudeAngle);
1949 if (!SunDirectional->CloudScatteredLuminanceScale.Equals(CloudScale))
1950 {
1951 SunDirectional->CloudScatteredLuminanceScale = CloudScale;
1952 SunDirectional->MarkRenderStateDirty();
1953 }
1954 }
1955 if (SkyActor->GetWorld() != nullptr && SkyActor->GetWorld()->IsGameWorld())
1956 {
1957 NoteSunRotation(SkyActor->GetWorld(), SunRotation);
1959 }
1960 }
1961
1962 // The moon likewise, anti-solar; pitch negated as for the sun. Held at
1963 // MoonMinAltitudeDeg or above.
1964 if (ULightComponent* MoonLightComponent = FindComponent(TEXT("DirectionalLightComponentMoon")))
1965 {
1966 const float MoonMinAltitude = FMath::Max(
1967 CVarCarlaWeatherMoonMinAltitudeDeg.GetValueOnGameThread(), 0.0f);
1968 const float MoonAltitude = FMath::Max(-Weather.SunAltitudeAngle, MoonMinAltitude);
1969 MoonLightComponent->SetWorldRotation(
1970 FRotator(-MoonAltitude, Weather.SunAzimuthAngle + 180.0f, 0.0f));
1971 }
1972
1973 // Set on every push: a push re-instances the components from their archetype.
1974 if (UDirectionalLightComponent* MoonAtmosphereLight =
1975 Cast<UDirectionalLightComponent>(FindComponent(TEXT("DirectionalLightComponentMoon"))))
1976 {
1977 const bool bWanted = ComputeNightBlend(Weather.SunAltitudeAngle) > 0.0f;
1978 // The setters dirty the render state themselves.
1979 if (MoonAtmosphereLight->AtmosphereSunLightIndex != 1)
1980 MoonAtmosphereLight->SetAtmosphereSunLightIndex(1);
1981 if (MoonAtmosphereLight->bAtmosphereSunLight != (bWanted ? 1u : 0u))
1982 MoonAtmosphereLight->SetAtmosphereSunLight(bWanted);
1983 const FLinearColor DiskScale = CVarCarlaWeatherMoonAtmosphereDisk.GetValueOnGameThread()
1984 ? FLinearColor::White : FLinearColor::Black;
1985 if (MoonAtmosphereLight->AtmosphereSunDiskColorScale != DiskScale)
1986 MoonAtmosphereLight->SetAtmosphereSunDiskColorScale(DiskScale);
1987 }
1988
1989 // Bounds the cloud shadow map's 512 texels to a radius that keeps them small
1990 // enough that a turning sun does not walk cloud density across them.
1991 if (UDirectionalLightComponent* SunDirectionalLight =
1992 Cast<UDirectionalLightComponent>(FindComponent(TEXT("DirectionalLightComponentSun"))))
1993 {
1994 bool bSunLightRenderStateDirty = false;
1995 const float CloudShadowExtentKm = CVarCarlaWeatherCloudShadowExtentKm.GetValueOnGameThread();
1996 if (CloudShadowExtentKm > 0.0f && SunDirectionalLight->CloudShadowExtent != CloudShadowExtentKm)
1997 {
1998 SunDirectionalLight->CloudShadowExtent = CloudShadowExtentKm;
1999 bSunLightRenderStateDirty = true;
2000 }
2001 if (bSunLightRenderStateDirty)
2002 {
2003 SunDirectionalLight->MarkRenderStateDirty();
2004 }
2005 }
2006 // The rig's Moon light ships with AffectsWorld off -- a disabled
2007 // light contributes nothing to the scene no matter what its
2008 // Intensity is set to below, which is why night always rendered
2009 // pitch black regardless of the moon/skylight floor cvars. Always
2010 // on, not just at night: harmless during the day (Intensity there is
2011 // whatever the curve/floor logic below leaves it at), and this way
2012 // there's nothing left to toggle when the sun crosses the horizon.
2013 if (ULightComponent* MoonLightComponentAffects = FindComponent(TEXT("DirectionalLightComponentMoon")))
2014 {
2015 if (!MoonLightComponentAffects->bAffectsWorld)
2016 {
2017 MoonLightComponentAffects->bAffectsWorld = true;
2018 MoonLightComponentAffects->MarkRenderStateDirty();
2019 }
2020 }
2021 // The rig ships EVERY component with bAutoActivate false on the
2022 // instance (the same UE4-era authoring defect documented for the moon
2023 // below and for street lamps in CarlaLight.cpp), and until now only
2024 // the night branch below ever re-activated the skylight -- so by day
2025 // the skylight was simply off and shadows received zero ambient light
2026 // against the physical 100k lux sun from SunIntensity_Curve: every
2027 // shaded surface rendered pitch black.
2028 //
2029 // Activating the skylight alone is not enough. Its authored source is
2030 // black (SLS_SpecifiedCubemap with a null cubemap) and the
2031 // bRealTimeCapture flag that overrides that source renders ONLY sky
2032 // components -- SkyAtmosphere, VolumetricCloud, IsSky-flagged meshes
2033 // -- into the capture. The displayed sky is the legacy sphere, which the
2034 // capture cannot see, and the rig's SkyAtmosphere is inactive, so the
2035 // capture rendered a black cubemap and no ambient at any intensity.
2036 // Activating the atmosphere gives the capture something to see; the
2037 // main view is unchanged, still showing the opaque sphere in front.
2038 //
2039 // Activation is irrelevant for the cloud component: the proxy is added
2040 // from ShouldComponentAddToScene() && ShouldRender() && IsRegistered(),
2041 // never from IsActive(). Visibility is the lever.
2042 {
2043 FObjectProperty* AtmosphereProperty = CastField<FObjectProperty>(
2044 SkyActor->GetClass()->FindPropertyByName(TEXT("SkyAtmosphereComponent")));
2045 UActorComponent* AtmosphereComponent = AtmosphereProperty != nullptr
2046 ? Cast<UActorComponent>(AtmosphereProperty->GetObjectPropertyValue_InContainer(SkyActor))
2047 : nullptr;
2048 if (AtmosphereComponent != nullptr && !AtmosphereComponent->IsActive())
2049 AtmosphereComponent->SetActive(true);
2050
2051 // Set every push: a push re-instances the component from its archetype.
2052 if (USkyAtmosphereComponent* Atmosphere = Cast<USkyAtmosphereComponent>(AtmosphereComponent))
2053 {
2054 // Released below the horizon, where the atmosphere's light is the moon.
2055 const float Release = CVarCarlaWeatherTransmittanceReleaseDeg.GetValueOnGameThread();
2056 const float Clamped = CVarCarlaWeatherTransmittanceMinElevationDeg.GetValueOnGameThread();
2057 float MinElevation = Clamped;
2058 if (Release < 0.0f)
2059 {
2060 const float T = FMath::Clamp(Weather.SunAltitudeAngle / Release, 0.0f, 1.0f);
2061 const float Eased = T * T * (3.0f - 2.0f * T); // smoothstep
2062 MinElevation = FMath::Lerp(Clamped, -90.0f, Eased);
2063 }
2064 if (!FMath::IsNearlyEqual(Atmosphere->TransmittanceMinLightElevationAngle, MinElevation))
2065 Atmosphere->SetTransmittanceMinLightElevationAngle(MinElevation);
2066 }
2067 }
2068 // Skylight: force active + real-time capture (the latter is already
2069 // set on the asset; enforced defensively in case a rig resave clears
2070 // it -- supported on Stationary mobility and on by default via
2071 // r.SkyLight.RealTimeReflectionCapture). SkyIntensity_Curve (1.0 by
2072 // day, 0 at night) remains the multiplier on the capture.
2073 if (USkyLightComponent* SkyLightComponent = FindSkyLightComponent(TEXT("SkyLightComponent")))
2074 {
2075 if (SkyLightMapAppliesTo(SkyActor->GetWorld()))
2076 {
2077 // Environment map set through set_sky_light_map on this
2078 // world: the measured panorama replaces the atmosphere
2079 // capture as the ambient and reflection source, and its
2080 // intensity is the caller's, not the curve's. Reapplied on
2081 // every weather push so that a set_weather call cannot
2082 // silently revert to the real-time capture. A rig in any
2083 // other world (a newly loaded level) takes the else branch.
2084 ApplySkyLightMapToComponent(SkyLightComponent);
2085 }
2086 else
2087 {
2088 if (!SkyLightComponent->bRealTimeCapture)
2089 SkyLightComponent->SetRealTimeCaptureEnabled(true);
2090 if (!SkyLightComponent->IsActive())
2091 SkyLightComponent->SetActive(true);
2092 if (UCurveFloat* SkyIntensityCurve = FindCurve(TEXT("SkyIntensity_Curve")))
2093 SkyLightComponent->SetIntensity(ApplyOvercastBlend(
2094 SkyIntensityCurve->GetFloatValue(Weather.SunAltitudeAngle),
2096 Weather.Cloudiness, /*bOnlyDarken=*/false, /*CurvePeak=*/0.0f));
2097 FObjectProperty* AtmosphereProperty = CastField<FObjectProperty>(
2098 SkyActor->GetClass()->FindPropertyByName(TEXT("SkyAtmosphereComponent")));
2099 const USkyAtmosphereComponent* Atmosphere = AtmosphereProperty != nullptr
2100 ? Cast<USkyAtmosphereComponent>(AtmosphereProperty->GetObjectPropertyValue_InContainer(SkyActor))
2101 : nullptr;
2102 float AmbientIntensityScale = 1.0f;
2103 SkyLightComponent->SetLightColor(ComputeOvercastNeutralTint(
2105 CVarCarlaWeatherOvercastAmbientNeutralize.GetValueOnGameThread(), AmbientIntensityScale));
2106 if (AmbientIntensityScale != 1.0f)
2107 SkyLightComponent->SetIntensity(SkyLightComponent->Intensity * AmbientIntensityScale);
2108 }
2109 UE_LOG(LogCarla, Verbose, TEXT(
2110 "AWeather sky light: active=%d intensity=%.3f realtimecapture=%d mobility=%d visible=%d"),
2111 SkyLightComponent->IsActive() ? 1 : 0,
2112 SkyLightComponent->Intensity,
2113 SkyLightComponent->IsRealTimeCaptureEnabled() ? 1 : 0,
2114 int(SkyLightComponent->Mobility.GetValue()),
2115 SkyLightComponent->IsVisible() ? 1 : 0);
2116 }
2117 else
2118 {
2119 UE_LOG(LogCarla, Warning, TEXT("AWeather: no SkyLightComponent found on %s"), *SkyActor->GetName());
2120 }
2121
2122 // Night ambient floor. BP_Carla_Sky's DirectionalLightComponentMoon
2123 // contributes nothing visible once the sun sets: either it carries
2124 // the same UE4-era authoring defect CarlaLight.cpp documents and
2125 // fixes for street lamps (bAutoActivate off on the component
2126 // instance, silently culled by UE5.8 instead of just ignored like
2127 // UE4), or its authored/curve-driven intensity is a physically-tiny
2128 // real-moonlight lux value that is invisible under this project's
2129 // fixed daylight-calibrated exposure -- or both. Address both
2130 // defensively: force the moon and sky light active, then clamp their
2131 // intensity up to a floor (never down, so this can never darken
2132 // whatever the curves already produced) whenever the sun is below
2133 // the horizon. See the cvar comments above for the exposure math
2134 // behind the default floor values.
2135 // The moon fades in on its own curve, starting above the horizon.
2136 const float MoonBlend = ComputeMoonBlend(Weather.SunAltitudeAngle);
2137 if (MoonBlend > 0.0f)
2138 {
2139 const float MoonFloor = CVarCarlaWeatherMoonIntensity.GetValueOnGameThread() * MoonBlend;
2140 if (ULightComponent* MoonLightComponent = FindComponent(TEXT("DirectionalLightComponentMoon")))
2141 {
2142 if (!MoonLightComponent->IsActive())
2143 MoonLightComponent->SetActive(true);
2144 // The rig's UpdateMoon hides the moon while the sun is up.
2145 if (!MoonLightComponent->IsVisible())
2146 MoonLightComponent->SetVisibility(true);
2147 // Set outright by day: nothing else lowers it as the sun climbs back.
2148 if (Weather.SunAltitudeAngle >= 0.0f)
2149 MoonLightComponent->SetIntensity(MoonFloor);
2150 else if (MoonFloor > 0.0f && MoonLightComponent->Intensity < MoonFloor)
2151 MoonLightComponent->SetIntensity(MoonFloor);
2152 }
2153 }
2154 UpdateMoonDisc(SkyActor, FindComponent(TEXT("DirectionalLightComponentMoon")),
2155 MoonBlend, Weather.SunAltitudeAngle);
2157
2158 if (Weather.SunAltitudeAngle < 0.0f)
2159 {
2160
2161 const float SkylightFloor = CVarCarlaWeatherNightSkylightIntensity.GetValueOnGameThread();
2162 // Not while an environment map is set: its intensity is the
2163 // caller's measurement, the night floor would override it.
2164 if (USkyLightComponent* SkyLightComponent = GSkyLightMapOverride.Cubemap == nullptr
2165 ? FindSkyLightComponent(TEXT("SkyLightComponent")) : nullptr)
2166 {
2167 if (!SkyLightComponent->IsActive())
2168 SkyLightComponent->SetActive(true);
2169 // Across twilight the capture still sees a lit sky, so the floor
2170 // is blended in from the day value instead of applied at once.
2171 float Floor = SkylightFloor;
2172 const float TwilightEnd = CVarCarlaWeatherSunTwilightEndDeg.GetValueOnGameThread();
2173 if (TwilightEnd < 0.0f && Weather.SunAltitudeAngle > TwilightEnd && SkylightFloor > 0.0f)
2174 {
2175 // From what the curve and the overcast blend give at the horizon.
2176 const float DayValue = FMath::Max(ApplyOvercastBlend(1.0f,
2178 Weather.Cloudiness, /*bOnlyDarken=*/false, /*CurvePeak=*/0.0f), 1e-3f);
2179 const float U = Weather.SunAltitudeAngle / TwilightEnd;
2180 Floor = FMath::Pow(DayValue, 1.0f - U) * FMath::Pow(SkylightFloor, U);
2181 }
2182 if (Floor > 0.0f && SkyLightComponent->Intensity < Floor)
2183 SkyLightComponent->SetIntensity(Floor);
2184 }
2185
2186 // Stars. SetSkySphere (in the UpdateFunctionNames loop above) has
2187 // already respawned this push's sphere actor by the time we get
2188 // here, so "SkySphere" always resolves to the fresh instance. See
2189 // the cvar comment above for the full reflection path and why
2190 // this stays gated to night. FindPropertyByAuthoredName is
2191 // shared, defined at the top of this function's SkyActor loop.
2192 FObjectProperty* SphereProperty = CastField<FObjectProperty>(
2193 FindPropertyByAuthoredName(SkyActor->GetClass(), TEXT("SkySphere")));
2194 if (SphereProperty == nullptr)
2195 SphereProperty = CastField<FObjectProperty>(
2196 FindPropertyByAuthoredName(SkyActor->GetClass(), TEXT("Sky Sphere")));
2197 AActor* SphereActor = SphereProperty != nullptr
2198 ? Cast<AActor>(SphereProperty->GetObjectPropertyValue_InContainer(SkyActor))
2199 : nullptr;
2200
2201 // The property is unset on this rig, so the sphere is reached through the
2202 // child actors instead.
2203 if (SphereActor == nullptr)
2204 {
2205 TInlineComponentArray<UChildActorComponent*> RigChildActors;
2206 SkyActor->GetComponents(RigChildActors);
2207 for (UChildActorComponent* RigChild : RigChildActors)
2208 {
2209 AActor* Candidate = RigChild != nullptr ? RigChild->GetChildActor() : nullptr;
2210 if (Candidate != nullptr &&
2211 FindPropertyByAuthoredName(Candidate->GetClass(), TEXT("Stars Brightness")) != nullptr)
2212 {
2213 SphereActor = Candidate;
2214 break;
2215 }
2216 }
2217 }
2218 UE_LOG(LogCarla, Verbose, TEXT("AWeather night sky: sphere property %s, actor %s"),
2219 SphereProperty ? TEXT("found") : TEXT("MISSING"),
2220 SphereActor ? *SphereActor->GetName() : TEXT("null"));
2221 if (SphereActor != nullptr)
2222 {
2223 if (FBoolProperty* ColorsBySunProperty = CastField<FBoolProperty>(
2224 FindPropertyByAuthoredName(SphereActor->GetClass(), TEXT("Colors Determined By Sun Position"))))
2225 ColorsBySunProperty->SetPropertyValue_InContainer(SphereActor, true);
2226 else
2227 UE_LOG(LogCarla, Verbose, TEXT("AWeather night sky: 'Colors Determined By Sun Position' MISSING on %s"),
2228 *SphereActor->GetClass()->GetName());
2229
2230 // UE5 blueprints store float variables as doubles, so match
2231 // any numeric property rather than FFloatProperty.
2232 const float StarsFloor = CVarCarlaWeatherStarsBrightness.GetValueOnGameThread();
2233 if (FNumericProperty* StarsBrightnessProperty = CastField<FNumericProperty>(
2234 FindPropertyByAuthoredName(SphereActor->GetClass(), TEXT("Stars Brightness"))))
2235 {
2236 void* ValuePtr = StarsBrightnessProperty->ContainerPtrToValuePtr<void>(SphereActor);
2237 // Sets rather than floors, so the value can go below the sphere's authored one.
2238 if (StarsFloor > 0.0f)
2239 StarsBrightnessProperty->SetFloatingPointPropertyValue(ValuePtr, StarsFloor);
2240 }
2241 else
2242 UE_LOG(LogCarla, Verbose, TEXT("AWeather night sky: 'Stars Brightness' MISSING on %s"),
2243 *SphereActor->GetClass()->GetName());
2244
2245 // Push the variables above into the sphere's dynamic material
2246 // instance. UpdateSkySphere/UpdateSkySphereColor may already
2247 // do this as part of their own (working, since day renders
2248 // correctly) exec chains; calling it again here is a cheap,
2249 // idempotent resync so our two variable writes are guaranteed
2250 // to reach the material regardless.
2251 UFunction* RefreshMaterialFunction = SphereActor->FindFunction(TEXT("RefreshMaterial"));
2252 if (RefreshMaterialFunction != nullptr && RefreshMaterialFunction->ParmsSize == 0)
2253 SphereActor->ProcessEvent(RefreshMaterialFunction, nullptr);
2254
2255 // After RefreshMaterial, which rewrites the colours and the cloud
2256 // opacity from the sphere's variables.
2257 const FLinearColor NightColor = ParseColorCVar(CVarCarlaWeatherSkySphereNightColor, FLinearColor(-1.0f, -1.0f, -1.0f));
2258 const float CloudOpacity = CVarCarlaWeatherSkySphereCloudOpacity.GetValueOnGameThread();
2259 TInlineComponentArray<UMeshComponent*> SphereMeshes;
2260 SphereActor->GetComponents(SphereMeshes);
2261 for (UMeshComponent* SphereMesh : SphereMeshes)
2262 {
2263 UMaterialInstanceDynamic* SphereMID = SphereMesh != nullptr
2264 ? Cast<UMaterialInstanceDynamic>(SphereMesh->GetMaterial(0)) : nullptr;
2265 if (SphereMID == nullptr)
2266 continue;
2267 if (NightColor.R >= 0.0f)
2268 {
2269 SphereMID->SetVectorParameterValue(TEXT("Horizon color"), NightColor);
2270 SphereMID->SetVectorParameterValue(TEXT("Zenith Color"), NightColor);
2271 SphereMID->SetVectorParameterValue(TEXT("Cloud color"), NightColor);
2272 }
2273 if (CloudOpacity >= 0.0f)
2274 SphereMID->SetScalarParameterValue(TEXT("Cloud opacity"), CloudOpacity);
2275 }
2276 }
2277 }
2278 else
2279 {
2280 // Day: bring the moon back down from whatever night floor last
2281 // set it to. Unlike the sun/skylight above (pushed unconditionally
2282 // from their curves every single call, so they self-correct both
2283 // ways), the moon has no such day-side reset anywhere -- only the
2284 // night-only floor clamp above, which only ever raises it. Without
2285 // this, one night floor-clamp leaves the moon lit at that
2286 // intensity forever, becoming a second active directional light
2287 // competing with the sun by day ("Multiple directional lights are
2288 // competing to be the single one used for forward shading..." --
2289 // confirmed via headless test: moon intensity clamped to a night
2290 // floor stayed there across a follow-up day update) and polluting
2291 // the SkyAtmosphere/SkyLight capture. bAffectsWorld/Active are
2292 // deliberately left alone (see the comment above where they're
2293 // forced on) -- zero intensity alone makes it contribute nothing.
2294 // Only outside the moon's fade-in, where the floor above owns it.
2295 ULightComponent* MoonLightComponent = MoonBlend <= 0.0f
2296 ? FindComponent(TEXT("DirectionalLightComponentMoon")) : nullptr;
2297 if (MoonLightComponent != nullptr)
2298 {
2299 if (MoonLightComponent->Intensity != 0.0f)
2300 MoonLightComponent->SetIntensity(0.0f);
2301 }
2302 }
2303 }
2304
2305 // Cloud density. Ported from BP_GeneralSceneSettings.UpdateClouds (that
2306 // actor is gone -- this used to depend on it being placed in the level,
2307 // which routinely wasn't the case in the editor, silently leaving clouds
2308 // disconnected from Weather.Cloudiness). See CVarCarlaWeatherOvercastThreshold
2309 // above for how this was reverse-engineered.
2310 {
2311 FObjectProperty* CloudComponentProperty = CastField<FObjectProperty>(
2312 SkyActor->GetClass()->FindPropertyByName(TEXT("VolumetricCloudComponent")));
2313 UVolumetricCloudComponent* CloudComponent = CloudComponentProperty != nullptr
2314 ? Cast<UVolumetricCloudComponent>(CloudComponentProperty->GetObjectPropertyValue_InContainer(SkyActor))
2315 : nullptr;
2316 if (CloudComponent != nullptr)
2317 {
2318 // Per-call loads, not GC-invisible static caches -- see FindCurve.
2319 UCurveFloat* const DensityCurve = LoadObject<UCurveFloat>(nullptr,
2320 TEXT("/Game/Carla/Blueprints/Weather/CloudsBillowy/C_BillowyDensity.C_BillowyDensity"));
2321 UMaterialInterface* const NormalCloudMaterial = LoadObject<UMaterialInterface>(nullptr,
2322 TEXT("/Game/Carla/Static/FX/VolumetricClouds/MI_Clouds.MI_Clouds"));
2323 UMaterialInterface* const BillowyCloudMaterial = LoadObject<UMaterialInterface>(nullptr,
2324 TEXT("/Game/Carla/Static/GenericMaterials/VolumetricClouds/Masters/M_VolumetricCloud_03_Profiles_Billowy_Inst.M_VolumetricCloud_03_Profiles_Billowy_Inst"));
2325
2326 const float OvercastThreshold = CVarCarlaWeatherOvercastThreshold.GetValueOnGameThread();
2327 const bool bOvercast = CVarCarlaWeatherEnableOvercastClouds.GetValueOnGameThread()
2328 && Weather.Cloudiness >= OvercastThreshold;
2329 UMaterialInterface* BaseMaterial = bOvercast ? BillowyCloudMaterial : NormalCloudMaterial;
2330 if (BaseMaterial != nullptr)
2331 {
2332 // A brand new MID every push (this runs on every weather
2333 // update, not just when Cloudiness crosses the threshold)
2334 // resets VolumetricCloudComponent's temporal accumulation
2335 // each time -- a visible pop/flash even when the parameters
2336 // end up identical. Reuse the existing MID when the base
2337 // material (Normal vs Billowy) hasn't actually changed; only
2338 // create a fresh one on the first push or an actual
2339 // threshold cross. Cached by component here rather than read
2340 // back via CloudComponent->GetMaterial(): that getter didn't
2341 // reliably report the MID just assigned by SetMaterial below
2342 // (still returned the previous push's, so the read-back
2343 // approach always saw a "different" parent and recreated
2344 // every time -- this side-steps whatever that mismatch was).
2345 static TMap<TWeakObjectPtr<UVolumetricCloudComponent>, TWeakObjectPtr<UMaterialInstanceDynamic>> CloudMIDCache;
2346 TWeakObjectPtr<UMaterialInstanceDynamic>* CachedMID = CloudMIDCache.Find(CloudComponent);
2347 UMaterialInstanceDynamic* CloudMID = (CachedMID != nullptr && CachedMID->IsValid()
2348 && CachedMID->Get()->Parent == BaseMaterial)
2349 ? CachedMID->Get()
2350 : nullptr;
2351 if (CloudMID == nullptr)
2352 {
2353 CloudMID = UMaterialInstanceDynamic::Create(BaseMaterial, CloudComponent);
2354 CloudMIDCache.Add(CloudComponent, CloudMID);
2355 }
2356 if (bOvercast)
2357 {
2358 // Below MI_Clouds's own "BaseNoiseExp" scalar is what
2359 // actually thins/thickens the cloud layer under the
2360 // overcast threshold (probed live against
2361 // BP_GeneralSceneSettings.UpdateClouds before removing
2362 // it: exactly linear -- 100 - 0.8*Cloudiness -- for
2363 // Cloudiness in [1, 89], collapsing to a huge exponent
2364 // (~invisible clouds) only right at 0. Leaving this
2365 // param at the material's own unrelated default is what
2366 // produced the single ugly cloud mass instead of a
2367 // normal layer.
2368 if (DensityCurve != nullptr)
2369 CloudMID->SetScalarParameterValue(TEXT("Cloud Density"), DensityCurve->GetFloatValue(Weather.Cloudiness));
2370 }
2371 else
2372 {
2373 float BaseNoiseExp = Weather.Cloudiness <= 0.0f
2374 ? 6000.0f
2375 : FMath::Clamp(100.0f - 0.8f * Weather.Cloudiness, 0.0f, 6000.0f);
2376 // See CVarCarlaWeatherDeckStartCloudiness. Every deck
2377 // parameter is written on every push, blended from the
2378 // base material's own value, so the reused MID never
2379 // keeps a deck value after the clouds open up again.
2380 const float Deck = ComputeCloudDeckFactor(Weather.Cloudiness);
2381 const float DeckNoiseExp = CVarCarlaWeatherDeckNoiseExp.GetValueOnGameThread();
2382 if (DeckNoiseExp > 0.0f && BaseNoiseExp > 0.0f)
2383 BaseNoiseExp = FMath::Exp(FMath::Lerp(FMath::Loge(BaseNoiseExp), FMath::Loge(DeckNoiseExp),
2384 FMath::Pow(Deck, FMath::Max(CVarCarlaWeatherDeckCoverageExponent.GetValueOnGameThread(), 0.01f))));
2385 CloudMID->SetScalarParameterValue(TEXT("BaseNoiseExp"), BaseNoiseExp);
2386 // Negative deck value: the authored one, still written so a
2387 // runtime change of the cvar does not leave a stale value.
2388 auto BlendToDeck = [CloudMID, BaseMaterial](const TCHAR* Name, float DeckValue, float Alpha)
2389 {
2390 float Authored = 0.0f;
2391 if (BaseMaterial->GetScalarParameterValue(Name, Authored))
2392 CloudMID->SetScalarParameterValue(Name, DeckValue >= 0.0f ? FMath::Lerp(Authored, DeckValue, Alpha) : Authored);
2393 };
2394 BlendToDeck(TEXT("ExtinctionScale"), CVarCarlaWeatherDeckExtinctionScale.GetValueOnGameThread(), Deck);
2395 // M_BasicClouds blends the last warp iteration by the
2396 // count's fraction, so the warp blends like the rest. A
2397 // DeckWarpSwitch in [0, 1] restores the single flip.
2398 const float WarpSwitch = CVarCarlaWeatherDeckWarpSwitch.GetValueOnGameThread();
2399 BlendToDeck(TEXT("Perlin FBM Domain Warp Count"), CVarCarlaWeatherDeckWarpCount.GetValueOnGameThread(),
2400 WarpSwitch < 0.0f ? Deck : (Deck >= WarpSwitch && Deck > 0.0f ? 1.0f : 0.0f));
2401 }
2402 CloudComponent->SetMaterial(CloudMID);
2403 }
2404 else
2405 {
2406 UE_LOG(LogCarla, Warning, TEXT("AWeather: cloud master material missing for %s"), *SkyActor->GetName());
2407 }
2408 }
2409 }
2410
2411 // Day/night light broadcast. AWeather::ApplyWeather already does this
2412 // (respecting the artist-facing DayNightCycle toggle) when an AWeather
2413 // actor exists, but street lamps otherwise only ever turned on when
2414 // actually playing: ASkyBase::RefreshWeather/LoadPreset fall back to
2415 // calling this function directly with no AWeather placed, and that path
2416 // never reached UpdateStreetLightsForDayNight. Notifying again here is
2417 // harmless when an AWeather is present too: the subsystem only
2418 // broadcasts when the day/night state actually changes.
2419 if (UWorld* World = SkyActor->GetWorld())
2420 {
2421 if (UCarlaLightSubsystem* CarlaLightSubsystem = World->GetSubsystem<UCarlaLightSubsystem>())
2422 CarlaLightSubsystem->NotifyDayTimeChange(
2424 }
2425
2426 // Wind + Wetness, both on the same global collection. WindIntensity: no
2427 // one was pushing it at all (UpdateAtmosphereAndPrecipitation's exec
2428 // chain for it is severed, same pattern as everything else natively
2429 // re-driven in this function) -- M_VegetationMaster divides it by 10
2430 // before feeding its wind shader function, so this pushes the raw 0-100
2431 // CARLA value, not normalized.
2432 //
2433 // Wetness: UpdateAtmosphereAndPrecipitation DOES push it (and Puddles/
2434 // Ripples/Precipitation alongside it) -- but normalized to 0-1 first
2435 // (confirmed live: Weather.Wetness=90 -> collection value 0.9). Puddles
2436 // reads fine at that scale. Wetness does not: MF_WetSurfaceFx's own
2437 // "Wetness" section divides the collection value by 100 *again* before
2438 // using it (confirmed in the material graph, and by hardcoding 100
2439 // straight into that Divide node -- wets correctly; the collection read
2440 // is the only broken link). A pre-normalized 0-1 input run through
2441 // another /100 lands at ~0.009 -- functionally zero, which is exactly
2442 // "wetness does nothing". Puddles/Ripples/Precipitation are left alone
2443 // (already correct at 0-1); only Wetness gets overridden here, raw, right
2444 // after the BP call above wrote the wrong value.
2445 {
2446 // Per-call load, not a GC-invisible static cache -- see FindCurve.
2447 UMaterialParameterCollection* const WeatherMPC = LoadObject<UMaterialParameterCollection>(nullptr,
2448 TEXT("/Game/Carla/Blueprints/Weather/Materials/WeatherMaterialParameters.WeatherMaterialParameters"));
2449 if (WeatherMPC != nullptr && SkyActor->GetWorld() != nullptr)
2450 {
2451 UKismetMaterialLibrary::SetScalarParameterValue(
2452 SkyActor->GetWorld(), WeatherMPC, TEXT("WindIntensity"), Weather.WindIntensity);
2453 UKismetMaterialLibrary::SetScalarParameterValue(
2454 SkyActor->GetWorld(), WeatherMPC, TEXT("Wetness"), Weather.Wetness);
2455 // 0 by day, 1 by night. Materials gate night-only emissives on it.
2456 const float NightFactor = ComputeNightBlend(Weather.SunAltitudeAngle);
2457 UKismetMaterialLibrary::SetScalarParameterValue(
2458 SkyActor->GetWorld(), WeatherMPC, TEXT("NightFactor"), NightFactor);
2459 // Both cloud emissions share the material input: the city glow at
2460 // night (see CVarCarlaWeatherCityGlow) and the overcast deck's
2461 // diffuse light by day (see CVarCarlaWeatherDeckStartCloudiness),
2462 // which follows the sun's own intensity so it fades with it.
2463 const FLinearColor CityGlow = ParseColorCVar(CVarCarlaWeatherCityGlowColor, FLinearColor(1.0f, 0.85f, 0.7f))
2464 * (CVarCarlaWeatherCityGlow.GetValueOnGameThread() * NightFactor);
2465 float SunFraction = 0.0f;
2466 if (UCurveFloat* SunCurve = LoadObject<UCurveFloat>(nullptr,
2467 TEXT("/Game/Carla/Blueprints/Weather/Weather2_Curves/SunIntensity_2.SunIntensity_2")))
2468 {
2469 const float Peak = SampleCurvePeakAboveHorizon(SunCurve);
2470 if (Peak > UE_KINDA_SMALL_NUMBER)
2471 SunFraction = FMath::Clamp(ApplySunAltitudeFalloff(
2472 SunCurve->GetFloatValue(Weather.SunAltitudeAngle), Peak, Weather.SunAltitudeAngle) / Peak, 0.0f, 1.0f);
2473 }
2474 const FLinearColor DeckGlow = ParseColorCVar(CVarCarlaWeatherDeckGlowColor, FLinearColor::White)
2475 * (FMath::Max(CVarCarlaWeatherDeckGlow.GetValueOnGameThread(), 0.0f)
2478 UKismetMaterialLibrary::SetVectorParameterValue(
2479 SkyActor->GetWorld(), WeatherMPC, TEXT("CityGlow"), CityGlow + DeckGlow);
2480 }
2481 }
2482
2483 // Viewport exposure fallback. The project ships with auto exposure
2484 // disabled by default (r.DefaultFeature.AutoExposure=False) and the rigs
2485 // were saved for the old frozen ~15 lux suns, so a physical 100k lux sun
2486 // white-outs the main view with no exposure settings at all. This fills
2487 // in the same histogram exposure the RGB sensor uses by default (bias 0,
2488 // EV100 range [10,12], speeds 3/1) -- but ONLY for fields the currently
2489 // loaded PostProcess profile (Content/Carla/Config/PostProcess/*.json)
2490 // doesn't already claim via bOverride_*. Profiles set their own Method
2491 // and Bias (e.g. GoPro.json: AEM_Manual/0) and rely on this to supply
2492 // Min/Max/SpeedUp/SpeedDown, which they intentionally leave unset. This
2493 // runs on every editor property edit (via OnConstruction), so forcing
2494 // these unconditionally used to stomp the profile's Method/Bias back to
2495 // Histogram/0 the instant you touched anything on the Sky actor,
2496 // including just switching ProfileName -- e.g. GoPro's Manual exposure
2497 // never stuck, and switching profiles never visibly changed exposure.
2498 {
2499 FObjectProperty* PostProcessProperty = CastField<FObjectProperty>(
2500 SkyActor->GetClass()->FindPropertyByName(TEXT("PostProcessComponent")));
2501 UPostProcessComponent* PostProcessComponent = PostProcessProperty != nullptr
2502 ? Cast<UPostProcessComponent>(PostProcessProperty->GetObjectPropertyValue_InContainer(SkyActor))
2503 : nullptr;
2504 if (PostProcessComponent != nullptr)
2505 {
2506 FillSkyPostProcessFallback(PostProcessComponent);
2507 }
2508 }
2509}
2510
2511void AWeather::FillSkyPostProcessFallback(UPostProcessComponent* PostProcessComponent)
2512{
2513 if (PostProcessComponent == nullptr)
2514 return;
2515 FPostProcessSettings& Settings = PostProcessComponent->Settings;
2516 if (!Settings.bOverride_AutoExposureMethod)
2517 {
2518 Settings.bOverride_AutoExposureMethod = true;
2519 Settings.AutoExposureMethod = AEM_Histogram;
2520 }
2521 if (!Settings.bOverride_AutoExposureBias)
2522 {
2523 Settings.bOverride_AutoExposureBias = true;
2524 Settings.AutoExposureBias = 0.0f;
2525 }
2526 if (!Settings.bOverride_AutoExposureMinBrightness)
2527 {
2528 Settings.bOverride_AutoExposureMinBrightness = true;
2529 Settings.AutoExposureMinBrightness = 10.0f;
2530 }
2531 if (!Settings.bOverride_AutoExposureMaxBrightness)
2532 {
2533 Settings.bOverride_AutoExposureMaxBrightness = true;
2534 Settings.AutoExposureMaxBrightness = 12.0f;
2535 }
2536 if (!Settings.bOverride_AutoExposureSpeedUp)
2537 {
2538 Settings.bOverride_AutoExposureSpeedUp = true;
2539 Settings.AutoExposureSpeedUp = 3.0f;
2540 }
2541 if (!Settings.bOverride_AutoExposureSpeedDown)
2542 {
2543 Settings.bOverride_AutoExposureSpeedDown = true;
2544 Settings.AutoExposureSpeedDown = 1.0f;
2545 }
2546 // Only when the cvar is set (off by default): forced over the profile.
2547 const float ExposureBias = CVarCarlaWeatherExposureBias.GetValueOnGameThread();
2548 if (ExposureBias >= 0.0f)
2549 {
2550 Settings.bOverride_AutoExposureBias = true;
2551 Settings.AutoExposureBias = ExposureBias;
2552 }
2553 // Only when the profile does not set bloom. The sky rig remembers that the
2554 // bloom is ours (until a profile is loaded), so the cvar stays live.
2555 ASkyBase* Sky = Cast<ASkyBase>(PostProcessComponent->GetOwner());
2556 const bool bBloomIsOurs = Sky != nullptr && Sky->bBloomFromWeatherFallback;
2557 const float BloomIntensity = CVarCarlaWeatherBloomIntensity.GetValueOnGameThread();
2558 if (BloomIntensity >= 0.0f && (!Settings.bOverride_BloomIntensity || bBloomIsOurs))
2559 {
2560 Settings.bOverride_BloomIntensity = true;
2561 Settings.BloomIntensity = BloomIntensity;
2562 if (Sky != nullptr)
2563 Sky->bBloomFromWeatherFallback = true;
2564 }
2565 else if (BloomIntensity < 0.0f && bBloomIsOurs)
2566 {
2567 // Cvar switched off: hand bloom back to the engine default.
2568 Settings.bOverride_BloomIntensity = false;
2569 Sky->bBloomFromWeatherFallback = false;
2570 }
2571 const float ExposureMaxEV = CVarCarlaWeatherExposureMaxEV.GetValueOnGameThread();
2572 if (ExposureMaxEV >= 0.0f)
2573 {
2574 Settings.bOverride_AutoExposureMaxBrightness = true;
2575 Settings.AutoExposureMaxBrightness = ExposureMaxEV;
2576 }
2577 PostProcessComponent->bUnbound = true;
2578}
2579
2581{
2582 if (!DayNightCycle)
2583 return;
2584
2585 UCarlaLightSubsystem* CarlaLightSubsystem = GetWorld()->GetSubsystem<UCarlaLightSubsystem>();
2586 if (CarlaLightSubsystem == nullptr)
2587 return;
2588
2589 // Sun above StreetLightsOnDeg = day, at or below = night. Notified on every
2590 // weather update; the subsystem only broadcasts an actual change.
2591 const bool bIsDay = Weather.SunAltitudeAngle > CVarCarlaWeatherStreetLightsOnDeg.GetValueOnGameThread();
2592 UE_LOG(LogCarla, Verbose, TEXT("AWeather: day/night state (bIsDay=%d) for %d registered CarlaLights"),
2593 bIsDay ? 1 : 0, CarlaLightSubsystem->NumLights());
2594 CarlaLightSubsystem->NotifyDayTimeChange(bIsDay);
2595}
2596
2598{
2599 ++ApplySerial;
2600 SetWeather(InWeather);
2602
2603#ifdef CARLA_WEATHER_EXTRA_LOG
2604 UE_LOG(LogCarla, Log, TEXT("Changing weather:"));
2605 UE_LOG(LogCarla, Log, TEXT(" - Cloudiness = %.2f"), Weather.Cloudiness);
2606 UE_LOG(LogCarla, Log, TEXT(" - Precipitation = %.2f"), Weather.Precipitation);
2607 UE_LOG(LogCarla, Log, TEXT(" - PrecipitationDeposits = %.2f"), Weather.PrecipitationDeposits);
2608 UE_LOG(LogCarla, Log, TEXT(" - WindIntensity = %.2f"), Weather.WindIntensity);
2609 UE_LOG(LogCarla, Log, TEXT(" - SunAzimuthAngle = %.2f"), Weather.SunAzimuthAngle);
2610 UE_LOG(LogCarla, Log, TEXT(" - SunAltitudeAngle = %.2f"), Weather.SunAltitudeAngle);
2611 UE_LOG(LogCarla, Log, TEXT(" - FogDensity = %.2f"), Weather.FogDensity);
2612 UE_LOG(LogCarla, Log, TEXT(" - FogDistance = %.2f"), Weather.FogDistance);
2613 UE_LOG(LogCarla, Log, TEXT(" - FogFalloff = %.2f"), Weather.FogFalloff);
2614 UE_LOG(LogCarla, Log, TEXT(" - Wetness = %.2f"), Weather.Wetness);
2615 UE_LOG(LogCarla, Log, TEXT(" - ScatteringIntensity = %.2f"), Weather.ScatteringIntensity);
2616 UE_LOG(LogCarla, Log, TEXT(" - MieScatteringScale = %.2f"), Weather.MieScatteringScale);
2617 UE_LOG(LogCarla, Log, TEXT(" - RayleighScatteringScale = %.2f"), Weather.RayleighScatteringScale);
2618 UE_LOG(LogCarla, Log, TEXT(" - DustStorm = %.2f"), Weather.DustStorm);
2619#endif // CARLA_WEATHER_EXTRA_LOG
2620
2621 // Call the blueprint that actually changes the weather.
2625 UpdateRain();
2626
2627 // record the weather event
2628 ACarlaRecorder *Recorder = UCarlaStatics::GetRecorder(GetWorld());
2629 if (Recorder && Recorder->IsEnabled())
2630 {
2631 CarlaRecorderWeather RecorderWeather;
2632 RecorderWeather.Cloudiness = InWeather.Cloudiness;
2633 RecorderWeather.Precipitation = InWeather.Precipitation;
2634 RecorderWeather.PrecipitationDeposits = InWeather.PrecipitationDeposits;
2635 RecorderWeather.WindIntensity = InWeather.WindIntensity;
2636 RecorderWeather.SunAzimuthAngle = InWeather.SunAzimuthAngle;
2637 RecorderWeather.SunAltitudeAngle = InWeather.SunAltitudeAngle;
2638 RecorderWeather.FogDensity = InWeather.FogDensity;
2639 RecorderWeather.FogDistance = InWeather.FogDistance;
2640 RecorderWeather.FogFalloff = InWeather.FogFalloff;
2641 RecorderWeather.Wetness = InWeather.Wetness;
2642 RecorderWeather.ScatteringIntensity = InWeather.ScatteringIntensity;
2643 RecorderWeather.MieScatteringScale = InWeather.MieScatteringScale;
2644 RecorderWeather.RayleighScatteringScale = InWeather.RayleighScatteringScale;
2645 RecorderWeather.DustStorm = InWeather.DustStorm;
2646 Recorder->AddWeather(RecorderWeather);
2647 }
2648}
2649
2651{
2652 // Only re-sync this new sensor's own postprocess blendables (rain/dust)
2653 // with the already-active weather. A full PushWeatherToSky() here
2654 // respawns BP_Sky_Sphere and reapplies the exposure clamp on every
2655 // camera sensor spawn, which is redundant (global weather state hasn't
2656 // changed) and visibly jitters clouds/postprocess each time a client
2657 // spawns a camera.
2659}
2660
2662{
2663 Weather = InWeather;
2664}
2665
2666void AWeather::SetDayNightCycle(const bool& active)
2667{
2668 DayNightCycle = active;
2669}
2670
2671#if WITH_EDITOR
2672void AWeather::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
2673{
2674 Super::PostEditChangeProperty(PropertyChangedEvent);
2676}
2677#endif
static bool IsValid(const ACarlaWheeledVehicle *Vehicle)
static TAutoConsoleVariable< float > CVarCarlaWeatherMoonHaloMieScale(TEXT("carla.Weather.MoonHaloMieScale"), 0.1f, TEXT("1 leaves it as the rig sets it."), ECVF_Default)
static float ComputeCloudDeckFactor(float Cloudiness)
Definition Weather.cpp:605
static TAutoConsoleVariable< float > CVarCarlaWeatherDayNightBlendDeg(TEXT("carla.Weather.DayNightBlendDeg"), 8.0f, TEXT("spread over. 0 gives a hard switch at the horizon."), ECVF_Default)
static float ApplySunAltitudeFalloff(float CurveValue, float CurvePeak, float SunAltitudeAngle)
Definition Weather.cpp:1044
static double GLightingJumpWorldSeconds
Definition Weather.cpp:810
static FRotator GLastSunRotation(ForceInitToZero)
static int32 GLightingJumpRestore[UE_ARRAY_COUNT(GLightingJumpCVars)]
Definition Weather.cpp:808
static TAutoConsoleVariable< float > CVarCarlaWeatherTransmittanceMinElevationDeg(TEXT("carla.Weather.TransmittanceMinElevationDeg"), 0.0f, TEXT("Values above ~5 light the city as if it were day under a night sky."), ECVF_Default)
static TAutoConsoleVariable< float > CVarCarlaWeatherSkySphereShowDeg(TEXT("carla.Weather.SkySphereShowDeg"), -12.0f, TEXT("atmosphere."), ECVF_Default)
static void ReleaseLightingUpdateIfSettled(const UWorld *World)
Definition Weather.cpp:848
static TAutoConsoleVariable< float > CVarCarlaWeatherSunAltitudeFalloff(TEXT("carla.Weather.SunAltitudeFalloff"), 1.0f, TEXT("curve. 1 is fully physical, 0 leaves the rig's curve alone."), ECVF_Default)
static TAutoConsoleVariable< float > CVarCarlaWeatherDeckGlow(TEXT("carla.Weather.DeckGlow"), 2.0f, TEXT("Cloud emission at Cloudiness 100 with the sun at its peak (0 off)."), ECVF_Default)
static float ComputeNightBlend(float SunAltitudeAngle)
Definition Weather.cpp:679
static TAutoConsoleVariable< float > CVarCarlaWeatherLightingJumpSettleSeconds(TEXT("carla.Weather.LightingJumpSettleSeconds"), 8.0f, TEXT("World seconds the full Lumen / sky light updates are held after a lighting jump."), ECVF_Default)
static FLinearColor ComputeTwilightCloudScale(float SunAltitudeAngle)
Definition Weather.cpp:666
static TAutoConsoleVariable< float > CVarCarlaWeatherBloomIntensity(TEXT("carla.Weather.BloomIntensity"), 1.0f, TEXT("does not set one, so the sun glows in the viewport. Negative leaves it alone."), ECVF_Default)
static TAutoConsoleVariable< float > CVarCarlaWeatherMoonDiscAngleDeg(TEXT("carla.Weather.MoonDiscAngleDeg"), 2.0f, TEXT("larger reads better on screen."), ECVF_Default)
static TAutoConsoleVariable< float > CVarCarlaWeatherMoonFullAltitudeDeg(TEXT("carla.Weather.MoonFullAltitudeDeg"), 0.0f, TEXT("Must be below MoonFadeStartDeg; otherwise the moon switches on hard there."), ECVF_Default)
static TAutoConsoleVariable< float > CVarCarlaWeatherDeckWarpCount(TEXT("carla.Weather.DeckWarpCount"), 1.0f, TEXT("Cloud material domain warp count at Cloudiness 100. Negative leaves the material's."), ECVF_Default)
static TAutoConsoleVariable< float > CVarCarlaWeatherSunIndirectIntensity(TEXT("carla.Weather.SunIndirectIntensity"), 1.0f, TEXT("IndirectLightingIntensity forced on the sky rig's sun. Negative leaves the rig's value."), ECVF_Default)
static TAutoConsoleVariable< float > CVarCarlaWeatherSkySphereScale(TEXT("carla.Weather.SkySphereScale"), 10.0f, TEXT("1 leaves it as authored."), ECVF_Default)
static float ApplyOvercastBlend(float CurveValue, float OvercastValue, float Cloudiness, bool bOnlyDarken, float CurvePeak)
Definition Weather.cpp:1073
static TAutoConsoleVariable< float > CVarCarlaWeatherMoonHaloMieAnisotropy(TEXT("carla.Weather.MoonHaloMieAnisotropy"), -1.0f, TEXT("leaves it as the rig sets it."), ECVF_Default)
static TAutoConsoleVariable< float > CVarCarlaWeatherMoonIntensity(TEXT("carla.Weather.MoonIntensity"), 0.5f, TEXT("leave the rig's authored/curve-driven moon intensity untouched."), ECVF_Default)
static void NoteLightingChange(const UWorld *World, float SunAltitude, float Cloudiness)
Definition Weather.cpp:831
static double GLastSunMoveWorldSeconds
Definition Weather.cpp:737
static TAutoConsoleVariable< float > CVarCarlaWeatherOvercastSunNeutralize(TEXT("carla.Weather.OvercastSunNeutralize"), 0.6f, TEXT("at sunset. 0 leaves the sun white."), ECVF_Default)
static TAutoConsoleVariable< float > CVarCarlaWeatherStarsBrightness(TEXT("carla.Weather.StarsBrightness"), 4.0f, TEXT("the only way to make the stars FAINTER than the 100 the asset ships."), ECVF_Default)
static TAutoConsoleVariable< float > CVarCarlaWeatherMoonHaloStartDeg(TEXT("carla.Weather.MoonHaloStartDeg"), 3.0f, TEXT("MoonHaloMieAnisotropy) start blending in."), ECVF_Default)
static TAutoConsoleVariable< float > CVarCarlaWeatherMoonDiscSphereFraction(TEXT("carla.Weather.MoonDiscSphereFraction"), 0.9f, TEXT("so the sphere never draws in front of it."), ECVF_Default)
static TAutoConsoleVariable< float > CVarCarlaWeatherSunTwilightEndFraction(TEXT("carla.Weather.SunTwilightEndFraction"), 0.0001f, TEXT("the fade is log-linear in between."), ECVF_Default)
static FLinearColor ParseColorCVar(const TAutoConsoleVariable< FString > &CVar, const FLinearColor &Fallback)
Definition Weather.cpp:633
static TAutoConsoleVariable< float > CVarCarlaWeatherMoonDiscDuskBrightness(TEXT("carla.Weather.MoonDiscDuskBrightness"), 3000.0f, TEXT("night value between MoonDiscBrightnessStartDeg and MoonDiscBrightnessEndDeg."), ECVF_Default)
static TAutoConsoleVariable< bool > CVarCarlaWeatherMoonDiscAdditive(TEXT("carla.Weather.MoonDiscAdditive"), true, TEXT("clouds more exactly."), ECVF_Default)
static TAutoConsoleVariable< float > CVarCarlaWeatherMoonHaloEndDeg(TEXT("carla.Weather.MoonHaloEndDeg"), 0.0f, TEXT("Sun altitude at which the night Mie settings are fully applied."), ECVF_Default)
static FLinearColor ComputeOvercastNeutralTint(const USkyAtmosphereComponent *Atmosphere, float SunAltitudeAngle, float Cloudiness, float NeutralizeAmount, float &OutIntensityScale)
Definition Weather.cpp:1088
static TAutoConsoleVariable< float > CVarCarlaWeatherStreetLightsOnDeg(TEXT("carla.Weather.StreetLightsOnDeg"), 0.8f, TEXT("Sun altitude at or below which street lights are switched on."), ECVF_Default)
static TAutoConsoleVariable< float > CVarCarlaWeatherOvercastAmbientMaxGain(TEXT("carla.Weather.OvercastAmbientMaxGain"), 3.0f, TEXT("the horizon cannot turn the ambient blue."), ECVF_Default)
static TAutoConsoleVariable< bool > CVarCarlaWeatherEnableOvercastClouds(TEXT("carla.Weather.EnableOvercastClouds"), false, TEXT("Billowy overcast one. Set false to always use the plain master instead."), ECVF_Default)
static TAutoConsoleVariable< float > CVarCarlaWeatherMoonFadeStartDeg(TEXT("carla.Weather.MoonFadeStartDeg"), 10.0f, TEXT("Sun altitude, in degrees, at which the moon starts to fade in."), ECVF_Default)
static TAutoConsoleVariable< FString > CVarCarlaWeatherCityGlowColor(TEXT("carla.Weather.CityGlowColor"), TEXT("1.0 0.85 0.7"), TEXT("Colour of the city glow on the clouds, \"R G B\"."), ECVF_Default)
static TAutoConsoleVariable< float > CVarCarlaWeatherOvercastAmbientNeutralize(TEXT("carla.Weather.OvercastAmbientNeutralize"), 0.4f, TEXT("Cloudiness 100 (scaled by Cloudiness). 0 leaves the sky light white."), ECVF_Default)
static TAutoConsoleVariable< float > CVarCarlaWeatherRainDeckSkyLightScale(TEXT("carla.Weather.RainDeckSkyLightScale"), 3.0f, TEXT("DeckSkyLightIntensity multiplier at Precipitation 100 (1 = rain does not change it)."), ECVF_Default)
static TAutoConsoleVariable< float > CVarCarlaWeatherDeckLightFullCloudiness(TEXT("carla.Weather.DeckLightFullCloudiness"), 84.0f, TEXT("Cloudiness at which the deck's emission and sky light reach their full value."), ECVF_Default)
static void SetForceLightingUpdate(bool bOn)
Definition Weather.cpp:814
static float ComputeMoonHaloBlend(float SunAltitudeAngle)
Definition Weather.cpp:700
static TAutoConsoleVariable< float > CVarCarlaWeatherDeckSkyLightIntensity(TEXT("carla.Weather.DeckSkyLightIntensity"), 1.0f, TEXT("Replaces OvercastSkyLightIntensity at Cloudiness 100 (blended by the deck factor)."), ECVF_Default)
static TAutoConsoleVariable< float > CVarCarlaWeatherSunTwilightStartDeg(TEXT("carla.Weather.SunTwilightStartDeg"), 1.0f, TEXT("Sun altitude at which the twilight fade starts, from the sun's value there."), ECVF_Default)
static TAutoConsoleVariable< float > CVarCarlaWeatherSunFalloffExponent(TEXT("carla.Weather.SunFalloffExponent"), 0.01f, TEXT("keeps a low sun brighter, 0 disables the falloff."), ECVF_Default)
static float RainDeckScale(const TAutoConsoleVariable< float > &CVar, float Precipitation)
Definition Weather.cpp:598
static TAutoConsoleVariable< float > CVarCarlaWeatherDeckNoiseExp(TEXT("carla.Weather.DeckNoiseExp"), 1.0f, TEXT("Cloud material BaseNoiseExp at Cloudiness 100 (lower = more coverage)."), ECVF_Default)
static float GLastPushedCloudiness
Definition Weather.cpp:812
static void UpdateMoonDisc(AActor *SkyActor, const USceneComponent *MoonLight, float MoonBlend, float SunAltitudeAngle)
Definition Weather.cpp:919
static float GetOvercastSkyLightTarget(float Cloudiness, float Precipitation)
Definition Weather.cpp:622
static TAutoConsoleVariable< float > CVarCarlaWeatherTwilightCloudOffDeg(TEXT("carla.Weather.TwilightCloudOffDeg"), 12.0f, TEXT("Sun altitude at or above which the clouds get the sun's full scattering again."), ECVF_Default)
static bool GForcingLightingUpdate
Definition Weather.cpp:809
static TAutoConsoleVariable< float > CVarCarlaWeatherRainDeckGlowScale(TEXT("carla.Weather.RainDeckGlowScale"), 0.25f, TEXT("DeckGlow multiplier at Precipitation 100 (1 = rain does not change the deck)."), ECVF_Default)
static TAutoConsoleVariable< float > CVarCarlaWeatherDeckWarpSwitch(TEXT("carla.Weather.DeckWarpSwitch"), -1.0f, TEXT("-1: blend the warp count with the deck factor. 0-1: flip it to DeckWarpCount at that deck factor."), ECVF_Default)
static void NoteSunRotation(const UWorld *World, const FRotator &Rotation)
Definition Weather.cpp:753
static TAutoConsoleVariable< bool > CVarCarlaWeatherVSMInvalidateWhileSunMoves(TEXT("carla.Weather.VSMInvalidateWhileSunMoves"), true, TEXT("changes. 0 leaves it alone."), ECVF_Default)
static TAutoConsoleVariable< float > CVarCarlaWeatherMoonDiscDistanceKm(TEXT("carla.Weather.MoonDiscDistanceKm"), 100.0f, TEXT("the clouds so they cover it. Clamped to MoonDiscSphereFraction of the sphere."), ECVF_Default)
static TAutoConsoleVariable< float > CVarCarlaWeatherDeckCoverageExponent(TEXT("carla.Weather.DeckCoverageExponent"), 2.0f, TEXT("Power applied to the deck factor for the cloud coverage (BaseNoiseExp) blend."), ECVF_Default)
static TAutoConsoleVariable< float > CVarCarlaWeatherSkySphereCloudOpacity(TEXT("carla.Weather.SkySphereCloudOpacity"), 0.0f, TEXT("asset's value."), ECVF_Default)
static TAutoConsoleVariable< float > CVarCarlaWeatherMoonDiscContrast(TEXT("carla.Weather.MoonDiscContrast"), 0.7f, TEXT("Contrast of the moon texture: 1 as authored, 0 flat grey."), ECVF_Default)
static TAutoConsoleVariable< float > CVarCarlaWeatherMoonDiscBrightnessStartDeg(TEXT("carla.Weather.MoonDiscBrightnessStartDeg"), 2.0f, TEXT("Sun altitude at which the moon disc starts dimming from its dusk brightness."), ECVF_Default)
static const int32 GLightingJumpValues[]
Definition Weather.cpp:807
static TAutoConsoleVariable< float > CVarCarlaWeatherCityGlow(TEXT("carla.Weather.CityGlow"), 0.000002f, TEXT("Night emission of the clouds from city lights (0 off)."), ECVF_Default)
static void UpdateMoonHalo(AActor *SkyActor, float HaloBlend)
Definition Weather.cpp:878
static TAutoConsoleVariable< float > CVarCarlaWeatherCloudShadowExtentKm(TEXT("carla.Weather.CloudShadowExtentKm"), 10.0f, TEXT("whatever the rig authored."), ECVF_Default)
static TAutoConsoleVariable< float > CVarCarlaWeatherSunTwilightEndDeg(TEXT("carla.Weather.SunTwilightEndDeg"), -10.0f, TEXT("0 or above leaves the rig's hard cut at the horizon."), ECVF_Default)
static TAutoConsoleVariable< float > CVarCarlaWeatherMoonDiscBrightnessEndDeg(TEXT("carla.Weather.MoonDiscBrightnessEndDeg"), -4.0f, TEXT("Sun altitude at which the moon disc reaches its night brightness."), ECVF_Default)
static float ComputeMoonBlend(float SunAltitudeAngle)
Definition Weather.cpp:689
static TAutoConsoleVariable< FString > CVarCarlaWeatherTwilightCloudScale(TEXT("carla.Weather.TwilightCloudScale"), TEXT("0.4 0.55 0.8"), TEXT("Sun CloudScatteredLuminanceScale at the horizon, \"R G B\" (\"1 1 1\" off)."), ECVF_Default)
static float SampleCurvePeakAboveHorizon(UCurveFloat *Curve)
Definition Weather.cpp:1063
static TAutoConsoleVariable< float > CVarCarlaWeatherLightingJumpSunDeg(TEXT("carla.Weather.LightingJumpSunDeg"), 5.0f, TEXT("jump and forces Lumen and the sky light capture to update in full. 0 disables it."), ECVF_Default)
static TAutoConsoleVariable< float > CVarCarlaWeatherMoonDiscNightBrightness(TEXT("carla.Weather.MoonDiscNightBrightness"), 40.0f, TEXT("around it, the only per-object bloom control there is."), ECVF_Default)
static TAutoConsoleVariable< FString > CVarCarlaWeatherDeckGlowColor(TEXT("carla.Weather.DeckGlowColor"), TEXT("1.0 1.0 1.0"), TEXT("Colour of the overcast deck emission, \"R G B\"."), ECVF_Default)
static float ComputeSunTwilightFactor(float SunAltitudeAngle)
Definition Weather.cpp:858
static void SetForceInvalidateDirectionalVSM(bool bOn)
Definition Weather.cpp:740
static TAutoConsoleVariable< float > CVarCarlaWeatherLightingJumpCloudiness(TEXT("carla.Weather.LightingJumpCloudiness"), 30.0f, TEXT("Cloudiness change between two weather pushes that counts as a lighting jump. 0 disables it."), ECVF_Default)
static TAutoConsoleVariable< float > CVarCarlaWeatherDeckExtinctionScale(TEXT("carla.Weather.DeckExtinctionScale"), 0.005f, TEXT("Cloud material ExtinctionScale at Cloudiness 100 (softer deck base). Negative leaves the material's."), ECVF_Default)
static TAutoConsoleVariable< float > CVarCarlaWeatherTwilightCloudFullDeg(TEXT("carla.Weather.TwilightCloudFullDeg"), 2.0f, TEXT("Sun altitude at or below which carla.Weather.TwilightCloudScale applies in full."), ECVF_Default)
static TAutoConsoleVariable< float > CVarCarlaWeatherNightSkylightIntensity(TEXT("carla.Weather.NightSkylightIntensity"), 10.0f, TEXT("leave the rig's curve-driven skylight intensity untouched."), ECVF_Default)
static TAutoConsoleVariable< float > CVarCarlaWeatherSunHorizonFloor(TEXT("carla.Weather.SunHorizonFloor"), 0.05f, TEXT("value. 0.004 is a real horizon sun's ~400 lux against ~100000 overhead."), ECVF_Default)
static bool GForcingDirectionalVSMInvalidate
Definition Weather.cpp:738
static TAutoConsoleVariable< float > CVarCarlaWeatherSkyLightIndirectIntensity(TEXT("carla.Weather.SkyLightIndirectIntensity"), 1.0f, TEXT("IndirectLightingIntensity forced on the sky rig's sky light. Negative leaves the rig's value."), ECVF_Default)
static TAutoConsoleVariable< float > CVarCarlaWeatherMoonMinAltitudeDeg(TEXT("carla.Weather.MoonMinAltitudeDeg"), 20.0f, TEXT("across dusk instead of from below the horizon. 0 is the plain anti-solar point."), ECVF_Default)
static TAutoConsoleVariable< float > CVarCarlaWeatherExposureBias(TEXT("carla.Weather.ExposureBias"), -1.0f, TEXT("Negative leaves the rig's authored value."), ECVF_Default)
static TAutoConsoleVariable< bool > CVarCarlaWeatherMoonDisc(TEXT("carla.Weather.MoonDisc"), true, TEXT("Draw the textured moon disc along the moon light's direction."), ECVF_Default)
static TAutoConsoleVariable< bool > CVarCarlaWeatherMoonAtmosphereDisk(TEXT("carla.Weather.MoonAtmosphereDisk"), false, TEXT("textured moon disc (carla.Weather.MoonDisc) replaces it."), ECVF_Default)
static TAutoConsoleVariable< float > CVarCarlaWeatherTransmittanceReleaseDeg(TEXT("carla.Weather.TransmittanceReleaseDeg"), -6.0f, TEXT("back to the engine's -90. Between 0 and this it eases off."), ECVF_Default)
static TAutoConsoleVariable< float > CVarCarlaWeatherDeckStartCloudiness(TEXT("carla.Weather.DeckStartCloudiness"), 80.0f, TEXT("Cloudiness at which the clouds start closing into the overcast deck (full at 100)."), ECVF_Default)
static TAutoConsoleVariable< float > CVarCarlaWeatherVSMSunStillSeconds(TEXT("carla.Weather.VSMSunStillSeconds"), 1.0f, TEXT("pushes of a moving sun, which brought the flicker back."), ECVF_Default)
static TAutoConsoleVariable< float > CVarCarlaWeatherOvercastSunIntensity(TEXT("carla.Weather.OvercastSunIntensity"), 1200.0f, TEXT("and clamped so it can only darken. Negative disables."), ECVF_Default)
static TAutoConsoleVariable< float > CVarCarlaWeatherOvercastThreshold(TEXT("carla.Weather.OvercastThreshold"), 90.0f, TEXT("from the plain cloud master material to the Billowy overcast one."), ECVF_Default)
static TAutoConsoleVariable< float > CVarCarlaWeatherFogDensityScale(TEXT("carla.Weather.FogDensityScale"), 0.001f, TEXT("The shipped 0.001 caps the engine at 0.1, too thin for a heavy-rain veil."), ECVF_Default)
static TAutoConsoleVariable< float > CVarCarlaWeatherOvercastSkyLightIntensity(TEXT("carla.Weather.OvercastSkyLightIntensity"), 10.0f, TEXT("Cloudiness and clamped so it can only brighten. Negative disables."), ECVF_Default)
static void ReleaseDirectionalVSMInvalidateIfSunStill(const UWorld *World)
Definition Weather.cpp:768
static float GLastPushedSunAltitude
Definition Weather.cpp:811
static float ComputeCloudDeckLightFactor(float Cloudiness)
Definition Weather.cpp:613
static TAutoConsoleVariable< float > CVarCarlaWeatherExposureMaxEV(TEXT("carla.Weather.ExposureMaxEV"), -1.0f, TEXT("Above 13 nothing measurably changes."), ECVF_Default)
static const TCHAR * GLightingJumpCVars[]
Definition Weather.cpp:803
static TAutoConsoleVariable< FString > CVarCarlaWeatherSkySphereNightColor(TEXT("carla.Weather.SkySphereNightColor"), TEXT("0.002 0.002 0.0025"), TEXT("else (e.g. \"off\") leaves the sphere's sun-driven colours."), ECVF_Default)
Recorder for the simulation.
bool IsEnabled(void)
void AddWeather(const CarlaRecorderWeather &Weather)
A sensor that captures images from the scene.
USceneCaptureComponent2D * GetCaptureComponent2D()
static FWeatherParameters * FindWeatherParameters(AActor *SkyActor)
Definition Sky.cpp:59
static bool SetSkyLightMap(UWorld *World, class UTextureCube *Cubemap, float Intensity)
Environment-map override for the sky light (set_sky_light_map RPC): every ASkyBase rig in World switc...
Definition Weather.cpp:1465
UCarlaRainCameraModifier * RainCameraModifier
Definition Weather.h:139
void SetWeather(const FWeatherParameters &WeatherParameters)
Update the weather parameters without notifing it to the blueprint's event.
Definition Weather.cpp:2661
FWeatherParameters Weather
Definition Weather.h:160
void SetDayNightCycle(const bool &active)
Update the day night cycle.
Definition Weather.cpp:2666
TMap< TWeakObjectPtr< ASceneCaptureCamera >, TWeakObjectPtr< UParticleSystemComponent > > SensorRain
Definition Weather.h:141
static void ApplyWeatherToSkyActor(AActor *SkyActor, const FWeatherParameters &Weather)
Pushes Weather into a single ASkyBase rig (sun/fog/clouds/atmosphere/ exposure) and runs its blueprin...
Definition Weather.cpp:1543
void ApplyWeather(const FWeatherParameters &WeatherParameters)
Update the weather parameters and notifies it to the blueprint's event.
Definition Weather.cpp:2597
void UpdateStreetLightsForDayNight()
Broadcast the current day/night state (derived from SunAltitudeAngle) to UCarlaLightSubsystem so regi...
Definition Weather.cpp:2580
UMaterial * PrecipitationPostProcessMaterial
Definition Weather.h:163
UParticleSystemComponent * ViewportRain
Definition Weather.h:136
static void FillSkyPostProcessFallback(class UPostProcessComponent *PostProcessComponent)
Exposure / bloom defaults for the fields the sky's loaded camera profile does not set (see the commen...
Definition Weather.cpp:2511
void UpdateRain()
Definition Weather.cpp:1227
void CheckWeatherPostProcessEffects()
Definition Weather.cpp:1334
UParticleSystem * RainTemplate
Definition Weather.h:133
UParticleSystemComponent * CreateRainEmitter(AActor *Owner, bool bSensor)
Definition Weather.cpp:1183
static bool HasSkyLightMap()
Definition Weather.cpp:1530
AWeather(const FObjectInitializer &ObjectInitializer)
Definition Weather.cpp:1155
void NotifyWeather(ASensor *Sensor=nullptr)
Notifing the weather to the blueprint's event.
Definition Weather.cpp:2650
bool DayNightCycle
Definition Weather.h:171
UMaterial * DustStormPostProcessMaterial
Definition Weather.h:166
void RefreshWeather(const FWeatherParameters &WeatherParameters)
uint64 ApplySerial
Definition Weather.h:173
void PushWeatherToSky()
Push the current weather into the ASkyBase rig (BP_Carla_Sky) and run its blueprint Update.
Definition Weather.cpp:1535
class UMaterialParameterCollection * RainParameters
Definition Weather.h:130
TMap< UMaterial *, float > ActiveBlendables
Definition Weather.h:168
virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override
Definition Weather.cpp:1318
virtual void BeginPlay() override
Definition Weather.cpp:1310
virtual void Tick(float DeltaSeconds) override
Definition Weather.cpp:1302
static void ClearSkyLightMap(UWorld *World)
Drops the override and restores the real-time atmosphere capture with the curve-driven intensity.
Definition Weather.cpp:1500
A simulation episode.
void NotifyDayTimeChange(bool bIsDay)
Broadcast a day/night change and then re-apply the UE4->UE5 component intensity conversion on every r...
virtual void ModifyPostProcess(float DeltaTime, float &BlendWeight, FPostProcessSettings &Settings) override
Definition Weather.cpp:1173
UMaterial * RainMaterial
Definition Weather.h:30
static UCarlaEpisode * GetCurrentEpisode(const UObject *WorldContextObject)
static ACarlaRecorder * GetRecorder(const UObject *WorldContextObject)