Ever wanted to make 90’s retro style sun-flares for your game? This effect was popular in games like Zelda ocarina of time, and basically every early 3D-accelerated game. It’s cheap and can easily be customized.
This code does a couple of noteworthy tricks:
-
It does its rendering by emitting particles that only last for a single frame. This is done by calling :Clear() and then :Emit(1) on a disabled emitter each render stepped. This is also very useful if you want to put stable glows on things…
-
The particles are drawn “on top of everything”. The emitters are set to draw at the screen near plane depth every frame; basically right up against the camera. This is achieved by using a Zoffset that is the same as the transformed screen depth. This can be used for making particles that render over everything else in the world, great for highlighting objects or doing map markers etc.
Update: Added an animated geometry occlusion test.
Video:
Uncopylocked place:
The sourcecode:
local sunDistance = 500
local objectDistance = 10
local brightScale = 0.15
local rim = 0.1 --fraction of screen near border to fade out at
local occludeLevel = 0
local occludeSpeed = 10
local list = {
script.SunFlare,
script.Bit0,
script.Bit1,
script.Bit2,
script.Bit3,
script.Bit4,
script.Bit5,
}
local brightness = 1
local rotation =0
game["Run Service"].RenderStepped:Connect(function(dt)
--Calculate the sun position
local sunPosition = workspace.CurrentCamera.CFrame.Position + game.Lighting:GetSunDirection() * sunDistance
local screenSpacePosition,onScreen = workspace.CurrentCamera:WorldToViewportPoint(sunPosition)
--Make a rod to place the other parts along
local vector = (Vector2.new(workspace.CurrentCamera.ViewportSize.x/2,workspace.CurrentCamera.ViewportSize.Y/2) - Vector2.new(screenSpacePosition.x, screenSpacePosition.y))
local dist = vector.Magnitude * 3 --length of the "rod"
local dir = vector.Unit
rotation = math.deg(math.atan2(-vector.y, vector.x)) + 180
local step = dist / #list
for j = 1, #list do
local dx = j - 1
local ray = workspace.CurrentCamera:ViewportPointToRay(screenSpacePosition.x + dir.x * dx * step, screenSpacePosition.y + dir.y * dx * step, 1)
list[j].Position = ray.Origin + ray.Direction.Unit * objectDistance
end
--calculate the falloff
local normalizedPos = Vector2.new(screenSpacePosition.X / game.Workspace.CurrentCamera.ViewportSize.X,screenSpacePosition.Y / game.Workspace.CurrentCamera.ViewportSize.Y)
brightness = 1
--Not visible
if (onScreen == false) then
brightness = 0
end
if (brightness > 0) then
--left
if (normalizedPos.X < rim) then
local dx = math.clamp(1-(rim - normalizedPos.X) / rim,0,1)
brightness = math.min(brightness,dx)
end
--right
if (normalizedPos.X > 1-rim) then
local dx = math.clamp(1-( normalizedPos.X-(1-rim)) / rim,0,1)
brightness = math.min(brightness,dx)
end
--top
if (normalizedPos.Y < rim) then
local dx = math.clamp(1-(rim - normalizedPos.Y) / rim,0,1)
brightness = math.min(brightness,dx)
end
--bottom
if (normalizedPos.Y > 1-rim) then
local dx = math.clamp(1-( normalizedPos.Y-(1-rim)) / rim,0,1)
brightness = math.min(brightness,dx)
end
end
--Do ray occlusion - just one + some animation seems fine
if (brightness > 0) then
local params = RaycastParams.new()
params.RespectCanCollide = true
local vec = (sunPosition - game.Workspace.CurrentCamera.CFrame.Position).Unit
local res = game.Workspace:Raycast(game.Workspace.CurrentCamera.CFrame.Position, vec * sunDistance, params)
if (res) then
occludeLevel -= dt * occludeSpeed
else
occludeLevel += dt * occludeSpeed
end
occludeLevel = math.clamp(occludeLevel,0,1)
brightness = math.min(occludeLevel, brightness)
end
end)
game["Run Service"]:BindToRenderStep("PreCamera",5000,function()
for key,value in list do
value.Parent = game.Workspace
for _,part in value:GetDescendants() do
if (part:IsA("ParticleEmitter")) then
part.Brightness = brightness * brightScale
part.Rotation = NumberRange.new(rotation)
--Super hackery, we can abuse the zoffset to make these appear flush with the camera in Z space
local screenSpacePosition,onScreen = workspace.CurrentCamera:WorldToViewportPoint(part.Parent.WorldPosition)
part.ZOffset = screenSpacePosition.Z-1
--Emit a particle for just 1 frame
part.Enabled = false
part:Clear()
part:Emit(1)
end
end
end
end)