[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]

[pygame] Bump mapping



As I posted a while ago, I want a clothify filter that I can use on
dynamically created maps.  After some investigation I found that this was
equivalent to bump mapping a carefully crafted image, so I hacked gimp to
spit out a bump map it generated for clothify, made it seamless, and am
tiling that to bump-map my map-images.

This was rather slow, so I've set it up so that the "shade map" that the
bump-mapping algortihm calculates from the bump map can be saved as an image
which can just be applied without any undue arithmetic.  Much faster, but
still taking ~7 seconds for larger images.

I'm looking for advice on improving this a bit.  Is this just a matter of
using surfarry for the shadeMap() loops, and locking the surface?  Also, I'm
using the red channel of a 24 bit image for the shade map; 8 bit pygame
surfaces just weren't working for me -- what's the trick?

If anyone wants to use this code, modify it, whatever, I'm copylefting it --
do what you like.  The algorithm is (loosely) taken from Gimp's clothify
"script-fu", which in turn uses an algorithm described by John Schlag in
"Graphics Gems IV", and ultimately probably comes from a presentation by
James Blinn in 1978.

-Jasper

PS  Insert the following scheme snippet near the end of clothify.scm to
extract the bump map images Gimp is generating:
(gimp-file-save 0 img bump-layer "YOUR_FULL_PATH/bumpmap.png" "bumpmap.png")


# Code Below

def bumpMap( surface, bumpMap, azimuth=80.0, elevation=55.0, depth=1,
compensate=False ):
    '''Bump map surface by bumpMap. Set compensate=True to avoid darkening'''
    shadeSurf = createShadeMap( bumpMap, azimuth, elevation, depth )
    return shadeMap( surface, shadeSurf, elevation, compensate )

def shadeMap( surface, shadeSurf, elevation=55.0, compensate=False ):
    '''Bump map from a pre calculated shade map'''
    surfW,  surfH  = surface.get_size()
    shadeW, shadeH = shadeSurf.get_size()
    compensation   = sin(elevation)
    resultSurf     = pygame.Surface( surface.get_size() )
    for x in range( surfW ):
        for y in range( surfH ):
            r,g,b,a = surface.get_at( (x,y) )
            shade   = shadeSurf.get_at( (x%shadeW,y%shadeH) )[0]  # red
            if compensate:
                r = min( 255, r * shade / (255*compensation) )
                g = min( 255, g * shade / (255*compensation) )
                b = min( 255, b * shade / (255*compensation) )
            else:
                r = r * shade / 255
                g = g * shade / 255
                b = b * shade / 255
            resultSurf.set_at( (x,y), (r,g,b,a) )
    return resultSurf


def createShadeMap( bumpMap, azimuth, elevation, depth ):
    '''Calc shade map from light vector and height map normal'''
    azimuth   = pi * azimuth   / 180.0
    elevation = pi * elevation / 180.0
    lx        = cos(azimuth) * cos(elevation) * 255.0;
    ly        = sin(azimuth) * cos(elevation) * 255.0;
    lz        = sin(elevation) * 255.0;
    nz        = (6 * 255) / depth;
    nz2       = nz * nz;
    nzlz      = nz * lz;

    heightMap    = createHeightMap( bumpMap )
    shadeSurf    = pygame.Surface( bumpMap.get_size() )
    bumpW, bumpH = bumpMap.get_size()
    for x in range( bumpW ):
        for y in range( bumpH ):
            nx, ny = calcSurfaceNormal( heightMap, x, y, bumpW, bumpH )
            nDotL  = nx*lx + ny*ly + nzlz
            if nDotL < 0:  shade = 0
            else:          shade = nDotL / sqrt( nx*nx + ny*ny + nz2 )
            shadeSurf.set_at( (x,y), (shade,0,0,255) )
    return shadeSurf

def calcSurfaceNormal( heightMap, x2, y2, bumpW, bumpH ):
    '''Aproximate surface normal based on surrounding heights'''
    x1 = (x2-1) % bumpW
    x2 = x2     % bumpW
    x3 = (x2+1) % bumpW
    y1 = (y2-1) % bumpH
    y2 = y2     % bumpH
    y3 = (y2+1) % bumpH

    nx = heightMap[(x1,y1)] + 1.5*heightMap[(x1,y2)] + heightMap[(x1,y3)] -\
         heightMap[(x3,y1)] - 1.5*heightMap[(x3,y2)] - heightMap[(x3,y3)]
    ny = heightMap[(x1,y3)] + 1.5*heightMap[(x2,y3)] + heightMap[(x3,y3)] -\
         heightMap[(x1,y1)] - 1.5*heightMap[(x2,y1)] - heightMap[(x3,y1)]

    return nx, ny


def createHeightMap( bumpMap ):
    '''Condense the bump map from RGB to greyscale'''
    heightMap = {}
    for x in range( bumpMap.get_width() ):
        for y in range( bumpMap.get_height() ):
            r,g,b,a          = bumpMap.get_at( (x,y) )
            heightMap[(x,y)] = calcIntensity( r, g, b )
    return heightMap

def calcIntensity( r, g, b ):
    return int( r*.30 + g*.59 + b*.11 + .5 )