1

I'm trying to read the depth buffer from a frame buffer during offscreen rendering using the gpu.types.GPUFrameBuffer.read_depth() function from the gpu module. No matter what I do, all values in the buffer are always 0. I found this answer, which works for getting the depth buffer of the viewport. I tried to adapt it to offscreen rendering, but with no luck. Here is the relevant code snipped of my script that does the rendering. I am using a custom shader to render the currently selected object.

offscreen = gpu.types.GPUOffScreen(DIM, DIM)
with offscreen.bind():
    gpu.state.depth_mask_set(True)
    gpu.state.depth_test_set('LESS')
    framebuffer = gpu.state.active_framebuffer_get()
batch.draw(shader)
depth_buffer = framebuffer.read_depth(0, 0, DIM, DIM)
# have to reset these because I run into https://projects.blender.org/blender/blender/issues/98486
gpu.state.depth_mask_set(False)
gpu.state.depth_test_set('NONE')

offscreen.free()

Interestingly, writing to the color buffer by not setting depth mask and depth test and using framebuffer.read_color() like this works:

offscreen = gpu.types.GPUOffScreen(DIM, DIM)
with offscreen.bind():
    framebuffer = gpu.state.active_framebuffer_get()
batch.draw(shader)
color_buffer = framebuffer.read_color(0, 0, DIM, DIM, 4, 0, 'FLOAT')

offscreen.free()

Is reading the depth buffer not supported when rendering offscreen or am I missing something here? I am very new to blender, so this might very well be a mistake on my side.

For reference, here is the entire script

import bpy
import gpu
import numpy as np
from gpu_extras.batch import batch_for_shader

def get_world_mesh_data(o): """ Returns the meshes of all passed objects combined.

Parameters
----------
obs : Iterable[bpy_types.Object]
    Objects to combine. Fails if non-mesh object is passed.

Returns
-------
verts : numpy.ndarray
    Nx3 float array of XYZ vertex coordinates in world space
indcs : numpy.ndarray
    Nx3 int array of triangle indices
info : list[tuple]
    Holds a tuple for each passed object. The first entry stores
    the index of the first element in 'verts' belonging to the
    corresponding object, the second entry the same for 'indcs'.
"""
# Accumulate vertex and triangle counts of all passed objects to
# later allocate the right amount of memory
mesh = o.data
mesh.calc_loop_triangles()

# Initialize joined lists
verts = np.empty(len(mesh.vertices) * 3, dtype=np.float32)
indcs = np.empty(len(mesh.loop_triangles) * 3, dtype=np.int32)
vstart = 0
istart = 0

# Calculate object's slice in combined list
mesh = o.data
vend = vstart + len(mesh.vertices) * 3
iend = istart + len(mesh.loop_triangles) * 3
vslice = verts[vstart:vend]
islice = indcs[istart:iend]

# Get vertex coordinates of current object
mesh.vertices.foreach_get('co', vslice)
# Transform vertices to world space
verts[vstart:vend] = transf_pts(
    o.matrix_world,
    vslice.reshape(-1, 3),
    ).ravel()

# Get triangle indices of current object
mesh.loop_triangles.foreach_get('vertices', islice)

vstart = vend
istart = iend

verts.shape = (-1, 3)
indcs.shape = (-1, 3)
return verts, indcs

def transf_pts(mat, pts): """ Apply a transformation matrix to every point in a list.

Parameters
----------
mat : Iterable
    4x4 transformation matrix to apply to every point
pts : Iterable
    List of 3D points

Returns
-------
pts : numpy.ndarray
    Copy of 'pts' with transformed coordinates
"""
# blender.stackexchange.com/a/139513
# return np.einsum('ij,aj->ai', mat, homog_vecs(pts))[:, :-1]
return (np.asanyarray(mat) @ append_one(pts).T).T[:, :3]

def append_one(pts): """ Append a coordinate equal to one to every vector.

Parameters
----------
pts : Iterable
    Points to append to.

Returns
-------
hpts : numpy.ndarray
    Copy of 'pts' with appended coordinates

Examples
--------
>>> append_one([[0, 1, 2], [3, 4, 5]])
array([[0, 1, 2, 1], [3, 4, 5, 1]])
"""
vs = np.asanyarray(pts)
hpts = np.ones(np.add(vs.shape, (0, 1)), dtype=vs.dtype)
hpts[:, :-1] = vs
return hpts

DIM = 512 COLOR_IMG_NAME = "Color" DEPTH_IMAGE_NAME = "Depth"

switch to edit mode

bpy.ops.object.mode_set(mode='EDIT')

Construct depthpass shader

shader = gpu.types.GPUShader( vertexcode=''' in vec3 pos; void main() { gl_Position = vec4(pos, 1); }''', fragcode=''' out vec4 col; void main() { col = vec4(0, 0, 1, 1); }''' ) shader.bind()

Create batch from all objects in edit mode

verts, indcs = get_world_mesh_data(bpy.context.active_object)

batch = batch_for_shader( shader, 'TRIS', {"pos": verts}, indices=indcs, )

offscreen = gpu.types.GPUOffScreen(DIM, DIM) with offscreen.bind(): gpu.state.depth_mask_set(True) gpu.state.depth_test_set('LESS') framebuffer = gpu.state.active_framebuffer_get()

batch.draw(shader)
depth_buffer = framebuffer.read_depth(0, 0, DIM, DIM)
# have to reset these because I run into https://projects.blender.org/blender/blender/issues/98486
gpu.state.depth_mask_set(False)
gpu.state.depth_test_set('NONE')

offscreen.free()

Sandroid
  • 11
  • 2

1 Answers1

0

Seems I simply forgot to clear the depth buffer before writing to it. This works:

offscreen = gpu.types.GPUOffScreen(DIM, DIM)
with offscreen.bind():
    gpu.state.depth_mask_set(True)
    gpu.state.depth_test_set('LESS')
    framebuffer = gpu.state.active_framebuffer_get()
    # This is the line I was missing
    framebuffer.clear(depth=1.0)
batch.draw(shader)
depth_buffer = framebuffer.read_depth(0, 0, DIM, DIM)
# have to reset these because I run into https://projects.blender.org/blender/blender/issues/98486
gpu.state.depth_mask_set(False)
gpu.state.depth_test_set('NONE')

offscreen.free()

Sandroid
  • 11
  • 2