Figured a way to reverse the LOD for ParticleEmitters this allows you to see the intended amount of particle rate from anywhere without setting quality & camera distance interfering your scenes. I see this to be really useful for graphics artists who want to add more detail to big scenes and to have lower end devices see visuals as it’d be on higher end preferences. Felt like letting people have this because my use-cases at the moment aren’t vast but I’m sure there’s many whom have wanted this since the dawn of time on Roblox.
Other resources you may be interested in also looking at which are similar for visuals in-engine…
Repro Code Make sure to give the tag of “Particle” through the Tag Editor so that the code picks it up!
edit note - recently became aware that the engine does have a cut-off distance, as in it’ll force stop emitter’s Rate property regardless of however high it is, but it’s a non-issue as of right now as users will not notice due to how far it cuts off at.
-- Credits to nurokoi
local CollectionService = game:GetService("CollectionService")
local RunService = game:GetService("RunService")
local function ReverseRate(inputRate : number, fromWorldPoint : Vector3)
local QualityLevel = math.clamp(UserSettings().GameSettings.SavedQualityLevel.Value / 10, 0.1, 1)
local DistanceCam = (workspace.CurrentCamera.CFrame.Position - fromWorldPoint).Magnitude
local DistanceQualityScalar = math.clamp(1 - (DistanceCam - 1e2 * 2) / (1e2 * 8), 0, 1)
local ActualRate = inputRate * (inputRate / (inputRate * QualityLevel * DistanceQualityScalar))
return ActualRate
end
local function ReverseParticleThrottleRate()
for _, particleEmitter : ParticleEmitter in CollectionService:GetTagged("Particle") do
local Source, EmissionCenter = particleEmitter.Parent, nil
if Source:IsA("BasePart") then
EmissionCenter = Source.Position
elseif Source:IsA("Attachment") then
EmissionCenter = Source.WorldPosition
end
if not EmissionCenter then
continue
end
local DesiredRate = particleEmitter:GetAttribute("DesiredRate")
if not DesiredRate then
DesiredRate = particleEmitter.Rate
particleEmitter:SetAttribute("DesiredRate", DesiredRate)
end
particleEmitter.Rate = ReverseRate(DesiredRate, EmissionCenter)
end
end
RunService:BindToRenderStep("ParticleLOD", Enum.RenderPriority.Camera.Value + 1, ReverseParticleThrottleRate)
Video Demonstration
Both particles have same rate, red particles do not have the Particle tag whereas the white ones do.
Great for things that need to have a certain detail/look to them constantly, not recommended for all other particles/effects. Aka, to anyone planning to use this, its a great resource but use sparingly on things you really need to maintain a “constant” look, else you’d run into some annoying performance issues.
Particle throttling is there for a reason, so it’s not a good idea to apply this to all the particles. If I had a horror game with something like “how many times have you seen this particle” then I would definitely use this.
Thanks for bringing this up, turns out yes you’re right and this was a mistake, the updated code should now work as intended for all devices.
As for distance when you mentioned, the code actually does account for distance for a specific distance but after that it starts to lose precision with maintaining the original rate due to the engines LOD causing floating point precision issues, so no matter how high it can go after said distance it’ll lower in rate despite setting a higher rate. However I think it’s a non-issue due to how far it only happens at, and players won’t really take such notice, there’s a trick where you could theoretically use another particle emitter to combat it but i sadly don’t have the time to do.
It was actually the “automatic” setting that bugs it. The code itself works for manual graphics. On a decent PC, automatic would generally be 10, but the function would get its graphic level at 1 or 0.1 since automatic is 0, this makes particle emit 10x more than expected in this case. It’s a small issue but noticeable in large games.
Right, I’ve just come to realise that there isn’t any way for the client to know the Automatic quality level as the only way we have right now is via UserSettings().GameSettings.SavedQualityLevel.Value which returns a value between 0 to 10, anything above 0 is manual values whereas 0 is automatic.
Quite unfortunate but I’ll be bumping this topic since this is a problem and can only be solved if Roblox simply just updates SavedQualityLevel with whatever the Automatic value is.
I found that there’s a way to do this without it failing cuz of the user having their graphics on auto, it just replaces the normal Roblox auto emitter with a custom one, it doesn’t use more resources than ur method as it has less calculations, it might be faster who knows, but this is just an edit I made in 30 mins that works all the time
local CollectionService = game:GetService("CollectionService")
local RunService = game:GetService("RunService")
local lastParticleEmited = {}
local function ReverseParticleThrottleRate()
for _, particleEmitter : ParticleEmitter in CollectionService:GetTagged("Particle") do
local DesiredRate = particleEmitter:GetAttribute("DesiredRate")
if not DesiredRate then
DesiredRate = particleEmitter.Rate
particleEmitter.Rate = 0
particleEmitter:SetAttribute("DesiredRate", DesiredRate)
end
local itterationTime = 1 / DesiredRate
local lastTick = lastParticleEmited[particleEmitter] or 0
local currentTick = tick()
local deltaTick = currentTick - lastTick
if deltaTick < itterationTime or not particleEmitter.Enabled then
continue
end
particleEmitter:Emit(1)
lastParticleEmited[particleEmitter] = tick()
end
end
RunService:BindToRenderStep("ParticleLOD", Enum.RenderPriority.Camera.Value + 1, ReverseParticleThrottleRate)
I found the iteration time from getting that the rate is how many times it’s emitted a second, so dividing a second by the rate makes u able to emit it the same amount
Increase the particle rate based on graphics settings and distance to compensate for the default rate reduction?
And once you hit the hard distance limit you still see no particles?
I think that’s what it does, but can someone confirm this.
This is pretty cool, but I think it would be more useful if it extended the default render distance and adjusted the rate down based on distance with a max distance set as a constant in the script.
This simplified method does override the default max render distance(which is cool).
It would be nice if it did allow for a reduction in rate based on distance, which you might want just to save on render bandwidth for something you can’t see very well. (or at all)
Seems like a combination of your version and the original would do that. I’m not sure how to calculate the rate reduction though.
I’m not sure, I didn’t know particles were emitted less when they are far away, also this is intended to replicate the rate system Roblox has, but without the limit they put on low graphics settings, and for the limit thing, as far as I know, yes, but the limit is very high so unless you have lots of particles on screen it wont ever reach it.