GLScatterPlotItem: use shader programs to allow specifying spot size by array

Reorganized shader programs
Infrastructure updates for OpenGL system
This commit is contained in:
Luke Campagnola 2012-10-26 21:47:45 -04:00
parent 450626a3bb
commit b09182d19a
10 changed files with 411 additions and 183 deletions

View File

@ -6,12 +6,12 @@ import pyqtgraph as pg
import numpy as np import numpy as np
import scipy.linalg import scipy.linalg
class SRTTransform3D(QtGui.QMatrix4x4): class SRTTransform3D(pg.Transform3D):
"""4x4 Transform matrix that can always be represented as a combination of 3 matrices: scale * rotate * translate """4x4 Transform matrix that can always be represented as a combination of 3 matrices: scale * rotate * translate
This transform has no shear; angles are always preserved. This transform has no shear; angles are always preserved.
""" """
def __init__(self, init=None): def __init__(self, init=None):
QtGui.QMatrix4x4.__init__(self) pg.Transform3D.__init__(self)
self.reset() self.reset()
if init is None: if init is None:
return return
@ -190,11 +190,11 @@ class SRTTransform3D(QtGui.QMatrix4x4):
self.update() self.update()
def update(self): def update(self):
QtGui.QMatrix4x4.setToIdentity(self) pg.Transform3D.setToIdentity(self)
## modifications to the transform are multiplied on the right, so we need to reverse order here. ## modifications to the transform are multiplied on the right, so we need to reverse order here.
QtGui.QMatrix4x4.translate(self, *self._state['pos']) pg.Transform3D.translate(self, *self._state['pos'])
QtGui.QMatrix4x4.rotate(self, self._state['angle'], *self._state['axis']) pg.Transform3D.rotate(self, self._state['angle'], *self._state['axis'])
QtGui.QMatrix4x4.scale(self, *self._state['scale']) pg.Transform3D.scale(self, *self._state['scale'])
def __repr__(self): def __repr__(self):
return str(self.saveState()) return str(self.saveState())

35
Transform3D.py Normal file
View File

@ -0,0 +1,35 @@
# -*- coding: utf-8 -*-
from .Qt import QtCore, QtGui
import pyqtgraph as pg
import numpy as np
class Transform3D(QtGui.QMatrix4x4):
"""
Extension of QMatrix4x4 with some helpful methods added.
"""
def __init__(self, *args):
QtGui.QMatrix4x4.__init__(self, *args)
def matrix(self, nd=3):
if nd == 3:
return np.array(self.copyDataTo()).reshape(4,4)
elif nd == 2:
m = np.array(self.copyDataTo()).reshape(4,4)
m[2] = m[3]
m[:,2] = m[:,3]
return m[:3,:3]
else:
raise Exception("Argument 'nd' must be 2 or 3")
def map(self, obj):
"""
Extends QMatrix4x4.map() to allow mapping (3, ...) arrays of coordinates
"""
if isinstance(obj, np.ndarray) and obj.ndim >= 2 and obj.shape[0] in (2,3):
return pg.transformCoordinates(self, obj)
else:
return QtGui.QMatrix4x4.map(self, obj)
def inverted(self):
inv, b = QtGui.QMatrix4x4.inverted(self)
return Transform3D(inv), b

123
Vector.py
View File

@ -1,59 +1,64 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
""" """
Vector.py - Extension of QVector3D which adds a few missing methods. Vector.py - Extension of QVector3D which adds a few missing methods.
Copyright 2010 Luke Campagnola Copyright 2010 Luke Campagnola
Distributed under MIT/X11 license. See license.txt for more infomation. Distributed under MIT/X11 license. See license.txt for more infomation.
""" """
from .Qt import QtGui, QtCore from .Qt import QtGui, QtCore
import numpy as np import numpy as np
class Vector(QtGui.QVector3D): class Vector(QtGui.QVector3D):
"""Extension of QVector3D which adds a few helpful methods.""" """Extension of QVector3D which adds a few helpful methods."""
def __init__(self, *args): def __init__(self, *args):
if len(args) == 1: if len(args) == 1:
if isinstance(args[0], QtCore.QSizeF): if isinstance(args[0], QtCore.QSizeF):
QtGui.QVector3D.__init__(self, float(args[0].width()), float(args[0].height()), 0) QtGui.QVector3D.__init__(self, float(args[0].width()), float(args[0].height()), 0)
return return
elif isinstance(args[0], QtCore.QPoint) or isinstance(args[0], QtCore.QPointF): elif isinstance(args[0], QtCore.QPoint) or isinstance(args[0], QtCore.QPointF):
QtGui.QVector3D.__init__(self, float(args[0].x()), float(args[0].y()), 0) QtGui.QVector3D.__init__(self, float(args[0].x()), float(args[0].y()), 0)
elif hasattr(args[0], '__getitem__'): elif hasattr(args[0], '__getitem__'):
vals = list(args[0]) vals = list(args[0])
if len(vals) == 2: if len(vals) == 2:
vals.append(0) vals.append(0)
if len(vals) != 3: if len(vals) != 3:
raise Exception('Cannot init Vector with sequence of length %d' % len(args[0])) raise Exception('Cannot init Vector with sequence of length %d' % len(args[0]))
QtGui.QVector3D.__init__(self, *vals) QtGui.QVector3D.__init__(self, *vals)
return return
elif len(args) == 2: elif len(args) == 2:
QtGui.QVector3D.__init__(self, args[0], args[1], 0) QtGui.QVector3D.__init__(self, args[0], args[1], 0)
return return
QtGui.QVector3D.__init__(self, *args) QtGui.QVector3D.__init__(self, *args)
def __len__(self): def __len__(self):
return 3 return 3
#def __reduce__(self): #def __reduce__(self):
#return (Point, (self.x(), self.y())) #return (Point, (self.x(), self.y()))
def __getitem__(self, i): def __getitem__(self, i):
if i == 0: if i == 0:
return self.x() return self.x()
elif i == 1: elif i == 1:
return self.y() return self.y()
elif i == 2: elif i == 2:
return self.z() return self.z()
else: else:
raise IndexError("Point has no index %s" % str(i)) raise IndexError("Point has no index %s" % str(i))
def __setitem__(self, i, x): def __setitem__(self, i, x):
if i == 0: if i == 0:
return self.setX(x) return self.setX(x)
elif i == 1: elif i == 1:
return self.setY(x) return self.setY(x)
elif i == 2: elif i == 2:
return self.setZ(x) return self.setZ(x)
else: else:
raise IndexError("Point has no index %s" % str(i)) raise IndexError("Point has no index %s" % str(i))
def __iter__(self):
yield(self.x())
yield(self.y())
yield(self.z())

View File

@ -165,6 +165,7 @@ from .WidgetGroup import *
from .Point import Point from .Point import Point
from .Vector import Vector from .Vector import Vector
from .SRTTransform import SRTTransform from .SRTTransform import SRTTransform
from .Transform3D import Transform3D
from .SRTTransform3D import SRTTransform3D from .SRTTransform3D import SRTTransform3D
from .functions import * from .functions import *
from .graphicsWindows import * from .graphicsWindows import *

View File

@ -15,48 +15,91 @@ w.show()
g = gl.GLGridItem() g = gl.GLGridItem()
w.addItem(g) w.addItem(g)
#pos = np.empty((53, 3))
#size = np.empty((53))
#color = np.empty((53, 4))
#pos[0] = (1,0,0); size[0] = 0.5; color[0] = (1.0, 0.0, 0.0, 0.5)
#pos[1] = (0,1,0); size[1] = 0.2; color[1] = (0.0, 0.0, 1.0, 0.5)
#pos[2] = (0,0,1); size[2] = 2./3.; color[2] = (0.0, 1.0, 0.0, 0.5)
#z = 0.5 ##
#d = 6.0 ## First example is a set of points with pxMode=False
#for i in range(3,53): ## These demonstrate the ability to have points with real size down to a very small scale
#pos[i] = (0,0,z) ##
#size[i] = 2./d pos = np.empty((53, 3))
#color[i] = (0.0, 1.0, 0.0, 0.5) size = np.empty((53))
#z *= 0.5 color = np.empty((53, 4))
#d *= 2.0 pos[0] = (1,0,0); size[0] = 0.5; color[0] = (1.0, 0.0, 0.0, 0.5)
pos[1] = (0,1,0); size[1] = 0.2; color[1] = (0.0, 0.0, 1.0, 0.5)
pos[2] = (0,0,1); size[2] = 2./3.; color[2] = (0.0, 1.0, 0.0, 0.5)
z = 0.5
d = 6.0
for i in range(3,53):
pos[i] = (0,0,z)
size[i] = 2./d
color[i] = (0.0, 1.0, 0.0, 0.5)
z *= 0.5
d *= 2.0
#sp = gl.GLScatterPlotItem(pos=pos, sizes=size, colors=color, pxMode=False) sp1 = gl.GLScatterPlotItem(pos=pos, size=size, color=color, pxMode=False)
sp1.translate(5,5,0)
w.addItem(sp1)
pos = (np.random.random(size=(100000,3)) * 10) - 5 ##
## Second example shows a volume of points with rapidly updating color
## and pxMode=True
##
pos = np.random.random(size=(100000,3))
pos *= [10,-10,10]
pos[0] = (0,0,0)
color = np.ones((pos.shape[0], 4)) color = np.ones((pos.shape[0], 4))
d = (pos**2).sum(axis=1)**0.5 d2 = (pos**2).sum(axis=1)**0.5
color[:,3] = np.clip(-np.cos(d*2) * 0.2, 0, 1) size = np.random.random(size=pos.shape[0])*10
sp = gl.GLScatterPlotItem(pos=pos, color=color, size=5) sp2 = gl.GLScatterPlotItem(pos=pos, color=(1,1,1,1), size=size)
phase = 0. phase = 0.
w.addItem(sp2)
##
## Third example shows a grid of points with rapidly updating position
## and pxMode = False
##
pos3 = np.zeros((100,100,3))
pos3[:,:,:2] = np.mgrid[:100, :100].transpose(1,2,0) * [-0.1,0.1]
pos3 = pos3.reshape(10000,3)
d3 = (pos3**2).sum(axis=1)**0.5
sp3 = gl.GLScatterPlotItem(pos=pos3, color=(1,1,1,.3), size=0.1, pxMode=False)
w.addItem(sp3)
def update(): def update():
global phase, color, sp, d ## update volume colors
s = -np.cos(d*2+phase) global phase, sp2, d2
color[:,3] = np.clip(s * 0.2, 0, 1) s = -np.cos(d2*2+phase)
color = np.empty((len(d2),4), dtype=np.float32)
color[:,3] = np.clip(s * 0.1, 0, 1)
color[:,0] = np.clip(s * 3.0, 0, 1) color[:,0] = np.clip(s * 3.0, 0, 1)
color[:,1] = np.clip(s * 1.0, 0, 1) color[:,1] = np.clip(s * 1.0, 0, 1)
color[:,2] = np.clip(s ** 3, 0, 1) color[:,2] = np.clip(s ** 3, 0, 1)
sp2.setData(color=color)
sp.setData(color=color)
phase -= 0.1 phase -= 0.1
## update surface positions and colors
global sp3, d3, pos3
z = -np.cos(d3*2+phase)
pos3[:,2] = z
color = np.empty((len(d3),4), dtype=np.float32)
color[:,3] = 0.3
color[:,0] = np.clip(z * 3.0, 0, 1)
color[:,1] = np.clip(z * 1.0, 0, 1)
color[:,2] = np.clip(z ** 3, 0, 1)
sp3.setData(pos=pos3, color=color)
t = QtCore.QTimer() t = QtCore.QTimer()
t.timeout.connect(update) t.timeout.connect(update)
t.start(50) t.start(50)
w.addItem(sp)
## Start Qt event loop unless running in interactive mode. ## Start Qt event loop unless running in interactive mode.
if sys.flags.interactive != 1: if sys.flags.interactive != 1:

View File

@ -1,4 +1,5 @@
from pyqtgraph.Qt import QtGui, QtCore from pyqtgraph.Qt import QtGui, QtCore
from pyqtgraph import Transform3D
class GLGraphicsItem(QtCore.QObject): class GLGraphicsItem(QtCore.QObject):
def __init__(self, parentItem=None): def __init__(self, parentItem=None):
@ -6,7 +7,7 @@ class GLGraphicsItem(QtCore.QObject):
self.__parent = None self.__parent = None
self.__view = None self.__view = None
self.__children = set() self.__children = set()
self.__transform = QtGui.QMatrix4x4() self.__transform = Transform3D()
self.__visible = True self.__visible = True
self.setParentItem(parentItem) self.setParentItem(parentItem)
self.setDepthValue(0) self.setDepthValue(0)
@ -50,7 +51,7 @@ class GLGraphicsItem(QtCore.QObject):
return self.__depthValue return self.__depthValue
def setTransform(self, tr): def setTransform(self, tr):
self.__transform = tr self.__transform = Transform3D(tr)
self.update() self.update()
def resetTransform(self): def resetTransform(self):
@ -73,12 +74,22 @@ class GLGraphicsItem(QtCore.QObject):
def transform(self): def transform(self):
return self.__transform return self.__transform
def viewTransform(self):
tr = self.__transform
p = self
while True:
p = p.parentItem()
if p is None:
break
tr = p.transform() * tr
return Transform3D(tr)
def translate(self, dx, dy, dz, local=False): def translate(self, dx, dy, dz, local=False):
""" """
Translate the object by (*dx*, *dy*, *dz*) in its parent's coordinate system. Translate the object by (*dx*, *dy*, *dz*) in its parent's coordinate system.
If *local* is True, then translation takes place in local coordinates. If *local* is True, then translation takes place in local coordinates.
""" """
tr = QtGui.QMatrix4x4() tr = Transform3D()
tr.translate(dx, dy, dz) tr.translate(dx, dy, dz)
self.applyTransform(tr, local=local) self.applyTransform(tr, local=local)
@ -88,7 +99,7 @@ class GLGraphicsItem(QtCore.QObject):
*angle* is in degrees. *angle* is in degrees.
""" """
tr = QtGui.QMatrix4x4() tr = Transform3D()
tr.rotate(angle, x, y, z) tr.rotate(angle, x, y, z)
self.applyTransform(tr, local=local) self.applyTransform(tr, local=local)
@ -97,7 +108,7 @@ class GLGraphicsItem(QtCore.QObject):
Scale the object by (*dx*, *dy*, *dz*) in its local coordinate system. Scale the object by (*dx*, *dy*, *dz*) in its local coordinate system.
If *local* is False, then scale takes place in the parent's coordinates. If *local* is False, then scale takes place in the parent's coordinates.
""" """
tr = QtGui.QMatrix4x4() tr = Transform3D()
tr.scale(x, y, z) tr.scale(x, y, z)
self.applyTransform(tr, local=local) self.applyTransform(tr, local=local)
@ -138,8 +149,29 @@ class GLGraphicsItem(QtCore.QObject):
return return
v.updateGL() v.updateGL()
def mapToParent(self, point):
tr = self.transform()
if tr is None:
return point
return tr.map(point)
def mapFromParent(self, point): def mapFromParent(self, point):
tr = self.transform() tr = self.transform()
if tr is None: if tr is None:
return point return point
return tr.inverted()[0].map(point) return tr.inverted()[0].map(point)
def mapToView(self, point):
tr = self.viewTransform()
if tr is None:
return point
return tr.map(point)
def mapFromView(self, point):
tr = self.viewTransform()
if tr is None:
return point
return tr.inverted()[0].map(point)

View File

@ -1,8 +1,8 @@
from pyqtgraph.Qt import QtCore, QtGui, QtOpenGL from pyqtgraph.Qt import QtCore, QtGui, QtOpenGL
from OpenGL.GL import * from OpenGL.GL import *
import numpy as np import numpy as np
from pyqtgraph import Vector
Vector = QtGui.QVector3D ##Vector = QtGui.QVector3D
class GLViewWidget(QtOpenGL.QGLWidget): class GLViewWidget(QtOpenGL.QGLWidget):
""" """
@ -181,10 +181,14 @@ class GLViewWidget(QtOpenGL.QGLWidget):
def pixelSize(self, pos): def pixelSize(self, pos):
""" """
Return the approximate size of a screen pixel at the location pos Return the approximate size of a screen pixel at the location pos
Pos may be a Vector or an (N,3) array of locations
""" """
cam = self.cameraPosition() cam = self.cameraPosition()
dist = (pos-cam).length() if isinstance(pos, np.ndarray) and pos.ndim == 2:
cam = np.array(cam).reshape(1,3)
dist = ((pos-cam)**2).sum(axis=1)**0.5
else:
dist = (pos-cam).length()
xDist = dist * 2. * np.tan(0.5 * self.opts['fov'] * np.pi / 180.) xDist = dist * 2. * np.tan(0.5 * self.opts['fov'] * np.pi / 180.)
return xDist / self.width() return xDist / self.width()

View File

@ -28,7 +28,7 @@ class GLMeshItem(GLGraphicsItem):
GLGraphicsItem.__init__(self) GLGraphicsItem.__init__(self)
def initializeGL(self): def initializeGL(self):
self.shader = shaders.getShader('balloon') self.shader = shaders.getShaderProgram('balloon')
l = glGenLists(1) l = glGenLists(1)
self.triList = l self.triList = l
@ -72,7 +72,9 @@ class GLMeshItem(GLGraphicsItem):
def paint(self): def paint(self):
shaders.glUseProgram(self.shader) with self.shader:
glCallList(self.triList) glCallList(self.triList)
shaders.glUseProgram(0) #shaders.glUseProgram(self.shader)
#glCallList(self.triList)
#shaders.glUseProgram(0)
#glCallList(self.meshList) #glCallList(self.meshList)

View File

@ -1,5 +1,7 @@
from OpenGL.GL import * from OpenGL.GL import *
from OpenGL.arrays import vbo
from .. GLGraphicsItem import GLGraphicsItem from .. GLGraphicsItem import GLGraphicsItem
from .. import shaders
from pyqtgraph import QtGui from pyqtgraph import QtGui
import numpy as np import numpy as np
@ -14,6 +16,7 @@ class GLScatterPlotItem(GLGraphicsItem):
self.size = 10 self.size = 10
self.color = [1.0,1.0,1.0,0.5] self.color = [1.0,1.0,1.0,0.5]
self.pxMode = True self.pxMode = True
#self.vbo = {} ## VBO does not appear to improve performance very much.
self.setData(**kwds) self.setData(**kwds)
def setData(self, **kwds): def setData(self, **kwds):
@ -39,13 +42,16 @@ class GLScatterPlotItem(GLGraphicsItem):
for k in kwds.keys(): for k in kwds.keys():
if k not in args: if k not in args:
raise Exception('Invalid keyword argument: %s (allowed arguments are %s)' % (k, str(args))) raise Exception('Invalid keyword argument: %s (allowed arguments are %s)' % (k, str(args)))
self.pos = kwds.get('pos', self.pos)
self.color = kwds.get('color', self.color) args.remove('pxMode')
self.size = kwds.get('size', self.size) for arg in args:
if arg in kwds:
setattr(self, arg, kwds[arg])
#self.vbo.pop(arg, None)
self.pxMode = kwds.get('pxMode', self.pxMode) self.pxMode = kwds.get('pxMode', self.pxMode)
self.update() self.update()
def initializeGL(self): def initializeGL(self):
## Generate texture for rendering points ## Generate texture for rendering points
@ -65,73 +71,105 @@ class GLScatterPlotItem(GLGraphicsItem):
glBindTexture(GL_TEXTURE_2D, self.pointTexture) glBindTexture(GL_TEXTURE_2D, self.pointTexture)
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, pData.shape[0], pData.shape[1], 0, GL_RGBA, GL_UNSIGNED_BYTE, pData) glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, pData.shape[0], pData.shape[1], 0, GL_RGBA, GL_UNSIGNED_BYTE, pData)
def paint(self): self.shader = shaders.getShaderProgram('point_sprite')
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
#def getVBO(self, name):
#if name not in self.vbo:
#self.vbo[name] = vbo.VBO(getattr(self, name).astype('f'))
#return self.vbo[name]
def setupGLState(self):
"""Prepare OpenGL state for drawing. This function is called immediately before painting."""
#glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) ## requires z-sorting to render properly.
glBlendFunc(GL_SRC_ALPHA, GL_ONE)
glEnable( GL_BLEND ) glEnable( GL_BLEND )
glEnable( GL_ALPHA_TEST ) glEnable( GL_ALPHA_TEST )
glEnable( GL_POINT_SMOOTH ) glDisable( GL_DEPTH_TEST )
#glEnable( GL_POINT_SMOOTH )
glHint(GL_POINT_SMOOTH_HINT, GL_NICEST) #glHint(GL_POINT_SMOOTH_HINT, GL_NICEST)
#glPointParameterfv(GL_POINT_DISTANCE_ATTENUATION, (0, 0, -1e-3)) #glPointParameterfv(GL_POINT_DISTANCE_ATTENUATION, (0, 0, -1e-3))
#glPointParameterfv(GL_POINT_SIZE_MAX, (65500,)) #glPointParameterfv(GL_POINT_SIZE_MAX, (65500,))
#glPointParameterfv(GL_POINT_SIZE_MIN, (0,)) #glPointParameterfv(GL_POINT_SIZE_MIN, (0,))
def paint(self):
self.setupGLState()
glEnable(GL_POINT_SPRITE) glEnable(GL_POINT_SPRITE)
glActiveTexture(GL_TEXTURE0) glActiveTexture(GL_TEXTURE0)
glEnable( GL_TEXTURE_2D ) glEnable( GL_TEXTURE_2D )
glBindTexture(GL_TEXTURE_2D, self.pointTexture) glBindTexture(GL_TEXTURE_2D, self.pointTexture)
glTexEnvi(GL_POINT_SPRITE, GL_COORD_REPLACE, GL_TRUE) glTexEnvi(GL_POINT_SPRITE, GL_COORD_REPLACE, GL_TRUE)
#glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE) ## use texture color exactly #glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE) ## use texture color exactly
glTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ) ## texture modulates current color #glTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ) ## texture modulates current color
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)
glEnable(GL_PROGRAM_POINT_SIZE)
if self.pxMode:
glVertexPointerf(self.pos)
if isinstance(self.color, np.ndarray):
glColorPointerf(self.color)
else:
if isinstance(self.color, QtGui.QColor):
glColor4f(*fn.glColor(self.color))
else:
glColor4f(*self.color)
if isinstance(self.size, np.ndarray): with self.shader:
raise Exception('Array size not yet supported in pxMode (hopefully soon)') #glUniform1i(self.shader.uniform('texture'), 0) ## inform the shader which texture to use
glPointSize(self.size)
glEnableClientState(GL_VERTEX_ARRAY) glEnableClientState(GL_VERTEX_ARRAY)
glEnableClientState(GL_COLOR_ARRAY) try:
glDrawArrays(GL_POINTS, 0, len(self.pos)) glVertexPointerf(self.pos)
else:
for i in range(len(self.pos)):
pos = self.pos[i]
if isinstance(self.color, np.ndarray): if isinstance(self.color, np.ndarray):
color = self.color[i] glEnableClientState(GL_COLOR_ARRAY)
glColorPointerf(self.color)
else: else:
color = self.color if isinstance(self.color, QtGui.QColor):
if isinstance(self.color, QtGui.QColor): glColor4f(*fn.glColor(self.color))
color = fn.glColor(self.color) else:
glColor4f(*self.color)
if isinstance(self.size, np.ndarray):
size = self.size[i]
else:
size = self.size
pxSize = self.view().pixelSize(QtGui.QVector3D(*pos))
glPointSize(size / pxSize) if not self.pxMode or isinstance(self.size, np.ndarray):
glBegin( GL_POINTS ) glEnableClientState(GL_NORMAL_ARRAY)
glColor4f(*color) # x is blue norm = np.empty(self.pos.shape)
#glNormal3f(size, 0, 0) if self.pxMode:
glVertex3f(*pos) norm[:,0] = self.size
glEnd() else:
gpos = self.mapToView(self.pos.transpose()).transpose()
pxSize = self.view().pixelSize(gpos)
norm[:,0] = self.size / pxSize
glNormalPointerf(norm)
else:
glPointSize(self.size)
glDrawArrays(GL_POINTS, 0, len(self.pos))
finally:
glDisableClientState(GL_NORMAL_ARRAY)
glDisableClientState(GL_VERTEX_ARRAY)
glDisableClientState(GL_COLOR_ARRAY)
#posVBO.unbind()
#for i in range(len(self.pos)):
#pos = self.pos[i]
#if isinstance(self.color, np.ndarray):
#color = self.color[i]
#else:
#color = self.color
#if isinstance(self.color, QtGui.QColor):
#color = fn.glColor(self.color)
#if isinstance(self.size, np.ndarray):
#size = self.size[i]
#else:
#size = self.size
#pxSize = self.view().pixelSize(QtGui.QVector3D(*pos))
#glPointSize(size / pxSize)
#glBegin( GL_POINTS )
#glColor4f(*color) # x is blue
##glNormal3f(size, 0, 0)
#glVertex3f(*pos)
#glEnd()

View File

@ -3,39 +3,107 @@ from OpenGL.GL import shaders
## For centralizing and managing vertex/fragment shader programs. ## For centralizing and managing vertex/fragment shader programs.
def initShaders():
global Shaders
Shaders = [
ShaderProgram('balloon', [ ## increases fragment alpha as the normal turns orthogonal to the view
VertexShader("""
varying vec3 normal;
void main() {
normal = normalize(gl_NormalMatrix * gl_Normal);
//vec4 color = normal;
//normal.w = min(color.w + 2.0 * color.w * pow(normal.x*normal.x + normal.y*normal.y, 2.0), 1.0);
gl_FrontColor = gl_Color;
gl_BackColor = gl_Color;
gl_Position = ftransform();
}
"""),
FragmentShader("""
varying vec3 normal;
void main() {
vec4 color = gl_Color;
color.w = min(color.w + 2.0 * color.w * pow(normal.x*normal.x + normal.y*normal.y, 5.0), 1.0);
gl_FragColor = color;
}
""")
]),
ShaderProgram('point_sprite', [ ## allows specifying point size using normal.x
## See:
##
## http://stackoverflow.com/questions/9609423/applying-part-of-a-texture-sprite-sheet-texture-map-to-a-point-sprite-in-ios
## http://stackoverflow.com/questions/3497068/textured-points-in-opengl-es-2-0
##
##
VertexShader("""
void main() {
gl_FrontColor=gl_Color;
gl_PointSize = gl_Normal.x;
gl_Position = ftransform();
}
"""),
#FragmentShader("""
##version 120
#uniform sampler2D texture;
#void main ( )
#{
#gl_FragColor = texture2D(texture, gl_PointCoord) * gl_Color;
#}
#""")
]),
]
Shaders = {
'balloon': ( ## increases fragment alpha as the normal turns orthogonal to the view CompiledShaderPrograms = {}
"""
varying vec3 normal;
void main() {
normal = normalize(gl_NormalMatrix * gl_Normal);
//vec4 color = normal;
//normal.w = min(color.w + 2.0 * color.w * pow(normal.x*normal.x + normal.y*normal.y, 2.0), 1.0);
gl_FrontColor = gl_Color;
gl_BackColor = gl_Color;
gl_Position = ftransform();
}
""",
"""
varying vec3 normal;
void main() {
vec4 color = gl_Color;
color.w = min(color.w + 2.0 * color.w * pow(normal.x*normal.x + normal.y*normal.y, 5.0), 1.0);
gl_FragColor = color;
}
"""
),
}
CompiledShaders = {}
def getShader(name): def getShaderProgram(name):
global Shaders, CompiledShaders return ShaderProgram.names[name]
class VertexShader:
def __init__(self, code):
self.code = code
self.compiled = None
def shader(self):
if self.compiled is None:
self.compiled = shaders.compileShader(self.code, GL_VERTEX_SHADER)
return self.compiled
class FragmentShader:
def __init__(self, code):
self.code = code
self.compiled = None
def shader(self):
if self.compiled is None:
self.compiled = shaders.compileShader(self.code, GL_FRAGMENT_SHADER)
return self.compiled
class ShaderProgram:
names = {}
if name not in CompiledShaders: def __init__(self, name, shaders):
vshader, fshader = Shaders[name] self.name = name
vcomp = shaders.compileShader(vshader, GL_VERTEX_SHADER) ShaderProgram.names[name] = self
fcomp = shaders.compileShader(fshader, GL_FRAGMENT_SHADER) self.shaders = shaders
prog = shaders.compileProgram(vcomp, fcomp) self.prog = None
CompiledShaders[name] = prog, vcomp, fcomp
return CompiledShaders[name][0] def program(self):
if self.prog is None:
compiled = [s.shader() for s in self.shaders] ## compile all shaders
self.prog = shaders.compileProgram(*compiled) ## compile program
return self.prog
def __enter__(self):
glUseProgram(self.program())
def __exit__(self, *args):
glUseProgram(0)
def uniform(self, name):
"""Return the location integer for a uniform variable in this program"""
return glGetUniformLocation(self.program(), name)
initShaders()