Python and openGL and fractals, oh my!
Author |
Message |
Insectoid
|
Posted: Sun Apr 29, 2012 2:14 pm Post subject: Python and openGL and fractals, oh my! |
|
|
I built this basic fractal heightmap generator/renderer in Python with openGL using the Pyglet library. It works, but very slowly.
As it happens, I know very little about Python, openGL, or fractals. Consequently, the code for it is not very python-like, and the openGL draws are very, very slow, and the generator is pretty slow too. In order to speed up the draws, I tried to move all my vertices into a Pyglet VertexList but couldn't quite get that to work. At the moment, I generate a 2D list of color data and where each list element is one pixel. I then iterate over this list and draw each point. This is my bottleneck, I believe. A VertexList is supposed to run a lot faster since it sits in video RAM and that's my objective (unless there's a better way, like drawing everything to a buffer initially and then flipping that buffer to the screen, then never updating the buffer).
You'll need Pyglet installed to run this. Also, Pyglet on Python 2.6+ is broken on OS X 10.6, so mac users will need to run with Python2.5.
Python: |
#!/usr/bin/python2.5
import random
import pyglet
from pyglet.gl import *
#This function generates a 3D heightmap
def fractal3D (_initial_range, passes, _roughness, size):
heightMap = [[255 for i in range (size)] for j in range (size)] #Create a new map, all points seeded to 255 (make this pretty much whatever you want)
randomRange = float (_initial_range) #Range to increase/decrease points by
roughness = float (2**(_roughness*-1)) #amount to reduce range by per iteration
sideLength = size - 1
while (sideLength >= 2):
halfSide = int (sideLength/2)
x = 0
#squaring step (using square-diamond algorithm)
for i in range (0, size-1, sideLength):
for j in range (0, size-1, sideLength):
avg = (heightMap[i][j]+ heightMap[i+sideLength][j]+heightMap[i][j+sideLength]+heightMap[i+sideLength][j+sideLength])/4.0 + random.randint (-1*int(randomRange), int(randomRange))
heightMap[i+halfSide][j+halfSide] = avg
#diamond step
for i in range (0, size, halfSide):
for j in range ((i+halfSide)%sideLength, size, sideLength):
avg = (heightMap[(i-halfSide+size-1)%(size-1)][j]+heightMap[(i+halfSide)%(size-1)][j]+heightMap[i][(j+halfSide)%(size-1)]+heightMap[i][(j-halfSide+size-1)%(size-1)])/4.0 + random.randint (-1*int(randomRange), int(randomRange))
heightMap[i][j] = avg
sideLength = int (sideLength/2)
randomRange = randomRange*roughness #reduce range
return heightMap
#These functions find the max/min of a 2D list.
def min2D (list2D):
smallest = list2D[0][0]
for i in list2D:
for j in i:
if (j < smallest): smallest = j
return smallest
def max2D (list2D):
largest = list2D[0][0]
for i in list2D:
for j in i:
if (j > largest): largest = j
return largest
#this function takes a 3D heightmap and returns a 2D array of color data
def buildColorMap3D(fractal):
minY = min2D(fractal)*-1 #this translates the array up, to remove negative values (no negative colors)
heightSpan = max2D(fractal)+minY #the difference between the highest and lowest point, used to compress large/small values to within the valid color spectrum
colorMap = [[0 for i in range(len(fractal[0]))] for j in range(len(fractal))] #initialized the color map
for i in range (len(colorMap)):
for j in range (len(colorMap)):
blue = green = 0 #initialize blue & green to zero
red = int((fractal[i][j]+minY)*767/heightSpan) #compresses height data to the valid color spectrum (256*256*256-1). NOT the full spectrum since you'll never have 0 red and blue/green >0
if (red > 255): #color overflow spills into the next color
blue = red-255
red = 255
if (blue > 255):
green = blue - 255
blue = 255
if (green > 255):
green = 255 #Didn't bother handling green overflow- it should never happen anyway.
colorMap[i][j] = (red, green, blue)
return colorMap
print "Generating fractal..."
#heightMap = createVertexList (fractal3D (100, 0, 1.0, 129))
heightMap = buildColorMap3D(fractal3D(100, 0, 1.0, 513)) #build the map
print "Done."
win = pyglet.window.Window()
win.set_size (513, 513)
@win.event
def on_draw():
glClear(GL_COLOR_BUFFER_BIT)
glBegin (GL_POINTS)
#heightMap.draw (GL_POINTS)
#drawFractal2D(pointList, 0, 0, 1400.0, 480.0)
#drawFractal3D(heightMap, 0, 0, 0,0)
for i in range (len(heightMap)): #iterate over each pixel and draw it.
for j in range (len(heightMap)):
glColor3ub (heightMap[i][j][0], heightMap[i][j][1], heightMap[i][j][2])
glVertex2i (i, j)
#glVertex2i (4*i, 4*j)
#glVertex2i (4*i-4, 4*j)
#glVertex2i (4*i-4, 4*j-4)
#glVertex2i (4*i, 4*j-4)
glEnd()
pyglet.app.run()
|
Any little tweaks to turn this into Python code (rather than C code with Python syntax, which is pretty much what this is) or to improve efficiency would be appreciated. |
|
|
|
|
|
Sponsor Sponsor
|
|
|
DemonWasp
|
Posted: Mon Apr 30, 2012 1:37 pm Post subject: RE:Python and openGL and fractals, oh my! |
|
|
The simplest way to improve efficiency is to batch your GL calls: right now you make two GL call per vertex, which is 2 * 256 * 256 = 131K calls per frame. There are calls where you can just pass an array (I'm not familiar with pyglet, so I don't know them offhand), which should let you make 2 calls per frame (ignoring glClear, glBegin, glEnd), assuming you set up your arrays correctly. |
|
|
|
|
|
|
|