4
$\begingroup$

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?

$\endgroup$

1 Answer 1

5
$\begingroup$

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

$\endgroup$
1
  • $\begingroup$ 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: blender.stackexchange.com/questions/221110/… $\endgroup$
    – reg.cs
    Commented Nov 16, 2021 at 23:25

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .