Wednesday, February 3, 2010

Polymorphic Cows

The PyStream compiler is getting more robust, and the cases that break it are getting hammered out.


A big milestone is that polymorphic types now work. In the screenshot, there are three different “material” types that control how light interacts with a given surface. There's a chalky DiffuseMaterial that wraps implements wrap lighting. There's a shiny PhongMaterial which implements straight-up Blinn-Phong lighting. There's also a non-photorealistic ToonMaterial that emulates the banded lighting commonly found in cartoons. The code for these materials is at the end of this post.

The screenshot isn't knock-your-socks-off amazing, the real magic is going on in the compiler. There are two significantly cools things going on:

First, Python code is being translated into GLSL. Python loves and abuses indirection. GLSL has no pointers, which makes indirection very difficult to implement. The PyStream compiler is capable of eliminating a vast majority of this indirection, and emulating the rest. In effect, it is now possible to run a significant subset of Python on the GPU. There are a number of existing approaches to metaprogram shaders in non-shader languages, but these approaches uniformly adopt the semantics of the shader language. To my knowledge, this is the first instance where non-shader code has actually been translated into shader code while preserving semantics.

Second, binding code is generated in such a way that executing shaders is entirely Pythonic. The user first composes the shader out of objects. For example, a ToonMaterial object could be stored into a shader's “material” field, colors can be stored into other fields, and so on. The user then calls a method on the shader object to execute the shader. Serialization between the CPU and the GPU is automatic, and the result is drawn into the frame buffer. Currently, shader programmers write a lot of boilerplate to transfer data, and this approach eliminates it entirely.

Code

class DiffuseMaterial(Material):
    __slots__ = 'wrap',

    def __init__(self, wrap):
        Material.__init__(self)
        self.wrap = wrap

    def diffuseTransfer(self, n, l, e):
        # Wrap lighting - approximates
        # sub-surface scattering
        ndl = n.dot(l)
        wrapped = (ndl+self.wrap)/(1.0+self.wrap)
        return max(wrapped, 0.0)

    def specularTransfer(self, n, l, e):
        return 0.0


class PhongMaterial(Material):
    __slots__ = 'shiny',

    def __init__(self, shiny):
        Material.__init__(self)
        self.shiny = shiny

    def specularTransfer(self, n, l, e):
        # Blinn-Phong transfer
        h = (l+e).normalize()
        ndh = nldot(n, h)
        # Scale by (shiny+8)/8 to approximate
        # energy conservation
        scale = (self.shiny+8.0)*0.125
        return (ndh**self.shiny)*scale

class ToonMaterial(Material):
    __slots__ = 'toonMap',

    def __init__(self, toonMap):
        Material.__init__(self)
        self.toonMap = toonMap

    def diffuseTransfer(self, n, l, e):
        amt = n.dot(l)*0.5+0.5
        uv = vec2(0.25, amt)
        return self.toonMap.texture(uv).x

    def specularTransfer(self, n, l, e):
        h = (l+e).normalize()
        amt = nldot(n, h)*0.5+0.5
        uv = vec2(0.75, amt)
        return self.toonMap.texture(uv).x

No comments:

Post a Comment