Why the perspective_matrix in Blender is different from projection_matrix in OpenGL?

This question can be difficult, but I’ll do it anyway.
In Blender, the call “bpy.context.region_data.view_matrix” returns a matrix identical to Matrix obtained with the “bgl.GL_MODELVIEW_MATRIX” when transposed.
ex:


def get_modelview_matrix():
    model_matrix = bgl.Buffer(bgl.GL_DOUBLE, [4, 4])
    bgl.glGetDoublev(bgl.GL_MODELVIEW_MATRIX, model_matrix)
    return model_matrix

print(Matrix(get_modelview_matrix().to_list()).transposed())
<Matrix 4x4 (-0.9935, -0.0763,   0.0844, 0.0000)
                   ( 0.1137, -0.6555,   0.7466, 0.0000)
                   (-0.0016,  0.7513,   0.6599, 0.0000)
                   ( 0.5504,  0.9845, -19.2413, 1.0000)>

print(bpy.context.region_data.view_matrix)
<Matrix 4x4 (-0.9935, -0.0763,   0.0844, 0.0000)
                   ( 0.1137, -0.6555,   0.7466, 0.0000)
                   (-0.0016,  0.7513,   0.6599, 0.0000)
                   ( 0.5504,  0.9845, -19.2413, 1.0000)>


That’s good :). Reduces modules. Reduces calculations

But now let’s try the same with the projection_matrix (or perspective_matrix).
In bgl: “bgl.GL_PROJECTION_MATRIX”,
in bpy: “bpy.context.region_data.perspective_matrix”


def get_projection_matrix():
    proj_matrix = bgl.Buffer(bgl.GL_DOUBLE, [4, 4])
    bgl.glGetDoublev(bgl.GL_PROJECTION_MATRIX, proj_matrix)
    return proj_matrix

print(Matrix(get_projection_matrix().to_list()))
<Matrix 4x4 (1.9111, 0.0000,  0.0000,  0.0000)
                   (0.0000, 1.0938,  0.0000,  0.0000)
                   (0.0000, 0.0000, -1.0000, -1.0000)
                   (0.0000, 0.0000, -0.0200,  0.0000)>

print(bpy.context.region_data.perspective_matrix)
<Matrix 4x4 (-1.8711,  0.3886, -0.0034,  1.0136)
                   (-0.1210, -0.5746,  0.9227,  1.1949)
                   (-0.1706, -0.8263, -0.5369, 19.4218)
                   (-0.1706, -0.8262, -0.5369, 19.4410)>


The values are different now. And that is bad :(.
I’m doing something wrong? How do I get the projection_matrix in the bpy module?

Why the values of perspective_matrix in Blender are different from the projection_matrix in OpenGL?

I found out :): perspective_matrix = project_matrix * view_matrix

EDIT: project_matrix = window_matrix in Blender 2.74

Also related to matrices. How to convert an existing matrix to buffer array?
(new_matrix_buffer = bgl.Buffer(bgl.GL_DOUBLE, [4, 4]))

Interesting question. You could do something like this where you turn the Blender Matrix object into a nested list of columns and use that to load your matrix:


def asNestedLists(mat):
  '''Assumes a square Matrix (mat) as input and converts it to a column-major nested lists [ [col1], [col2], ..., [col n]]'''
  ret = []
  for j in range(len(mat)):
    ret.append([mat[i][j] for i in range(len(mat)))
  
  return ret

# Turn an existing Matrix into a 4x4 bgl.Buffer object.
mat = Matrix()
matBuffer = bgl.Buffer(bgl.GL_DOUBLE, [4,4], asNestedList(mat))

The odd thing is that in “normal” OpenGL, the glLoadMatrix method takes a flat array of 16 values and treats it as a column-major array. For example if you had this 4x4 matrix:
| 01 02 03 04 |
| 05 06 07 08 |
| 09 10 11 12 |
| 13 14 15 16 |

The “column-major” array form would just be the list:
[1,5,9,13, 2,6,10,14, 3,7,11,15, 4,8,12,16]

In normal OpenGL, if you wanted to load a matrix with this 4x4 value, you would pass in that list, not a set of nested lists or a multi-dimensional array like [[1,5,9,13], [2,6,10,14], …etc].

I’m not sure if the BGL Buffer object automatically knows how to convert multi-dimensional buffers to the column-major arrays that normal OpenGL expects. If the above code does not work, you can try something like this to turn the 4x4 matrix into a flat 16-value Buffer object in column-major format.


def columnMajor(mat):
  '''Takes a square Matrix object and returns the values in column major order.'''
  for j in range(len(mat)):     # Iterate over the Columns
    for i in range(len(mat)):   # Iterate over the Rows
      yield mat[i][j]

# Load a 16-float Buffer with the column-major values in the matrix.
mat = Matrix()
matBuffer = bgl.Buffer(bgl.GL_DOUBLE, [16], [v for v in columnMajor(mat)])

Cool, very interesting. I’m still trying to understand some parts :confused:. I will try to make these changes later, after I notice if it worked. :slight_smile:

As a one liner using the itertools module.


bgl.Buffer(bgl.GL_DOUBLE, 16, list(itertools.chain(*mat.transposed())))

I knew there had to be some clever way to do that in one line!

I analyzed all posted solutions. And I have find out how it works. Are very instructive.
However, why don’t just this way?

mat = Matrix()
Matrix_buffer = bgl.Buffer(bgl.GL_DOUBLE, [4, 4], mat)

Here’s a script that I made that shows how it works:


import bpy, bgl
from mathutils import Vector, Matrix

#bgl.glEnable(bgl.GL_DEPTH)

def get_viewport():
    view = bgl.Buffer(bgl.GL_INT, 4)
    bgl.glGetIntegerv(bgl.GL_VIEWPORT, view)
    return view

def get_modelview_matrix():
    model_matrix = bgl.Buffer(bgl.GL_DOUBLE, [4, 4])
    bgl.glGetDoublev(bgl.GL_MODELVIEW_MATRIX, model_matrix)
    return model_matrix

def get_projection_matrix():
    proj_matrix = bgl.Buffer(bgl.GL_DOUBLE, [4, 4])
    bgl.glGetDoublev(bgl.GL_PROJECTION_MATRIX, proj_matrix)
    return proj_matrix

def get_depth(x, y):    
    depth = bgl.Buffer(bgl.GL_FLOAT, [0.0])
    bgl.glReadPixels(x, y, 1, 1, bgl.GL_DEPTH_COMPONENT, bgl.GL_FLOAT, depth)
    return depth[0]

def UnProject(x, y, z, view_matrix, projection_matrix, viewport):
    world_x = bgl.Buffer(bgl.GL_DOUBLE, 1, [0.0])
    world_y = bgl.Buffer(bgl.GL_DOUBLE, 1, [0.0])
    world_z = bgl.Buffer(bgl.GL_DOUBLE, 1, [0.0])
    bgl.gluUnProject(x, y, z, view_matrix, projection_matrix, viewport, world_x, world_y, world_z)        
    return Vector((world_x[0], world_y[0], world_z[0]))

def Project(x, y, z, view_matrix, projection_matrix, viewport):
    world_x = bgl.Buffer(bgl.GL_DOUBLE, 1, [0.0])
    world_y = bgl.Buffer(bgl.GL_DOUBLE, 1, [0.0])
    world_z = bgl.Buffer(bgl.GL_DOUBLE, 1, [0.0])
    bgl.gluProject(x, y, z, view_matrix, projection_matrix, viewport, world_x, world_y, world_z)
    return (int(world_x[0]), int(world_y[0]))
    
def region_3d_view(self, context):
    x, y = self.mouse_co
    
    rv3d = bpy.context.region_data
    region = bpy.context.region

    # BGL Buffer Matrices
    buffer_bgl_viewport = get_viewport()
    buffer_bgl_view_matrix = get_modelview_matrix()
    buffer_bgl_projection_matrix = get_projection_matrix()
    
    # BPY Matrices
    bpy_viewport = [region.x, region.y, region.width, region.height]
    bpy_view_matrix = rv3d.view_matrix.transposed()
    #bpy_projection_matrix = (rv3d.perspective_matrix*rv3d.view_matrix.inverted()).transposed()
    bpy_projection_matrix = rv3d.window_matrix.transposed()

    # BPY Buffer Matrices
    buffer_bpy_viewport = bgl.Buffer(bgl.GL_INT, 4, bpy_viewport)
    buffer_bpy_view_matrix = bgl.Buffer(bgl.GL_DOUBLE, [4,4],  bpy_view_matrix)
    buffer_bpy_projection_matrix = bgl.Buffer(bgl.GL_DOUBLE, [4,4],  bpy_projection_matrix)

    # TESTING BUFFERS
    DEPTH = get_depth(x, y)
    
    RAY_CAST_W_BPY = UnProject(x, y, DEPTH, buffer_bpy_view_matrix, buffer_bpy_projection_matrix, buffer_bpy_viewport)
    #RAY_CAST_W_BGL = UnProject(x, y, DEPTH, buffer_bgl_view_matrix, buffer_bgl_projection_matrix, buffer_bgl_viewport)
    
    # VIEWING BGL
    bgl.glEnable(bgl.GL_BLEND)
    bgl.glColor4f(1.0, 0.5, 0.5, 1.0)
    bgl.glPointSize(50)    
    bgl.glBegin(bgl.GL_POINTS)
    bgl.glVertex3f(*RAY_CAST_W_BPY)
    bgl.glEnd()
    bgl.glDisable(bgl.GL_BLEND)
    
    # restore opengl defaults
    bgl.glDepthRange(0,1)
    bgl.glPointSize(1)
    bgl.glLineWidth(1)
    bgl.glDisable(bgl.GL_BLEND)
    bgl.glColor4f(0.0, 0.0, 0.0, 1.0)
    
    context.area.header_text_set(str(RAY_CAST_W_BPY))
    
class TestesOperator(bpy.types.Operator):
    """using mouse events"""
    bl_idname = "view3d.testes_operator"
    bl_label = "View_testes_Operator"
    
    def modal(self, context, event):
        if context.area:
            context.area.tag_redraw()
        
        if event.type == 'MOUSEMOVE':
            self.mouse_co = (event.mouse_x, event.mouse_y)
            #return {'FINISHED'}
             
        elif event.type in {'RIGHTMOUSE', 'ESC'}:
            bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
            context.area.header_text_set()
            return {'FINISHED'}

        return {'RUNNING_MODAL'} 

    def invoke(self, context, event):        
        if context.space_data.type == 'VIEW_3D':
            self._handle = bpy.types.SpaceView3D.draw_handler_add(region_3d_view, (self, context), 'WINDOW', 'POST_VIEW')
            context.window_manager.modal_handler_add(self)
            return {'RUNNING_MODAL'}
        else:
            self.report({'WARNING'}, "Active space must be a View3d")
            return {'CANCELLED'}

def register():
    bpy.utils.register_class(TestesOperator)

def unregister():
    bpy.utils.unregister_class(TestesOperator)

if __name__ == "__main__":
    register()

Simply because reading the documentation is not clear how smart the bgl.Buffer object is. If that works then it seems like the right way to go. Since I didn’t find any documentation for the Buffer accepting a Matrix object, I assumed it had to be manually loaded.

As someone who has only dabled with BGL, I think this is really really cool. If I understand correctly, you are using the graphics card via bgl to ray_cast/depth test the scene instead of the API functions like Object.ray_cast() or Scene.ray_cast()

Yes. But I do it more to study how it works. Since I can’t find information about:
“bpy.types.SpaceView3D.draw_handler_add()”
and
“context.window_manager.modal_handler_add (self)”