Dynamic Depth of Field
A dynamic depth of field shader that changes focus based on the distance to the camera using raycasting.
This shader uses the (brilliant) box blur shader written by NightEyes: https://godotshaders.com/shader/configurable-box-blur/
3D ONLY
INSTRUCTIONS:
1. Add a MeshInstance3D to the Camera in your scene and set the mesh to a Quad.
2. Set the Quad size to 2×2.
3. Enable “Flip Faces” under the Geometry section.
4. Set Position to (0,0,-1) under the Transform section
5. Add a new script to the Quad and copy/paste ## GD SCRIPT ## section
6. Add a new shader to the Surface Material Override section and copy/paste ## SHADER ## section
NOTES
– Godot 4.1
– If objects in your scene are rendered invisible by the Quad, under the Surface Material Override section lower the Render Priorty until the objects are visible. In my project the Quad has a Render Priority of -1.
– DOF may not be applied to objects that use shader materials due to the esoteric ways in which Godot renders objects. I do plan on addressing these issues in the future.
– Interpolation between near and far objects may produce graphical artifacts; I plan on adding a max filter to the code in order to smooth object edges (please be patient).
– Make sure the COLLISION_MASK var in the GD SCRIPT code section includes the objects you wish the DOF to be applied to.
Shader code
## GD SCRIPT ##
extends MeshInstance3D
var DOF_LENGTH = 250;
var COLLISION_MASK = 1;
func _physics_process(_delta):
var end = global_position - global_transform.basis.z * DOF_LENGTH
var rayParams = PhysicsRayQueryParameters3D.create(global_position, end, COLLISION_MASK)
var ray = get_world_3d().direct_space_state.intersect_ray(rayParams)
if !ray.is_empty():
end = ray["position"]
var sm = get_surface_override_material(0) as ShaderMaterial
sm.set_shader_parameter("ray_position", end)
## SHADER ##
shader_type spatial;
render_mode unshaded, cull_back;
uniform vec3 ray_position;
uniform int blur_strength: hint_range(1,8) = 3;
uniform float blur_falloff: hint_range(1.0, 20.0) = 7.0;
uniform float blur_near: hint_range(0.0, 100.0) = 20.0;
uniform float blur_far: hint_range(0.0, 250.0) = 200.0;
uniform sampler2D depth_texture : source_color, hint_depth_texture, filter_linear;
uniform sampler2D screen_texture : hint_screen_texture, repeat_disable, filter_linear;
void vertex() {
POSITION = vec4(VERTEX, 1.0);
}
// Configurable Box Blur by Nighteyes -> https://godotshaders.com/shader/configurable-box-blur/
vec3 blur_size(sampler2D tex,vec2 uv, vec2 pixelSize) {
vec2 pixel = uv / pixelSize;
int x_min = max(int(pixel.x) - blur_strength, 0);
int y_min = max(int(pixel.y) - blur_strength, 0);
int x_max = min(int(pixel.x) + blur_strength, int(1.0 / pixelSize.x));
int y_max = min(int(pixel.y) + blur_strength, int(1.0 / pixelSize.y));
int count = 0;
vec3 color = vec3(0.0);
for(int x = x_min; x <= x_max; x++) {
for(int y = y_min; y <= y_max; y++) {
color += texture(tex, vec2(float(x), float(y)) * pixelSize).rgb;
count++;
}
}
return color / float(count);
}
void fragment() {
float dist = distance(CAMERA_POSITION_WORLD, ray_position);
float depth = texture(depth_texture, SCREEN_UV).r;
depth = depth * 2.0 - 1.0;
float z = -PROJECTION_MATRIX[3][2] / (depth + PROJECTION_MATRIX[2][2]);
z = z + dist/2.0;
// Calculate clear/blur threshold
float w = dist/blur_falloff;
float dz = dist >= blur_far ? 1.0 : (1.0 - smoothstep(0.001, w, -(z + w)));
dz *= dist <= blur_near ? 1.0 : smoothstep(0.001, w, -(z - w));
vec3 screen = texture(screen_texture, SCREEN_UV).rgb;
vec3 blur = blur_size(screen_texture, SCREEN_UV, 1.0/VIEWPORT_SIZE);
ALBEDO = mix(blur, screen, dz);
}
Somehow its not working for me( Anyway thx for your work!
Thank You very much! Excellent….I love it!