Now with the updation of Blender 3.0 (see https://developer.blender.org/rB1b44b47f69bc55af0531516fa4b2f0b5d1e0e472), we can get access to the depth buffer with the PyGPU API. Here is an example which I adapted from @reg.cs's code.
import bpy
import gpu
import numpy as np
# Parameters used to visualize depth.
linearize_depth_buffer = True
scale_factor = 10
# Draw function which copies data from the 3D View
def draw(self, context):
if self.modal_redraw == True:
# get currently bound framebuffer
self.framebuffer = gpu.state.active_framebuffer_get()
# get information on current viewport
self.viewport_info = gpu.state.viewport_get()
self.width = self.viewport_info[2]
self.height = self.viewport_info[3]
# Write copied data to image
######################################################
# resize image obect to fit the current 3D View size
self.framebuffer_image.scale(self.width, self.height)
# obtain depth from the framebuffer
self.depth_buffer = self.framebuffer.read_depth(0, 0, self.width, self.height)
depth_array = np.array(self.depth_buffer.to_list())
# original depth is encoded nonlinearly between 0 and 1. We can linearize and scale it for visualization
if linearize_depth_buffer:
for a in bpy.context.screen.areas:
if a.type == 'VIEW_3D':
f = a.spaces.active.clip_end
n = a.spaces.active.clip_start
depth_array = n / (f - (f - n) * depth_array) * scale_factor
x = np.expand_dims(depth_array, axis=2)
pixel_array = np.pad(np.repeat(x, 3, 2), ((0,0),(0,0),(0,1)), 'constant', constant_values=1).flatten().tolist()
self.framebuffer_image.pixels.foreach_set(pixel_array)
# reset draw variable:
# This is here to prevent excessive redrawing
self.modal_redraw = False
# Modal operator for controlled redrawing of the image object
# NOTE: This code is only for a more conveniant testing of the draw function
# If you want to stop the test, press 'ESC'
class ModalFramebufferCopy(bpy.types.Operator):
bl_idname = "view3d.modal_framebuffer_copy"
bl_label = "Draw 3D View Framebuffer"
def __init__(self):
print("Start example code")
# init variables
self.width = 32
self.height = 32
self.modal_redraw = False
self.image_name = "depth_buffer_copy"
self.framebuffer = None
self.viewport_info = None
self.depth_buffer = None
# create or update image object to which the framebuffer
# data will be copied
if not self.image_name in bpy.data.images:
self.framebuffer_image = bpy.data.images.new(self.image_name , 32, 32, alpha=False, float_buffer=True, is_data=True)
else:
self.framebuffer_image = bpy.data.images[self.image_name]
#
def __del__(self):
print("End example code")
# modal operator for controlled redraw of the image
def modal(self, context, event):
# stop the execution of this example code if 'ESC' is pressed
if event.type in {'ESC'}:
bpy.types.SpaceView3D.draw_handler_remove(self._handle_3d, 'WINDOW')
print("Removing draw handler")
return {'CANCELLED'}
else:
# set draw variable to update:
# This is here to prevent excessive redrawing
self.modal_redraw = True
return {'PASS_THROUGH'}
#return {'RUNNING_MODAL'}
def invoke(self, context, event):
print("Invoking modal operator")
# Add the region OpenGL drawing callback
# draw in view space with 'POST_VIEW' and 'PRE_VIEW'
# self._handle_3d = bpy.types.SpaceView3D.draw_handler_add(draw, (self, context), 'WINDOW', 'PRE_VIEW') # this does not work for me. It seems depth value has been cleared.
self._handle_3d = bpy.types.SpaceView3D.draw_handler_add(draw, (self, context), 'WINDOW', 'POST_VIEW') # this draws all the objects
context.window_manager.modal_handler_add(self)
return {'RUNNING_MODAL'}
def register():
bpy.utils.register_class(ModalFramebufferCopy)
def unregister():
bpy.utils.unregister_class(ModalFramebufferCopy)
if __name__ == "__main__":
register()
# Invoke modal operator for the example code
bpy.ops.view3d.modal_framebuffer_copy('INVOKE_DEFAULT')
The following image shows the effect of a simple animation.