Skip to content

Instantly share code, notes, and snippets.

@etale-cohomology
Created March 11, 2016 00:51
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save etale-cohomology/e64a96c686eef2f0ded2 to your computer and use it in GitHub Desktop.
Save etale-cohomology/e64a96c686eef2f0ded2 to your computer and use it in GitHub Desktop.
A small working example of vispy, along with some notes
"""Vispy is based on OpenGL ES 2.0.
Vispy offers a Pythonic object-oriented interface to OpenGL, useful to those
who know OpenGL.
It is critical to use as few OpenGL draw calls as possible. Every draw incurs
a significant overhead. High performance is achieved by rendering all similar
primitive types at once (batch rendering).
The vertex shader is executed for EACH vertex that is given to the rendering
pipeline.
The fragment shader is executed on EACH fragment that is generated after the
vertex stage.
The idea of modern GL is that vertices are stored on the GPU and need to be
uploaded (only once) to the GPU before rendering. Any transformation to the
vertices should be computed on the GPU (after uploading), and not on the CPU
(before uploading)! The way to do that is to build buffers onto the CPU and to
send them onto the GPU. If your data does not change, no need to upload it
again. That is the big difference with the previous fixed pipeline where data
were uploaded at each rendering call (only display lists were built into GPU
memory).
All vertices from a buffer have the same structure (possibly with different
content). This again is a big difference with the fixed pipeline where OpenGL
was doing a lot of complex rendering stuff for you (projections, lighting,
normals, etc.) with an implicit fixed vertex structure.
Let's take a simple example of a vertex structure where we want each vertex to
hold a position and a color. The easies way is to store position and color
into a NumPy array!
In OpenGL there's:
1. shaders
2. buffers
Shaders have 3 types of variables:
1. uniforms
2. attributes
3. varyings
Gloo takes care of any change in **uniform** or **attribute** values.
A fragment shader only *needs* to do one thing: generate a gl_FragColor.
The shader can also decide to write different colors to different color
buffers, or even change the position of the fragment, but its primary job is
just to determine the color of the pixel, vec4().
If several shaders are specified (on gloo.Program), only one can contain the
main function. OpenGL ES 2.0 does not support a list of shaders!!
A **uniform** is an input to the shader which is the same for all vertices
An **attribute** is a vertex-specific input to the vertex shader
A **varying** is output to the vertex shader and input to the fragment shader
A Vertex Buffer Object (VBO) is an OpenGL feature that provides methods for
uploading vertex data (position, normal vector, color, etc.) to the video
device for non-immediate-mode rendering. VBOs offer substantial performance
gains over immediate mode rendering primarily because the data resides in the
video device memory rather than the system memory and so it can be rendered
directly by the video device.
###################################################################################################
# vispy.gloo
Program Attach shaders here. Set uniforms and attributes
GLObject GL object on the GPU. Linked to active Canvas
buffer.Buffer GPU buffer. Not aware of data type or element size
buffer.DataBuffer GPU buffer. Aware of data type and element size
VertexBuffer Buffer for vertex attribute data
IndexBuffer Buffer for index data
texture.BaseTexture Represent a topological set of scalars
Texture2D 2D texture
Texture3D 3D texture
TextureAtlas Group small data regions into a large texture
RenderBuffer Base class for render buffer object
FrameBuffer Frame buffer object
clear
create_shader
finish
flush
flush_commands
get_state_presents
set_blend_color
set_blend_equation
set_blend_func
set_clear_color
set_clear_stencil
set_color_mask
set_cull_face
set_depth_func
set_depth_mask
set_depth_range
set_front_face
set_hint
set_line_width
set_state Set OpenGL rendering state, optionally using a preset
set_stencil_func
set_stencil_mask
set_stencil_op
set_viewport
## vispy.gloo.Program
bind(data)
delete()
draw([mode, indices])
set_shaders(ver, frag)
# vispy.app
app.use_app Get/create the default Application object
app.create()
app.run()
app.quit()
app.process_events()
app.Application Wraps a native GUI application instance
app.Canvas Representation of a GUI element with an OpenGL context
###################################################################################################
Most OpenGL programs use a perspective projection matrix to transform
the model-space coordinates of a cartesian model into the "view coordinate"
space of the screen.
The final vertex position in view coordinates is calculated with a simple dot
product of the model-view matrix and the vertex to be transformed.
Modern OpenGL wants you to load your data onto your video card as much as
possible. For geometric data, this is generally done via a Vertex Buffer
Object.
The vertex shader is executed once per vertex
Data transfer from system memory to graphics memory should be made only when
necessary. Also, the number of calls to OpenGL commands should be minimal in
the rendering phase.
###################################################################################################
OpenGL was using a fixed pipeline and you may still find a lot of tutorials
that still use this fixed pipeline.
Functions related to transformations, lighting, texturing, etc. are
deprecated and should be implemented in vertex and fragment shaders
GL commands using the old pipeline:
glVertex, glColor, glLight, glMaterial, glBegin, glEnd
glMatrix, glMatrixMode, glLoadIdentity, glPushMatrix, glPopMatrix
glRect, glPolygonMode, glFrustum, glOrtho, glBitmap, glAphaFunc
glNewList, glDisplayList, glPushAttrib, glPopAttrib
glVertexPointer, glColorPointer, glTexCoordPointer, glNormalPointer
###################################################################################################
## Vertex shaders!
gl_Position Output position to the viewport!
gl_PointSize
## Fragment shaders!
gl_FragColor Output color to the viewport!
"""
from vispy import app # app and gloo are relatively stable at this point...
from vispy import gloo # When using vispy.gloo, we need to write shaders!
from vispy import visuals # For freetype text!
import numpy as np
from mathisart import *
from copy import copy
# Vertex shader: position, gl_Position (vec4). Runs once per vertex
vertex_shader = '''
uniform float u_size;
attribute vec2 a_position;
attribute vec4 a_color;
varying vec4 v_color;
void main() {
gl_PointSize = u_size;
gl_Position = vec4(a_position, 0, 1);
v_color = a_color;
}
'''
# Fragment shader: interpolated color, gl_Color (vec4). Runs once per pixel
fragment_shader = '''
varying vec4 v_color;
void main() {
gl_FragColor = v_color;
}
'''
class Canvas(app.Canvas):
"""We inherit from app.Canvas and override a few of its methods.
"""
def __init__(self, *args, **kwargs):
app.Canvas.__init__(self, *args, **kwargs)
self.program = gloo.Program(vertex_shader, fragment_shader)
self.timer = app.Timer('auto', connect=self.on_timer, start=True)
ps = self.pixel_scale # Drawing epsilon!
self.measure_fps()
# Canvas parameters!
self.bgcolor = palette('black')/255
self.vertices = copy(vertices)
self.colors = colors
self.unicorns = 0
self.move_x = move_x
self.move_y = move_y
# Text!
self.texts = [visuals.TextVisual('fps', color='w',
font_size=20, pos=(200, 75)), ]
# Vertex shaders!
self.program['u_size'] = 5*ps
self.program['a_position'] = self.vertices
self.program['a_color'] = self.colors
def on_draw(self, event):
self.context.clear(self.bgcolor) # gloo.clear(self.bgcolor)
self.program.draw('points')
# points lines line_strip line_loop triangles triangle_strip
# triangle_fan
def on_resize(self, event):
gloo.set_viewport(0, 0, *grid) # Preserve aspect ratio!
# gloo.set_viewport(0, 0, *event.size) # Warp aspect ratio!
def on_timer(self, event):
"""Redraw at 60fps due to our self.timer!"""
self.vertices += self.move_x
self.program['a_position'] = self.vertices
self.update()
def on_key_press(self, event):
if event.key == 'Right':
self.vertices += self.move_x*10
elif event.key == 'Left':
self.vertices -= self.move_x*10
elif event.key == 'Up':
self.vertices += self.move_y*10
elif event.key == 'Down':
self.vertices -= self.move_y*10
self.program['a_position'] = self.vertices
if event.key == 'R':
self.vertices = copy(vertices) # Notice the indirect update!
self.unicorns += 1
print(self.unicorns, event.key.name, event.text, self.size, sep=' | ')
def on_mouse_press(self, event):
if event.button == 1:
print('L-click down!', end=' ')
if event.button == 2:
print('R-click down!', end=' ')
print(event.pos)
def on_mouse_release(self, event):
if event.button == 1:
print('L-click up! ', end=' ')
if event.button == 2:
print('R-click up! ', end=' ')
print(event.pos)
def on_mouse_move(self, event):
pass
def on_mouse_wheel(self, event):
print(event.delta[1])
###################################################################################################
"""Parameters!"""
# Window
W = 1024
size = (W*16/9, W)
pos = (0, 0)
# Create a CPU buffer with vertices, each having a position and a color
grid = (1000, 1000)
n = 50
ran = 0.99
x = np.linspace(-ran, ran, n)
corners = [[ran, ran], [-ran, ran], [-ran, -ran], [ran, -ran]]
corners = np.array(corners).astype(np.float32)
vertices = cartesian(x, x).astype(np.float32)
colors = [palette('red')/255 for i in range(n**2)]
# Paint corners blue!
indices = [np.where((vertices == corner).all(axis=1)) for corner in corners]
indices = np.array(indices).astype(np.int32) # Floats can't be indices!
for index in indices:
colors[index] = palette('main')/255
# Moving epsilons!
move_x = np.array([0.001, 0], dtype=np.float32) # Horizontal movement!
move_y = np.array([0, 0.001], dtype=np.float32) # Vertical movement!
###############################################################################
canvas = Canvas(size=size, position=pos, keys='interactive')
canvas.show()
app.run()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment