Also why are people complaining about performance? couldn’t you just hook this up to a CollectionService tag so that you don’t have to loop through the whole workspace every frame. And if you have a lot of beams you can only update the ones near a player.
Yes there are ways further to optimize this, but I can leave that up to the person who is need of further optimization, I’m mainly advocating for this to happen in games.
Okay thank you this is really useful to know and keep in mind!
I was testing it out and see the free flashlight in the toolbox has the same problem as well.
I have acknowledged it’s less effective with beams pointing horizontally, however you can reposition the ancestor BasePart
and adjust the Min and Max distance values to accommodate best for it.
If I come up with a very good fix or something for it, I’ll give it a shot. Also let me know how it works out or if you got any questions.
To solve the performance hit from iterating through so many (unnececary) instances at once, you can force only tagged instances of beams to have this effect (thus narrowing it down to possibly tens of instances instead of thousands).
Although I’m not too experienced in how these things would work, I do have a theoretical way of fixing the issue @batteryday forwarded. The beam could be hidden in replacement of a billboard gui at the source location when the camera intersects with the beam and vice versa.
To be clear, the image of the flashlight is how it comes out of the toolbox by default not after running the script.
I could consider trying something like this. Doesn’t sound like a bad idea (the billboards)
You should save the instances named “VolumetricBeam” so you don’t need to loop through every descendant every frame.
Optimized code:
--Client sided script
local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
local Player = Players.LocalPlayer
local Character = Player.Character or Player.CharacterAdded:Wait()
local HumanoidRootPart = Character:WaitForChild("HumanoidRootPart")
local Camera = workspace.CurrentCamera
local MaxDistance = 50 --Maximum distance in studs at which beams gradually begin to disappear.
local MinDistance = 4 --Lowest distance in studs at which beams are completely transparent.
local BasedOnCamera = false --Whether distance from the beams is based on the position of the camera or the player's character.
local volumetricBeams = {}
local function onDescendantAdded(descendant)
if descendant:IsA("Beam") and descendant.Name == "VolumetricBeam" then
local sourcePart = descendant:FindFirstAncestorWhichIsA("BasePart")
if sourcePart then
volumetricBeams[descendant] = sourcePart
end
end
end
workspace.DescendantAdded:Connect(onDescendantAdded)
workspace.DescendantRemoving:Connect(function(descendant)
volumetricBeams[descendant] = nil
end)
for i, descendant in workspace:GetDescendants() do
onDescendantAdded(descendant)
end
local function UpdateBeamTransparency()
for Object, SourcePart in volumetricBeams do
local NormalizedSourcePartPosition = Vector3.new(SourcePart.Position.X, 0, SourcePart.Position.Z)
local NormalizedSubjectPosition = BasedOnCamera and Vector3.new(Camera.CFrame.Position.X, 0, Camera.CFrame.Position.Z)
or Vector3.new(HumanoidRootPart.Position.X, 0, HumanoidRootPart.Position.Z)
local Magnitude = (NormalizedSubjectPosition - NormalizedSourcePartPosition).Magnitude - MinDistance
local NormalizedValue = 1 - math.min(1, Magnitude / MaxDistance)
Object.Transparency = NumberSequence.new({
NumberSequenceKeypoint.new(0, NormalizedValue),
NumberSequenceKeypoint.new(1, 1),
})
end
end
RunService.RenderStepped:Connect(UpdateBeamTransparency) --Updated before every frame. However, you can connect it to a different event like only when the camera or player's character is actively moving.
This looks very nice though, good work.
Yes, this is not a bad solution to the previous concern. I encourage anybody else here to use this method you’ve presented. But if it’s not already obvious it doesn’t need to be updated every single frame anyway, it’s just for the example. This is also an extremely great method for saving the initial transparency of the beams if you need to restore it later, or you want the transparency to be at it’s lowest value, x instead of 0.
Here is a more optimized and new version of the original design. It includes @Katrist’s optimizations of adding the beam instances to their own table to avoid looping through potentially thousands of instances in the workspace. Additionally, it now remembers the initial transparency of the beam before the script modifies it.
local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
local Player = Players.LocalPlayer
local Character = Player.Character or Player.CharacterAdded:Wait()
local HumanoidRootPart = Character:WaitForChild("HumanoidRootPart")
local Camera = workspace.CurrentCamera
local MaxDistance = 50 --Maximum distance in studs at which beams gradually begin to disappear.
local MinDistance = 4 --Lowest distance in studs at which beams are completely transparent.
local BasedOnCamera = false --Whether distance from the beams is based on the position of the camera or the player's character.
local VolumetricBeams = {}
local function OnDescendantAdded(Descendant: Instance)
if Descendant:IsA("Beam") and Descendant.Name == "VolumetricBeam" then
local SourcePart = Descendant:FindFirstAncestorWhichIsA("BasePart")
if SourcePart then
VolumetricBeams[Descendant] = {SourcePart = SourcePart, InitialTransparency = Descendant.Transparency}
end
end
end
local function OnDescendantRemoved(Descendant: Instance)
VolumetricBeams[Descendant] = nil
end
local function UpdateVolumetricBeamsTable()
for _, Descendant in workspace:GetDescendants() do
if not VolumetricBeams[Descendant] then
OnDescendantAdded(Descendant)
end
end
end
local function UpdateVolumetricBeams()
for Beam, Details in VolumetricBeams do
local NormalizedSourcePartPosition = Vector3.new(Details.SourcePart.Position.X, 0, Details.SourcePart.Position.Z)
local NormalizedSubjectPosition
if BasedOnCamera then
NormalizedSubjectPosition = Vector3.new(Camera.CFrame.Position.X, 0, Camera.CFrame.Position.Z)
else
NormalizedSubjectPosition = Vector3.new(HumanoidRootPart.Position.X, 0, HumanoidRootPart.Position.Z)
end
local StartTransparency = Details.InitialTransparency.Keypoints[1]
local EndTransparency = Details.InitialTransparency.Keypoints[#Details.InitialTransparency.Keypoints]
local Magnitude = (NormalizedSubjectPosition - NormalizedSourcePartPosition).Magnitude - MinDistance
local NormalizedValue = math.max(StartTransparency.Value, 1 - math.min(1, Magnitude / MaxDistance))
Beam.Transparency = NumberSequence.new({
NumberSequenceKeypoint.new(0, NormalizedValue),
NumberSequenceKeypoint.new(1, EndTransparency.Value),
})
end
end
RunService.RenderStepped:Connect(UpdateVolumetricBeams)
workspace.DescendantAdded:Connect(OnDescendantAdded)
workspace.DescendantRemoving:Connect(OnDescendantRemoved)
UpdateVolumetricBeamsTable()
I may completely rewrite my original post in the future if additional features are added or come along. Considering methods of preventing the beams from looking funny when pointing horizontally. Possibly @VegetationBush’s suggestion.
… Or you could just use collectionservice
I purposefully kept the same use as the original. You could easily change it to CollectionService if you wanted to.
Also, your suggestion was already mentioned earlier:
it’s as far as personal preference goes
Where do I put the script? and make the fake volumetric lighting to work
Literally drops my games fps from 60 to 30 and I can get the exact same look with roblox’s
atmosphere
I personally prefer collectionservice because if you have streaming enabled, this will not affect anything unloaded- which for this script thats fine but for other cases it isnt
And I’m pretty sure if you use collectionservice, it still affects streamed objects
correct me if I’m wrong!
I was completely unaware of this, so if that is the case, that’s very nice!
Could you share how? As far as I am aware, Atmosphere
is just fog with realistically-based parameters like Density
, except the fog appears like the skybox. This is quite different to what both Volumika and this resource should be doing, which is to create localized “light shafts” instead of there just being uniformly-distributed fog that just… sits there.
From: “Roblox - Volumetric Lighting advanced implementation”
I didnt even know volumika could do this because no matter how I used it, it just looked like plain fog, which is what roblox’s atmosphere is. I just thought it was straight up fog that wasnt affected by the sun in that way
also it puts large circles on the players screen and you cant remove it
without:
And the settings I used for my atmosphere, its blue but you can make it white
It looks very similar to the volumika fog
elttob mentioned it in their blog that the implementation of it is rather ridiculous and best works indoors.
i recreated the same environment as it was in the blog and the banding was quite obvious and it looked ridiculous as expected outdoors.
We really do need an official implementation of it though. Poor mobile compartibility/performance is no excuse to this. It’s up to a developer how would they handle the multi-platform compartibility (to either propose changes for cross-platform or restrict it to PC users)