Работа с OpenGL и GLUT в языке Python, библиотека PyOpenGL, расширение Python'а

Благодаря своей гибкости и простоте язык Python позволяет легко и быстро писать целый ряд приложений и утилит. При этом для работы с "непитоновскими" библиотеками (например OpenGL) необходимы модули, обеспечивающие возможность вызова функций библиотеки непосредственно из программы на языке Python.

Библиотека PyOpenGL как раз и является таким модулем, позволяющим в программах на языке Python легко работать с функциями OpenGL, GLU и GLUT, а также с рядом расширений OpenGL.

При этом простейшая программа, использующая PyOpenGL очень похожа на аналогичную программу на С++.

from OpenGL.GL   import *
from OpenGL.GLU  import *
from OpenGL.GLUT import *
import Image                    # PIL
import sys
import math

angle    = 0.0
angle2   = 0.0
texture  = 0
object   = 0
d        = 3.0 / math.sqrt ( 3 )

def drawBox ( x1, x2, y1, y2, z1, z2 ):
    glBindTexture ( GL_TEXTURE_2D, texture )
    glBegin       ( GL_POLYGON )                        # front face
    glNormal3f    ( 0.0, 0.0, 1.0 )
    glTexCoord2f  ( 0, 0 )
    glVertex3f    ( x1, y1, z2 )
    glTexCoord2f  ( 1, 0 )
    glVertex3f    ( x2, y1, z2 )
    glTexCoord2f  ( 1, 1 )
    glVertex3f    ( x2, y2, z2 )
    glTexCoord2f  ( 0, 1 )
    glVertex3f    ( x1, y2, z2 )
    glEnd         ()
    
    glBegin      ( GL_POLYGON )                         # back face
    glNormal3f   ( 0.0, 0.0, -1.0 )
    glTexCoord2f ( 1, 0 )
    glVertex3f   ( x2, y1, z1 )
    glTexCoord2f ( 0, 0 )
    glVertex3f   ( x1, y1, z1 )
    glTexCoord2f ( 0, 1 )
    glVertex3f   ( x1, y2, z1 )
    glTexCoord2f ( 1, 1 )
    glVertex3f   ( x2, y2, z1 )
    glEnd        ()
    
    glBegin      ( GL_POLYGON )                         # left face
    glNormal3f   ( -1.0, 0.0, 0.0 )
    glTexCoord2f ( 0, 0 )
    glVertex3f   ( x1, y1, z1 )
    glTexCoord2f ( 0, 1 )
    glVertex3f   ( x1, y1, z2 )
    glTexCoord2f ( 1, 1 )
    glVertex3f   ( x1, y2, z2 )
    glTexCoord2f ( 1, 0 )
    glVertex3f   ( x1, y2, z1 )
    glEnd        ()
    
    glBegin      ( GL_POLYGON )                         #  right face
    glNormal3f   ( 1.0, 0.0, 0.0 )
    glTexCoord2f ( 0, 1 )
    glVertex3f   ( x2, y1, z2 )
    glTexCoord2f ( 0, 0 )
    glVertex3f   ( x2, y1, z1 )
    glTexCoord2f ( 1, 0 )
    glVertex3f   ( x2, y2, z1 )
    glTexCoord2f ( 1, 1 )
    glVertex3f   ( x2, y2, z2 )
    glEnd        ()
    
    glBegin      ( GL_POLYGON )                 # top face
    glNormal3f   ( 0.0, 1.0, 0.0 )
    glTexCoord2f ( 0, 1 )
    glVertex3f   ( x1, y2, z2 )
    glTexCoord2f ( 1, 1 )
    glVertex3f   ( x2, y2, z2 )
    glTexCoord2f ( 1, 0 )
    glVertex3f   ( x2, y2, z1 )
    glTexCoord2f ( 0, 0 )
    glVertex3f   ( x1, y2, z1 )
    glEnd        ()
    
    glBegin      ( GL_POLYGON )                 #  bottom face
    glNormal3f   ( 0.0, -1.0, 0.0 )
    glTexCoord2f ( 1, 1 )
    glVertex3f   ( x2, y1, z2 )
    glTexCoord2f ( 1, 0 )
    glVertex3f   ( x1, y1, z2 )
    glTexCoord2f ( 0, 0 )
    glVertex3f   ( x1, y1, z1 )
    glTexCoord2f ( 0, 1 )
    glVertex3f   ( x2, y1, z1 )
    glEnd        ()

def loadTexture ( fileName ):
    image  = Image.open ( fileName )
    width  = image.size [0]
    height = image.size [1]
    image  = image.tostring ( "raw", "RGBX", 0, -1 )
    texture = glGenTextures ( 1 )
    
    glBindTexture     ( GL_TEXTURE_2D, texture )   # 2d texture (x and y size)
    glPixelStorei     ( GL_UNPACK_ALIGNMENT,1 )
    glTexParameterf   ( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT )
    glTexParameterf   ( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT )
    glTexParameteri   ( GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR )
    glTexParameteri   ( GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_LINEAR )
    gluBuild2DMipmaps ( GL_TEXTURE_2D, 3, width, height, GL_RGBA, GL_UNSIGNED_BYTE, image )
    
    return texture

def init ():
    glClearColor ( 0.0, 0.0, 0.0, 0.0 )
    glClearDepth ( 1.0 )                
    glDepthFunc  ( GL_LEQUAL )
    glEnable     ( GL_DEPTH_TEST )
    glEnable     ( GL_TEXTURE_2D )
    glHint       ( GL_POLYGON_SMOOTH_HINT,         GL_NICEST )
    glHint       ( GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST )

def reshape ( width, height ):
    glViewport     ( 0, 0, width, height )
    glMatrixMode   ( GL_PROJECTION )
    glLoadIdentity ()
    gluPerspective ( 60.0, float(width)/float (height), 1.0, 60.0 )
    glMatrixMode   ( GL_MODELVIEW )
    glLoadIdentity ()
    gluLookAt      ( 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 0.0, 1.0, 0.0 )

def display ():
    glClear        ( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT )
    glMatrixMode   ( GL_MODELVIEW )
    glLoadIdentity () 
    gluLookAt      ( 0, 0, 0, 1, 1, 1, 0, 1, 0 )
    glTranslatef   ( 4, 4, 4 )
    glRotatef      ( angle2, 1, -1, 0 )
    glTranslatef   ( d - 1.5, d - 1.5, d - 1.5 )
    glTranslatef   ( 1.5, 1.5, 1.5 )                    # move cube from the center
    glRotatef      ( angle, 1.0, 1.0, 0.0 )
    glTranslatef   ( -1.5, -1.5, -1.5 )                 # move cube into the center
    drawBox        ( 1, 2, 1, 2, 1, 2 )
    
    glutSwapBuffers ()

def keyPressed ( *args ):
    if args [0] == '\033':
        sys.exit ()

def animate ():
    global angle, angle2
    
    angle  = 0.04 * glutGet ( GLUT_ELAPSED_TIME )
    angle2 = 0.01 * glutGet ( GLUT_ELAPSED_TIME )
    
    glutPostRedisplay ()

def main ():
    global texture
    
    glutInit               ( sys.argv )
    glutInitDisplayMode    ( GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH )
    glutInitWindowSize     ( 500, 500 )
    glutInitWindowPosition ( 0, 0 )
    
    glutCreateWindow ( "Simple PyOpenGL example" )
    glutDisplayFunc  ( display    )
    glutIdleFunc     ( animate    )
    glutReshapeFunc  ( reshape    )
    glutKeyboardFunc ( keyPressed )
    init             ()
    
    texture = loadTexture ( "../../Textures/block.bmp" )
    
    glutMainLoop()

print "Hit ESC key to quit."
main()

Обратите внимание , что для загрузки текстур из файла была использована еще одна библиотека для Python'а - PIL.

Крайне просто осуществляется и использование расширений OpenGL, ниже приводится листинг программы, использующей расширение ARB_texture_cubemap.

from OpenGL.GL import *
from OpenGL.GLU import *
from OpenGL.GLUT import *
from OpenGL.GL.ARB.texture_cube_map import *
import Image                # PIL
import sys

angle    = 0.0
angle2   = 0.0
texture  = 0
object   = 0
reflMode = GL_REFLECTION_MAP_ARB

def loadTexture ( fileName ):
    image  = Image.open ( fileName )
    width  = image.size [0]
    height = image.size [1]
    image  = image.tostring ( "raw", "RGBX", 0, -1 )
    
    texture = glGenTextures ( 1 )
    glBindTexture     ( GL_TEXTURE_2D, texture )   # 2d texture (x and y size)
    glPixelStorei     ( GL_UNPACK_ALIGNMENT,1 )
    glTexParameterf   ( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT )
    glTexParameterf   ( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT )
    glTexParameteri   ( GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR )
    glTexParameteri   ( GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_LINEAR )
    gluBuild2DMipmaps ( GL_TEXTURE_2D, 3, width, height, GL_RGBA, GL_UNSIGNED_BYTE, image )
    
    return texture

def loadCubemap ( faces, path = "" ):
    texture = glGenTextures ( 1 )
    target  = GL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB
    glBindTexture     ( GL_TEXTURE_CUBE_MAP_ARB, texture )   # 2d texture (x and y size)
    glPixelStorei     ( GL_UNPACK_ALIGNMENT,1 )
    glTexParameteri   ( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,     GL_REPEAT )
    glTexParameteri   ( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T,     GL_REPEAT )
    glTexParameteri   ( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR )
    glTexParameteri   ( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR )
    glEnable          ( GL_TEXTURE_CUBE_MAP_ARB )
    
    for f in faces:
        if path != "":
            file = path + "/" + f
        else:
            file = f
        
        image  = Image.open ( file )
        width  = image.size [0]
        height = image.size [1]
        image  = image.tostring ( "raw", "RGBX", 0, -1 )
        gluBuild2DMipmaps ( target, 3, width, height, GL_RGBA, GL_UNSIGNED_BYTE, image )
        target = target + 1
    
    return texture

def extensionInit ():
    """ Determine if the fog coord extentsion is availble """

    # After calling this, we will be able to invoke glFogCoordEXT ()
    if not glInitTextureCubeMapARB ():
        print "ARB_texture_cubemap not supported !"
        sys.exit ( 1 )

def init ():
    glClearColor ( 0.0, 0.0, 0.0, 0.0 )
    glClearDepth ( 1.0 )                
    glDepthFunc  ( GL_LEQUAL )
    glEnable     ( GL_DEPTH_TEST )
    glHint       ( GL_POLYGON_SMOOTH_HINT,         GL_NICEST )
    glHint       ( GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST )

def reshape ( width, height ):
    glViewport     ( 0, 0, width, height )
    glMatrixMode   ( GL_PROJECTION )
    glLoadIdentity ()
    gluPerspective ( 60.0, float(width)/float (height), 1.0, 60.0 )
    glMatrixMode   ( GL_MODELVIEW )
    glLoadIdentity ()
    gluLookAt      ( 0.0, 0.0, 0.0, 5.0, 5.0, 5.0, 0.0, 1.0, 0.0 )

def display ():
    global texture, reflMode
    
    glClear   ( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT )
    glEnable  ( GL_TEXTURE_CUBE_MAP_ARB )
    glEnable  ( GL_TEXTURE_GEN_S )
    glEnable  ( GL_TEXTURE_GEN_T )
    glEnable  ( GL_TEXTURE_GEN_R )
    glTexGeni ( GL_S, GL_TEXTURE_GEN_MODE, reflMode )
    glTexGeni ( GL_T, GL_TEXTURE_GEN_MODE, reflMode )
    glTexGeni ( GL_R, GL_TEXTURE_GEN_MODE, reflMode )
    
    glBindTexture   ( GL_TEXTURE_CUBE_MAP_ARB, texture )
    glPushMatrix    ()
    glTranslatef    ( 2, 2, 2 )
    glRotatef       ( angle,  1, 1, 0 )
    glRotatef       ( angle2, 0, 1, 1 )
    
    if object == 0:
        glutSolidTeapot ( 1 )
    elif object == 1:
        glutSolidSphere ( 1, 30, 30 )
    elif object == 2:
        glutSolidCube ( 1 )
    elif object == 3:
        glutSolidCone ( 0.7, 1.2, 30, 30 )
    elif object == 4:
        glutSolidTorus ( 0.5, 1, 40, 40 )
    elif object == 5:
        glutSolidDodecahedron ()
    elif object == 6:
        glutSolidOctahedron ()
    elif object == 7:
        glutSolidTetrahedron ()
    elif object == 8:
        glutSolidIcosahedron ();
    
    glPopMatrix     ()
    glutSwapBuffers ()

def keyPressed ( *args ):
    global object, reflMode
    
    key = args [0]
    if key == '\033':
        sys.exit ()
    elif key == 'n' or key == 'N':
        reflMode = GL_NORMAL_MAP_ARB
    elif key == 'r' or key == 'R':
        reflMode = GL_REFLECTION_MAP_ARB
    elif key == '0':
        object = 0
    elif key == '1':
        object = 1
    elif key == '2':
        object = 2
    elif key == '3':
        object = 3
    elif key == '4':
        object = 4
    elif key == '5':
        object = 5
    elif key == '6':
        object = 6
    elif key == '7':
        object = 7
    elif key == '8':
        object = 8
    elif key == '+':
        object = object + 1
        if object > 8:
            object = 0
    elif key == '-':
        object = object-1
        if object < 0:
            object = 8

def animate ():
    global angle, angle2
    
    angle  = 0.04 * glutGet ( GLUT_ELAPSED_TIME )
    angle2 = 0.01 * glutGet ( GLUT_ELAPSED_TIME )
    
    glutPostRedisplay ()

def main ():
    global texture
    
    glutInit               ( sys.argv )
    glutInitDisplayMode    ( GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH )
    glutInitWindowSize     ( 500, 500 )
    glutInitWindowPosition ( 0, 0 )
    
    glutCreateWindow ( "ARB_texture_cubemap demo" )
    glutDisplayFunc  ( display )
    glutIdleFunc     ( animate )
    glutReshapeFunc  ( reshape )
    glutKeyboardFunc ( keyPressed )

    init          ()
    extensionInit ();
    
    texture = loadCubemap ( ( "cm_left.tga", "cm_right.tga", "cm_top.tga", "cm_bottom.tga", "cm_back.tga", "cm_front.tga" ), "../../Textures/Cubemaps" )
    print texture
    glutMainLoop()

print "Hit ESC key to quit."
main()

Обратите внимание на использование конструкции global - без нее соответствующие переменные будут считаться локальными, т.е. доступа к правильным значениям, определенным вне функций, не произойдет.

К сожалению используемая мною версия библиотеки PyOpenGL обладает двумя недостатками - в ней отсутствует поддержка расширения EXT_framebuffer_object и мне так и не удалось найти способ как передать текст шейдера - по стандарту он должен передаваться как указатель на массив строк, т.е. иметь тип char **.

Поэтому я написал два модуля расширения для Python'а, позволяющее использовать в программах на Python'е классы GlslProgram и FrameBuffer.

Ниже на примере первого из этих классов мы рассмотрим каким образом можно расширить Python при помощи классов, написанных на С++.

Посмотрим на начало файла, "заворачивающего" класс GlslProgram (на С++) для его использования в программах на языке Python.

#include	<Python.h>
#include	"GlslProgram.h"
#include	"libExt.h"
#include	"Data.h"
#include	"Vector4D.h"
#include	"Vector3D.h"
#include	"Vector2D.h"

Файл Python.h подключает определения необходимых типов и структур, а также описания функций, убедитесь, что у вас установлен путь к этому файлу в списке путей для поиска заголовочных файлов.

Каждому вводимому типу соответствует описывающая его структура, всегда начинающееся с одного и того же заданного фрагмента. Этот фрагмент обычно определяют как макрос и описание структуры типа в нашем случае выглядит следующим образом:

struct  GlslProgramObject
{
    PyObject_HEAD
	
    GlslProgram * p;
};

Далее обычно помещаются предварительные описания вводимых функций. Большинство из них на вход получают указатель на сам объект (self), а также на объект - список всех параметров.

static PyObject * loadShaders         ( GlslProgramObject * self, PyObject * args );
static PyObject * clear               ( GlslProgramObject * self, PyObject * args );
static PyObject * getLog              ( GlslProgramObject * self, PyObject * args );
static PyObject * isOk                ( GlslProgramObject * self, PyObject * args );
static PyObject * bindFunc            ( GlslProgramObject * self, PyObject * args );
static PyObject * unbind              ( GlslProgramObject * self, PyObject * args );
static PyObject * isSupported         ( PyObject          * self, PyObject * args );
static PyObject * setUniform          ( GlslProgramObject * self, PyObject * args );
static PyObject * setTexture          ( GlslProgramObject * self, PyObject * args );
static PyObject * setUniformMatrix    ( GlslProgramObject * self, PyObject * args );
static PyObject * getattr             ( GlslProgramObject * self, char * name );
static PyObject * repr                ( GlslProgramObject * self );
static void       GlslProgram_dealloc ( GlslProgramObject * self );

Каждый вводимый тип (класс) должен быть задан при помощи структуры PyTypeObject, содержащей всю необходимую информацию о данном классе:

static PyTypeObject GlslProgram_Type = 
{
    PyObject_HEAD_INIT(NULL)
    0,                                  /*ob_size*/
    "GlslProgram",                      /*tp_name*/
    sizeof(GlslProgramObject),          /*tp_basicsize*/
    0,                                  /*tp_itemsize*/
                                        /* methods */
    (destructor)GlslProgram_dealloc,    /*tp_dealloc*/
    0,                                  /*tp_print*/
    (getattrfunc)getattr,               /*tp_getattr*/
    0,                                  /*tp_setattr*/
    0,                                  /*tp_compare*/
    (reprfunc)repr,                     /*tp_repr*/
    0,                                  /*tp_as_number*/
    0,                                  /*tp_as_sequence*/
    0,                                  /*tp_as_mapping*/
    0,                                  /*tp_hash*/
};

Наиболее важными полями этой структуры являются имя типа (поле tp_name), размер объекта данного типа (tp_basicsize), указатель на деструктор для экземпляров данного класса (tp_dealloc) и функция доступа к атрибутам и методам (tp_getattr).

Также м добавим функцию tp_repr, выдающую строчное представление данного объекта.

Кроме этого нам также необходимо задать два массива указателей на методы - один для методов модуля и один для методов экземпляров данного класса.

static PyMethodDef GlslProgram_Module_methods [] = 
{
    { "isSupported", (PyCFunction)isSupported, METH_VARARGS },
    { NULL,		     NULL}                                     /* sentinel */
};

static PyMethodDef GlslProgram_methods [] = 
{
    { "loadShaders",       (PyCFunction)loadShaders,       METH_VARARGS, "" },
    { "clear",             (PyCFunction) clear,	           METH_VARARGS, "" },
    { "getLog",	           (PyCFunction) getLog,           METH_VARARGS, "" },
    { "isOk",              (PyCFunction) isOk, 	           METH_VARARGS, "" },
    { "bind",              (PyCFunction) bindFunc,         METH_VARARGS, "" },
    { "unbind",            (PyCFunction) unbind,           METH_VARARGS, "" },
    { "setUniform",        (PyCFunction) setUniform, 	   METH_VARARGS, "" },
    { "setUniformMatrix",  (PyCFunction) setUniformMatrix, METH_VARARGS, "" },
    { "setTexture",        (PyCFunction) setTexture,       METH_VARARGS, "" },
    { NULL,                NULL}		                       /* sentinel */
};

Как методы экземпляров класса, так и методы модуля задаются в виде массива структур PyMethodDef, последний элемент массива должен начиняться в двух NULL'ов.

Каждая структура PyMethodDef состоит из следующих полей:

Поле ml_flags поддерживает следующие битовые значения (соединенные побитовым ИЛИ - |):

Рассмотрим реализацию нескольких методов класса GlslProgram:

static PyObject * getLog ( GlslProgramObject * self, PyObject * args )
{
    return PyString_FromString ( self -> p -> getLog ().c_str () );
}

static PyObject * setTexture  ( GlslProgramObject * self, PyObject * args )
{
    char  * name;
    int     unit;

    if ( !PyArg_ParseTuple ( args, "si", &name, &unit ) )
        return NULL;

    self -> p -> setTexture ( name, unit );

    return Py_BuildValue ( "i", true );
}

При передаче аргументов в виде массива (tuple) для извлечения из массива аргументов заданных типов удобно использовать функцию PyArg_ParseTuple.

На вход этой функции передается указатель на список аргументов (как PyObject *), строку с форматом (определяющим какие значения и каким именно типов следует извлечь) и указатели на на переменные, куда будут помещены значения, извлеченные из списка.

В целом структура функции очень напоминает функцию sscanf, только данные читаются из объекта-списка и форматная строка имеет другой формат.

Наиболее часто используемыми символами типов в строке формата являются "O" (объект языка Python), "s" (строка), "i" (целое число) и "f" (float-число). Также допустимо использование символа "|" обозначающего, что все следующие далее аргументы являются необязательными (в этом случае соответствующие им переменные не инициализируются). Полный список всех допустимых конструкцией в строке формата можно найти в описании функции PyArg_ParseTuple.

В случае успешного извлечения всех обязательных аргументов (и полного соответствия типов) функция возвращает ненулевое значение, при ошибках возвращается ноль.

Есть очень похожая на нее функция Py_BuildValue, берущая в качестве первого параметра строку формата и набор значений. Строка формата определяет способ построения объекта языка Python по переданным значениям.

При этом данная функция может принимать на вход сразу много значений и строить из них массивы, списки и словари.

Py_BuildValue ( "[sii]", "abc", 1, 3 );

Так приведенный выше вызов возвращает следующий объект ["abc", 1, 3].

Помимо реализации всех обычных методов необходимо реализовать еще несколько специальных функций - создание и уничтожение экземпляров класса и регистрация данного модуля.

Ниже приводится код для функция создания и уничтожения экземпляров класса GlslProgram.

static PyObject * GlslProgram_new ( PyTypeObject * type, PyObject * args, PyObject * kwds )
{
    initExtensions ();

    GlslProgramObject * object = PyObject_NEW ( GlslProgramObject, &GlslProgram_Type );

    if ( object == NULL )
        return NULL;

    object -> p = new GlslProgram ();

    return (PyObject *) object;
}

static void GlslProgram_dealloc ( GlslProgramObject * self )
{
    delete ((GlslProgramObject *) self) -> p;

    PyObject_DEL ( self );
}

Важной частью любого объекта является метод getattr - фактически обращение ко всем методам и instance-переменным объекта идет именно через этот метод (есть еще парный ему метод setattr, позволяющий устанавливать значения instance-переменных и заменять методы).

Этот метод получает на вход указатель на сам объект (self) и имя атрибута/метода и возвращает значение атрибута или вызываемый (callable) объект для методов.

Проще всего реализовать его через функцию Py_FindMethod, осуществляющей поиск метода по таблице методов. Ниже приводится его реализация для класса FrameBuffer, где данный метод используется также для создания псевдо-переменных width и height (вместо соответствующих методов класса на С++).

static PyObject * getattr ( FrameBufferObject * self, char * name )
{
    PyObject * res = Py_FindMethod ( FrameBuffer_methods, (PyObject*) self, name );

    if ( res )
        return res;

    PyErr_Clear ();

    if ( !strcmp ( name, "width" ) )							// attributes
        return PyInt_FromLong ( self -> fb -> getWidth () );
    else
    if ( !strcmp ( name, "height" ) )
        return PyInt_FromLong ( self -> fb -> getHeight () );
    else
    if ( !strcmp ( name, "hasStencil" ) )
        return PyInt_FromLong ( self -> fb -> hasStencil () );
    else
    if ( !strcmp ( name, "hasDepth" ) )
        return PyInt_FromLong ( self -> fb -> hasDepth () );

    return NULL;
}

Для регистрации модуля (чтобы его можно было загружать из Python-программы) он должен быть скомпилирован в dll (so)-файл с расширением .pyd и обязательно содержать экспортируемую функцию с именем вида init<имя модуля> (для нашего примера это будет initGlslProgram).

При использовании Visual C++ этот метод должен объявлен как extern "C" __declspec(dllexport), для Linux и Mac OS X никакого дополнительного описателя не требуется). Ниже приводится реализация данного метода для "заворачивания" класса GlslProgram.

extern "C" void __declspec(dllexport) initGlslProgram ()
{
    GlslProgram_Type.ob_type   = &PyType_Type;
    GlslProgram_Type.tp_new    = GlslProgram_new;
    GlslProgram_Type.tp_flags |= Py_TPFLAGS_CHECKTYPES;

                             // Create the module and add the functions
    PyObject * m = Py_InitModule ( "GlslProgram", GlslProgram_Module_methods );

    Py_INCREF          ( &GlslProgram_Type );
    PyModule_AddObject ( m, "GlslProgram", (PyObject *)&GlslProgram_Type );
}

Обратите внимание на инициализацию полей ob_type, tp_new и tp_flags, а также сам код, непосредственно создающий модуль при помощи вызова Py_InitModule, увеличивающий число ссылок на него (иначе он будет сразу же уничтожен сборщиком мусора) и добавляющий новый тип (класс) в данный модуль.

После того, как модуль успешно скомпилирован и слинкован и получен файл GlslProgram.pyd его можно использовать из программ на Python как обычный модуль языка Python.

from OpenGL.GL import *
from OpenGL.GLU import *
from OpenGL.GLUT import *
from OpenGL.GL.ARB.vertex_shader import *
from OpenGL.GL.ARB.fragment_shader import *
from OpenGL.GL.ARB.shader_objects import *
from OpenGL.GL.ARB.shading_language_100 import *
import Image                # PIL
import sys
import math
import GlslProgram
from utils import *

angle    = 0.0
angle2   = 0.0
texture  = 0
object   = 0
d        = 3.0 / math.sqrt ( 3 )
program  = None

def initExtensions ():
    if not glInitShaderObjectsARB ():
        sys.exit ( 1 )
    
    if not glInitShadingLanguage100ARB ():
        sys.exit ( 1 )
    
    if not glInitFragmentShaderARB ():
        print "Fragment shaders not supported"
        sys.exit ( 1 )
        
    if not glInitVertexShaderARB ():
        print "Vertex shaders not supported"
        sys.exit ( 1 )

def init ():
    glClearColor ( 0.0, 0.0, 0.0, 0.0 )
    glClearDepth ( 1.0 )                
    glDepthFunc  ( GL_LEQUAL )
    glEnable     ( GL_DEPTH_TEST )
    glEnable     ( GL_TEXTURE_2D )
    glHint       ( GL_POLYGON_SMOOTH_HINT,         GL_NICEST )
    glHint       ( GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST )

def reshape ( width, height ):
    glViewport     ( 0, 0, width, height )
    glMatrixMode   ( GL_PROJECTION )
    glLoadIdentity ()
    gluPerspective ( 60.0, float(width)/float (height), 1.0, 60.0 )
    glMatrixMode   ( GL_MODELVIEW )
    glLoadIdentity ()
    gluLookAt      ( 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 0.0, 1.0, 0.0 )

def display ():
    glClear        ( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT )
    glMatrixMode   ( GL_MODELVIEW )
    glLoadIdentity () 
    program.bind   ()

    gluLookAt      ( 0, 0, 0, 1, 1, 1, 0, 1, 0 )
    glTranslatef   ( 4, 4, 4 )
    glRotatef      ( angle2, 1, -1, 0 )
    glTranslatef   ( d - 1.5, d - 1.5, d - 1.5 )
    glTranslatef   ( 1.5, 1.5, 1.5 )                    # move cube from the center
    glRotatef      ( angle, 1.0, 1.0, 0.0 )
    glTranslatef   ( -1.5, -1.5, -1.5 )                 # move cube into the center
    drawBox        ( 1, 2, 1, 2, 1, 2, texture )
    
    program.unbind  ()
    glutSwapBuffers ()

def keyPressed ( *args ):
    if args [0] == '\033':
        sys.exit ()

def animate ():
    global angle, angle2
    
    angle  = 0.04 * glutGet ( GLUT_ELAPSED_TIME )
    angle2 = 0.01 * glutGet ( GLUT_ELAPSED_TIME )
    
    glutPostRedisplay ()

def main ():
    global texture, program
    
    glutInit               ( sys.argv )
    glutInitDisplayMode    ( GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH )
    glutInitWindowSize     ( 500, 500 )
    glutInitWindowPosition ( 0, 0 )
    
    glutCreateWindow ( "Simple PyOpenGL example" )
    glutDisplayFunc  ( display    )
    glutIdleFunc     ( animate    )
    glutReshapeFunc  ( reshape    )
    glutKeyboardFunc ( keyPressed )
    init             ()
    initExtensions   ()
    
    vSource = open ( "simplest.vsh" ).read ()
    fSource = open ( "simplest.fsh" ).read ()
    program = GlslProgram.GlslProgram ()
    program.loadShaders ( vSource, fSource );
    texture = loadTexture ( "../../Textures/block.bmp" )
    print program.getLog ()
    
    glutMainLoop()

print "Hit ESC key to quit."
main()       

Ниже приводится пример использования как модуля GlslProgram, так и модуля FrameBuffer для реализации эффекта постообработки sepia.

from OpenGL.GL import *
from OpenGL.GLU import *
from OpenGL.GLUT import *
from OpenGL.GL.ARB.vertex_shader import *
from OpenGL.GL.ARB.fragment_shader import *
from OpenGL.GL.ARB.shader_objects import *
from OpenGL.GL.ARB.shading_language_100 import *
from OpenGL.GL.ARB.texture_cube_map import *
import Image                # PIL
import sys
import math
import GlslProgram
import FrameBuffer
from utils import *

decalMap      = None
stoneMap      = None
teapotMap     = None
screenMap     = None
angle         = 0.0
rot           = 0.0
angle         = 0.0
useFilter     = 1
program       = None
buffer        = None

def initExtensions ():
    if not glInitShaderObjectsARB ():
        sys.exit ( 1 )
    
    if not glInitShadingLanguage100ARB ():
        sys.exit ( 1 )
    
    if not glInitFragmentShaderARB ():
        print "Fragment shaders not supported"
        sys.exit ( 1 )
        
    if not glInitVertexShaderARB ():
        print "Vertex shaders not supported"
        sys.exit ( 1 )

def displayBoxes ():
    global angle, rot, stoneMap, decalMap, teapotMap
    
    glMatrixMode ( GL_MODELVIEW )
    glPushMatrix ()
    glRotatef    ( rot, 0, 0, 1 )
    
    drawBoxCull  ( -5, -5, 0, 10, 10, 3, stoneMap, None )
    drawBoxCull  ( 3, 2, 0.5, 1,  2,  2, decalMap, 1 )
    
    glBindTexture   ( GL_TEXTURE_2D, teapotMap )
    glTranslatef    ( 0.2, 1, 1.5 )
    glRotatef       ( angle * 45.3, 1, 0, 0 )
    glRotatef       ( angle * 57.2, 0, 1, 0 )
    glutSolidTeapot ( 0.3 )
    glPopMatrix     ()

def startOrtho ():
    glMatrixMode   ( GL_PROJECTION )                    # select the projection matrix
    glPushMatrix   ()                                   # store the projection matrix
    glLoadIdentity ()                                   # reset the projection matrix
                                                        # set up an ortho screen
    glOrtho        ( 0, 512, 0, 512, -1, 1 )
    glMatrixMode   ( GL_MODELVIEW )                     # select the modelview matrix
    glPushMatrix   ()                                   # store the modelview matrix
    glLoadIdentity ()                                   # reset the modelview matrix

def endOrtho ():
    glMatrixMode ( GL_PROJECTION )                      # select the projection matrix
    glPopMatrix  ()                                     # restore the old projection matrix
    glMatrixMode ( GL_MODELVIEW )                       # select the modelview matrix
    glPopMatrix  ()                                     # restore the old projection matrix

def init ():
    glClearColor ( 0.0, 0.0, 0.0, 0.0 )
    glClearDepth ( 1.0 )                
    glDepthFunc  ( GL_LEQUAL )
    glEnable     ( GL_DEPTH_TEST )
    glEnable     ( GL_TEXTURE_2D )
    glHint       ( GL_POLYGON_SMOOTH_HINT,         GL_NICEST )
    glHint       ( GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST )

def reshape ( width, height ):
    glViewport     ( 0, 0, width, height )
    glMatrixMode   ( GL_PROJECTION )
    glLoadIdentity ()
    gluPerspective ( 60.0, float(width)/float (height), 1.0, 60.0 )
    glMatrixMode   ( GL_MODELVIEW )
    glLoadIdentity ()
    gluLookAt      ( -0.5, -0.5, 1.5, 3.0, 3.0, 1.0, 0.0, 0.0, 1.0 )

def display ():
    global screenMap, program, useFilter
    
    renderToBuffer ()
    
    glClear       ( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT )
    startOrtho    ()
    glBindTexture ( GL_TEXTURE_2D, screenMap )
    
    if useFilter:
        program.bind ()
    
    glBegin      ( GL_QUADS )
    glTexCoord2f ( 0, 0 )
    glVertex2f   ( 0, 0 )
    glTexCoord2f ( 1,   0 )
    glVertex2f   ( 512, 0 )
    glTexCoord2f ( 1, 1 )
    glVertex2f   ( 512, 512 )
    glTexCoord2f ( 0, 1 )
    glVertex2f   ( 0, 512 )
    glEnd        ()
    
    if useFilter:
        program.unbind ()
    
    endOrtho        ()
    glutSwapBuffers ()

def keyPressed ( *args ):
    global  useFilter
    
    if args [0] == '\033' or args [0] == 'q' or args [0] == 'Q':
        sys.exit ()
    
    if args [0] == 'f' or args [0] == 'F':
        useFilter = not useFilter

def specialKey ( key, x, y ):
    global  rot
    
    if key == GLUT_KEY_LEFT:
        rot -= 5.0
    elif key == GLUT_KEY_RIGHT:
        rot += 5.0
    else:
        return
    
    glutPostRedisplay()

def animate ():
    global angle, program
    angle  = 0.004 * glutGet ( GLUT_ELAPSED_TIME )
    
    glutPostRedisplay ()

def renderToBuffer ():
    global buffer
    glBindTexture ( GL_TEXTURE_2D, 0 )
    
    buffer.bind ()
    
    glClearColor ( 0, 0, 1, 1 )
    glClear      ( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT )
    
    reshape ( buffer.width, buffer.height )
    
    displayBoxes ()
    
    buffer.unbind ()

def main ():
    global decalMap, stoneMap, teapotMap, screenMap, program, buffer
    
    glutInit               ( sys.argv )
    glutInitDisplayMode    ( GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH )
    glutInitWindowSize     ( 512, 512 )
    glutInitWindowPosition ( 0, 0 )
    
    glutCreateWindow ( "PyOpenGL sepia example" )
    glutDisplayFunc  ( display    )
    glutIdleFunc     ( animate    )
    glutReshapeFunc  ( reshape    )
    glutKeyboardFunc ( keyPressed )
    glutSpecialFunc  ( specialKey )
    init             ()
    initExtensions   ()
    
    vSource = open ( "sepia.vsh" ).read ()
    fSource = open ( "sepia.fsh" ).read ()
    program = GlslProgram.GlslProgram ()
    program.loadShaders ( vSource, fSource );
    
    decalMap  = loadTexture ( "../../Textures/oak.bmp" )
    stoneMap  = loadTexture ( "../../Textures/block.bmp" )
    teapotMap = loadTexture ( "../../Textures/Oxidated.jpg" )
    
    buffer    = FrameBuffer.FrameBuffer ( 512, 512, 4 )
    screenMap = buffer.createColorTexture ();
    buffer.create ()
    buffer.bind   ()
    buffer.attachColorTexture ( GL_TEXTURE_2D, screenMap )

    program.bind       ()
    program.setTexture ( "mainTex", 0 )
    program.unbind     ()
    
    glutMainLoop()

print "Hit ESC key to quit."
main()      

По этой ссылке можно скачать весь исходный код к этой статье. Код является кроссплатформенным и расширения компилируются под Linux и Mac OS X.

Valid HTML 4.01 Transitional

Напиши мне
Используются технологии uCoz