Created
May 15, 2014 07:16
-
-
Save kbrafford/18665bdafa2b0a822f4f to your computer and use it in GitHub Desktop.
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
# Visualization of particles with gravity | |
# Adapted from pyopencl example | |
# Source: http://enja.org/2010/08/27/adventures-in-opencl-part-2-particles-with-opengl/ | |
# | |
# Refactoring example to show shader usage: | |
# Uses the x-ray shader found in MeshLab | |
# Code is a modified version of http://www.pygame.org/wiki/GLSLExample | |
# | |
# OpenCL support | |
import pyopencl as cl # OpenCL - GPU computing interface | |
from pyopencl.tools import get_gl_sharing_context_properties | |
mf = cl.mem_flags | |
# OpenGL support | |
from OpenGL.GL import * # OpenGL - GPU rendering interface | |
from OpenGL.GLU import * # OpenGL tools (mipmaps, NURBS, perspective projection, shapes) | |
from OpenGL.GLUT import * # OpenGL tool to make a visualization window | |
from OpenGL.arrays import vbo | |
# PyOpenGL 3.0.1 introduces this convenience module... | |
from OpenGL.GL.shaders import * | |
import numpy # Number tools -- useful for efficient management of | |
# native numerical types even though we're using Python | |
import sys # System tools (path, modules, maxint) | |
import time # for, get this, time functions | |
class Shader(object): | |
"""Shader abstract base class""" | |
def __init__(self): | |
self.program = compileProgram(compileShader(self.VERTEX_PROGRAM, | |
GL_VERTEX_SHADER), | |
compileShader(self.FRAGMENT_PROGRAM, | |
GL_FRAGMENT_SHADER), | |
) | |
class XRayShader(Shader): | |
VERTEX_PROGRAM = """ | |
// Application to vertex shader | |
varying vec3 P; | |
varying vec3 N; | |
varying vec3 I; | |
void main() | |
{ | |
//Transform vertex by modelview and projection matrices | |
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; | |
// Position in clip space | |
P = vec3(gl_ModelViewMatrix * gl_Vertex); | |
// Normal transform (transposed model-view inverse) | |
N = gl_NormalMatrix * gl_Normal; | |
// Incident vector | |
I = P; | |
// Forward current color and texture coordinates after applying texture matrix | |
gl_FrontColor = gl_Color; | |
//gl_TexCoord[0] = gl_TextureMatrix[0] * gl_MultiTexCoord0; | |
} | |
""" | |
FRAGMENT_PROGRAM = """ | |
varying vec3 P; | |
varying vec3 N; | |
varying vec3 I; | |
uniform float edgefalloff; | |
void main() | |
{ | |
float opacity = dot(normalize(N), normalize(-I)); | |
opacity = abs(opacity); | |
opacity = 1.0 - pow(opacity, edgefalloff); | |
gl_FragColor = opacity * gl_Color; | |
} | |
""" | |
def __init__(self): | |
Shader.__init__(self) | |
self.falloffValue = 1.0 | |
def mod_falloff(self, val): | |
if self.program: | |
edgefalloff = glGetUniformLocation(self.program, "edgefalloff") | |
if not edgefalloff in (None,-1): | |
self.falloffValue = self.falloffValue + val | |
glUniform1f(edgefalloff, self.falloffValue) | |
class ComputeObject(object): | |
_INITIALIZED = False | |
def __init__(self): | |
if not self._INITIALIZED: | |
self.platform = cl.get_platforms()[0] | |
print self.platform | |
self.context = cl.Context(properties=[(cl.context_properties.PLATFORM, | |
self.platform)] + \ | |
get_gl_sharing_context_properties()) | |
print self.context | |
self.queue = cl.CommandQueue(self.context) | |
self._INITIALIZED | |
class ParticleObject(ComputeObject): | |
def __init__(self): | |
ComputeObject.__init__(self) | |
def create_program(self, kernel): | |
self.program = cl.Program(self.context, kernel).build() | |
class Fountain(ParticleObject): | |
NUM_PARTICLES = int(2*65536) | |
CALCS_PER_KERNEL = 1 | |
GLOBAL_SIZE = (NUM_PARTICLES/CALCS_PER_KERNEL,) | |
LOCAL_SIZE = None | |
(SHOW_DOTS, SHOW_TEAPOT, SHOW_DODEC) = (True, True, True) | |
def __init__(self): | |
# We need to set up the GL world before we initialize the Compute context | |
self.window = self.glut_window() | |
# Let's create our shader | |
self.shader = XRayShader() | |
self.shader_program = self.shader.program | |
# To set the uniform, we need to tell GL to select our shader | |
# as the active program | |
glUseProgram(self.shader_program) | |
self.shader.mod_falloff(0.0) | |
glUseProgram(0) | |
# Let's create our buffer objects with initial data calculated | |
(self.np_position, | |
self.np_velocity, | |
self.gl_position, | |
self.gl_color) = self.initial_buffers(self.NUM_PARTICLES) | |
# Set up the compute context now | |
ParticleObject.__init__(self) | |
# Create the CL buffers (the ones only used by OpenCL) | |
self.cl_velocity = cl.Buffer(self.context, | |
mf.COPY_HOST_PTR, | |
hostbuf=self.np_velocity) | |
self.cl_start_position = cl.Buffer(self.context, | |
mf.READ_ONLY | mf.COPY_HOST_PTR, | |
hostbuf=self.np_position) | |
self.cl_start_velocity = cl.Buffer(self.context, | |
mf.READ_ONLY | mf.COPY_HOST_PTR, | |
hostbuf=self.np_velocity) | |
# Create the GL buffers (the ones used both by OpenCL and OpenGL) | |
self.cl_gl_position = cl.GLBuffer(self.context, | |
mf.READ_WRITE, | |
int(self.gl_position.buffers[0])) | |
self.cl_gl_color = cl.GLBuffer(self.context, | |
mf.READ_WRITE, | |
int(self.gl_color.buffers[0])) | |
self.create_program(self.create_kernel()) | |
self.time_func = time.time | |
self.start_time = self.time_func() | |
self.frame_count = 0 | |
def create_kernel(self): | |
return """ | |
__kernel void particle_fountain(__global float4* position, | |
__global float4* color, | |
__global float4* velocity, | |
__global float4* start_position, | |
__global float4* start_velocity, | |
float time_step, | |
int calcs_per_kernel) | |
{ | |
unsigned int j = get_global_size(0); | |
unsigned int i = get_global_id(0); | |
int k; | |
for (k = 0; k < calcs_per_kernel*j; k += j) { | |
float4 p = position[i+k]; | |
float4 v = velocity[i+k]; | |
float life = velocity[i+k].w; | |
life -= time_step; | |
if (life <= 0.f) | |
{ | |
p = start_position[i+k]; | |
v = start_velocity[i+k]; | |
life = 1.0f; | |
} | |
v.z -= 9.8f*time_step; | |
p.x += v.x*time_step; | |
p.y += v.y*time_step; | |
p.z += v.z*time_step; | |
v.w = life; | |
position[i+k] = p; | |
velocity[i+k] = v; | |
color[i+k].w = life; /* Fade points as life decreases */ | |
} | |
}""" | |
def glut_window(self): | |
self.width = 1280 | |
self.height = 720 | |
self.time_step = .01 | |
self.mouse_down = False | |
self.mouse_old = {'x': 0., 'y': 0.} | |
self.rotate = {'x': -38., 'y': 14., 'z': 0.} | |
self.translate = {'x': 0., 'y': 0., 'z': 0.} | |
self.initial_translate = {'x': 0., 'y': 0., 'z': -2.5} | |
glutInit(sys.argv) | |
glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH) | |
glutInitWindowSize(self.width, self.height) | |
glutInitWindowPosition(0, 0) | |
window = glutCreateWindow( | |
"Enjalot's Particle Simulation, Made Exceptionally Goofy" | |
) | |
glutDisplayFunc(self.on_display) # Called by GLUT every frame | |
glutKeyboardFunc(self.on_key) | |
glutMouseFunc(self.on_click) | |
glutMotionFunc(self.on_mouse_move) | |
glutTimerFunc(10, self.on_timer, 15) # Call draw every 16 ms | |
glViewport(0, 0, self.width, self.height) | |
glMatrixMode(GL_PROJECTION) | |
glLoadIdentity() | |
gluPerspective(60., self.width / float(self.height), .1, 1000.) | |
return(window) | |
def initial_buffers(self, num_particles, colorful=True): | |
np_position = numpy.ndarray((num_particles, 4), dtype=numpy.float32) | |
np_color = numpy.ndarray((num_particles, 4), dtype=numpy.float32) | |
np_velocity = numpy.ndarray((num_particles, 4), dtype=numpy.float32) | |
np_position[:,0] = numpy.sin(numpy.arange(0., num_particles) * 2.001 * numpy.pi / num_particles) | |
np_position[:,0] *= numpy.random.random_sample((num_particles,)) / 3. + .2 | |
np_position[:,1] = numpy.cos(numpy.arange(0., num_particles) * 2.001 * numpy.pi / num_particles) | |
np_position[:,1] *= numpy.random.random_sample((num_particles,)) / 3. + .2 | |
np_position[:,2] = 0. | |
np_position[:,3] = 1. | |
if colorful: | |
np_color[:,0] = numpy.random.random_sample((num_particles,)) | |
np_color[:,1] = numpy.random.random_sample((num_particles,)) | |
np_color[:,2] = numpy.random.random_sample((num_particles,)) | |
np_color[:,3] = 1. | |
else: | |
np_color[:,:] = [1.,1.,1.,1.] | |
np_velocity[:,0] = np_position[:,0] * 2. | |
np_velocity[:,1] = np_position[:,1] * 2. | |
np_velocity[:,2] = 3. | |
np_velocity[:,3] = numpy.random.random_sample((num_particles, )) | |
gl_position = vbo.VBO(data=np_position, usage=GL_DYNAMIC_DRAW, target=GL_ARRAY_BUFFER) | |
gl_position.bind() | |
gl_color = vbo.VBO(data=np_color, usage=GL_DYNAMIC_DRAW, target=GL_ARRAY_BUFFER) | |
gl_color.bind() | |
return (np_position, np_velocity, gl_position, gl_color) | |
def on_timer(self, t): | |
glutTimerFunc(t, self.on_timer, t) | |
glutPostRedisplay() | |
def on_key(self, *args): | |
if args[0] == '\033' or args[0] == 'q': | |
sys.exit() | |
def on_click(self, button, state, x, y): | |
self.mouse_old['x'] = x | |
self.mouse_old['y'] = y | |
def on_mouse_move(self, x, y): | |
self.rotate['x'] += (y - self.mouse_old['y']) * .2 | |
self.rotate['y'] += (x - self.mouse_old['x']) * .2 | |
self.mouse_old['x'] = x | |
self.mouse_old['y'] = y | |
#print rotate | |
def on_display(self): | |
"""Render the particles""" | |
# Update or particle positions by calling the OpenCL kernel | |
cl.enqueue_acquire_gl_objects(self.queue, (self.cl_gl_position, self.cl_gl_color)) | |
kernelargs = (self.cl_gl_position, | |
self.cl_gl_color, | |
self.cl_velocity, | |
self.cl_start_position, | |
self.cl_start_velocity, | |
numpy.float32(self.time_step), | |
numpy.int32(self.CALCS_PER_KERNEL)) | |
self.program.particle_fountain(self.queue, | |
self.GLOBAL_SIZE, | |
self.LOCAL_SIZE, | |
*(kernelargs)) | |
cl.enqueue_release_gl_objects(self.queue, (self.cl_gl_position, self.cl_gl_color)) | |
self.queue.finish() | |
glFlush() | |
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) | |
glMatrixMode(GL_MODELVIEW) | |
glLoadIdentity() | |
rz = self.rotate['z'] | |
rz += 37. | |
rz = rz if rz < 360 else 0 | |
self.rotate['z'] = rz | |
# Handle mouse transformations | |
glTranslatef(self.initial_translate['x'], | |
self.initial_translate['y'], | |
self.initial_translate['z']) | |
glRotatef(self.rotate['x'], 1, 0, 0) | |
glRotatef(self.rotate['y'], 0, 1, 0) #we switched around the axis so make this rotate_z | |
#glRotatef(self.rotate['z'], 0, 0, 1) # krb | |
glTranslatef(self.translate['x'], self.translate['y'], self.translate['z']) | |
glEnable(GL_POINT_SMOOTH) | |
glPointSize(1) | |
glEnable(GL_BLEND) | |
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) | |
if self.SHOW_DOTS: | |
# Render the particles | |
# Set up the VBOs | |
self.gl_color.bind() | |
glColorPointer(4, GL_FLOAT, 0, self.gl_color) | |
self.gl_position.bind() | |
glVertexPointer(4, GL_FLOAT, 0, self.gl_position) | |
glEnableClientState(GL_VERTEX_ARRAY) | |
glEnableClientState(GL_COLOR_ARRAY) | |
# Draw the VBOs | |
glDrawArrays(GL_POINTS, 0, self.NUM_PARTICLES) | |
# we use our shader for the dodec/teapot | |
glUseProgram(self.shader_program) | |
if self.SHOW_DODEC: | |
glScalef(.1,.1,.1) | |
glutSolidDodecahedron() | |
if self.SHOW_TEAPOT: | |
glScalef(1.5,1.5,1.5) | |
glutSolidTeapot(2.0) | |
# then we use no shader | |
glUseProgram(0) | |
glDisableClientState(GL_COLOR_ARRAY) | |
glDisableClientState(GL_VERTEX_ARRAY) | |
glDisable(GL_BLEND) | |
glutSwapBuffers() | |
self.frame_count += 1 | |
current_time = self.time_func() | |
elapsed_time = current_time - self.start_time | |
if elapsed_time >= 1.0: | |
self.start_time = current_time | |
print "\r%.2f" % (self.frame_count / elapsed_time) | |
self.frame_count = 0 | |
def animate(self): | |
glutMainLoop() | |
def main(): | |
Fountain().animate() | |
if __name__ == "__main__": | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment