Instanced rendering¶
Quick tutorial on how to use the instanced drawing in ipywebgl
Create instance data¶
First we will create a set of random matrices we want to use to render stuff.
[1]:
import numpy as np
# random axis
axis_count = 20
axis_matrices = np.eye(4, dtype=np.float32)[np.newaxis,...].repeat(axis_count, axis=0)
def randomize_axis():
#pick a position
axis_matrices[:,3,:3] = np.random.random([axis_count,3]) * 50
#make z aim away from center
axis_matrices[:,2,:3] = axis_matrices[:,3,:3] / np.linalg.norm(axis_matrices[:,3,:3], axis=1).reshape([axis_count,1])
#randomly generate x (around z)
axis_matrices[:,0,:3] = np.random.random([axis_count,3])
axis_matrices[:,0,:3] = axis_matrices[:,0,:3] - axis_matrices[:,2,:3] * np.einsum('ij,ij->i', axis_matrices[:,2,:3], axis_matrices[:,0,:3])[..., np.newaxis]
axis_matrices[:,0,:3] /= np.linalg.norm(axis_matrices[:,0,:3], axis=1).reshape([axis_count,1])
#create y
axis_matrices[:,1,:3] = np.cross(axis_matrices[:,2,:3], axis_matrices[:,0,:3])
randomize_axis()
Create the viewer¶
[2]:
import ipywebgl
w = ipywebgl.GLViewer()
w.clear_color(.8, .8, .8 ,1)
w.clear()
w.enable(depth_test=True)
w.execute_commands(execute_once=True)
Create the matrices buffer¶
We create the buffer that will holds the matrices and set it as dynamic. This is to let opengl knows that we will update it frequently
[3]:
mat_vbo = w.create_buffer_ext(
'ARRAY_BUFFER',
axis_matrices,
'DYNAMIC_DRAW'
)
Manual method of settings the buffers¶
In this first example we will use raw webgl commands to create all the buffers and binding
Create the program and vbo¶
A transform is just in this case 3 lines aligned toward x, y, and z and colored red, green and blue
In this version we will force the attributes index using the dictionary at the end of the create_program_ext method.
[4]:
axis_prog = w.create_program_ext(
'''#version 300 es
//the ViewBlock that is automatically filled by ipywebgl
layout(std140) uniform ViewBlock
{
mat4 u_cameraMatrix; //the camera matrix in world space
mat4 u_viewMatrix; //the inverse of the camera matrix
mat4 u_projectionMatrix; //the projection matrix
mat4 u_viewProjectionMatrix; //the projection * view matrix
};
uniform float u_scale;
in vec3 in_vert;
in vec3 in_color;
in mat4 in_world;
out vec3 v_color;
void main() {
vec4 world = in_world * vec4(in_vert * u_scale, 1.0);
gl_Position = u_viewProjectionMatrix * world;
v_color = in_color;
}
''',
'''#version 300 es
precision highp float;
in vec3 v_color;
out vec4 f_color;
void main() {
f_color = vec4(v_color, 1.0);
}
''',
# let's force the order of the inputs
# in_world is a matrix so it takes 4 consecutives attributes (0,1,2,3)
{
'in_world' : 0,
'in_vert' : 4,
'in_color' : 5,
})
axis_vbo = w.create_buffer_ext(
src_data= np.array([
# x, y ,z red, green, blue
0, 0, 0, 1, 0, 0,
1, 0, 0, 1, 0, 0,
0, 0, 0, 0, 1, 0,
0, 1, 0, 0, 1, 0,
0, 0, 0, 0, 0, 1,
0, 0, 1, 0, 0, 1,
], dtype=np.float32)
)
Bind the attributes¶
We create the vertex array and we bind all the attributes
[5]:
axis_vao = w.create_vertex_array()
w.bind_vertex_array(axis_vao)
# bind the vertex buffer we want to use in this vertex array
w.bind_buffer('ARRAY_BUFFER', axis_vbo)
# enable and set the pointer to the attribute in the vertex array
w.enable_vertex_attrib_array(4)
w.vertex_attrib_pointer(4, 3, "FLOAT", False, 24, 0)
w.enable_vertex_attrib_array(5)
w.vertex_attrib_pointer(5, 3, "FLOAT", False, 24, 12)
# bind the matrix buffer
w.bind_buffer('ARRAY_BUFFER', mat_vbo)
# bind all 4 vec4 of the mat4 attribute
w.enable_vertex_attrib_array(0)
w.vertex_attrib_pointer(0, 4, "FLOAT", False, 64, 0)
# set the divisor to tell that we must update this attribute only after each instance
w.vertex_attrib_divisor(0, 1)
w.enable_vertex_attrib_array(1)
w.vertex_attrib_pointer(1, 4, "FLOAT", False, 64, 16)
w.vertex_attrib_divisor(1, 1)
w.enable_vertex_attrib_array(2)
w.vertex_attrib_pointer(2, 4, "FLOAT", False, 64, 32)
w.vertex_attrib_divisor(2, 1)
w.enable_vertex_attrib_array(3)
w.vertex_attrib_pointer(3, 4, "FLOAT", False, 64, 48)
w.vertex_attrib_divisor(3, 1)
w.bind_vertex_array(None)
# execute binding commands
w.execute_commands(execute_once=True)
Render¶
[6]:
w.clear()
w.use_program(axis_prog)
w.uniform('u_scale', np.array([5.0], dtype=np.float32))
w.bind_vertex_array(axis_vao)
w.draw_arrays_instanced('LINES', 0, 6, axis_count)
# render in loop
w.execute_commands()
w
[6]:
Extend method of setting attributes¶
Now we will create a program that will draw a spline from a list of pair of matrices. So we draw a line between point 1 and 2, then 3 and 4, …
For this we will not force the attribute location but we will use the extended method of creating the vao.
The extended method can take the signature ‘1mat4:1’ which means : 1 mat4 as input with a divisor set to 1.
[7]:
line_prog = w.create_program_ext(
'''#version 300 es
//the ViewBlock that is automatically filled by ipywebgl
layout(std140) uniform ViewBlock
{
mat4 u_cameraMatrix; //the camera matrix in world space
mat4 u_viewMatrix; //the inverse of the camera matrix
mat4 u_projectionMatrix; //the projection matrix
mat4 u_viewProjectionMatrix; //the projection * view matrix
};
uniform float u_scale;
in float in_vert;
in mat4 in_a;
in mat4 in_b;
void main() {
vec3 p0 = in_a[3].xyz;
vec3 p1 = (in_a * vec4(u_scale,0,0,1)).xyz;
vec3 p2 = (in_b * vec4(u_scale,0,0,1)).xyz;
vec3 p3 = in_b[3].xyz;
vec3 p01 = mix(p0, p1, vec3(in_vert));
vec3 p12 = mix(p1, p2, vec3(in_vert));
vec3 p23 = mix(p2, p3, vec3(in_vert));
vec3 p012 = mix(p01, p12, vec3(in_vert));
vec3 p123 = mix(p12, p23, vec3(in_vert));
vec3 p = mix(p012, p123, vec3(in_vert));
gl_Position = u_viewProjectionMatrix * vec4(p, 1.0);
}
''',
'''#version 300 es
precision highp float;
out vec4 f_color;
void main() {
f_color = vec4(.0,.0,.0, 1.0);
}
'''
)
# spline point values (form 0 to 1)
line_vert_count = 20
line_vbo = w.create_buffer_ext(
src_data= np.linspace(0,1,line_vert_count, dtype=np.float32)
)
# create the binding automatically to the 2 buffers
line_vao = w.create_vertex_array_ext(
line_prog,
[
(line_vbo, '1f32', 'in_vert'),
(mat_vbo, '1mat4:1 1mat4:1', 'in_a', 'in_b'),
]
)
Render¶
[8]:
w.clear()
w.use_program(axis_prog)
w.uniform('u_scale', np.array([5.0], dtype=np.float32))
w.bind_vertex_array(axis_vao)
w.draw_arrays_instanced('LINES', 0, 6, axis_count)
w.use_program(line_prog)
w.uniform('u_scale', np.array([5.0], dtype=np.float32))
w.bind_vertex_array(line_vao)
w.draw_arrays_instanced('LINE_STRIP', 0, line_vert_count, axis_count/2)
# render in loop
w.execute_commands()
w
[8]:
Interactive rendering¶
We can use the interact function to animate the transfomrs.
In this case we use the play action and we just smoothly push all the transforms into a sphere. Then when the animation replays we randomize all the positions again.
[9]:
from ipywidgets import widgets, interact
def render(p):
if p==0:
randomize_axis()
center = np.sum(axis_matrices[:,3,:3], axis=0)/axis_count
for i in range(axis_count):
axis = axis_matrices[i,3,:3] - center
axis /= np.linalg.norm(axis, axis=0)
axis *= 50.0
pos = center + axis
axis_matrices[i,3,:3] = (pos*0.1 + axis_matrices[i,3,:3]*0.9)
w.bind_buffer('ARRAY_BUFFER', mat_vbo)
w.buffer_data('ARRAY_BUFFER', axis_matrices, 'DYNAMIC_DRAW')
w.clear()
w.use_program(axis_prog)
w.uniform('u_scale', np.array([5.0], dtype=np.float32))
w.bind_vertex_array(axis_vao)
w.draw_arrays_instanced('LINES', 0, 6, axis_count)
w.use_program(line_prog)
w.uniform('u_scale', np.array([5.0], dtype=np.float32))
w.bind_vertex_array(line_vao)
w.draw_arrays_instanced('LINE_STRIP', 0, line_vert_count, axis_count/2)
# render in loop
w.execute_commands()
play = widgets.Play(
value=0,
min=0,
max=50,
step=1,
interval=50,
description="play",
disabled=False
)
interact(render, p=play)
w
[9]: