--[[ Aurora effect. Activates for tracks with nothern coordinates, taking into account some major known solar flares (last one occuring on February of 2022). Chance is calculated per-day in a fixed manner, meaning same track would get the effect on the same day on different AC runs. Should look like this: https://files.acstuff.ru/shared/jg7d/20220416-195754-karelia-bmw_m3_e30.jpg If you want to integrate it to your WeatherFX script, feel free to copy this implementation with all of its details. You would only need to change `computeAuroraIntensity()`: now it relies on some global variables set by default script, such as sun and moon direction. Few key points of how it works: • First, it draws a bunch of squiggly lines on the sky plane, above 10 km from surface. Then, to get the shape, it blurs those lines vertically. • Squiggly lines are drawn into a texture A, blurred version is drawn into a texture B1, and after that resulting blurred version is drawn onto the main render target texture. • For blurred version, two textures are made, B1 and B2. This allows to mix together current and previous frame taking into account camera motion (see `temporalSmoothing()` in shaders), greatly increasing quality for a very low cost. Otherwise, to achieve compatible quality shaders would require to use more steps for blurring, which would be more expensive. For that, B1 and B2 get swapped around each frame. Blur things into B1 and use B2 as a previous texture. • Also, original squiggly lines texture (A) are drawn with extra padding covering more than visible on the screen. This is necessary for lines outside of the frame to be able to blur into main image. • Whole thing is repeated second time with vertical offset and much smaller resolution for so called “high shape”: this is a cheap way to get reddish shape above green shape. • With all that, you can see that it requires a few extra textures for the whole effect to work. That’s why there is a `texData` table and `createPassData()` function. Each cubemap face, each rear view mirror, each side in triple screen mode or eye in VR mode, all give their own `uniqueKey`. For each of those keys new set of textures is prepared. ]] if not ScriptSettings.EXTRA_EFFECTS.AURORAS then UpdateAurora = function (dt) end return end local texData = {} local auroraAmbientColor = rgb(0.3, 1, 0) local auroraTime = -math.random() * 1e6 local temporalSmoothing = 0.05 table.insert(OnResolutionChange, function () table.clear(texData) end) local function createPassData(uniqueKey) local size = render.getRenderTargetSize() local mainFrame = uniqueKey == 100 -- ac.debug('aur key:'..tostring(uniqueKey), size) return { txBaseNoise = ui.ExtraCanvas(size * 0.7, 1, render.TextureFormat.R8.UNorm):setName(mainFrame and 'Aurora: base shape' or nil), txBaseBlurred = { ui.ExtraCanvas(size * 0.5, 1, render.TextureFormat.R8.UNorm):setName(mainFrame and 'Aurora: blurred base shape (1)' or nil), ui.ExtraCanvas(size * 0.5, 1, render.TextureFormat.R8.UNorm):setName(mainFrame and 'Aurora: blurred base shape (2)' or nil) }, txHighNoise = ui.ExtraCanvas(size / 8, 1, render.TextureFormat.R8.UNorm):setName(mainFrame and 'Aurora: high shape' or nil), txHighBlurred = { ui.ExtraCanvas(size / 8, 1, render.TextureFormat.R8.UNorm):setName(mainFrame and 'Aurora: blurred high shape (1)' or nil), ui.ExtraCanvas(size / 8, 1, render.TextureFormat.R8.UNorm):setName(mainFrame and 'Aurora: blurred high shape (2)' or nil) }, gNoiseScale = vec2(0.02, 0.02 * size.y / size.x), } end ---@param passID render.PassID local function renderAurora(passID, frameIndex, uniqueKey) if passID == render.PassID.CubeMap and frameIndex == 3 then return end local tex = table.getOrCreate(texData, uniqueKey, createPassData, uniqueKey) local time = auroraTime local uvPadding = 1.6 tex.txBaseNoise:updateSceneWithShader({ textures = { txNoiseLr = 'rain_fx/puddles.dds', }, values = { gTime = time, gUVPadding = uvPadding, gTemporalSmoothing = temporalSmoothing }, shader = 'shaders/aurora_shape.fx', cacheKey = 0 }) tex.txBaseBlurred[1], tex.txBaseBlurred[2] = tex.txBaseBlurred[2], tex.txBaseBlurred[1] tex.txBaseBlurred[1]:updateSceneWithShader({ textures = { ['txPrepared.1'] = tex.txBaseNoise, ['txPrevious.1'] = tex.txBaseBlurred[2], txNoiseLr = 'rain_fx/puddles.dds', }, values = { gTime = time, gShuffle = vec2(math.random() * 32, math.random() * 32), gUVPadding = uvPadding, gTemporalSmoothing = temporalSmoothing }, shader = 'shaders/aurora_blur.fx', cacheKey = 0 }) tex.txHighNoise:updateSceneWithShader({ textures = { txNoiseLr = 'rain_fx/puddles.dds', }, values = { gTime = time, gUVPadding = uvPadding, gTemporalSmoothing = temporalSmoothing }, shader = 'shaders/aurora_shape_high.fx', cacheKey = 0 }) tex.txHighBlurred[1], tex.txHighBlurred[2] = tex.txHighBlurred[2], tex.txHighBlurred[1] tex.txHighBlurred[1]:updateSceneWithShader({ textures = { ['txPrepared.1'] = tex.txHighNoise, ['txPrevious.1'] = tex.txHighBlurred[2], txNoiseLr = 'rain_fx/puddles.dds', }, values = { gTime = time, gShuffle = vec2(math.random() * 32, math.random() * 32), gUVPadding = uvPadding, gNoiseScale = tex.gNoiseScale, gTemporalSmoothing = temporalSmoothing }, shader = 'shaders/aurora_blur_high.fx', cacheKey = 0 }) render.fullscreenPass({ blendMode = render.BlendMode.BlendAdd, textures = { ['txMain.1'] = tex.txBaseBlurred[1], ['txHigh.1'] = tex.txHighBlurred[1], }, values = { gBrightnessMult = AuroraIntensity * (passID == render.PassID.CubeMap and 1.6 or 1) * (UseGammaFix and GammaFixBrightnessOffset * 3 or 1), }, shader = 'shaders/aurora_apply.fx', cacheKey = 0 }) end local auroraGlow ---@type ac.SkyExtraGradient local subscribed ---@type fun()? local function setAuroraActive(active) if active ~= (subscribed ~= nil) then if subscribed == nil then if not auroraGlow then auroraGlow = ac.SkyExtraGradient() auroraGlow.direction = vec3(0, 1, 0) auroraGlow.sizeFull = 0.5 auroraGlow.sizeStart = 1 auroraGlow.exponent = 0.3 end subscribed = RenderSkySubscribe(render.PassID.All, renderAurora, 1) ac.skyExtraGradients:push(auroraGlow) else ac.skyExtraGradients:erase(auroraGlow) subscribed() subscribed = nil end end end local solarStorms = stringify.tryParse(ac.storage.solarStorms, nil, { '0501', '1709', '2202' }) local currentTime = os.time() -- Updating list of auroras every three days if not ac.storage.solarStormsTime or currentTime > tonumber(ac.storage.solarStormsTime) + 3 * 24 * 60 * 60 then web.get('https://acstuff.ru/j/auroras.txt', function (err, response) if err then return end local newList = table.filter(response.body:split('\n'), function (s) return #s == 4 end) if #newList > 4 then solarStorms = newList ac.storage.solarStorms = stringify(newList) ac.storage.solarStormsTime = currentTime end end) end local sim = ac.getSim() local lattitude = math.abs(ac.getTrackCoordinatesDeg().x) local function computeAuroraChance() -- if true then return 1 end local solarStorm = table.contains(solarStorms, os.dateGlobal('%y%m', sim.timestamp)) local solarStormMult = solarStorm and 1 - math.abs(tonumber(os.dateGlobal('%d', sim.timestamp)) / 15 - 1) or 1 local bandHalfWidth = solarStorm and 20/2 or 5/2 local bandFullHalfWidth = bandHalfWidth / 2 local bandCenter = 70 return math.lerpInvSat(lattitude, bandCenter - bandHalfWidth, bandCenter - bandFullHalfWidth) * math.lerpInvSat(lattitude, bandCenter + bandHalfWidth, bandCenter + bandFullHalfWidth) * math.lerp(0.1, 1, solarStorm and solarStormMult or 0) end local function computeAuroraIntensity(chance) -- if true then return 1 end local diceBase = bit.bxor(328383, math.floor(sim.dayOfYear + sim.timeTotalSeconds / (24 * 60 * 60) + 0.5) + math.floor(lattitude * 1e4)) local dice = math.seededRandom(diceBase) if chance < dice * 0.2 then return 0 end local ret = chance < 0.1 and (dice * 1e3 % 1) or math.min((chance - dice * 0.2) * 4, 1) ret = ret * math.saturateN(-SunDir.y * 10 - 0.5) -- fade when sun is above horizon ret = ret * math.lerp(1, 0.6, math.saturateN(MoonDir.y)) -- lower intensity if moon is high ret = ret * math.max(0, 1 - LightPollutionValue * 4) -- lower intensity if light pollution is present ret = ret * math.max(0, 1 - CurrentConditions.clouds * 2) -- lower intensity if there are a lot of clouds (doesn’t look too good otherwise) ret = ret * CurrentConditions.clear -- lower intensity if weather is not clear return ret end local prevHour, chance function UpdateAurora(dt) local curHour = math.floor(sim.timestamp / (60 * 60)) if curHour ~= prevHour then chance = computeAuroraChance() prevHour = curHour end if chance == 0 and (Overrides.aurora or 0) <= 0 then if subscribed then setAuroraActive(false) end return end auroraTime = auroraTime + dt temporalSmoothing = os.preciseClock() < 0.3 and 0.5 or (dt < 0.5 and (dt < 0.05 and 0.05 or 0.1) or 0.5) AuroraIntensity = Overrides.aurora or computeAuroraIntensity(chance) AuroraIntensity = AuroraIntensity * (1 - SpaceLook) setAuroraActive(AuroraIntensity > 0) if subscribed and auroraGlow then local noise = math.sin(auroraTime * 0.1) * 0.45 + math.sin(auroraTime * 0.713) * 0.3 + math.sin(auroraTime * 1.303) * 0.25 auroraGlow.color:set(auroraAmbientColor):scale((1 + 0.3 * noise) * AuroraIntensity * (UseGammaFix and 0.001 or 0.15)) end end