Created
March 11, 2016 00:51
-
-
Save etale-cohomology/e64a96c686eef2f0ded2 to your computer and use it in GitHub Desktop.
A small working example of vispy, along with some notes
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
"""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