Dynamic Depth of Field

Create a dynamic changing depth of field effect for your game.

by Pr0pelled

Author Avatar

In this tutorial you will learn how to create a Depth of Field effect that changes in a dynamic way to simulate the focus on a camera or the human eye. This effect is best used in a cinematic type game or even a showcase. It can be used for other games aswell, but thats what I mainly use it for!

This tutorial is for people with medium/intermediate scripting knowledge. Most things will be explained, but simple things will not be explained in depth because if you are using this tuorial you should already know what those things are.

NOTE: This script is made for use in FIRST PERSON. If your game allows for players to use THIRD PERSON, the depth of field distance will be calculated based on the mouse's "hit" point on the screen.

To set the game to be first person open "StartPlayer" in the explorer and scroll down in the properties tab to find "CameraMaxZoomDistance" set it to 0.5. NOTE: When play testing the game in studio you will be unable to move the mouse, so you must use the hotkeys to start and stop the test. F5 starts testing. Shift+F5 ends testing.

this script belongs in Starter Player Scripts

START

TO START OFF THE SCRIPT, YOU'LL NEED TO SET UP SOME VARIABLES FOR LATER.

local cam = game.Workspace.CurrentCamera --The Current Camera the player is using. 
local player = game.Players.LocalPlayer    --Referrencing the Player.
local mouse = player:GetMouse() --The players mouse, it's used to find the distance for the DOF.
local RunService = game:GetService("RunService") --The Runservice, it's used in the listener.
local UserInputService = game:GetService("UserInputService") --The UserInputService. It's used later to hide the mouse.
local TweenService = game:GetService("TweenService") --The TweenService, it's used to create a smooth transition with the DOF.
local tar = nil --Used later, it along with all of the other nil variables are here so they can be local to the script and can be used within mutliple functions.
local cc = nil
local tweensettings = TweenInfo.new(0.05,Enum.EasingStyle.Quint,Enum.EasingDirection.InOut,0,false,0) --The Tween Settings, if you know how to work with tweens go ahead and change it to suit your needs.
local tween = nil
local MaxDistance = 500 --This sets how far the player can focus.

You may notice that the Mouse Cursor is very big and obstructing the effect. I noticed this aswell and I chose to remove it.

UserInputService.MouseIconEnabled = false

Now that you have your variables, we can move into creating the Depth of Field Effect

local dof = Instance.new("DepthOfFieldEffect",cam) --Creating the Depth of Field effect.

This next step is setting up the properties of "DepthOfFieldEffect"

You could skip this step if you clone an existing DepthOfFieldEffect instead of creating a new one.

Example of cloning an existing DepthOfFieldEffect:

game.ReplicatedStorage.DepthOfFieldEffect:Clone()

if you choose this route, you will not need to set up the properties in the script because you can set them using the Properties window in Roblox Studio.

You can change these settings from what I have set. These are just my preferences:

dof.FarIntensity = 0.75
dof.FocusDistance = 0 --Changing this won't change anything
dof.InFocusRadius = 10 -- This is the main setting to change. Tweak it and find a look you like.
dof.NearIntensity = 0.25
dof.Enabled = true --If you set this to "False" it will disable the Depth of Field Effect. (Don't set this to False)

At this point, the rest of the script is the main update function.

This is the function. I will break be breaking it down, but if you understand what all of this stuff is doing, feel free to skip past the breakdown.

function UpdateDOF()
    local target = mouse.Hit
    if target ~= tar or player.Character.Head.CFrame ~= cc then
        if target then
            local tx = target.X
            local ty = target.Y
            local tz = target.Z
            local cx = cam.CFrame.X
            local cy = cam.CFrame.Y
            local cz = cam.CFrame.Z
            tar = target
            target = nil
            cc = player.Character.Head.CFrame


            local dx = math.abs(cx - tx)
            local dy = math.abs(cy - ty)
            local dz = math.abs(cz - tz)

            local dis = dx + dy + dz
            if dis > MaxDistance then
                dis = MaxDistance
            end
            tween = TweenService:Create(dof, tweensettings, {FocusDistance = dis})
            tween:Play()
        end
    end
end

BREAKDOWN:

if target ~= tar or player.Character.Head.CFrame ~= cc then
    if target then

This section is Optimization so that the Depth of Field won't be updated if it is not required.

    local tx = target.X
    local ty = target.Y
    local tz = target.Z
    local cx = cam.CFrame.X
    local cy = cam.CFrame.Y
    local cz = cam.CFrame.Z

This is just a lot of Variables for use later to calculate the FocusDistance.

    tar = target
    target = nil
    cc = player.Character.Head.CFrame

This section is assigning those nil variables from earlier so that the Optimization IF statement works next time the function is run.

    local dx = math.abs(cx - tx)
    local dy = math.abs(cy - ty)
    local dz = math.abs(cz - tz)

This is finding the difference between the Camera's CFrame and the Mouse.Hit CFrame. math.abs gets the Absolute Value of that expression so that the FocusDistanc is always positive, even if the Mouse.Hit has a lower value CFrame than the Camera.

    local dis = dx + dy + dz
    if dis > MaxDistance then
        dis = MaxDistance
    end

This section is adding the seperate axis' differences into one value. It then checks to see if the distance is greater than the max distance value set earlier. If it is greater, the distance variable is set to the Maximum Distance. Having a maximum distance helps with quickly focusing on objects, it also can be helpful to make sure the player can't, for example, read the text off of a sign 1000 studs away, even if they find a way to zoom in.

    tween = TweenService:Create(dof, tweensettings, {FocusDistance = dis})
    tween:Play()

This section uses the "tween" variable that was set to nil above to create a Tween using the TweenService. If you do not know what a Tween is, look it up. I will now briefly explain what this Tween is doing. The first argument within the parenthesis is calling the object that is being tweened. The second argument is for the tweensettings. Tween settings were created in a variable earlier, but if you do not want to create a variable, you can set the tweensettings in that argument. The final argument is a "Dictionary" of properties that you would like to change. In this tween the object being tweened is the DepthOfFieldEffect, and tweensettings are the variable we set earlier, and the property being changed is the FocusDistance, it is being set to the "dis" variable. The TweenService takes care of all of the calculations and changes for you, all you have to do is call the :Play() function for the tween.

Finally create a listener that calls the function.

RunService.RenderStepped:Connect(UpdateDOF)

This listener fires the UpdateDOF function every frame. The "RenderStepped" event fires every frame prior to the frame being rendered. If you want to call this after the frame is rendered you can use "Stepped" which is fired every frame before the physics are simulated. If necessary, you could also use the "Heartbeat" event which is fired after the physics simulation is done.

END

I hope you found this tutorial useful! If you did feel free to write a review. If there is anything I could improve with this tutorial or script please let me know with a review as well!

View in-game to comment, award, and more!