I’m trying to recreate the effect in this video at 3:39 where the camera blurs but the blur effect has this drag on it when the player looks around. I have seen this effect does in this game but I would like to recreate it.
From what I noticed playing it with peed pants I can say it uses Glass material parts to do the “not-refreshing” screen pixels, don’t know how though. Or with viewport frames, because changing my graphics settings seem to affect it
Directional motion blur like this is not yet possible in Roblox; games that do this have an extra velocity buffer computed during the render pass that makes this sort of thing possible to do with a post-process shader. The best you can do right now is probably to use just a regular Blur effect whose strength increases with movement (rotational and/or linear) of the camera. It won’t look the same, it probably won’t even look good. I’d focus on something else, tbh, a lot of people feel sick from motion blur and turn it off, or turn it off just to get better framerate.
Haven’t tried this before but I had the same interest as you for a while.
I noticed some games simply use what’s rendered to the player’s screen as a low resolution texture.
I would personally try to utilize CaptureService for this.
The idea is simple:
- Capture the player’s screen at a low frequency (be generous, e.g. between 10 and 30 hertz).
- Instance the resulting texture as a UI taking up the player’s entire screen, but highly transparent.
- Move the individual texture in the opposite direction the player’s cursor moves towards (to simulate texture movement), while the texture is fading out quickly.
- Do this rapidly for every screen capture to simulate a “ghosting” effect.
Again, just a very rough idea based on my own observations.
I did try that and ur right it looks mid, im just trying to recreate this effect for flashback cutscenes in my game that last like 10 seconds
ill see what I can do with captureservice
If it’s fast enough, that could be an interesting effect. Traditionally though, when this was done in 2D games (or replicated in a 3D game, using a shader), the frame stacking is done with just a couple of buffers, and blitting semi-transparent copies onto what’s already in the buffer.
I don’t think even EditableImages have a way to do this quickly enough for a realtime effect, and I’d kind expect that if you had enough CaptureService images stacked, that the effect is really only going to be viable on gaming PCs due to the huge amount of texture memory this would use. But definitely try it. Worst case maybe it works on PC but you have to do something else on mobile or lower-end machines.
Frame-stacking blur is also visually a very different effect from motion blur; but if you’re just after something disorienting, it could still work.
i managed to make it work but the problem is captureservice cant update faster than 60 fps so it works but its too slow
Could you show a clip of how it looks?
I didn’t think you’d need anywhere near up to 60hz for this, but my speculation could be off.
i cant record but heres the code i used, js put a localscript in starterplayerscripts
--// Directional Capture Blur / Amnesia Low Health Vision
--// Place in StarterPlayerScripts
local Players = game:GetService("Players")
local CaptureService = game:GetService("CaptureService")
local RunService = game:GetService("RunService")
local UserInputService = game:GetService("UserInputService")
local player = Players.LocalPlayer
local playerGui = player:WaitForChild("PlayerGui")
--==================================================
-- SETTINGS
--==================================================
--==================================================
-- HEALTH
--==================================================
-- Effect begins below this health.
local HEALTH_THRESHOLD = 15
-- Maximum effect strength.
-- 1 = full effect.
local MAX_HEALTH_EFFECT = 9999
--==================================================
-- CAPTURE
--==================================================
-- Desired capture request rate.
--
-- CaptureService may physically capture slower than this,
-- but the script will request captures as frequently as
-- the service allows.
local CAPTURE_RATE = 9999
--==================================================
-- DIRECTIONAL SMEAR
--==================================================
-- How strongly the captured image follows behind
-- the player's mouse.
local DRAG_MULTIPLIER = 4
-- Maximum distance a ghost can travel.
local MAX_DRAG = 500
--==================================================
-- GHOST APPEARANCE
--==================================================
-- Lower = stronger.
local START_TRANSPARENCY = 0.1
-- How long each ghost exists.
local FADE_TIME = 2
-- Maximum ghosts at once.
local MAX_GHOSTS = 999
-- Ignore extremely tiny mouse movement.
local MOVEMENT_DEADZONE = 0.05
--==================================================
-- CONTINUOUS SMEAR
--==================================================
-- How quickly the smear reacts to mouse movement.
local DRAG_RESPONSE = 45
-- How quickly the smear settles when movement stops.
local TRAIL_DECAY = 2
--==================================================
-- GUI
--==================================================
local screenGui = Instance.new("ScreenGui")
screenGui.Name = "DirectionalCaptureBlur"
screenGui.IgnoreGuiInset = true
screenGui.ResetOnSpawn = false
screenGui.DisplayOrder = 999999
screenGui.ZIndexBehavior =
Enum.ZIndexBehavior.Sibling
screenGui.Parent = playerGui
--==================================================
-- STATE
--==================================================
local humanoid = nil
local healthEffect = 0
local lastMousePosition =
UserInputService:GetMouseLocation()
local mouseVelocity =
Vector2.zero
local smoothMouseVelocity =
Vector2.zero
local smearVelocity =
Vector2.zero
local smearOffset =
Vector2.zero
local ghosts = {}
local capturing = false
local accumulator = 0
--==================================================
-- CHARACTER
--==================================================
local function setupCharacter(character)
humanoid =
character:WaitForChild("Humanoid")
-- Reset effect when respawning.
healthEffect = 0
end
if player.Character then
setupCharacter(
player.Character
)
end
player.CharacterAdded:Connect(
setupCharacter
)
--==================================================
-- HEALTH + MOUSE VELOCITY
--==================================================
RunService.RenderStepped:Connect(function(deltaTime)
--==================================================
-- HEALTH INTENSITY
--==================================================
if humanoid then
local health =
humanoid.Health
if health < HEALTH_THRESHOLD then
-- 15 HP = 0%
-- 0 HP = 100%
healthEffect =
math.clamp(
(
HEALTH_THRESHOLD
-
health
)
/
HEALTH_THRESHOLD,
0,
MAX_HEALTH_EFFECT
)
else
healthEffect = 0
end
end
--==================================================
-- MOUSE VELOCITY
--==================================================
local mousePosition =
UserInputService:GetMouseLocation()
local movement =
mousePosition
-
lastMousePosition
if deltaTime > 0 then
mouseVelocity =
movement
/
deltaTime
end
lastMousePosition =
mousePosition
-- Smooth mouse movement.
smoothMouseVelocity =
smoothMouseVelocity:Lerp(
mouseVelocity,
math.clamp(
deltaTime * 30,
0,
1
)
)
end)
--==================================================
-- REMOVE OLDEST GHOST
--==================================================
local function removeOldestGhost()
if #ghosts == 0 then
return
end
local oldest =
table.remove(
ghosts,
1
)
if oldest
and oldest.image
and oldest.image.Parent then
oldest.image:Destroy()
end
end
--==================================================
-- CREATE GHOST
--==================================================
local function createGhost(contentId)
-- Don't create anything if the effect is inactive.
if healthEffect <= 0 then
return
end
if not contentId then
return
end
-- Keep ghost count under control.
while #ghosts >= MAX_GHOSTS do
removeOldestGhost()
end
local ghost =
Instance.new("ImageLabel")
ghost.Name =
"CaptureGhost"
ghost.AnchorPoint =
Vector2.new(
0.5,
0.5
)
-- Overscan prevents edges from appearing.
ghost.Size =
UDim2.new(
1,
800,
1,
800
)
ghost.Position =
UDim2.fromScale(
0.5,
0.5
)
ghost.BackgroundTransparency =
1
ghost.BorderSizePixel =
0
ghost.Image =
contentId
ghost.ImageTransparency =
1 -
(
(
1
-
START_TRANSPARENCY
)
*
healthEffect
)
ghost.ScaleType =
Enum.ScaleType.Stretch
ghost.ZIndex =
1
ghost.Parent =
screenGui
--==================================================
-- GHOST DATA
--==================================================
table.insert(
ghosts,
{
image = ghost,
age = 0,
strength =
0.5
+
(
#ghosts
/
MAX_GHOSTS
)
*
1.5,
healthAtCreation =
healthEffect,
}
)
end
--==================================================
-- CONTINUOUS SMEAR
--==================================================
RunService.RenderStepped:Connect(function(deltaTime)
--==================================================
-- TARGET VELOCITY
--==================================================
local velocity =
smoothMouseVelocity
if velocity.Magnitude <
MOVEMENT_DEADZONE then
velocity =
Vector2.zero
end
-- Old vision moves opposite mouse movement.
local targetVelocity =
-velocity
*
DRAG_MULTIPLIER
*
healthEffect
-- Limit.
if targetVelocity.Magnitude >
MAX_DRAG then
targetVelocity =
targetVelocity.Unit
*
MAX_DRAG
end
--==================================================
-- FOLLOW
--==================================================
smearVelocity =
smearVelocity:Lerp(
targetVelocity,
math.clamp(
deltaTime
*
DRAG_RESPONSE,
0,
1
)
)
--==================================================
-- MOVE
--==================================================
smearOffset +=
smearVelocity
*
deltaTime
*
healthEffect
--==================================================
-- DECAY
--==================================================
smearOffset *=
math.exp(
-TRAIL_DECAY
*
deltaTime
)
--==================================================
-- LIMIT
--==================================================
if smearOffset.Magnitude >
MAX_DRAG then
smearOffset =
smearOffset.Unit
*
MAX_DRAG
end
--==================================================
-- UPDATE GHOSTS
--==================================================
for i = #ghosts, 1, -1 do
local ghostData =
ghosts[i]
local ghost =
ghostData.image
if not ghost
or not ghost.Parent then
table.remove(
ghosts,
i
)
continue
end
ghostData.age +=
deltaTime
--==================================================
-- LIFE
--==================================================
local alpha =
math.clamp(
ghostData.age
/
FADE_TIME,
0,
1
)
--==================================================
-- POSITION
--==================================================
local offset =
smearOffset
*
ghostData.strength
ghost.Position =
UDim2.new(
0.5,
offset.X,
0.5,
offset.Y
)
--==================================================
-- TRANSPARENCY
--==================================================
if healthEffect <= 0 then
ghost.ImageTransparency = 1
else
local baseTransparency =
1
-
(
(
1
-
START_TRANSPARENCY
)
*
healthEffect
)
ghost.ImageTransparency =
baseTransparency
+
(
1
-
baseTransparency
)
*
alpha
end
--==================================================
-- REMOVE
--==================================================
if alpha >= 1
or healthEffect <= 0 then
ghost:Destroy()
table.remove(
ghosts,
i
)
end
end
--==================================================
-- RESET WHEN EFFECT IS OFF
--==================================================
if healthEffect <= 0 then
smearOffset =
smearOffset:Lerp(
Vector2.zero,
math.clamp(
deltaTime * 10,
0,
1
)
)
smearVelocity =
smearVelocity:Lerp(
Vector2.zero,
math.clamp(
deltaTime * 10,
0,
1
)
)
end
end)
--==================================================
-- CAPTURE
--==================================================
local function captureScreen()
-- Don't capture when the effect isn't needed.
if healthEffect <= 0 then
return
end
if capturing then
return
end
capturing = true
local success =
pcall(function()
CaptureService:CaptureScreenshot(
function(contentId)
if contentId
and healthEffect > 0 then
createGhost(
contentId
)
end
capturing = false
end
)
end)
if not success then
capturing = false
end
end
--==================================================
-- CAPTURE LOOP
--==================================================
RunService.RenderStepped:Connect(function(deltaTime)
-- Completely stop capture above 15 HP.
if healthEffect <= 0 then
accumulator = 0
return
end
accumulator +=
deltaTime
local interval =
1
/
CAPTURE_RATE
if accumulator >= interval then
accumulator -=
interval
if not capturing then
captureScreen()
end
end
end)
This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.