Расширение ARB_separate_shader_objects и его использование

Графический конвейер для современных GPU состоит из целого ряда шагов, реализуемых в виде шейдеров на GLSL (вершинный, фрагментный, геометрический, тесселляционные шейдеры).

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

Такой подход предоставляет ряд удобств - все стадии собраны вместе в один объект, осуществляется проверка их соответствия друг другу, все стадии внутри программы разделяют общий набор uniform-переменных.

Однако этому подходу присущи также и свои недостатки - уже нельзя свободно группировать шейдеры разных шагов конвейера (как это было возможно для расширений ARB_vertex_program и ARB_fragment_program). Например нельзя использовать один и тот же вершинный шейдер в двух разных программах с различными фрагментными шейдерами - мы будем вынуждены дублировать вершинный шейдер.

Расширение ARB_separare_shader_objects (вошедшее в состав OpenGL 4.1) вводит новый тип программного объекта - program pipeline. Данный объект является набором программ, реализующих отдельные стадии конвейера и может использоваться вместо традиционной программы. Этот объект легко собирается из программ, реализующих отдельные стадии конвейера.

Однако подобная возможность несет с себе ряд важных вопросов, которые должны быть корректно решены. Во-первых, в данном расширении за каждую стадию отвечает отдельный программный объект, состоящий из шейдера для всего одной стадии конвейера (также возможно создать pipeline object и из полноценной программы со всеми стадиями).

Понятно, что каждая составляющая его программа не может быть успешно слинкована и выбрана (bind) в рамках стандартной работа с шейдерами (так состоит всего из одной стадии). Поэтому в OpenGL был введен специальный тип программы - separate. Программы данного типа могут состоять всего лишь из одного шейдера, соответствующего одной стадии конвейера рендеринга, и именно такие программы и используются для создания pipeline-объектов.

Для создания таких программ расширение вводит новую функцию glCreateShaderProgramv:

GLuint glCreateShaderProgramv ( GLenum type, GLsizei count, const char ** strings );

По стандарту вызов этой функции эквивалентен следующему фрагменту кода.

const uint shader = glCreateShader( type );

if ( shader ) 
{
    glShaderSource  ( shader, count, strings, NULL );
    glCompileShader ( shader );

    const uint program = glCreateProgram ();
	
    if ( program ) 
    {
        int compiled = FALSE;
		
        glGetShaderiv       ( shader, COMPILE_STATUS, &compiled );
        glProgramParameteri ( program, PROGRAM_SEPARABLE, TRUE );

        if ( compiled ) 
        {
            glAttachShader ( program, shader );
            glLinkProgram  ( program );
            glDetachShader ( program, shader );
        }
        append-shader-info-log-to-program-info-log
    }
    glDeleteShader ( shader );
	
    return program;
}
else
    return 0;

Следующим моментом, который должен быть решен, является способ задания значений uniform-переменных без явного выбора (bind) соответствующей программы. Для решения этой задачи был использован подход из расширения EXT_direct_state_access и введены следующие функции, позволяющие явно устанавливать значения uniform-переменных без их явного выбора (bind'а).

void glProgramUniform1i ( GLuint program, int location, int x );
void glProgramUniform2i ( GLuint program, int location, int x,  int y );
void glProgramUniform3i ( GLuint program, int location, int x,  int y, int z );
void glProgramUniform4i ( GLuint program, int location, int x,  int y, int z, int w );

void glProgramUniform1ui ( GLuint program, int location, GLuint x );
void glProgramUniform2ui ( GLuint program, int location, GLuint x, GLuint y );
void glProgramUniform3ui ( GLuint program, int location, GLuint x, GLuint y, GLuint z );
void glProgramUniform4ui ( GLuint program, int location, GLuint x, GLuint y, GLuint z, GLuint w );

void glProgramUniform1f ( GLuint program, int location, float x );
void glProgramUniform2f ( GLuint program, int location, float x, float y );
void glProgramUniform3f ( GLuint program, int location, float x, float y, float z );
void glProgramUniform4f ( GLuint program, int location, float x, float y, float z, float w );

void glProgramUniform1d ( GLuint program, int location, double x );
void glProgramUniform2d ( GLuint program, int location, double x, double y );
void glProgramUniform3d ( GLuint program, int location, double x, double y, double z );
void glProgramUniform4d ( GLuint program, int location, double x, double y, double z, double w );

void glProgramUniform1iv ( GLuint program, int location, GLsizei count, const int * value );
void glProgramUniform2iv ( GLuint program, int location, GLsizei count, const int * value );
void glProgramUniform3iv ( GLuint program, int location, GLsizei count, const int * value );
void glProgramUniform4iv ( GLuint program, int location, GLsizei count, const int * value );

void glProgramUniform1uiv ( GLuint program, int location, GLsizei count, const GLuint * value );
void glProgramUniform2uiv ( GLuint program, int location, GLsizei count, const GLuint * value );
void glProgramUniform3uiv ( GLuint program, int location, GLsizei count, const GLuint * value );
void glProgramUniform4uiv ( GLuint program, int location, GLsizei count, const GLuint * value );

void glProgramUniform1fv ( GLuint program, int location, GLsizei count, const float * value );
void glProgramUniform2fv ( GLuint program, int location, GLsizei count, const float * value );
void glProgramUniform3fv ( GLuint program, int location, GLsizei count, const float * value );
void glProgramUniform4fv ( GLuint program, int location, GLsizei count, const float * value );

void glProgramUniform1dv ( GLuint program, int location, GLsizei count, const double * value );
void glProgramUniform2dv ( GLuint program, int location, GLsizei count, const double * value );
void glProgramUniform3dv ( GLuint program, int location, GLsizei count, const double * value );
void glProgramUniform4dv ( GLuint program, int location, GLsizei count, const double * value );

void glProgramUniformMatrix2fv ( GLuint program, int location,  GLsizei count, 
                                 GLboolean transpose, const float * value );
void glProgramUniformMatrix3fv ( GLuint program, int location, GLsizei count, 
                                 GLboolean transpose, const float * value );
void glProgramUniformMatrix4fv ( GLuint program, int location, GLsizei count, 
                                 GLboolean transpose, const float * value );

void glProgramUniformMatrix2dv ( GLuint program, int location, GLsizei count, 
                                 GLboolean transpose, const double * value );
void glProgramUniformMatrix3dv ( GLuint program, int location, GLsizei count, 
                                 GLboolean transpose, const double * value );
void glProgramUniformMatrix4dv ( GLuint program, int location, GLsizei count, 
                                 GLboolean transpose, const double * value );

void glProgramUniformMatrix2x3fv ( GLuint program, int location, GLsizei count, 
                                   GLboolean transpose, const float * value );
void glProgramUniformMatrix3x2fv ( GLuint program, int location, GLsizei count, 
                                   GLboolean transpose, const float * value );
void glProgramUniformMatrix2x4fv ( GLuint program, int location, GLsizei count, 
                                   GLboolean transpose, const float * value );
void glProgramUniformMatrix4x2fv ( GLuint program, int location, GLsizei count, 
                                   GLboolean transpose, const float * value );
void glProgramUniformMatrix3x4fv ( GLuint program, int location, GLsizei count, 
                                   GLboolean transpose, const float * value );
void glProgramUniformMatrix4x3fv ( GLuint program, int location, GLsizei count, 
                                   GLboolean transpose, const float * value );
void glProgramUniformMatrix2x3dv ( GLuint program, int location, GLsizei count, 
                                   GLboolean transpose, const double * value );
void glProgramUniformMatrix3x2dv ( GLuint program, int location, GLsizei count, 
                                   GLboolean transpose, const double * value );
void glProgramUniformMatrix2x4dv ( GLuint program, int location, GLsizei count, 
                                   GLboolean transpose, const double * value );
void glProgramUniformMatrix4x2dv ( GLuint program, int location, GLsizei count, 
                                   GLboolean transpose, const double * value );
void glProgramUniformMatrix3x4dv ( GLuint program, int location, GLsizei count, 
                                   GLboolean transpose, const double * value );
void glProgramUniformMatrix4x3dv ( GLuint program, int location, GLsizei count, 
                                   GLboolean transpose, const double * value );

Еще одним (и очень важным) моментом является взаимодействие между собой различных стадий, а точнее - передача данных от шейдера одной стадии к шейдеру следующей стадии. Теперь мы уже не можем на этапе линковки проверить соответствие входных данных одного шейдера выходным данным другого.

Для решения этой задачи были введены специальные правила соответствия. Обычные входные и выходные переменные должны совпадать по имени, типу и дополнительным описателям. Для блоков важно не только совпадение всех переменных по именам, типам и описателям, но и совпадение имен блоков и порядка переменных внутри блока.

Альтернативным вариантом для задания соответствия между входными переменными одного шейдера и выходными другого является использование в шейдера описателя location, сопоставляющего каждой переменной свое положение (номер). В этом случае соответствие имен (и типов) уже не важно. Ниже приводится пример такого описания как для входных, так и для выходных значений.

layout(location = 0) in  vec4  pos;  
layout(location = 1) in  vec3  normal;

layout(location = 0) out vec3  n;
layout(location = 1) out vec3  v;
layout(location = 2) out vec3  l;
layout(location = 3) out vec3  h;

Работа с новым типом объекта - program pipeline - осуществляется аналогично работ6е с другими типами объектов в OpenGL. Каждый такой объект идентифицируется беззнаковым целым числом, отличным от нуля. Для выделения таких чисел (идентификаторов) и их освобождения служат функции glGenProgramPipelines и glDeleteProgramPipelines. Для проверки того, является ли заданное число идентификатором какого-либо объекта program pipeline служит функция glIsProgramPipeline.

void      glGenProgramPipelines    ( GLsizei n, GLuint * pipelines );
void      glDeleteProgramPipelines ( GLsizei n, const GLuint * pipelines );
GLboolean glIsProgramPipeline      ( GLuint pipeline );

Выбрать объект (и собственно создать его при первом выборе) можно при помощи команды glBindProgramPipeline.

void glBindProgramPipeline ( GLuint pipeline );

Данная команда создает соответствующий pipeline-объект, если он еще не был создан, и делает его текущим. Если в качестве идентификатора был передан ноль, то мы переходим в режим, когда нет установленного pipeline-объекта.

Сам объект состоит из набора программ, соответствующих различным типам шейдеров (GL_VERTEX_SHADER, GL_FRAGMENT_SHADER, GL_GEOMETRY_SHADER, GL_TESS_CONTROL_SHADER и GL_TESS_EVALUATION_SHADER), а также активной программы (GL_ACTIVE_PROGRAM).

Для того, чтобы подключить программу (возможно содержащую сразу несколько стадий конвейера) к заданному pipeline-объекту служит команда glUseProgramStages.

void glUseProgramStages ( GLuint pipeline, GLbitfield stages, GLuint program );

Здесь параметр pipeline является идентификатором соответствующего pipeline-объекта, параметр stages определяет стадии конвейера pipeline-объекта для которых с идентификатором program становится текущей. Параметр stages является набором битов, соответствующих различным стадиям конвейера. Для задания соответствующих стадий используются константы GL_VERTEX_SHADER_BIT, GL_FRAGMENT_SHADER_BIT, GL_GEOMETRY_SHADER_BIT, GL_TESS_CONTROL_SHADER_BIT и GL_TESS_EVALUATION_SHADER_BIT. Также для этого параметра можно использовать значения GL_ALL_SHADER_BITS, говорящее о том, что данная программа должны стать текущей для всех стадий конвейера.

Если программа, соответствующая идентификатору program, не имеет атрибута GL_PROGRAM_SEPARATE или же не была успешно слинкована, то возникает ошибка GL_INVALID_OPERATION и состояние pipeline-объекта не изменяется.

Также для pipeline-объекта можно задать активную программу (GL_ACTIVE_PROGRAM) при помощи следующей команды:

void glActiveShaderProgram ( GLuint pipeline, GLuint program );

Активная программа в pipeline-объекте - это та программа, к которой будут относиться традиционные команды для работы с uniform-переменными (т.е. использующие "привязку" программы вместо явного задания идентификатора программы). Однако гораздо удобнее (и правильнее) использовать для работы с uniform-переменными функции, вводимые данным расширением.

Поскольку при сборке pipeline-объекта из отдельных компонент могут возникать проблемы (например, отсутствие необходимых стадий или несоответствии по входным и выходным значениям), то для проверки pipeline-объекта можно использовать функцию glValidateProgramPipeline.

void glValidateProgramPipeline ( GLuint pipeline );

Данная команда проверяет соответствующий объект и по результатам проверки выставляет атрибут с именем GL_VALIDATE_STATUS.

Для доступа к свойствам (атрибутам) pipeline-объекта служит функция glGetProgramPipelineiv.

void glGetProgramPipelineiv ( GLuint pipeline, GLenum pname, int * params );

Параметр pname задает свойство (атрибут), значение которого будет возвращено через параметр params. В следующей таблице приведены допустимые значения для этого параметра.

Атрибут Значение
GL_ACTIVE_PROGRAM Идентификатор активной программы
GL_VERTEX_SHADER Идентификатор программы, содержащей используемый вершинный шейдер
GL_FRAGMENT_SHADER Идентификатор программы, содержащей используемый фрагментный шейдер
GL_GEOMETRY_SHADER Идентификатор программы, содержащей используемый геометрический шейдер
GL_TESS_CONTROL_SHADER Идентификатор программы, содержащей используемый tessellation control шейдер
GL_TESS_EVALUATION_SHADER Идентификатор программы, содержащей используемый tessellation evaluation шейдер
GL_INFO_LOG_LENGTH Размер лога (включая нулевой байт в конце) в байтах
GL_VALIDATE_STATUS Результат проверки объекта

Для получения лога, соответствующего данному pipeline-объекту служит команда glGetProgramPipelineInfoLog

void glGetProgramPipelineInfoLog ( GLuint pipeline, GLsizei bufSize, GLsizei * length, char * infoLog );

Данная команда копирует содержимое лога в виде ASCIIZ-строки в буфер, задаваемый параметром infoLog. Размер буфера передается через параметр bufSize, он определяет максимальное число символов, включая завершающий нулевой байт, которое будет скопировано в буфер. Через параметр length возвращается число скопированных в буфер символов (не считая нулевой байт в конце).

Фактически данное расширение позволяет иметь набор программ, реализующих отдельные стадии конвейера, и из них собирать на ходу готовую программу, которая и будет использована при рендеринге. При этом каждая используемая программа загружается/компилируется/хранится всего один раз.

Для удобства работы с этим расширением "завернем" pipeline-объект в соответствующий класс С++. На следующем листинге приводится описание данного класса.

class   Pipeline
{
    GLuint  id;

    Pipeline ( const Pipeline& );
    Pipeline& operator = ( const Pipeline& );
public:

    Pipeline  ();
    ~Pipeline ();

    GLuint  getId () const
    {
        return id;
    }
    
    bool    isOk () const
    {
        return id != 0;
    }

    string getLog () const;

    bool    create ();
    bool    bind   ();
    bool    unbind ();

    bool    setStage ( GLbitfield mask, Program * );
    bool    setActiveProgram ( Program * );
    bool    validate ();

    static int active ();       // id of active pipeline object
};

Ниже приводится реализация этого класса.

Pipeline :: Pipeline ()
{
    id = 0;
}

Pipeline :: ~Pipeline ()
{
    if ( id != 0 )
        glDeleteProgramPipelines ( 1, &id );
}

bool    Pipeline :: create ()
{
    glGenProgramPipelines ( 1, &id );
    
    return id != 0;
}

bool    Pipeline :: bind ()
{
    if ( id == 0 )
        return false;
        
    glBindProgramPipeline ( id );
    
    return true;
}

bool    Pipeline :: unbind ()
{
    glBindProgramPipeline ( 0 );
    
    return true;
}

bool    Pipeline :: setStage ( GLbitfield mask, Program * program )
{
    if ( id == 0 || program == NULL || !program -> isOk () )
        return false;
        
    glUseProgramStages ( id, mask, program -> getProgram () );
    
    return true;
}

bool    Pipeline :: setActiveProgram ( Program * program )
{
    if ( id == 0 || program == NULL || !program -> isOk () )
        return false;
        
    glActiveShaderProgram ( id, program -> getProgram () );
    
    return true;
}

bool    Pipeline :: validate ()
{
    GLint   status;
    
    glValidateProgramPipeline ( id );
    glGetProgramPipelineiv    ( id, GL_VALIDATE_STATUS, &status );
    
    return status != 0;
}

string  Pipeline :: getLog () const
{
    int     len;
    string  log;
    
    glGetProgramPipelineiv ( id, GL_INFO_LOG_LENGTH, &len );
    
    if ( len < 1 )
        return "";
        
    char * buf = (char *) malloc ( len + 1 );
    
    glGetProgramPipelineInfoLog ( id, len+1, NULL, buf );
    
    log = buf;
    
    free ( buf );
    
    return log;
}

int     Pipeline :: active ()       // id of active pipeline object
{
    int pipeline;
    
    glGetIntegerv ( GL_PROGRAM_PIPELINE_BINDING, &pipeline );
    
    return pipeline;
}

Для поддержки данного расширения также необходимо внести некоторые изменения в класс Program, а именно - поддержку работы с separate-программами.

bool    Program :: loadSeparate ( GLenum type, Data * data )
{
    if ( !data -> isOk () || data -> getLength () < 1 )
    {
        log += "Error loading separate shader\n";
        
        return false;
    }

        // now create an array of zero-terminated strings
        
    const char * body  = (const char *) data -> getPtr ( 0 );
    GLint        len   = data -> getLength ();
    char       * buf   = (char *) malloc ( len + 1 );
    
    memcpy ( buf, body, len );
    buf [len] = '\0';
    
    program      = glCreateShaderProgramv ( type, 1, (const char **)&buf );
    separate     = true;
    linkRequired = false;
    ok           = true;
    
    loadProgramLog ( program );

    free ( buf );
    
    return program != 0;
}

bool    Program :: loadSeparate ( GLenum type, const string& fileName )
{
    Data * data = new Data ( fileName );

    if ( data == NULL )
    {
        log += "Cannot open \"";
        log += fileName;
        log += "\"\n";
        
        return false;
    }

    bool    res = loadSeparate ( type, data );
    
    delete data;
    
    return res;
}

Рассмотрим теперь практическое применение данного расширения. Для этого введем два pipeline-объекта с общим вершинным шейдером и разными фрагментными.

Ниже приводится пример общего вершинного шейдера. В этом примере передача значений в фрагментный шейдер происходит через спецификатор location.

#version 410 core

uniform mat4 proj;
uniform mat4 mv;
uniform mat3 nm;
uniform vec3 eye;
uniform vec3 light;

layout(location = 0) in vec4  pos;
layout(location = 1) in vec3  normal;

layout (location = 0) out vec3  n;
layout (location = 1) out vec3  v;
layout (location = 2) out vec3  l;
layout (location = 3) out vec3  h;

void main(void)
{
    vec4    p = mv * vec4 ( pos.xyz, 1.0 );
    
    gl_Position  = proj * p;
    
    n            = normalize ( nm * normal );
    v            = normalize ( eye - p.xyz );
    l            = normalize ( light - p.xyz );
    h            = normalize ( l + v ); 

}

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

#version 410 core

layout (location = 0) in vec3 n;
layout (location = 1) in vec3 v;
layout (location = 2) in vec3 l;
layout (location = 3) in vec3 h;

out vec4 color;

void main(void)
{

    const vec4  diffColor = vec4 ( 0.5, 0.0, 0.0, 1.0 );
    const vec4  specColor = vec4 ( 0.7, 0.7, 0.0, 1.0 );
    const float specPower = 30.0;

    vec3    n2   = normalize ( n );
    vec3    l2   = normalize ( l );
    vec3    h2   = normalize ( h );
    vec4    diff = diffColor * max ( dot ( n2, l2 ), 0.1 );
    vec4    spec = specColor * pow ( max ( dot ( n2, h2 ), 0.0 ), specPower );

    color = diff + spec;
}

Фрагментный шейдер для модели Гуч приведен на следующем листинге.

#version 410 core

layout (location = 0) in vec3 n;
layout (location = 1) in vec3 v;
layout (location = 2) in vec3 l;
layout (location = 3) in vec3 h;

out vec4 color;

void main(void)
{
    const   vec3  surfaceColor = vec3 ( 0.75, 0.75, 0.75 );
    const   vec3  warmColor    = vec3 ( 0.6, 0.6, 0.0 );
    const   vec3  coolColor    = vec3 ( 0.0, 0.0, 0.6 );
    const   float diffuseWarm  = 0.45;
    const   float diffuseCool  = 0.45;

    vec3    n2    = normalize ( n );
    vec3    l2    = normalize ( l );
    vec3    v2    = normalize ( h );
    vec3    r     = normalize ( reflect ( -l2, n2 ) );
    vec3    cool  = min ( coolColor + diffuseCool * surfaceColor, 1.0 );
    vec3    warm  = min ( warmColor + diffuseWarm * surfaceColor, 1.0 );
    vec3    final = mix ( cool, warm, max(0,dot(n2,l2)));

    float   spec = pow ( max ( dot ( r, v2 ), 0.0 ), 32.0 );

    color = vec4 ( min ( final + spec, 1.0 ), 1.0 );
}

Ниже приводится фрагмент программы, создающей соответствующие pipeline-объекты.

Pipeline    p1, p2;         // create two pipelines
Program     vp, fp1, fp2;   // create one common vertex program and two fragment programs

. . .

 p1.create   ();
 p1.bind     ();

if ( !vp.loadSeparate  ( GL_VERTEX_SHADER, "sep-vp.glsl" ) )
     printf ( "Error loadSeparate vp\n" );
    
p1.setStage ( GL_VERTEX_SHADER_BIT, &vp );

if ( !fp1.loadSeparate ( GL_FRAGMENT_SHADER, "sep-fp1.glsl" ) )
    printf ( "Error loadSeparate fp\n" );
    
p1.setStage ( GL_FRAGMENT_SHADER_BIT, &fp1 );
    
if ( !p1.validate () )
    printf ( "Error validating: %s\n", p1.getLog ().c_str () );

p1.unbind ();   
        
p2.create ();
p2.bind   ();

p2.setStage ( GL_VERTEX_SHADER_BIT, &vp );

if ( !fp2.loadSeparate ( GL_FRAGMENT_SHADER, "sep-fp2.glsl" ) )
    printf ( "Error loadSeparate fp\n" );
    
p2.setStage ( GL_FRAGMENT_SHADER_BIT, &fp2 );
    
if ( !p2.validate () )
    printf ( "Error validating: %s\n", p2.getLog ().c_str () );

p2.unbind ();

Следующий листинг содержит функцию, отвечающую непосредственно за рендеринг.

void display ()
{
    glClear ( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );

    mat4    mv = mat4 :: rotateZ ( toRadians ( rot.z ) ) * mat4 :: rotateX ( toRadians ( rot.x ) ) * mat4 :: rotateY ( toRadians ( rot.y ) );
    mat3    nm = normalMatrix ( mv );
    
    glProgramUniformMatrix4fv ( vp.getProgram (), mvLoc, 1, GL_TRUE, mv.data () );
    glProgramUniformMatrix3fv ( vp.getProgram (), nmLoc, 1, GL_TRUE, nm.data () );

    if ( first )
        p1.bind ();
    else
        p2.bind ();
        
    vao.bind ();

    glDrawElements ( GL_TRIANGLES, 3*mesh.numTris, GL_UNSIGNED_INT, NULL );

    vao.unbind ();
    
    if ( first )
        p1.unbind ();
    else
        p2.unbind ();
        

    glutSwapBuffers ();
}

Ниже приводятся вершинный и фрагментный листинги, не использующие спецификатора location, а полагающиеся на совпадение имен, типов и спецификаторов.

Вершинный шейдер:

#version 410 core

uniform mat4 proj;
uniform mat4 mv;
uniform mat3 nm;
uniform vec3 eye;
uniform vec3 light;

layout(location = 0) in vec4  pos;
layout(location = 1) in vec3  normal;

out vec3  n;
out vec3  v;
out vec3  l;
out vec3  h;

void main(void)
{
    vec4    p = mv * vec4 ( pos.xyz, 1.0 );
    
    gl_Position  = proj * p;
    
    n            = normalize ( nm * normal );
    v            = normalize ( eye - p.xyz );
    l            = normalize ( light - p.xyz );
    h            = normalize ( l + v ); 

}

Один из фрагментных шейдеров:

#version 410 core

in  vec3 n;
in  vec3 v;
in  vec3 l;
in  vec3 h;
out     vec4 color;

void main(void)
{
    const   vec3  surfaceColor = vec3 ( 0.75, 0.75, 0.75 );
    const   vec3  warmColor    = vec3 ( 0.6, 0.6, 0.0 );
    const   vec3  coolColor    = vec3 ( 0.0, 0.0, 0.6 );
    const   float diffuseWarm  = 0.45;
    const   float diffuseCool  = 0.45;

    vec3    n2   = normalize ( n );
    vec3    l2   = normalize ( l );
    vec3    v2   = normalize ( h );
    vec3    r    = normalize ( reflect ( -l2, n2 ) );
    vec3    cool    = min ( coolColor + diffuseCool * surfaceColor, 1.0 );
    vec3    warm    = min ( warmColor + diffuseWarm * surfaceColor, 1.0 );
    vec3    final   = mix ( cool, warm, max(0,dot(n2,l2)));

    float   spec = pow ( max ( dot ( r, v2 ), 0.0 ), 32.0 );

    color = vec4 ( min ( final + spec, 1.0 ), 1.0 );
}

По этой ссылке можно скачать весь исходный код к этой статье. Также доступны для скачивания откомпилированные версии для M$ Windows и Linux.

Обратите внимание, что для сборки примеров нужна последняя версия фреймворка, доступная через svn при помощи команды

svn checkout http://steps-framework.googlecode.com/svn/trunk/ steps-framework-read-only