4

I need to render depth buffer of Blender's scene into a texture to further use it my shaders. I tried using the common OpenGL recipes for doing that, but none of them worked as it seems there is something wrong with Framebuffer access.

Can this somehow be done using the new GPU.Offscreen thing? Or maybe some BGL approach?

D. Skarn
  • 695
  • 4
  • 16

1 Answers1

5

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.

enter image description here

username
  • 201
  • 3
  • 5
  • Great example! Was just looking for a possibility to do that, thanks. :) I believe the code can be sped up a lot, if you use the buffer protocol: https://blender.stackexchange.com/questions/221110/fastest-way-copying-from-bgl-buffer-to-numpy-array/230242#230242 – reg.cs Nov 16 '21 at 23:25