Sun Bear
Sun Bear

Reputation: 8295

How to express a C++ or GLM struct as a numpy array?

I want to express the below struct into as numpy array.

struct UniformBufferObject {
    glm::mat4 model;
    glm::mat4 view;
    glm::mat4 proj;
};

Thereafter, the numpy array is meant to be read in as a GLSL layout:

layout(binding = 0) uniform UniformBufferObject {
    mat4 model;
    mat4 view;
    mat4 proj;
} ubo;

In the above statements, mat4 denotes a 4x4 matrix.

Below, I created a python list which I then converts to a numpy array (w/o copying) with a 4x4 shape where each element is a dtype=float32.

Question: What should I do next to represent the UniformBufferObject struct as a numpy array?

>>> model = [0.1, 0.1, 0.1, 1, 0.1, 0.1, 0.1, 1, 0.1, 0.1, 0.1, 1, 0.1, 0.1, 0.1, 0,]
>>> view = [0.2, 0.2, 0.2, 1, 0.2, 0.2, 0.2, 1, 0.2, 0.2, 0.2, 1, 0.2, 0.2, 0.2, 0,]
>>> proj = [0.3, 0.3, 0.3, 1, 0.3, 0.3, 0.3, 1, 0.3, 0.3, 0.3, 1, 0.3, 0.3, 0.3, 0,]
>>> modeln = np.asarray(model, dtype= 'f4').reshape(4,4)
>>> viewn = np.asarray(view, dtype= 'f4').reshape(4,4)
>>> projn = np.asarray(proj, dtype= 'f4').reshape(4,4)
>>> modeln
array([[0.1, 0.1, 0.1, 1. ],
       [0.1, 0.1, 0.1, 1. ],
       [0.1, 0.1, 0.1, 1. ],
       [0.1, 0.1, 0.1, 0. ]], dtype=float32)
>>> viewn
array([[0.2, 0.2, 0.2, 1. ],
       [0.2, 0.2, 0.2, 1. ],
       [0.2, 0.2, 0.2, 1. ],
       [0.2, 0.2, 0.2, 0. ]], dtype=float32)
>>> projn
array([[0.3, 0.3, 0.3, 1. ],
       [0.3, 0.3, 0.3, 1. ],
       [0.3, 0.3, 0.3, 1. ],
       [0.3, 0.3, 0.3, 0. ]], dtype=float32)
>>> 

Upvotes: 1

Views: 1355

Answers (1)

Rabbid76
Rabbid76

Reputation: 211135

If in GLSL a Uniform block is specified like this

 layout(binding = 0) uniform UniformBufferObject {
     mat4 model;
     mat4 view;
     mat4 proj;
 } ubo;

then OpenGL does not guarantee, that the members in th block are stored in consecutive order. OpenGL gives no guarantees about the memory layout, alignment or order of the elements.

To achieve a well defined order and memory layout the memory layout qualifier std140 ahs to be used. The std140 layout is provided since OpenGL 3.1 respectively OpenGL ES 3.0 or the OpenGL extension ARB_uniform_buffer_object:

layout(std140, binding = 0) uniform UniformBufferObject {
    mat4 model;
    mat4 view;
    mat4 proj;
} ubo;

A detailed specification about the layout can be found at:

In case of the above uniform block with std140 layout the 3 matrices are tightly packed. The memory layout are 48 (3*4*4) floats in a row and has a size of 192 bytes

mat4 model     byte offset   0: size in bytes  64 = 4*4*sizeof(float)
mat4 view      byte offset  64: size in bytes  64 = 4*4*sizeof(float)
mat4 proj      byte offset 128: size in bytes  64 = 4*4*sizeof(float)
--------------------------------------------------------------------
                                size in bytes 192 = 3*4*4*sizeof(float)

To create a buffer which corresponds to this memory layout, a flat numpy.array can be used:

e.g.

 model = [0.1, 0.1, 0.1, 1, 0.1, 0.1, 0.1, 1, 0.1, 0.1, 0.1, 1, 0.1, 0.1, 0.1, 0,]
 view = [0.2, 0.2, 0.2, 1, 0.2, 0.2, 0.2, 1, 0.2, 0.2, 0.2, 1, 0.2, 0.2, 0.2, 0,]
 proj = [0.3, 0.3, 0.3, 1, 0.3, 0.3, 0.3, 1, 0.3, 0.3, 0.3, 1, 0.3, 0.3, 0.3, 0,]
import numpy as np

ubo_data = np.array([model, view, proj], dtype='f4')

It is not necessary to reshap() the array. But of course you can do so, if you want to access the elements of the model matrix (ubo_data[0]), the view matrix (ubo_data[1]) and the projection matrix (ubo_data[2]) directly:

ubo_data = np.array([model, view, proj], dtype='f4').reshape(3, 4, 4)

Note, it is even not necessary to use NumPy. A buffer with the same memory layout can be generated by a ctypes array:

import ctypes

ubo_data = (ctypes.c_float * 48)(*model, *view, *proj)

Using PyOpenGL, each of the above data arrays can be used to crate and initialize the buffer store of a uniform block buffer by glBufferData

ubo = glGenBuffers( 1 )
glBindBuffer(GL_UNIFORM_BUFFER, ubo)
glBufferData(GL_UNIFORM_BUFFER, ubo_data, GL_STATIC_DRAW)
glBindBufferBase(GL_UNIFORM_BUFFER, 0, ubo)
glBindBuffer(GL_UNIFORM_BUFFER, 0)

or update the data store of an existing buffer by glBufferSubData:

glBindBuffer(GL_UNIFORM_BUFFER, ubo)
glBufferSubData(GL_UNIFORM_BUFFER, 0, ubo_data)

Note, when using PyGLM, then the data of a uniform buffer like this can be updated directly:

e.g.:

import glm
model = glm.mat4(1)
view  = glm.lookAt(glm.vec3(0,-3,0), glm.vec3(0,0,0), glm.vec3(0,0,1))
proj  = glm.perspective(glm.radians(90), 800.0/600.0, 0.1, 100)

glBufferSubData(GL_SHADER_STORAGE_BUFFER, 0,                      glm.sizeof(glm.mat4), glm.value_ptr(model))
glBufferSubData(GL_SHADER_STORAGE_BUFFER, 1*glm.sizeof(glm.mat4), glm.sizeof(glm.mat4), glm.value_ptr(view))
glBufferSubData(GL_SHADER_STORAGE_BUFFER, 2*glm.sizeof(glm.mat4), glm.sizeof(glm.mat4), glm.value_ptr(proj))

Upvotes: 1

Related Questions