Поиск…


замечания

В этом разделе представлен обзор того, что такое glsl, и почему разработчик может захотеть его использовать.

Следует также упомянуть о любых крупных предметах в glsl и ссылаться на связанные темы. Поскольку документация для glsl нова, вам может потребоваться создать начальные версии этих связанных тем.

Версии

Язык затенения OpenGL

Версия GLSL Версия OpenGL Препроцессор шейдеров Дата выхода
1,10 2,0 #version 110 2004-09-07
1,20 2,1 #version 120 2006-07-02
1,30 3.0 #version 130 2008-08-11
1,40 3,1 #version 140 2009-03-24
1,50 3,2 #version 150 2009-08-03
3.30 3,3 #version 330 2010-02-12
4,00 4,0 #version 400 2010-03-11
4,10 4,1 #version 410 2010-07-26
4,20 4,2 #version 420 2011-08-08
4,30 4,3 #version 430 2012-08-06
4,40 4,4 #version 440 2013-07-22
4,50 4.5 #version 450 2014-08-11

Язык затенения OpenGL ES

Версия GLSL ES Версия OpenGL ES Препроцессор шейдеров Дата выхода
1.00 ES 2,0 #version 100 2007-03-05
3,00 ES 3.0 #version 300 es 2012-08-06
3.10 ES 3,1 #version 310 es 2014-03-17
3.20 ES 3,2 #version 320 es 2015-08-10

Установка или настройка

Подробные инструкции по установке или установке glsl.

Первая шейдерная программа OGL 4.0 GLSL

Простая программа шейдеров OGL 4.0 GLSL с атрибутом вершин и атрибутом цвета. Программа выполняется с помощью phyton-скрипта. Чтобы запустить скрипт, необходимо установить PyOpenGL.

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

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

first.vet

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

В нашем примере мы рисуем один треугольник, поэтому вершинный шейдер выполняется 3 раза, один раз для каждой угловой точки треугольника. В этом случае вход в вершинный шейдер является вершинной позицией in vec3 inPos и атрибутом цвета in vec3 inCol . Атрибуты цвета передаются на следующую стадию шейдера ( out vec3 vertCol ).

#version 400

layout (location = 0) in vec3 inPos;
layout (location = 1) in vec3 inCol;

out vec3 vertCol;

void main()
{
    vertCol = inCol;
    gl_Position = vec4( inPos, 1.0 );
}

Фрагментный шейдер

first.frag

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

#version 400

in vec3 vertCol;

out vec4 fragColor;

void main()
{
    fragColor = vec4( vertCol, 1.0 );
}

Скрипт Phyton

Сценарий python - это просто компилировать, связывать и выполнять программу шейдеров и рисовать геометрию. Его можно было бы тривиально переписать на C или что-нибудь еще. Это не та часть этой документации, которой нужно уделять наибольшее внимание.

from OpenGL.GL import *
from OpenGL.GLUT import *
from OpenGL.GLU import *
from sys import *
from array import array
             
# draw event
def OnDraw(): 
    glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT )
    glUseProgram( shaderProgram )
    glBindVertexArray( vaObj )
    glDrawArrays( GL_TRIANGLES, 0, 3 )
    glutSwapBuffers()

# read vertex shader program
with open( 'first.vert', 'r' ) as vertFile:
    vertCode = vertFile.read()
print( '\nvertex shader code:' )
print( vertCode )

# read fragment shader program
with open( 'first.frag', 'r' ) as fragFile:
    fragCode = fragFile.read()
print( '\nfragment shader code:' )
print( fragCode )

# initialize glut
glutInit()

# create window
wndW = 800
wndH = 600
glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_ALPHA | GLUT_DEPTH)
glutInitWindowPosition(0, 0)
glutInitWindowSize(wndW, wndH)
wndID = glutCreateWindow(b'OGL window') 
glutDisplayFunc(OnDraw) 
glutIdleFunc(OnDraw)

# define triangle data
posData = [ -0.636, -0.45, 0.0, 0.636, -0.45, 0.0, 0.0, 0.9, 0.0 ]
colData = [ 1.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 0.0, 1.0 ]
posAr = array( "f", posData )
colAr = array( "f", colData )

# create buffers
posBuffer = glGenBuffers(1)
glBindBuffer( GL_ARRAY_BUFFER, posBuffer )
glBufferData( GL_ARRAY_BUFFER, posAr.tostring(), GL_STATIC_DRAW )
colBuffer = glGenBuffers(1)
glBindBuffer( GL_ARRAY_BUFFER, colBuffer )
glBufferData( GL_ARRAY_BUFFER, colAr.tostring(), GL_STATIC_DRAW )

# create vertex array opject
vaObj = glGenVertexArrays( 1 )
glBindVertexArray( vaObj )
glEnableVertexAttribArray( 0 )
glEnableVertexAttribArray( 1 )
glBindBuffer( GL_ARRAY_BUFFER, posBuffer )
glVertexAttribPointer( 0, 3, GL_FLOAT, GL_FALSE, 0, None )
glBindBuffer( GL_ARRAY_BUFFER, colBuffer )
glVertexAttribPointer( 1, 3, GL_FLOAT, GL_FALSE, 0, None )

# compile vertex shader
vertShader = glCreateShader( GL_VERTEX_SHADER )
glShaderSource( vertShader, vertCode )
glCompileShader( vertShader )
result = glGetShaderiv( vertShader, GL_COMPILE_STATUS )
if not (result):
    print( glGetShaderInfoLog( vertShader ) )
    sys.exit()

# compile fragment shader
fragShader = glCreateShader( GL_FRAGMENT_SHADER )
glShaderSource( fragShader, fragCode )
glCompileShader( fragShader )
result = glGetShaderiv( fragShader, GL_COMPILE_STATUS )
if not (result):
    print( glGetShaderInfoLog( fragShader ) )
    sys.exit()

# link shader program
shaderProgram = glCreateProgram()
glAttachShader( shaderProgram, vertShader )
glAttachShader( shaderProgram, fragShader )
glLinkProgram( shaderProgram )
result = glGetProgramiv( shaderProgram, GL_LINK_STATUS )
if not (result):
    print( 'link error:' )
    print( glGetProgramInfoLog( shaderProgram ) )
    sys.exit()

# start main loop
glutMainLoop()

Использование матрицы Model, View и Projection в OGL 4.0 GLSL

Простая программа шейдеров OGL 4.0 GLSL, которая показывает использование матрицы модели, представления и проекции. Программа выполняется с помощью phyton-скрипта. Для запуска скрипта необходимо установить PyOpenGL и NumPy.

  • Матрица проецирования: матрица проекции описывает отображение камеры-обскуры из трехмерных точек в мире на двумерные точки окна просмотра. В этом примере мы используем проекционную матрицу с полем обзора 90 градусов.

  • Матрица просмотра: матрица вида определяет положение глаза и направление просмотра на сцене. В этом примере мы движемся вокруг сцены, сохраняя направление просмотра в центр сцены.

  • Модельная матрица: матрица модели определяет местоположение и относительный размер объекта в сцене. В этом примере модельные матрицы перемещают объекты вверх и вниз.

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

mvp.vet

#version 400

layout (location = 0) in vec3 inPos;
layout (location = 1) in vec3 inCol;

out vec3 vertCol;

uniform mat4 projectionMat44;
uniform mat4 viewMat44;
uniform mat4 modelMat44;

void main()
{
    vertCol = inCol;
    vec4 modolPos = modelMat44 * vec4( inPos, 1.0 );
    vec4 viewPos = viewMat44 * modolPos;
    gl_Position = projectionMat44 * viewPos;
}

Фрагментный шейдер

mvp.frag

#version 400

in vec3 vertCol;

out vec4 fragColor;

void main()
{
    fragColor = vec4( vertCol, 1.0 );
}

Скрипт Phyton

from OpenGL.GL import *
from OpenGL.GLUT import *
from OpenGL.GLU import *
import numpy as np
from time import time
import math
import sys

# draw event
def OnDraw():
    currentTime = time()
    # set up projection matrix
    prjMat = perspective( 90.0, wndW/wndH, 0.5, 100.0) 
    # set up view matrix
    viewMat = Translate( np.matrix(np.identity(4), copy=False, dtype='float32'), np.array( [0.0, 0.0, -8.0] ) )
    viewMat = RotateView( viewMat, [10.0, CalcAng( currentTime, 10.0 ), 0.0] )
    
    # set up tetrahedron model matrix
    tetModelMat = np.matrix(np.identity(4), copy=False, dtype='float32')
    tetModelMat = RotateX( tetModelMat, -90.0 )
    tetModelMat = Scale( tetModelMat, np.repeat( 2.0, 3 ) )
    tetModelMat = Translate( tetModelMat, np.array( [-2.0, 0.0, CalcMove(currentTime, 6.0, [-1.0, 1.0])] ) )

    # set up icosahedron model matrix
    icoModelMat = np.matrix(np.identity(4), copy=False, dtype='float32')
    icoModelMat = RotateX( icoModelMat, -90.0 )
    icoModelMat = Scale( icoModelMat, np.repeat( 2.0, 3 ) )
    icoModelMat = Translate( icoModelMat, np.array( [2.0, 0.0, CalcMove(currentTime, 6.0, [1.0, -1.0])] ) )
 
    # set up attributes and shader program
    glEnable( GL_DEPTH_TEST )
    glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT )
    glUseProgram( shaderProgram )
    glUniformMatrix4fv( projectionMatLocation, 1, GL_FALSE, prjMat )
    glUniformMatrix4fv( viewMatLocation, 1, GL_FALSE, viewMat )
    
    # draw tetrahedron
    glUniformMatrix4fv( modelMatLocation, 1, GL_FALSE, tetModelMat )
    glBindVertexArray( tetVAObj )
    glDrawElements(GL_TRIANGLES, len(tetIndices), GL_UNSIGNED_INT, tetIndices)

    # draw tetrahedron
    glUniformMatrix4fv( modelMatLocation, 1, GL_FALSE, icoModelMat )
    glBindVertexArray( icoVAObj )
    glDrawArrays( GL_TRIANGLES, 0, len(icoPosData) )
    
    glutSwapBuffers()

def Fract(val): return val - math.trunc(val)
def CalcAng(currentTime, intervall): return Fract( (currentTime - startTime) / intervall ) * 360.0
def CalcMove(currentTime, intervall, range):
    pos = Fract( (currentTime - startTime) / intervall ) * 2.0
    pos = pos if pos < 1.0 else (2.0-pos)
    return range[0] + (range[1] - range[0]) * pos
       
# read shader program and compile shader
def CompileShader( sourceFileName, shaderStage ):
    with open( sourceFileName, 'r' ) as sourceFile:
        sourceCode = sourceFile.read()
    nameMap = { GL_VERTEX_SHADER: 'vertex', GL_FRAGMENT_SHADER: 'fragment' }    
    print( '\n%s shader code:' % nameMap.get(shaderStage, '') )
    print( sourceCode )
    shaderObj = glCreateShader( shaderStage )
    glShaderSource( shaderObj, sourceCode )
    glCompileShader( shaderObj )
    result = glGetShaderiv( shaderObj, GL_COMPILE_STATUS )
    if not (result):
        print( glGetShaderInfoLog( shaderObj ) )
        sys.exit()
    return shaderObj

# linke shader objects to shader program
def LinkProgram( shaderObjs ):
    shaderProgram = glCreateProgram()
    for shObj in shaderObjs:
        glAttachShader( shaderProgram, shObj )
    glLinkProgram( shaderProgram )
    result = glGetProgramiv( shaderProgram, GL_LINK_STATUS )
    if not (result):
        print( 'link error:' )
        print( glGetProgramInfoLog( shaderProgram ) )
        sys.exit()
    return shaderProgram

# create vertex array opject
def CreateVAO( dataArrays ):
    noOfBuffers = len(dataArrays)
    buffers = glGenBuffers(noOfBuffers)
    newVAObj = glGenVertexArrays( 1 )
    glBindVertexArray( newVAObj )
    for inx in range(0, noOfBuffers):
        vertexSize, dataArr = dataArrays[inx]
        arr = np.array( dataArr, dtype='float32' )
        glBindBuffer( GL_ARRAY_BUFFER, buffers[inx] )
        glBufferData( GL_ARRAY_BUFFER, arr, GL_STATIC_DRAW )
        glEnableVertexAttribArray( inx )
        glVertexAttribPointer( inx, vertexSize, GL_FLOAT, GL_FALSE, 0, None )
    return newVAObj 

def Translate(matA, trans):
    matB = np.copy(matA)
    for i in range(0, 4): matB[3,i] = matA[0,i] * trans[0] + matA[1,i] * trans[1] + matA[2,i] * trans[2] + matA[3,i] 
    return matB

def Scale(matA, s):
    matB = np.copy(matA)
    for i0 in range(0, 3):
        for i1 in range(0, 4): matB[i0,i1] = matA[i0,i1] * s[i0] 
    return matB

def RotateHlp(matA, angDeg, a0, a1):
    matB = np.copy(matA)
    ang = math.radians(angDeg)
    sinAng, cosAng = math.sin(ang), math.cos(ang)
    for i in range(0, 4):
        matB[a0,i] = matA[a0,i] * cosAng + matA[a1,i] * sinAng
        matB[a1,i] = matA[a0,i] * -sinAng + matA[a1,i] * cosAng
    return matB

def RotateX(matA, angDeg): return RotateHlp(matA, angDeg, 1, 2)
def RotateY(matA, angDeg): return RotateHlp(matA, angDeg, 2, 0)
def RotateZ(matA, angDeg): return RotateHlp(matA, angDeg, 0, 1)
def RotateView(matA, angDeg): return RotateZ(RotateY(RotateX(matA, angDeg[0]), angDeg[1]), angDeg[2])

def perspective(fov, aspectRatio, near, far):
    fn, f_n = far + near, far - near
    r, t = aspectRatio, 1.0 / math.tan( math.radians(fov) / 2.0 )
    return np.matrix( [ [t/r,0,0,0], [0,t,0,0], [0,0,-fn/f_n,-2.0*far*near/f_n], [0,0,-1,0] ] )

# initialize glut
glutInit()

# create window
wndW, wndH = 800, 600
glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_ALPHA | GLUT_DEPTH)
glutInitWindowPosition(0, 0)
glutInitWindowSize(wndW, wndH)
wndID = glutCreateWindow(b'OGL window') 
glutDisplayFunc(OnDraw) 
glutIdleFunc(OnDraw)

# define tetrahedron vertex array opject
sin120 = 0.8660254
tetPposData = [ 0.0, 0.0, 1.0, 0.0, -sin120, -0.5, sin120 * sin120, 0.5 * sin120, -0.5, -sin120 * sin120, 0.5 * sin120,     -0.5 ]
tetColData = [ 1.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 1.0, 0.0, ]
tetIndices = [ 0, 1, 2, 0, 2, 3, 0, 3, 1, 1, 3, 2 ]
tetVAObj = CreateVAO( [ (3, tetPposData), (3, tetColData) ] )
tetInxArr = np.array( tetIndices, dtype='uint' )

# define icosahedron vertex array opject
icoPts = [
    [ 0.000,  0.000,  1.000], [ 0.894,  0.000,  0.447], [ 0.276,  0.851,  0.447], [-0.724,  0.526,  0.447],
    [-0.724, -0.526,  0.447], [ 0.276, -0.851,  0.447], [ 0.724,  0.526, -0.447], [-0.276,  0.851, -0.447], 
    [-0.894,  0.000, -0.447], [-0.276, -0.851, -0.447], [ 0.724, -0.526, -0.447], [ 0.000,  0.000, -1.000] ]
icoCol = [ [1.0, 0.0, 0.0], [0.0, 0.0, 1.0], [1.0, 1.0, 0.0], [0.0, 1.0, 0.0], [1.0, 0.5, 0.0], [1.0, 0.0, 1.0] ]
icoIndices = [
  2,  0,  1,  3,  0,  2,  4,  0,  3,  5,  0,  4,  1,  0,  5, 11,  7,  6, 11,  8,  7, 11,  9,  8, 11, 10,  9, 11,  6, 10, 
  1,  6,  2,  2,  7,  3,  3,  8,  4,  4,  9,  5,  5, 10,  1,  2,  6,  7,  3,  7,  8,  4,  8,  9,  5,  9, 10,  1, 10,  6  ]
icoPosData = []
for inx in icoIndices:
    for inx_s in range(0, 3):
        icoPosData.append( icoPts[inx][inx_s] )
icoColData = []
for inx in range(0, len(icoPosData) // 9):
    inx_col = inx % len(icoCol)
    for inx_p in range(0, 3):
        for inx_s in range(0, 3):
                icoColData.append( icoCol[inx_col][inx_s] )
icoVAObj = CreateVAO( [ (3, icoPosData), (3, icoColData) ] )

# load, compile and link shader
shaderProgram = LinkProgram( [
        CompileShader( 'mvp.vert', GL_VERTEX_SHADER ), 
        CompileShader( 'mvp.frag', GL_FRAGMENT_SHADER )
    ] )
projectionMatLocation = glGetUniformLocation(shaderProgram, "projectionMat44")
viewMatLocation = glGetUniformLocation(shaderProgram, "viewMat44")
modelMatLocation = glGetUniformLocation(shaderProgram, "modelMat44")

# start main loop
startTime = time()
glutMainLoop()

Поместите текстуру на модель и используйте текстурную матрицу в OGL 4.0 GLSL

Простая программа OGL 4.0 GLSL shader, которая показывает, как сопоставить 2D-текстуру на сетке. Программа выполняется с помощью phyton-скрипта. Для запуска скрипта необходимо установить PyOpenGL и NumPy.

Матрица текстуры определяет, как текстура отображается на сетке. Управляя текстурной матрицей, текстуру можно смещать, масштабировать и поворачивать.

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

tex.vert

#version 400

layout (location = 0) in vec3 inPos;
layout (location = 1) in vec2 inTex;

out vec2 vertTex;

uniform mat4 u_projectionMat44;
uniform mat4 u_viewMat44;
uniform mat4 u_modelMat44;
uniform mat4 u_textureMat44;

void main()
{
    vertTex = ( u_textureMat44 * vec4( inTex, 0.0, 1.0 ) ).st;
    vec4 modolPos = u_modelMat44 * vec4( inPos, 1.0 );
    vec4 viewPos = u_viewMat44 * modolPos;
    gl_Position = u_projectionMat44 * viewPos;
}

Фрагментный шейдер

tex.frag

#version 400

in vec2 vertTex;

out vec4 fragColor;

uniform sampler2D u_texture;

void main()
{
    vec4 texCol = texture( u_texture, vertTex.st );
    fragColor = vec4( texCol.rgb, 1.0 );
}

Скрипт Phyton

from OpenGL.GL import *
from OpenGL.GLUT import *
from OpenGL.GLU import *
import numpy as np
from time import time
import math
import sys

# draw event
def OnDraw():
    currentTime = time()
    # set up projection matrix
    prjMat = perspective( 90.0, wndW/wndH, 0.5, 100.0) 
    # set up view matrix
    viewMat = Translate( np.matrix(np.identity(4), copy=False, dtype='float32'), np.array( [0.0, 0.0, -15.0] ) )
    viewMat = RotateView( viewMat, [30.0, CalcAng( currentTime, 60.0 ), 0.0] )
    
    # set up tetrahedron model matrix
    cubeModelMat = np.matrix(np.identity(4), copy=False, dtype='float32')
    cubeModelMat = RotateX( cubeModelMat, -90.0 )
    cubeModelMat = Scale( cubeModelMat, np.repeat( 5.0, 3 ) )
    
    # set up texture matrix
    texMat = np.matrix(np.identity(4), copy=False, dtype='float32')
    deltaT = Fract( (currentTime - startTime) / 28.0 ) * 28.0
    if deltaT < 7.0 or deltaT >= 21.0:
        texMat = Scale( texMat, np.repeat( CalcMove(currentTime, 7.0, [1.0, 2.0]), 3 ) )
    if deltaT >= 7.0 and deltaT < 14.0 or deltaT >= 21.0:
        transAng = math.radians( CalcAng(currentTime, 7.0) )
        texMat = Translate( texMat, np.array( [math.sin(transAng)*0.5, math.cos(transAng)*0.5-0.5, 0.0] ) )
    if deltaT >= 14.0:
        texMat = RotateZ( texMat, CalcAng(currentTime, 7.0) )
    
    # set up attributes and shader program
    glEnable( GL_DEPTH_TEST )
    glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT )
    glUseProgram( shaderProgram )
    glUniformMatrix4fv( projectionMatLocation, 1, GL_FALSE, prjMat )
    glUniformMatrix4fv( viewMatLocation, 1, GL_FALSE, viewMat )
    glUniformMatrix4fv( textureMatLocation, 1, GL_FALSE, texMat )
    glUniform1i( textureLocation, 0 )
    
    # draw cube
    glUniformMatrix4fv( modelMatLocation, 1, GL_FALSE, cubeModelMat )
    glBindVertexArray( cubeVAObj )
    glDrawElements(GL_TRIANGLES, len(cubeIndices), GL_UNSIGNED_INT, cubeIndices)
    
    glutSwapBuffers()

def Fract(val): return val - math.trunc(val)
def CalcAng(currentTime, intervall): return Fract( (currentTime - startTime) / intervall ) * 360.0
def CalcMove(currentTime, intervall, range):
    pos = Fract( (currentTime - startTime) / intervall ) * 2.0
    pos = pos if pos < 1.0 else (2.0-pos)
    return range[0] + (range[1] - range[0]) * pos
    
# read shader program and compile shader
def CompileShader( sourceFileName, shaderStage ):
    with open( sourceFileName, 'r' ) as sourceFile:
        sourceCode = sourceFile.read()
    nameMap = { GL_VERTEX_SHADER: 'vertex', GL_FRAGMENT_SHADER: 'fragment' }    
    print( '\n%s shader code:' % nameMap.get(shaderStage, '') )
    print( sourceCode )
    shaderObj = glCreateShader( shaderStage )
    glShaderSource( shaderObj, sourceCode )
    glCompileShader( shaderObj )
    result = glGetShaderiv( shaderObj, GL_COMPILE_STATUS )
    if not (result):
        print( glGetShaderInfoLog( shaderObj ) )
        sys.exit()
    return shaderObj

# linke shader objects to shader program
def LinkProgram( shaderObjs ):
    shaderProgram = glCreateProgram()
    for shObj in shaderObjs:
        glAttachShader( shaderProgram, shObj )
    glLinkProgram( shaderProgram )
    result = glGetProgramiv( shaderProgram, GL_LINK_STATUS )
    if not (result):
        print( 'link error:' )
        print( glGetProgramInfoLog( shaderProgram ) )
        sys.exit()
    return shaderProgram

# create vertex array object
def CreateVAO( dataArrays ):
    noOfBuffers = len(dataArrays)
    buffers = glGenBuffers(noOfBuffers)
    newVAObj = glGenVertexArrays( 1 )
    glBindVertexArray( newVAObj )
    for inx in range(0, noOfBuffers):
        vertexSize, dataArr = dataArrays[inx]
        arr = np.array( dataArr, dtype='float32' )
        glBindBuffer( GL_ARRAY_BUFFER, buffers[inx] )
        glBufferData( GL_ARRAY_BUFFER, arr, GL_STATIC_DRAW )
        glEnableVertexAttribArray( inx )
        glVertexAttribPointer( inx, vertexSize, GL_FLOAT, GL_FALSE, 0, None )
    return newVAObj 

def Translate(matA, trans):
    matB = np.copy(matA)
    for i in range(0, 4): matB[3,i] = matA[0,i] * trans[0] + matA[1,i] * trans[1] + matA[2,i] * trans[2] + matA[3,i] 
    return matB

def Scale(matA, s):
    matB = np.copy(matA)
    for i0 in range(0, 3):
        for i1 in range(0, 4): matB[i0,i1] = matA[i0,i1] * s[i0] 
    return matB

def RotateHlp(matA, angDeg, a0, a1):
    matB = np.copy(matA)
    ang = math.radians(angDeg)
    sinAng, cosAng = math.sin(ang), math.cos(ang)
    for i in range(0, 4):
        matB[a0,i] = matA[a0,i] * cosAng + matA[a1,i] * sinAng
        matB[a1,i] = matA[a0,i] * -sinAng + matA[a1,i] * cosAng
    return matB

def RotateX(matA, angDeg): return RotateHlp(matA, angDeg, 1, 2)
def RotateY(matA, angDeg): return RotateHlp(matA, angDeg, 2, 0)
def RotateZ(matA, angDeg): return RotateHlp(matA, angDeg, 0, 1)
def RotateView(matA, angDeg): return RotateZ(RotateY(RotateX(matA, angDeg[0]), angDeg[1]), angDeg[2])

def perspective(fov, aspectRatio, near, far):
    fn, f_n = far + near, far - near
    r, t = aspectRatio, 1.0 / math.tan( math.radians(fov) / 2.0 )
    return np.matrix( [ [t/r,0,0,0], [0,t,0,0], [0,0,-fn/f_n,-2.0*far*near/f_n], [0,0,-1,0] ] )

# initialize glut
glutInit()

# create window
wndW, wndH = 800, 600
glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_ALPHA | GLUT_DEPTH)
glutInitWindowPosition(0, 0)
glutInitWindowSize(wndW, wndH)
wndID = glutCreateWindow(b'OGL window') 
glutDisplayFunc(OnDraw) 
glutIdleFunc(OnDraw)

# define cube vertex array opject
icoPts = [
    [-1.0, -1.0,  1.0], [ 1.0, -1.0,  1.0], [ 1.0,  1.0,  1.0], [-1.0,  1.0,  1.0],
    [-1.0, -1.0, -1.0], [ 1.0, -1.0, -1.0], [ 1.0,  1.0, -1.0], [-1.0,  1.0, -1.0] ]
cubePosData = []
for inx in [ 0, 1, 2, 3, 1, 5, 6, 2, 5, 4, 7, 6, 4, 0, 3, 7, 3, 2, 6, 7, 1, 0, 4, 5 ]:
    for inx_s in range(0, 3): cubePosData.append( icoPts[inx][inx_s] )
cubeTexData = []
for inx in range(0, 6):
    for texCoord in [-0.5, -0.5, 0.5, -0.5, 0.5, 0.5, -0.5, 0.5]: cubeTexData.append( texCoord )
icoCol = [ [1.0, 0.0, 0.0], [1.0, 0.5, 0.0], [1.0, 0.0, 1.0], [1.0, 1.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0] ]
cubeIndices = []
for inx in range(0, 6):
    for inx_s in [0, 1, 2, 0, 2, 3]: cubeIndices.append( inx * 4 + inx_s )
cubeVAObj = CreateVAO( [ (3, cubePosData), (2, cubeTexData) ] )
cubeInxArr = np.array( cubeIndices, dtype='uint' )

# load, compile and link shader
shaderProgram = LinkProgram( [
        CompileShader( 'python/ogl4tex/tex.vert', GL_VERTEX_SHADER ), 
        CompileShader( 'python/ogl4tex/tex.frag', GL_FRAGMENT_SHADER )
    ] )
projectionMatLocation = glGetUniformLocation(shaderProgram, "u_projectionMat44")
viewMatLocation = glGetUniformLocation(shaderProgram, "u_viewMat44")
modelMatLocation = glGetUniformLocation(shaderProgram, "u_modelMat44")
textureMatLocation = glGetUniformLocation(shaderProgram, "u_textureMat44")
textureLocation = glGetUniformLocation(shaderProgram, "u_texture")

# create texture
texCX, texCY = 128, 128
texPlan = np.zeros( texCX * texCY * 4, dtype=np.uint8 )
for inx_x in range(0, texCX):
    for inx_y in range(0, texCY):
        val_x = math.sin( math.pi * 6.0 * inx_x / texCX )
        val_y = math.sin( math.pi * 6.0 * inx_y / texCY )
        inx_tex = inx_y * texCX * 4 + inx_x * 4
        texPlan[inx_tex + 0] = int( 128 + 127 * val_x )
        texPlan[inx_tex + 1] = 63
        texPlan[inx_tex + 2] = int( 128 + 127 * val_y )
        texPlan[inx_tex + 3] = 255
glActiveTexture( GL_TEXTURE0 )
texObj = glGenTextures( 1  )
glBindTexture( GL_TEXTURE_2D, texObj )
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, texCX, texCY, 0, GL_RGBA, GL_UNSIGNED_BYTE, texPlan)
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT)
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT) 

# start main loop
startTime = time()
glutMainLoop()

Использование блока интерфейса и единого блока: модель света Cook-Torrance в OGL 4.0 GLSL

Простая программа шейдеров шейдеров OGL 4.0 GLSL, которая показывает использование блока интерфейса и единый блок для реализации модели света с микрофоном Cook-Torrance. Программа выполняется с помощью phyton-скрипта. Для запуска скрипта необходимо установить PyOpenGL и NumPy.

Интерфейсный блок представляет собой группу входных, выходных, равномерных или буферных переменных GLSL. Равномерное Blockis является блоком интерфейса с хранением спецификатором uniform .

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

ibub.vert

#version 400

layout (location = 0) in vec3 inPos;
layout (location = 1) in vec3 inNV;
layout (location = 2) in vec3 inCol;

out TVertexData
{
    vec3 pos;
    vec3 nv;
    vec3 col;
} outData;

uniform mat4 u_projectionMat44;
uniform mat4 u_modelViewMat44;
uniform mat3 u_normalMat33;

void main()
{
    vec4 viewPos = u_modelViewMat44 * vec4( inPos, 1.0 );
    
    outData.pos = viewPos.xyz / viewPos.w;
    outData.nv  = u_normalMat33 * normalize( inNV );
    outData.col = inCol;
    
    gl_Position = u_projectionMat44 * viewPos;
}

Фрагментный шейдер

ibub.frag

#version 400

in TVertexData
{
    vec3 pos;
    vec3 nv;
    vec3 col;
} inData;

out vec4 fragColor;

uniform UB_material
{
    float u_roughness;
    float u_fresnel0;
    vec4  u_specularTint;
};

struct TLightSource
{
    vec4 ambient;
    vec4 diffuse;
    vec4 specular;
    vec4 dir;
};

uniform UB_lightSource
{
    TLightSource u_lightSource;
};

vec3 CookTorrance( vec3 esPt, vec3 esPtNV, vec3 col, vec4 specularTint, float roughness, float fresnel0 )
{
  vec3  esVLight      = normalize( -u_lightSource.dir.xyz );
  vec3  esVEye        = normalize( -esPt );
  vec3  halfVector    = normalize( esVEye + esVLight );
  vec3  reflVector    = normalize( reflect( -esVLight, esPtNV ) );
  float VdotR         = dot( esVEye, reflVector );
  float HdotL         = dot( halfVector, esVLight );
  float NdotL         = dot( esPtNV, esVLight );
  float NdotV         = dot( esPtNV, esVEye );
  float NdotH         = dot( esPtNV, halfVector );
  float NdotH2        = NdotH * NdotH;
  float NdotL_clamped = max( NdotL, 0.0 );
  float NdotV_clamped = max( NdotV, 0.0 );
  float m2            = roughness * roughness;
  
  // Lambertian diffuse
  float k_diffuse = NdotL_clamped;
  
  // Cook-Torrance fresnel
  float theta = HdotL;
  float n = (1.0 + sqrt(fresnel0)) / (1.0 - sqrt(fresnel0));
  float g = sqrt( n*n + theta * theta + 1.0 );
  float gc = g + theta;
  float g_c = g - theta;
  float q = (gc * theta - 1.0) / (g_c * theta + 1.0);
  float fresnel = 0.5 * (g_c * g_c) / (gc * gc) * (1.0 + q * q);

  // Gaussian  distribution
  float psi = acos( VdotR );
  float distribution = max( 0.0, HdotL * exp( - psi * psi / m2 ) );
  
  // Torrance-Sparrow geometric term
  float geometric_att = min( 1.0, min( 2.0 * NdotH * NdotV_clamped / HdotL, 2.0 * NdotH * NdotL_clamped / HdotL ) );

  // Microfacet bidirectional reflectance distribution function 
  float brdf_spec = fresnel * distribution * geometric_att / ( 4.0 * NdotL_clamped * NdotV_clamped );
  float k_specular = brdf_spec;

  vec3 lightColor = col.rgb * u_lightSource.ambient.rgb
                  + max( 0.0, k_diffuse ) * col.rgb * u_lightSource.diffuse.rgb +
                  + max( 0.0, k_specular ) * mix( col.rgb, specularTint.rgb, specularTint.a ) *     u_lightSource.specular.rgb;
  return lightColor;
}

void main()
{
    vec3 lightCol = CookTorrance( inData.pos, inData.nv, inData.col, u_specularTint, u_roughness, u_fresnel0 );
    fragColor = vec4( lightCol, 1.0 );
}

Скрипт Phyton

from OpenGL.GL import *
from OpenGL.GLUT import *
from OpenGL.GLU import *
import numpy as np
from time import time
import math
import sys

sin120 = 0.8660254
rotateCamera = False

# draw event
def OnDraw():
    dist = 3.0
    currentTime = time()
    comeraRotAng = CalcAng( currentTime, 10.0 ) 
    # set up projection matrix
    prjMat = Perspective(90.0, wndW/wndH, 0.5, 100.0) 
    # set up view matrix
    viewMat = Translate( np.matrix(np.identity(4), copy=False, dtype='float32'), np.array( [0.0, 0.0, -12.0] ) )
    viewMat = RotateView( viewMat, [30.0, comeraRotAng if rotateCamera else 0.0, 0.0] )

    # set up light source
    lightSourceBuffer.BindDataFloat(b'u_lightSource.dir', TransformVec4([-3.0, -2.0, -1.0, 0.0], viewMat) )
    
    # set up tetrahedron model matrix
    tetModelMat = np.matrix(np.identity(4), copy=False, dtype='float32')
    if not rotateCamera: tetModelMat = RotateY( tetModelMat, comeraRotAng )
    tetModelMat = RotateX( tetModelMat, -90.0 )
    tetModelMat = Scale( tetModelMat, np.repeat( 2.4, 3 ) )
    tetModelMat = Translate( tetModelMat, np.array( [0.0, dist, 0.0] ) )
    tetModelMat = RotateY( tetModelMat, CalcAng( currentTime, 20.0 ) )
    tetModelMat = RotateX( tetModelMat, CalcAng( currentTime, 9.0 ) )
    
    # set up icosahedron model matrix
    icoModelMat = np.matrix(np.identity(4), copy=False, dtype='float32')
    if not rotateCamera: icoModelMat = RotateY( icoModelMat, comeraRotAng )
    icoModelMat = RotateX( icoModelMat, -90.0 )
    icoModelMat = Scale( icoModelMat, np.repeat( 2.0, 3 ) )
    icoModelMat = Translate( icoModelMat, np.array( [dist * -sin120, dist * -0.5, 0.0] ) )
    icoModelMat = RotateY( icoModelMat, CalcAng( currentTime, 20.0 ) )
    icoModelMat = RotateX( icoModelMat, CalcAng( currentTime, 11.0 ) )

    # set up cube model matrix
    cubeModelMat = np.matrix(np.identity(4), copy=False, dtype='float32')
    if not rotateCamera: cubeModelMat = RotateY( cubeModelMat, comeraRotAng )
    cubeModelMat = RotateX( cubeModelMat, -90.0 )
    cubeModelMat = Scale( cubeModelMat, np.repeat( 1.6, 3 ) )
    cubeModelMat = Translate( cubeModelMat, np.array( [dist * sin120, dist * -0.5, 0.0] ) )
    cubeModelMat = RotateY( cubeModelMat, CalcAng( currentTime, 20.0 ) )
    cubeModelMat = RotateX( cubeModelMat, CalcAng( currentTime, 13.0 ) )
 
    # set up attributes and shader program
    glEnable( GL_DEPTH_TEST )
    glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT )
    glUseProgram( shaderProgram )
    glUniformMatrix4fv( projectionMatLocation, 1, GL_FALSE, prjMat )
    lightSourceBuffer.BindToTarget()
    
    # draw tetrahedron
    tetMaterialBuffer.BindToTarget()
    modelViewMat = Multiply(viewMat, tetModelMat)
    glUniformMatrix4fv( modelViewMatLocation, 1, GL_FALSE, modelViewMat )
    glUniformMatrix3fv( normalMatLocation, 1, GL_FALSE, ToMat33(modelViewMat) )
    glBindVertexArray( tetVAObj )
    glDrawArrays( GL_TRIANGLES, 0, len(tetPosData) )

    # draw icosahedron
    icoMaterialBuffer.BindToTarget()
    modelViewMat = Multiply(viewMat, icoModelMat)
    glUniformMatrix4fv( modelViewMatLocation, 1, GL_FALSE, modelViewMat )
    glUniformMatrix3fv( normalMatLocation, 1, GL_FALSE, ToMat33(modelViewMat) )
    glBindVertexArray( icoVAObj )
    glDrawArrays( GL_TRIANGLES, 0, len(icoPosData) )

    # draw cube
    cubeMaterialBuffer.BindToTarget()
    modelViewMat = Multiply(viewMat, cubeModelMat)
    glUniformMatrix4fv( modelViewMatLocation, 1, GL_FALSE, modelViewMat )
    glUniformMatrix3fv( normalMatLocation, 1, GL_FALSE, ToMat33(modelViewMat) )
    glBindVertexArray( cubeVAObj )
    glDrawElements(GL_TRIANGLES, len(cubeIndices), GL_UNSIGNED_INT, cubeIndices)
    
    glutSwapBuffers()

def Fract(val): return val - math.trunc(val)
def CalcAng(currentTime, intervall): return Fract( (currentTime - startTime) / intervall ) * 360.0
def CalcMove(currentTime, intervall, range):
    pos = Fract( (currentTime - startTime) / intervall ) * 2.0
    pos = pos if pos < 1.0 else (2.0-pos)
    return range[0] + (range[1] - range[0]) * pos
    
# read shader program and compile shader
def CompileShader( sourceFileName, shaderStage ):
    with open( sourceFileName, 'r' ) as sourceFile:
        sourceCode = sourceFile.read()
    nameMap = { GL_VERTEX_SHADER: 'vertex', GL_FRAGMENT_SHADER: 'fragment' }    
    print( '\n%s shader code:' % nameMap.get(shaderStage, '') )
    print( sourceCode )
    shaderObj = glCreateShader( shaderStage )
    glShaderSource( shaderObj, sourceCode )
    glCompileShader( shaderObj )
    result = glGetShaderiv( shaderObj, GL_COMPILE_STATUS )
    if not (result):
        print( glGetShaderInfoLog( shaderObj ) )
        sys.exit()
    return shaderObj

# linke shader objects to shader program
def LinkProgram( shaderObjs ):
    shaderProgram = glCreateProgram()
    for shObj in shaderObjs:
        glAttachShader( shaderProgram, shObj )
    glLinkProgram( shaderProgram )
    result = glGetProgramiv( shaderProgram, GL_LINK_STATUS )
    if not (result):
        print( 'link error:' )
        print( glGetProgramInfoLog( shaderProgram ) )
        sys.exit()
    return shaderProgram

# create vertex array object
def CreateVAO( dataArrays ):
    noOfBuffers = len(dataArrays)
    buffers = glGenBuffers(noOfBuffers)
    newVAObj = glGenVertexArrays( 1 )
    glBindVertexArray( newVAObj )
    for inx in range(0, noOfBuffers):
        vertexSize, dataArr = dataArrays[inx]
        arr = np.array( dataArr, dtype='float32' )
        glBindBuffer( GL_ARRAY_BUFFER, buffers[inx] )
        glBufferData( GL_ARRAY_BUFFER, arr, GL_STATIC_DRAW )
        glEnableVertexAttribArray( inx )
        glVertexAttribPointer( inx, vertexSize, GL_FLOAT, GL_FALSE, 0, None )
    return newVAObj 

# representation of a uniform block
class UniformBlock:
     def __init__(self, shaderProg, name):
        self.shaderProg = shaderProg 
        self.name = name
     def Link(self, bindingPoint):
        self.bindingPoint = bindingPoint
        self.noOfUniforms = glGetProgramiv(self.shaderProg, GL_ACTIVE_UNIFORMS)
        self.maxUniformNameLen = glGetProgramiv(self.shaderProg, GL_ACTIVE_UNIFORM_MAX_LENGTH)
        self.index = glGetUniformBlockIndex(self.shaderProg, self.name)
        intData = np.zeros(1, dtype=int)
        glGetActiveUniformBlockiv(self.shaderProg, self.index, GL_UNIFORM_BLOCK_ACTIVE_UNIFORMS, intData)
        self.count = intData[0]
        self.indices = np.zeros(self.count, dtype=int)
        glGetActiveUniformBlockiv(self.shaderProg, self.index, GL_UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES, self.indices)
        self.offsets = np.zeros(self.count, dtype=int)
        glGetActiveUniformsiv(self.shaderProg, self.count, self.indices, GL_UNIFORM_OFFSET, self.offsets)
        self.size = 0
        strLengthData = np.zeros(1, dtype=int)
        arraysizeData = np.zeros(1, dtype=int)
        typeData = np.zeros(1, dtype='uint32')
        nameData = np.chararray(self.maxUniformNameLen+1)
        self.namemap = {}
        self.dataSize = 0 
        for inx in range(0, len(self.indices)):
            glGetActiveUniform( self.shaderProg, self.indices[inx], self.maxUniformNameLen, strLengthData, arraysizeData,     typeData, nameData.data )
            name = nameData.tostring()[:strLengthData[0]]
            self.namemap[name] = inx
            self.dataSize = max(self.dataSize, self.offsets[inx] + arraysizeData * 16) 
        glUniformBlockBinding(self.shaderProg, self.index, self.bindingPoint)
        print('\nuniform block %s size:%4d' % (self.name, self.dataSize))
        for uName in self.namemap:
            print( '    %-40s index:%2d    offset:%4d' % (uName, self.indices[self.namemap[uName]], self.offsets    [self.namemap[uName]]) ) 

# representation of a uniform block buffer
class UniformBlockBuffer:
    def __init__(self, ub):
        self.namemap = ub.namemap
        self.offsets = ub.offsets
        self.bindingPoint = ub.bindingPoint
        self.object = glGenBuffers(1)
        self.dataSize = ub.dataSize
        glBindBuffer(GL_UNIFORM_BUFFER, self.object)
        dataArray = np.zeros(self.dataSize//4, dtype='float32')
        glBufferData(GL_UNIFORM_BUFFER, self.dataSize, dataArray, GL_DYNAMIC_DRAW)
    def BindToTarget(self):
        glBindBuffer(GL_UNIFORM_BUFFER, self.object)
        glBindBufferBase(GL_UNIFORM_BUFFER, self.bindingPoint, self.object)
    def BindDataFloat(self, name, dataArr):
        glBindBuffer(GL_UNIFORM_BUFFER, self.object)
        dataArray = np.array(dataArr, dtype='float32')
        glBufferSubData(GL_UNIFORM_BUFFER, self.offsets[self.namemap[name]], len(dataArr)*4, dataArray)


def Translate(matA, trans):
    matB = np.copy(matA)
    for i in range(0, 4): matB[3,i] = matA[0,i] * trans[0] + matA[1,i] * trans[1] + matA[2,i] * trans[2] + matA[3,i] 
    return matB

def Scale(matA, s):
    matB = np.copy(matA)
    for i0 in range(0, 3):
        for i1 in range(0, 4): matB[i0,i1] = matA[i0,i1] * s[i0] 
    return matB

def RotateHlp(matA, angDeg, a0, a1):
    matB = np.copy(matA)
    ang = math.radians(angDeg)
    sinAng, cosAng = math.sin(ang), math.cos(ang)
    for i in range(0, 4):
        matB[a0,i] = matA[a0,i] * cosAng + matA[a1,i] * sinAng
        matB[a1,i] = matA[a0,i] * -sinAng + matA[a1,i] * cosAng
    return matB

def RotateX(matA, angDeg): return RotateHlp(matA, angDeg, 1, 2)
def RotateY(matA, angDeg): return RotateHlp(matA, angDeg, 2, 0)
def RotateZ(matA, angDeg): return RotateHlp(matA, angDeg, 0, 1)
def RotateView(matA, angDeg): return RotateZ(RotateY(RotateX(matA, angDeg[0]), angDeg[1]), angDeg[2])

def Multiply(matA, matB):
    matC = np.copy(matA)
    for i0 in range(0, 4):
        for i1 in range(0, 4):
            matC[i0,i1] = matB[i0,0] * matA[0,i1] + matB[i0,1] * matA[1,i1] + matB[i0,2] * matA[2,i1] + matB[i0,3] * matA    [3,i1]    
    return matC

def ToMat33(mat44):
    mat33 = np.matrix(np.identity(3), copy=False, dtype='float32')
    for i0 in range(0, 3):
        for i1 in range(0, 3): mat33[i0, i1] = mat44[i0, i1]
    return mat33

def TransformVec4(vecA,mat44):
    vecB = np.zeros(4, dtype='float32')
    for i0 in range(0, 4):
        vecB[i0] = vecA[0] * mat44[0,i0] + vecA[1] * mat44[1,i0] + vecA[2] * mat44[2,i0]  + vecA[3] * mat44[3,i0]
    return vecB

def Perspective(fov, aspectRatio, near, far):
    fn, f_n = far + near, far - near
    r, t = aspectRatio, 1.0 / math.tan( math.radians(fov) / 2.0 )
    return np.matrix( [ [t/r,0,0,0], [0,t,0,0], [0,0,-fn/f_n,-2.0*far*near/f_n], [0,0,-1,0] ] )

def AddToBuffer( buffer, data, count=1 ): 
    for inx_c in range(0, count):
        for inx_s in range(0, len(data)): buffer.append( data[inx_s] ) 

# initialize glut
glutInit()

# create window
wndW, wndH = 800, 600
glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_ALPHA | GLUT_DEPTH)
glutInitWindowPosition(0, 0)
glutInitWindowSize(wndW, wndH)
wndID = glutCreateWindow(b'OGL window') 
glutDisplayFunc(OnDraw) 
glutIdleFunc(OnDraw)

# define tetrahedron vertex array opject
tetPts = [ (0.0, 0.0, 1.0), (0.0, -sin120, -0.5), (sin120 * sin120, 0.5 * sin120, -0.5), (-sin120 * sin120, 0.5 * sin120,     -0.5) ]
tetCol = [ [1.0, 0.0, 0.0], [1.0, 1.0, 0.0], [0.0, 0.0, 1.0], [0.0, 1.0, 0.0], ]
tetInxdices = [ 0, 1, 2, 0, 2, 3, 0, 3, 1, 1, 3, 2 ]
tetPosData = []
for inx in tetInxdices: AddToBuffer( tetPosData, tetPts[inx] )
tetNVData = []
for inx_nv in range(0, len(tetInxdices) // 3):
    nv = [0.0, 0.0, 0.0]
    for inx_p in range(0, 3): 
        for inx_s in range(0, 3): nv[inx_s] += tetPts[ tetInxdices[inx_nv*3 + inx_p] ][inx_s]
    AddToBuffer( tetNVData, nv, 3 )
tetColData = []
for inx_col in range(0, len(tetInxdices) // 3): AddToBuffer( tetColData, tetCol[inx_col % len(tetCol)], 3 )
tetVAObj = CreateVAO( [ (3, tetPosData), (3, tetNVData), (3, tetColData) ] )

# define icosahedron vertex array opject
icoPts = [
    ( 0.000,  0.000,  1.000), ( 0.894,  0.000,  0.447), ( 0.276,  0.851,  0.447), (-0.724,  0.526,  0.447),
    (-0.724, -0.526,  0.447), ( 0.276, -0.851,  0.447), ( 0.724,  0.526, -0.447), (-0.276,  0.851, -0.447), 
    (-0.894,  0.000, -0.447), (-0.276, -0.851, -0.447), ( 0.724, -0.526, -0.447), ( 0.000,  0.000, -1.000) ]
icoCol = [ [1.0, 0.0, 0.0], [0.0, 0.0, 1.0], [1.0, 1.0, 0.0], [0.0, 1.0, 0.0], [1.0, 0.5, 0.0], [1.0, 0.0, 1.0] ]
icoIndices = [
  2,  0,  1,  3,  0,  2,  4,  0,  3,  5,  0,  4,  1,  0,  5, 11,  7,  6, 11,  8,  7, 11,  9,  8, 11, 10,  9, 11,  6, 10, 
  1,  6,  2,  2,  7,  3,  3,  8,  4,  4,  9,  5,  5, 10,  1,  2,  6,  7,  3,  7,  8,  4,  8,  9,  5,  9, 10,  1, 10,  6  ]
icoPosData = []
for inx in icoIndices: AddToBuffer( icoPosData, icoPts[inx] )
icoNVData = []
for inx in icoIndices: AddToBuffer( icoNVData, icoPts[inx] )
#for inx_nv in range(0, len(icoIndices) // 3):
#    nv = [0.0, 0.0, 0.0]
#    for inx_p in range(0, 3): 
#        for inx_s in range(0, 3): nv[inx_s] += icoPts[ icoIndices[inx_nv*3 + inx_p] ][inx_s]
#    AddToBuffer( icoNVData, nv, 3 )
icoColData = []
for inx_col in range(0, len(icoIndices) // 3): AddToBuffer( icoColData, icoCol[inx_col % len(icoCol)], 3 )
icoVAObj = CreateVAO( [ (3, icoPosData), (3, icoNVData), (3, icoColData) ] )

# define cube vertex array opject
cubePts = [
    (-1.0, -1.0,  1.0), ( 1.0, -1.0,  1.0), ( 1.0,  1.0,  1.0), (-1.0,  1.0,  1.0),
    (-1.0, -1.0, -1.0), ( 1.0, -1.0, -1.0), ( 1.0,  1.0, -1.0), (-1.0,  1.0, -1.0) ]
cubeCol = [ [1.0, 0.0, 0.0], [1.0, 0.5, 0.0], [1.0, 0.0, 1.0], [1.0, 1.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0] ]
cubeHlpInx = [ 0, 1, 2, 3, 1, 5, 6, 2, 5, 4, 7, 6, 4, 0, 3, 7, 3, 2, 6, 7, 1, 0, 4, 5 ] 
cubePosData = []
for inx in cubeHlpInx: AddToBuffer( cubePosData, cubePts[inx] )
cubeNVData = []
for inx_nv in range(0, len(cubeHlpInx) // 4):
    nv = [0.0, 0.0, 0.0]
    for inx_p in range(0, 4):
        for inx_s in range(0, 3): nv[inx_s] += cubePts[ cubeHlpInx[inx_nv*4 + inx_p] ][inx_s]
    AddToBuffer( cubeNVData, nv, 4 )
cubeColData = []
for inx_col in range(0, 6):
    AddToBuffer( cubeColData, cubeCol[inx_col % len(cubeCol)], 4 )
cubeIndices = []
for inx in range(0, 6):
    for inx_s in [0, 1, 2, 0, 2, 3]: cubeIndices.append( inx * 4 + inx_s )
cubeVAObj = CreateVAO( [ (3, cubePosData), (3, cubeNVData), (3, cubeColData) ] )

# load, compile and link shader
shaderProgram = LinkProgram( [
        CompileShader( 'ibub.vert', GL_VERTEX_SHADER ), 
        CompileShader( 'ibub.frag', GL_FRAGMENT_SHADER )
    ] )
# get unifor locations
projectionMatLocation = glGetUniformLocation(shaderProgram, "u_projectionMat44")
modelViewMatLocation  = glGetUniformLocation(shaderProgram, "u_modelViewMat44")
normalMatLocation     = glGetUniformLocation(shaderProgram, "u_normalMat33")
# linke uniform blocks
ubMaterial = UniformBlock(shaderProgram, "UB_material")
ubLightSource = UniformBlock(shaderProgram, "UB_lightSource")
ubMaterial.Link(1)
ubLightSource.Link(2)

# create uniform block buffers
lightSourceBuffer = UniformBlockBuffer(ubLightSource)
lightSourceBuffer.BindDataFloat(b'u_lightSource.ambient', [0.1, 0.1, 0.1, 1.0])
lightSourceBuffer.BindDataFloat(b'u_lightSource.diffuse', [0.4, 0.4, 0.4, 1.0])
lightSourceBuffer.BindDataFloat(b'u_lightSource.specular', [1.0, 1.0, 1.0, 1.0])

tetMaterialBuffer = UniformBlockBuffer(ubMaterial)
tetMaterialBuffer.BindDataFloat(b'u_roughness', [0.3])
tetMaterialBuffer.BindDataFloat(b'u_fresnel0', [0.5])
tetMaterialBuffer.BindDataFloat(b'u_specularTint',[1.0, 1.0, 1.0, 0.7])

icoMaterialBuffer = UniformBlockBuffer(ubMaterial)
icoMaterialBuffer.BindDataFloat(b'u_roughness', [0.1])
icoMaterialBuffer.BindDataFloat(b'u_fresnel0', [0.2])
icoMaterialBuffer.BindDataFloat(b'u_specularTint',[1.0, 1.0, 1.0, 0.7])

cubeMaterialBuffer = UniformBlockBuffer(ubMaterial)
cubeMaterialBuffer.BindDataFloat(b'u_roughness', [0.5])
cubeMaterialBuffer.BindDataFloat(b'u_fresnel0', [0.3])
cubeMaterialBuffer.BindDataFloat(b'u_specularTint',[1.0, 1.0, 1.0, 0.7])

# start main loop
startTime = time()
glutMainLoop()

Создание геометрии с использованием геометрического шейдера в OGL 4.0 GLSL

Простая программа шейдеров OGL 4.0 GLSL, которая показывает использование геометрических шейдеров. Программа выполняется с помощью phyton-скрипта. Для запуска скрипта необходимо установить PyOpenGL и NumPy.

В этом примере вся геометрия (цилиндр) генерируется в геометрическом шейдере.

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

geo.vert

#version 400

layout (location = 0) in vec3 inPos;
layout (location = 1) in vec3 inNormal;
layout (location = 2) in vec3 inTangent;

out TVertexData
{
    mat3 orientationMat;
} outData;

void main()
{
    vec3 normal   = normalize( inNormal );
    vec3 tangent  = normalize( inTangent );
    vec3 binormal = cross( tangent, normal );
    
    outData.orientationMat = mat3( normal, cross( binormal, normal ), binormal );
    gl_Position = vec4( inPos, 1.0 );
}

Геометрический шейдер

geo.geo

#version 400

layout( invocations = 3 ) in;
layout( points ) in;
layout( triangle_strip, max_vertices = 160 ) out;

in TVertexData
{
    mat3 orientationMat;
} inData[];

out TGeometryData
{
    vec3 pos;
    vec3 nv;
    vec3 col;
} outData;

uniform mat4 u_projectionMat44;
uniform mat4 u_viewMat44;
uniform mat4 u_modelMat44;

void NewVertex( in vec3 pt, in mat4 transMat )
{
    vec4 viewPos = transMat * vec4( pt, 1.0 );
    outData.pos = viewPos.xyz / viewPos.w;
    gl_Position = u_projectionMat44 * viewPos;
    EmitVertex();
}

const int circumferenceTile = 36;

void main()
{
    vec4 origin = gl_in[0].gl_Position;
    origin /= origin.w;
    mat4 orintationMat = mat4( vec4( inData[0].orientationMat[0], 0.0 ),
                               vec4( inData[0].orientationMat[1], 0.0 ),
                               vec4( inData[0].orientationMat[2], 0.0 ),
                               origin );
    mat4 modelViewMat = u_viewMat44 * u_modelMat44 * orintationMat;
    mat3 normalMat = mat3( modelViewMat );
  
    outData.col = vec3( 0.5, 0.7, 0.6 );

    if ( gl_InvocationID == 0 ) // top of the cylinder
    {
        outData.nv  = normalMat * vec3(0.0, 0.0, 1.0);
        vec2 prevPt = vec2( 0.0, 1.0 );
        for ( int inx = 1; inx <= circumferenceTile; inx += 2 )
        {
            float ang1 = 2.0 * 3.14159 * float(inx) / float(circumferenceTile);
            float ang2 = 2.0 * 3.14159 * float(inx+1) / float(circumferenceTile);
            vec2 actPt1 = vec2( sin(ang1), cos(ang1) );
            vec2 actPt2 = vec2( sin(ang2), cos(ang2) );
      
            NewVertex( vec3(prevPt.xy, 1.0), modelViewMat );
            NewVertex( vec3(actPt1.xy, 1.0), modelViewMat );
            NewVertex( vec3(0.0, 0.0, 1.0), modelViewMat );
            NewVertex( vec3(actPt2.xy, 1.0), modelViewMat );
            
            EndPrimitive();
            prevPt = actPt2;
        }
    }

    if ( gl_InvocationID == 1 ) // bottom of the cylinder  
    {
        outData.nv  = normalMat * vec3(0.0, 0.0, -1.0);    
        vec2 prevPt = vec2( 0.0, 1.0 );
        for ( int inx = circumferenceTile-1; inx >= 0; inx -= 2 )
        {
            float ang1 = 2.0 * 3.14159 * float(inx) / float(circumferenceTile);
            float ang2 = 2.0 * 3.14159 * float(inx-1) / float(circumferenceTile);
            vec2 actPt1 = vec2( sin(ang1), cos(ang1) );
            vec2 actPt2 = vec2( sin(ang2), cos(ang2) );    
            NewVertex( vec3(prevPt.xy, -1.0), modelViewMat );
            NewVertex( vec3(actPt1.xy, -1.0), modelViewMat );
            NewVertex( vec3(0.0, 0.0, -1.0), modelViewMat );
            NewVertex( vec3(actPt2.xy, -1.0), modelViewMat );
            
            EndPrimitive();
            prevPt = actPt2;
        }
    }

    if ( gl_InvocationID == 2 ) // hull of the cylinder
    {
        vec2 prevPt = vec2( 0.0, 1.0 );
        for ( int inx = 1; inx <= circumferenceTile; ++ inx )
        {
            float ang = 2.0 * 3.14159 * float(inx) / float(circumferenceTile);
            vec2 actPt = vec2( sin(ang), cos(ang) );
            
            outData.nv = normalMat * vec3(prevPt, 0.0);
            NewVertex( vec3(prevPt.xy, -1.0), modelViewMat );
            outData.nv = normalMat * vec3(actPt, 0.0);
            NewVertex( vec3(actPt.xy, -1.0), modelViewMat );
            outData.nv = normalMat * vec3(prevPt, 0.0);
            NewVertex( vec3(prevPt.xy, 1.0), modelViewMat );
            outData.nv = normalMat * vec3(actPt, 0.0);
            NewVertex( vec3(actPt.xy, 1.0), modelViewMat );
            
            prevPt = actPt;
        }
        EndPrimitive();
    }
}

Фрагментный шейдер

geo.frag

#version 400

in TGeometryData
{
    vec3 pos;
    vec3 nv;
    vec3 col;
} inData;

out vec4 fragColor;

uniform UB_material
{
    float u_roughness;
    float u_fresnel0;
    vec4  u_specularTint;
};

struct TLightSource
{
    vec4 ambient;
    vec4 diffuse;
    vec4 specular;
    vec4 dir;
};

uniform UB_lightSource
{
    TLightSource u_lightSource;
};

float Fresnel_Schlick( float theta )
{
    float m = clamp( 1.0 - theta, 0.0, 1.0 );
    float m2 = m * m;
    return m2 * m2 * m; // pow( m, 5.0 )
}

vec3 LightModel( vec3 esPt, vec3 esPtNV, vec3 col, vec4 specularTint, float roughness, float fresnel0 )
{
  vec3  esVLight      = normalize( -u_lightSource.dir.xyz );
  vec3  esVEye        = normalize( -esPt );
  vec3  halfVector    = normalize( esVEye + esVLight );
  float HdotL         = dot( halfVector, esVLight );
  float NdotL         = dot( esPtNV, esVLight );
  float NdotV         = dot( esPtNV, esVEye );
  float NdotH         = dot( esPtNV, halfVector );
  float NdotH2        = NdotH * NdotH;
  float NdotL_clamped = max( NdotL, 0.0 );
  float NdotV_clamped = max( NdotV, 0.0 );
  float m2            = roughness * roughness;
  
  // Lambertian diffuse
  float k_diffuse = NdotL_clamped;
  // Schlick approximation
  float fresnel = fresnel0 + ( 1.0 - fresnel0 ) * Fresnel_Schlick( HdotL );
  // Beckmann distribution
  float distribution = max( 0.0, exp( ( NdotH2 - 1.0 ) / ( m2 * NdotH2 ) ) / ( 3.14159265 * m2 * NdotH2 * NdotH2 ) );
  // Torrance-Sparrow geometric term
  float geometric_att = min( 1.0, min( 2.0 * NdotH * NdotV_clamped / HdotL, 2.0 * NdotH * NdotL_clamped / HdotL ) );
  // Microfacet bidirectional reflectance distribution function 
  float k_specular = fresnel * distribution * geometric_att / ( 4.0 * NdotL_clamped * NdotV_clamped );
  
  vec3 lightColor = col.rgb * u_lightSource.ambient.rgb +
                    max( 0.0, k_diffuse ) * col.rgb * u_lightSource.diffuse.rgb +
                    max( 0.0, k_specular ) * mix( col.rgb, specularTint.rgb, specularTint.a ) *     u_lightSource.specular.rgb;
  return lightColor;
}

void main()
{
    vec3 lightCol = LightModel( inData.pos, inData.nv, inData.col, u_specularTint, u_roughness, u_fresnel0 );
    fragColor = vec4( clamp( lightCol, 0.0, 1.0 ), 1.0 );
}

Скрипт Phyton

from OpenGL.GL import *
from OpenGL.GLUT import *
from OpenGL.GLU import *
import numpy as np
from time import time
import math
import sys

sin120 = 0.8660254
rotateCamera = False

# draw event
def OnDraw():
    dist = 3.0
    currentTime = time()
    comeraRotAng = CalcAng( currentTime, 10.0 ) 
    # set up projection matrix
    prjMat = Perspective(90.0, wndW/wndH, 0.5, 100.0) 
    # set up view matrix
    viewMat = np.matrix(np.identity(4), copy=False, dtype='float32')
    viewMat = Translate( viewMat, np.array( [0.0, 0.0, -12.0] ) )
    viewMat = RotateView( viewMat, [30.0, comeraRotAng if rotateCamera else 0.0, 0.0] )

    # set up light source
    lightSourceBuffer.BindDataFloat(b'u_lightSource.dir', TransformVec4([-0.1, 1.0, -5.0, 0.0], viewMat) )
    
    # set up the model matrix
    modelMat = np.matrix(np.identity(4), copy=False, dtype='float32')
    if not rotateCamera: modelMat = RotateY( modelMat, comeraRotAng )
    modelMat = Scale( modelMat, np.repeat( 4, 3 ) )
    #modelMat = Translate( modelMat, np.array( [0.0, 0.0, 1.0] ) )
    #modelMat = RotateY( modelMat, CalcAng( currentTime, 20.0 ) )
    modelMat = RotateX( modelMat, CalcAng( currentTime, 9.0 ) )
 
    # set up attributes and shader program
    glEnable( GL_DEPTH_TEST )
    glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT )
    glUseProgram( shaderProgram )
    glUniformMatrix4fv( projectionMatLocation, 1, GL_FALSE, prjMat )
    glUniformMatrix4fv( viewMatLocation, 1, GL_FALSE, viewMat )
    lightSourceBuffer.BindToTarget()
    
    # draw point
    materialBuffer.BindToTarget()
    glUniformMatrix4fv( modelMatLocation, 1, GL_FALSE, modelMat )
    glBindVertexArray( pointVAObj )
    glDrawArrays( GL_POINTS, 0, 1 )
    
    glutSwapBuffers()

def Fract(val): return val - math.trunc(val)
def CalcAng(currentTime, intervall): return Fract( (currentTime - startTime) / intervall ) * 360.0
def CalcMove(currentTime, intervall, range):
    pos = Fract( (currentTime - startTime) / intervall ) * 2.0
    pos = pos if pos < 1.0 else (2.0-pos)
    return range[0] + (range[1] - range[0]) * pos
    
# read shader program and compile shader
def CompileShader( sourceFileName, shaderStage ):
    with open( sourceFileName, 'r' ) as sourceFile:
        sourceCode = sourceFile.read()
    nameMap = { GL_VERTEX_SHADER: 'vertex', GL_GEOMETRY_SHADER: 'geometry', GL_FRAGMENT_SHADER: 'fragment' }    
    print( '\n%s shader code:' % nameMap.get(shaderStage, '') )
    print( sourceCode )
    shaderObj = glCreateShader( shaderStage )
    glShaderSource( shaderObj, sourceCode )
    glCompileShader( shaderObj )
    result = glGetShaderiv( shaderObj, GL_COMPILE_STATUS )
    if not (result):
        print( glGetShaderInfoLog( shaderObj ) )
        sys.exit()
    return shaderObj

# linke shader objects to shader program
def LinkProgram( shaderObjs ):
    shaderProgram = glCreateProgram()
    for shObj in shaderObjs:
        glAttachShader( shaderProgram, shObj )
    glLinkProgram( shaderProgram )
    result = glGetProgramiv( shaderProgram, GL_LINK_STATUS )
    if not (result):
        print( 'link error:' )
        print( glGetProgramInfoLog( shaderProgram ) )
        sys.exit()
    return shaderProgram

# create vertex array object
def CreateVAO( dataArrays ):
    noOfBuffers = len(dataArrays)
    buffers = glGenBuffers(noOfBuffers)
    newVAObj = glGenVertexArrays( 1 )
    glBindVertexArray( newVAObj )
    for inx in range(0, noOfBuffers):
        vertexSize, dataArr = dataArrays[inx]
        arr = np.array( dataArr, dtype='float32' )
        glBindBuffer( GL_ARRAY_BUFFER, buffers[inx] )
        glBufferData( GL_ARRAY_BUFFER, arr, GL_STATIC_DRAW )
        glEnableVertexAttribArray( inx )
        glVertexAttribPointer( inx, vertexSize, GL_FLOAT, GL_FALSE, 0, None )
    return newVAObj 

# representation of a uniform block
class UniformBlock:
     def __init__(self, shaderProg, name):
        self.shaderProg = shaderProg 
        self.name = name
     def Link(self, bindingPoint):
        self.bindingPoint = bindingPoint
        self.noOfUniforms = glGetProgramiv(self.shaderProg, GL_ACTIVE_UNIFORMS)
        self.maxUniformNameLen = glGetProgramiv(self.shaderProg, GL_ACTIVE_UNIFORM_MAX_LENGTH)
        self.index = glGetUniformBlockIndex(self.shaderProg, self.name)
        intData = np.zeros(1, dtype=int)
        glGetActiveUniformBlockiv(self.shaderProg, self.index, GL_UNIFORM_BLOCK_ACTIVE_UNIFORMS, intData)
        self.count = intData[0]
        self.indices = np.zeros(self.count, dtype=int)
        glGetActiveUniformBlockiv(self.shaderProg, self.index, GL_UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES, self.indices)
        self.offsets = np.zeros(self.count, dtype=int)
        glGetActiveUniformsiv(self.shaderProg, self.count, self.indices, GL_UNIFORM_OFFSET, self.offsets)
        strLengthData = np.zeros(1, dtype=int)
        arraysizeData = np.zeros(1, dtype=int)
        typeData = np.zeros(1, dtype='uint32')
        nameData = np.chararray(self.maxUniformNameLen+1)
        self.namemap = {}
        self.dataSize = 0 
        for inx in range(0, len(self.indices)):
            glGetActiveUniform( self.shaderProg, self.indices[inx], self.maxUniformNameLen, strLengthData, arraysizeData,     typeData, nameData.data )
            name = nameData.tostring()[:strLengthData[0]]
            self.namemap[name] = inx
            self.dataSize = max(self.dataSize, self.offsets[inx] + arraysizeData * 16) 
        glUniformBlockBinding(self.shaderProg, self.index, self.bindingPoint)
        print('\nuniform block %s size:%4d' % (self.name, self.dataSize))
        for uName in self.namemap:
            print( '    %-40s index:%2d    offset:%4d' % (uName, self.indices[self.namemap[uName]], self.offsets    [self.namemap[uName]]) ) 

# representation of a uniform block buffer
class UniformBlockBuffer:
    def __init__(self, ub):
        self.namemap = ub.namemap
        self.offsets = ub.offsets
        self.bindingPoint = ub.bindingPoint
        self.object = glGenBuffers(1)
        self.dataSize = ub.dataSize
        glBindBuffer(GL_UNIFORM_BUFFER, self.object)
        dataArray = np.zeros(self.dataSize//4, dtype='float32')
        glBufferData(GL_UNIFORM_BUFFER, self.dataSize, dataArray, GL_DYNAMIC_DRAW)
    def BindToTarget(self):
        glBindBuffer(GL_UNIFORM_BUFFER, self.object)
        glBindBufferBase(GL_UNIFORM_BUFFER, self.bindingPoint, self.object)
    def BindDataFloat(self, name, dataArr):
        glBindBuffer(GL_UNIFORM_BUFFER, self.object)
        dataArray = np.array(dataArr, dtype='float32')
        glBufferSubData(GL_UNIFORM_BUFFER, self.offsets[self.namemap[name]], len(dataArr)*4, dataArray)

def Translate(matA, trans):
    matB = np.copy(matA)
    for i in range(0, 4): matB[3,i] = matA[0,i] * trans[0] + matA[1,i] * trans[1] + matA[2,i] * trans[2] + matA[3,i] 
    return matB

def Scale(matA, s):
    matB = np.copy(matA)
    for i0 in range(0, 3):
        for i1 in range(0, 4): matB[i0,i1] = matA[i0,i1] * s[i0] 
    return matB

def RotateHlp(matA, angDeg, a0, a1):
    matB = np.copy(matA)
    ang = math.radians(angDeg)
    sinAng, cosAng = math.sin(ang), math.cos(ang)
    for i in range(0, 4):
        matB[a0,i] = matA[a0,i] * cosAng + matA[a1,i] * sinAng
        matB[a1,i] = matA[a0,i] * -sinAng + matA[a1,i] * cosAng
    return matB

def RotateX(matA, angDeg): return RotateHlp(matA, angDeg, 1, 2)
def RotateY(matA, angDeg): return RotateHlp(matA, angDeg, 2, 0)
def RotateZ(matA, angDeg): return RotateHlp(matA, angDeg, 0, 1)
def RotateView(matA, angDeg): return RotateZ(RotateY(RotateX(matA, angDeg[0]), angDeg[1]), angDeg[2])

def Multiply(matA, matB):
    matC = np.copy(matA)
    for i0 in range(0, 4):
        for i1 in range(0, 4):
            matC[i0,i1] = matB[i0,0] * matA[0,i1] + matB[i0,1] * matA[1,i1] + matB[i0,2] * matA[2,i1] + matB[i0,3] * matA    [3,i1]    
    return matC

def ToMat33(mat44):
    mat33 = np.matrix(np.identity(3), copy=False, dtype='float32')
    for i0 in range(0, 3):
        for i1 in range(0, 3): mat33[i0, i1] = mat44[i0, i1]
    return mat33

def TransformVec4(vecA,mat44):
    vecB = np.zeros(4, dtype='float32')
    for i0 in range(0, 4):
        vecB[i0] = vecA[0] * mat44[0,i0] + vecA[1] * mat44[1,i0] + vecA[2] * mat44[2,i0]  + vecA[3] * mat44[3,i0]
    return vecB

def Perspective(fov, aspectRatio, near, far):
    fn, f_n = far + near, far - near
    r, t = aspectRatio, 1.0 / math.tan( math.radians(fov) / 2.0 )
    return np.matrix( [ [t/r,0,0,0], [0,t,0,0], [0,0,-fn/f_n,-2.0*far*near/f_n], [0,0,-1,0] ] )

def AddToBuffer( buffer, data, count=1 ): 
    for inx_c in range(0, count):
        for inx_s in range(0, len(data)): buffer.append( data[inx_s] ) 

# initialize glut
glutInit()

# create window
wndW, wndH = 800, 600
glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_ALPHA | GLUT_DEPTH)
glutInitWindowPosition(0, 0)
glutInitWindowSize(wndW, wndH)
wndID = glutCreateWindow(b'OGL window') 
glutDisplayFunc(OnDraw) 
glutIdleFunc(OnDraw)

# define location vertex array opject
pointVAObj = CreateVAO( [ (3, [0.0, 0.0, 0.0] ), (3, [0.0, 0.0, -1.0]), (3, [1.0, 0.0, 0.0]) ] )

# load, compile and link shader
shaderProgram = LinkProgram( [
        CompileShader( 'geo.vert', GL_VERTEX_SHADER ), 
        CompileShader( 'geo.geo', GL_GEOMETRY_SHADER ), 
        CompileShader( 'geo.frag', GL_FRAGMENT_SHADER )
    ] )
# get unifor locations
projectionMatLocation = glGetUniformLocation(shaderProgram, "u_projectionMat44")
viewMatLocation       = glGetUniformLocation(shaderProgram, "u_viewMat44")
modelMatLocation      = glGetUniformLocation(shaderProgram, "u_modelMat44")
# linke uniform blocks
ubMaterial = UniformBlock(shaderProgram, "UB_material")
ubLightSource = UniformBlock(shaderProgram, "UB_lightSource")
ubMaterial.Link(1)
ubLightSource.Link(2)

# create uniform block buffers
lightSourceBuffer = UniformBlockBuffer(ubLightSource)
lightSourceBuffer.BindDataFloat(b'u_lightSource.ambient', [0.2, 0.2, 0.2, 1.0])
lightSourceBuffer.BindDataFloat(b'u_lightSource.diffuse', [0.2, 0.2, 0.2, 1.0])
lightSourceBuffer.BindDataFloat(b'u_lightSource.specular', [1.0, 1.0, 1.0, 1.0])

materialBuffer = UniformBlockBuffer(ubMaterial)
materialBuffer.BindDataFloat(b'u_roughness', [0.5])
materialBuffer.BindDataFloat(b'u_fresnel0', [0.2])
materialBuffer.BindDataFloat(b'u_specularTint',[1.0, 0.5, 0.5, 0.8])

# start main loop
startTime = time()
glutMainLoop()

Переключение геометрии и представления поверхности с помощью подпрограмм в OGL 4.0 GLSL

Простая программа шейдеров шейдеров OGL 4.0 GLSL, которая показывает использование шейдерных подпрограмм. Программа выполняется с помощью phyton-скрипта. Для запуска скрипта необходимо установить PyOpenGL и NumPy.

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

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

subr.vert

#version 400

layout (location = 0) in vec3 inPos;
layout (location = 1) in vec3 inNormal;
layout (location = 2) in vec3 inTangent;

out TVertexData
{
    mat3 orientationMat;
} outData;

void main()
{
    vec3 normal   = normalize( inNormal );
    vec3 tangent  = normalize( inTangent );
    vec3 binormal = cross( tangent, normal );
    
    outData.orientationMat = mat3( normal, cross( binormal, normal ), binormal );
    gl_Position = vec4( inPos, 1.0 );
}

Геометрический шейдер

subr.geo

#version 400

layout( points ) in;
layout( triangle_strip, max_vertices = 512 ) out;

in TVertexData
{
    mat3 orientationMat;
} inData[];

out TGeometryData
{
    vec3 pos;
    vec3 nv;
    vec2 tex;
} outData;

uniform mat4 u_projectionMat44;
uniform mat4 u_viewMat44;
uniform mat4 u_modelMat44;
uniform mat4 u_textureMat44;

void SetTextureCoord( in vec2 tecCoord )
{
    vec4 tex = u_textureMat44 * vec4( tecCoord, 0.0, 1.0 );
    outData.tex = tex.xy;
}

void NewVertex( in vec3 pt, in mat4 transMat )
{
    vec4 viewPos = transMat * vec4( pt, 1.0 );
    outData.pos = viewPos.xyz / viewPos.w;
    gl_Position = u_projectionMat44 * viewPos;
    EmitVertex();
}

void NewVertexAndTex( in vec3 pt, in mat4 transMat )
{
    SetTextureCoord( pt.xy * 0.5 + 0.5 );
    NewVertex( pt, transMat ); 
}

void NewVertexNvTex( in vec3 pt, in mat4 transMat, in vec3 nv, in vec2 tex )
{
    outData.nv = nv;
    SetTextureCoord( tex );
    vec4 viewPos = transMat * vec4( pt, 1.0 );
    outData.pos = viewPos.xyz / viewPos.w;
    gl_Position = u_projectionMat44 * viewPos;
    EmitVertex();
}

subroutine void TShape( in mat4 );
subroutine uniform TShape su_shape;

void main()
{
    vec4 origin = gl_in[0].gl_Position;
    origin /= origin.w;
    mat4 orintationMat = mat4( vec4( inData[0].orientationMat[0], 0.0 ),
                               vec4( inData[0].orientationMat[1], 0.0 ),
                               vec4( inData[0].orientationMat[2], 0.0 ),
                               origin );
    mat4 modelMat = u_modelMat44 * orintationMat;

    su_shape( modelMat );
}

subroutine(TShape) void DrawSphere( in mat4 modelMat )
{           
    const int circumferenceTile = 18;
    const int layersTile        = 11;

    mat4 modelViewMat = u_viewMat44 * modelMat;
    mat3 normalMat    = mat3( modelViewMat );

    float preStepLay = 0.0; 
    vec2  prePtLay   = vec2( 0.0, -1.0 );
    for ( int inxLay = 1; inxLay <= layersTile; ++ inxLay )
    {
        float stepLay = float(inxLay) / float(layersTile);
        float angLay  = 3.14159 * stepLay;
        vec2  ptLay   = vec2( sin(angLay), -cos(angLay) );

        float preStepCir = 0.0; 
        vec2  prePtCir   = vec2( 0.0, 1.0 );
        for ( int inxCir = 0; inxCir <= circumferenceTile; ++ inxCir )
        {    
            float stepCir = float(inxCir) / float(circumferenceTile);
            float angCir  = 2.0 * 3.14159 * stepCir;
            vec2  ptCir   = vec2( sin(angCir), cos(angCir) );

            if ( inxLay == 1 )
            {
                if ( inxCir >= 0 )
                {
                    vec3 pt1 = vec3( ptLay.x * prePtCir.x, ptLay.x * prePtCir.y, ptLay.y );
                    vec3 pt2 = vec3( 0.0, 0.0, -1.0 );
                    vec3 pt3 = vec3( ptLay.x * ptCir.x, ptLay.x * ptCir.y, ptLay.y );
                    NewVertexNvTex( pt1, modelViewMat, normalMat * pt1, vec2( preStepCir * 2.0, stepLay ) );
                    NewVertexNvTex( pt2, modelViewMat, normalMat * pt2, vec2( preStepCir + stepCir, preStepLay )  );   
                    NewVertexNvTex( pt3, modelViewMat, normalMat * pt3, vec2( stepCir * 2.0, stepLay )  ); 
                    EndPrimitive();
                }  
            }
            else if ( inxLay == layersTile )
            {
                if ( inxCir > 0 )
                {
                    vec3 pt1 = vec3( prePtLay.x * prePtCir.x, prePtLay.x * prePtCir.y, prePtLay.y );
                    vec3 pt2 = vec3( prePtLay.x * ptCir.x, prePtLay.x * ptCir.y, prePtLay.y );
                    vec3 pt3 = vec3( 0.0, 0.0, 1.0 );
                    NewVertexNvTex( pt1, modelViewMat, normalMat * pt1, vec2( preStepCir * 2.0, preStepLay ) );
                    NewVertexNvTex( pt2, modelViewMat, normalMat * pt2, vec2( stepCir * 2.0, preStepLay )  );   
                    NewVertexNvTex( pt3, modelViewMat, normalMat * pt3, vec2( preStepCir + stepCir, stepLay )  ); 
                    EndPrimitive();
                }    
            }
            else
            {
                vec3 pt1 = vec3( prePtLay.x * ptCir.x, prePtLay.x * ptCir.y, prePtLay.y );
                vec3 pt2 = vec3( ptLay.x * ptCir.x, ptLay.x * ptCir.y, ptLay.y );
                NewVertexNvTex( pt1, modelViewMat, normalMat * pt1, vec2( stepCir * 2.0, preStepLay ) );
                NewVertexNvTex( pt2, modelViewMat, normalMat * pt2, vec2( stepCir * 2.0, stepLay )  );
            }

            preStepCir = stepCir;
            prePtCir   = ptCir;
        }
        if ( inxLay > 1 && inxLay < layersTile )
            EndPrimitive();
  
        preStepLay = stepLay;
        prePtLay   = ptLay;
    }
}

subroutine(TShape) void DrawTorus( in mat4 modelMat )
{
    const int   circumferenceTile = 12;
    const int   layersTile        = 18;
    const float torusRad          = 0.8;
    const float ringRad           = 0.4;

    mat4 modelViewMat = u_viewMat44 * modelMat;
    mat3 normalMat    = mat3( modelViewMat );

    float preStepLay = 0.0; 
    mat4  prePosMat;
    for ( int inxLay = 0; inxLay <= layersTile; ++ inxLay )
    {
        float stepLay = float(inxLay) / float(layersTile);
        float angLay  = 2.0 * 3.14159 * stepLay;
        mat4  posMat = mat4( 
            vec4( cos(angLay), sin(angLay), 0.0, 0.0 ),
            vec4( sin(angLay), cos(angLay), 0.0, 0.0 ),
            vec4( 0.0, 0.0, 1.0, 0.0 ),
            vec4( cos(angLay) * torusRad, sin(angLay) * torusRad, 0.0, 1.0 ) );
        
        for ( int inxCir = 0; inxLay > 0 && inxCir <= circumferenceTile; ++ inxCir )
        {    
            float stepCir = float(inxCir) / float(circumferenceTile);
            float angCir  = 2.0 * 3.14159 * stepCir;
            vec2  ptCir   = vec2( sin(angCir), cos(angCir) );

            vec4 tempPt = vec4( ptCir.x * ringRad, 0.0, ptCir.y * ringRad, 1.0 );
            vec4 pt1 = prePosMat * tempPt;
            vec4 pt2 = posMat * tempPt;
            NewVertexNvTex( pt1.xyz, modelViewMat, normalMat * normalize(pt1.xyz - prePosMat[3].xyz), vec2(stepCir,     preStepLay*2.0) );
            NewVertexNvTex( pt2.xyz, modelViewMat, normalMat * normalize(pt2.xyz - posMat[3].xyz), vec2(stepCir, stepLay*2.0)      );
        }
        EndPrimitive();
  
        preStepLay = stepLay;
        prePosMat  = posMat;
    }
}

Фрагментный шейдер

subr.frag

#version 400

in TGeometryData
{
    vec3 pos;
    vec3 nv;
    vec2 tex;
} inData;

out vec4 fragColor;

uniform sampler2D u_texture;

uniform UB_material
{
    float u_roughness;
    float u_fresnel0;
    vec4  u_color;
    vec4  u_specularTint;
};

struct TLightSource
{
    vec4 ambient;
    vec4 diffuse;
    vec4 specular;
    vec4 dir;
};

uniform UB_lightSource
{
    TLightSource u_lightSource;
};

subroutine vec4 TSurface( void );
subroutine uniform TSurface su_surface;

float Fresnel_Schlick( in float theta );
vec3 LightModel( in vec3 esPt, in vec3 esPtNV, in vec3 col, in vec4 specularTint, in float roughness, in float fresnel0 );

void main()
{
    vec4 fragCol = su_surface();
    vec3 lightCol = LightModel( inData.pos, inData.nv, fragCol.rgb, u_specularTint, u_roughness, u_fresnel0 );
    
    fragColor = vec4( clamp( lightCol, 0.0, 1.0 ), fragCol.a );
}

subroutine(TSurface) vec4 SurfaceColor( void )
{
  return u_color;
} 

subroutine(TSurface) vec4 SurfaceTexture( void )
{
  return texture( u_texture, inData.tex.st );
} 

float Fresnel_Schlick( in float theta )
{
    float m = clamp( 1.0 - theta, 0.0, 1.0 );
    float m2 = m * m;
    return m2 * m2 * m; // pow( m, 5.0 )
}

vec3 LightModel( in vec3 esPt, in vec3 esPtNV, in vec3 col, in vec4 specularTint, in float roughness, in float fresnel0 )
{
  vec3  esVLight      = normalize( -u_lightSource.dir.xyz );
  vec3  esVEye        = normalize( -esPt );
  vec3  halfVector    = normalize( esVEye + esVLight );
  float HdotL         = dot( halfVector, esVLight );
  float NdotL         = dot( esPtNV, esVLight );
  float NdotV         = dot( esPtNV, esVEye );
  float NdotH         = dot( esPtNV, halfVector );
  float NdotH2        = NdotH * NdotH;
  float NdotL_clamped = max( NdotL, 0.0 );
  float NdotV_clamped = max( NdotV, 0.0 );
  float m2            = roughness * roughness;
  
  // Lambertian diffuse
  float k_diffuse = NdotL_clamped;
  // Schlick approximation
  float fresnel = fresnel0 + ( 1.0 - fresnel0 ) * Fresnel_Schlick( HdotL );
  // Beckmann distribution
  float distribution = max( 0.0, exp( ( NdotH2 - 1.0 ) / ( m2 * NdotH2 ) ) / ( 3.14159265 * m2 * NdotH2 * NdotH2 ) );
  // Torrance-Sparrow geometric term
  float geometric_att = min( 1.0, min( 2.0 * NdotH * NdotV_clamped / HdotL, 2.0 * NdotH * NdotL_clamped / HdotL ) );
  // Microfacet bidirectional reflectance distribution function 
  float k_specular = fresnel * distribution * geometric_att / ( 4.0 * NdotL_clamped * NdotV_clamped );
  
  vec3 lightColor = col.rgb * u_lightSource.ambient.rgb +
                    max( 0.0, k_diffuse ) * col.rgb * u_lightSource.diffuse.rgb +
                    max( 0.0, k_specular ) * mix( col.rgb, specularTint.rgb, specularTint.a ) * u_lightSource.specular.rgb;
  return lightColor;
}

Скрипт Phyton

from OpenGL.GL import *
from OpenGL.GLUT import *
from OpenGL.GLU import *
import numpy as np
from time import time
import math
import sys

sin120 = 0.8660254
rotateCamera = False

# draw event
def OnDraw():
    dist = 3.0
    currentTime = time()
    comeraRotAng = CalcAng( currentTime, 10.0 ) 
    # set up projection matrix
    prjMat = Perspective(90.0, wndW/wndH, 0.5, 100.0) 
    # set up view matrix
    viewMat = np.matrix(np.identity(4), copy=False, dtype='float32')
    viewMat = Translate( viewMat, np.array( [0.0, 0.0, -14.0] ) )
    viewMat = RotateView( viewMat, [30.0, comeraRotAng if rotateCamera else 0.0, 0.0] )

    # set up light source
    lightSourceBuffer.BindDataFloat(b'u_lightSource.dir', TransformVec4([-1.0, -1.0, -5.0, 0.0], viewMat) )
    
    # set up model matrices
    modelMat = []
    for inx in range(0, 2):
        modelMat.append( np.matrix(np.identity(4), copy=False, dtype='float32') )
        if not rotateCamera: modelMat[inx] = RotateY( modelMat[inx], comeraRotAng )
    
    modelMat[0] = Scale( modelMat[0], np.repeat( 3, 3 ) )
    modelMat[0] = Translate( modelMat[0], np.array( [0.0, 0.0, -2.0] ) )
    modelMat[0] = RotateY( modelMat[0], CalcAng( currentTime, 23.0 ) )
    modelMat[0] = RotateX( modelMat[0], CalcAng( currentTime, 13.0 ) )
    
    modelMat[1] = Scale( modelMat[1], np.repeat( 3, 3 ) )
    modelMat[1] = Translate( modelMat[1], np.array( [0.0, 0.0, 2.0] ) )
    modelMat[1] = RotateY( modelMat[1], CalcAng( currentTime, 17.0 ) )
    modelMat[1] = RotateX( modelMat[1], CalcAng( currentTime, 9.0 ) )

    # set up texture matrix
    texMat = np.matrix(np.identity(4), copy=False, dtype='float32')
 
    # set up attributes and shader program
    glEnable( GL_DEPTH_TEST )
    glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT )
    glUseProgram( shaderProgram )
    glUniformMatrix4fv( projectionMatLocation, 1, GL_FALSE, prjMat )
    glUniformMatrix4fv( viewMatLocation, 1, GL_FALSE, viewMat )
    glUniformMatrix4fv( textureMatLocation, 1, GL_FALSE, texMat )
    glUniform1i( textureLocation, 0 )
    lightSourceBuffer.BindToTarget()
    
    # draw points
    glBindVertexArray( pointVAObj )
    for inx in range(0, 2):
        # set up geometry shader subroutine
        shape = 1 if inx==0 else 0 # 0: sphere, 1: torus 
        glUniformSubroutinesuiv(GL_GEOMETRY_SHADER, 1, np.array( [shape], dtype='uint' ))
        # set up fragment shader subroutine
        surfaceKind = inx # 0: color, 1: texture
        glUniformSubroutinesuiv(GL_FRAGMENT_SHADER, 1, np.array( [surfaceKind], dtype='uint' ))
       
        materialBuffer[inx].BindToTarget()
        glUniformMatrix4fv( modelMatLocation, 1, GL_FALSE, modelMat[inx] )
        glDrawArrays( GL_POINTS, 0, 1 )
    
    glutSwapBuffers()

def Fract(val): return val - math.trunc(val)
def CalcAng(currentTime, intervall): return Fract( (currentTime - startTime) / intervall ) * 360.0
def CalcMove(currentTime, intervall, range):
    pos = Fract( (currentTime - startTime) / intervall ) * 2.0
    pos = pos if pos < 1.0 else (2.0-pos)
    return range[0] + (range[1] - range[0]) * pos
    
# read shader program and compile shader
def CompileShader( sourceFileName, shaderStage ):
    with open( sourceFileName, 'r' ) as sourceFile:
        sourceCode = sourceFile.read()
    nameMap = { GL_VERTEX_SHADER: 'vertex', GL_GEOMETRY_SHADER: 'geometry', GL_FRAGMENT_SHADER: 'fragment' }    
    print( '\n%s shader code:' % nameMap.get(shaderStage, '') )
    print( sourceCode )
    shaderObj = glCreateShader( shaderStage )
    glShaderSource( shaderObj, sourceCode )
    glCompileShader( shaderObj )
    result = glGetShaderiv( shaderObj, GL_COMPILE_STATUS )
    if not (result):
        print( glGetShaderInfoLog( shaderObj ) )
        sys.exit()
    return shaderObj

# linke shader objects to shader program
def LinkProgram( shaderObjs ):
    shaderProgram = glCreateProgram()
    for shObj in shaderObjs:
        glAttachShader( shaderProgram, shObj )
    glLinkProgram( shaderProgram )
    result = glGetProgramiv( shaderProgram, GL_LINK_STATUS )
    if not (result):
        print( 'link error:' )
        print( glGetProgramInfoLog( shaderProgram ) )
        sys.exit()
    return shaderProgram

# create vertex array object
def CreateVAO( dataArrays ):
    noOfBuffers = len(dataArrays)
    buffers = glGenBuffers(noOfBuffers)
    newVAObj = glGenVertexArrays( 1 )
    glBindVertexArray( newVAObj )
    for inx in range(0, noOfBuffers):
        vertexSize, dataArr = dataArrays[inx]
        arr = np.array( dataArr, dtype='float32' )
        glBindBuffer( GL_ARRAY_BUFFER, buffers[inx] )
        glBufferData( GL_ARRAY_BUFFER, arr, GL_STATIC_DRAW )
        glEnableVertexAttribArray( inx )
        glVertexAttribPointer( inx, vertexSize, GL_FLOAT, GL_FALSE, 0, None )
    return newVAObj 

# representation of a uniform block
class UniformBlock:
     def __init__(self, shaderProg, name):
        self.shaderProg = shaderProg 
        self.name = name
     def Link(self, bindingPoint):
        self.bindingPoint = bindingPoint
        self.noOfUniforms = glGetProgramiv(self.shaderProg, GL_ACTIVE_UNIFORMS)
        self.maxUniformNameLen = glGetProgramiv(self.shaderProg, GL_ACTIVE_UNIFORM_MAX_LENGTH)
        self.index = glGetUniformBlockIndex(self.shaderProg, self.name)
        intData = np.zeros(1, dtype=int)
        glGetActiveUniformBlockiv(self.shaderProg, self.index, GL_UNIFORM_BLOCK_ACTIVE_UNIFORMS, intData)
        self.count = intData[0]
        self.indices = np.zeros(self.count, dtype=int)
        glGetActiveUniformBlockiv(self.shaderProg, self.index, GL_UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES, self.indices)
        self.offsets = np.zeros(self.count, dtype=int)
        glGetActiveUniformsiv(self.shaderProg, self.count, self.indices, GL_UNIFORM_OFFSET, self.offsets)
        strLengthData = np.zeros(1, dtype=int)
        arraysizeData = np.zeros(1, dtype=int)
        typeData = np.zeros(1, dtype='uint32')
        nameData = np.chararray(self.maxUniformNameLen+1)
        self.namemap = {}
        self.dataSize = 0 
        for inx in range(0, len(self.indices)):
            glGetActiveUniform( self.shaderProg, self.indices[inx], self.maxUniformNameLen, strLengthData, arraysizeData,     typeData, nameData.data )
            name = nameData.tostring()[:strLengthData[0]]
            self.namemap[name] = inx
            self.dataSize = max(self.dataSize, self.offsets[inx] + arraysizeData * 16) 
        glUniformBlockBinding(self.shaderProg, self.index, self.bindingPoint)
        print('\nuniform block %s size:%4d' % (self.name, self.dataSize))
        for uName in self.namemap:
            print( '    %-40s index:%2d    offset:%4d' % (uName, self.indices[self.namemap[uName]], self.offsets[self.namemap    [uName]]) ) 

# representation of a uniform block buffer
class UniformBlockBuffer:
    def __init__(self, ub):
        self.namemap = ub.namemap
        self.offsets = ub.offsets
        self.bindingPoint = ub.bindingPoint
        self.object = glGenBuffers(1)
        self.dataSize = ub.dataSize
        glBindBuffer(GL_UNIFORM_BUFFER, self.object)
        dataArray = np.zeros(self.dataSize//4, dtype='float32')
        glBufferData(GL_UNIFORM_BUFFER, self.dataSize, dataArray, GL_DYNAMIC_DRAW)
    def BindToTarget(self):
        glBindBuffer(GL_UNIFORM_BUFFER, self.object)
        glBindBufferBase(GL_UNIFORM_BUFFER, self.bindingPoint, self.object)
    def BindDataFloat(self, name, dataArr):
        glBindBuffer(GL_UNIFORM_BUFFER, self.object)
        dataArray = np.array(dataArr, dtype='float32')
        glBufferSubData(GL_UNIFORM_BUFFER, self.offsets[self.namemap[name]], len(dataArr)*4, dataArray)

def Translate(matA, trans):
    matB = np.copy(matA)
    for i in range(0, 4): matB[3,i] = matA[0,i] * trans[0] + matA[1,i] * trans[1] + matA[2,i] * trans[2] + matA[3,i] 
    return matB

def Scale(matA, s):
    matB = np.copy(matA)
    for i0 in range(0, 3):
        for i1 in range(0, 4): matB[i0,i1] = matA[i0,i1] * s[i0] 
    return matB

def RotateHlp(matA, angDeg, a0, a1):
    matB = np.copy(matA)
    ang = math.radians(angDeg)
    sinAng, cosAng = math.sin(ang), math.cos(ang)
    for i in range(0, 4):
        matB[a0,i] = matA[a0,i] * cosAng + matA[a1,i] * sinAng
        matB[a1,i] = matA[a0,i] * -sinAng + matA[a1,i] * cosAng
    return matB

def RotateX(matA, angDeg): return RotateHlp(matA, angDeg, 1, 2)
def RotateY(matA, angDeg): return RotateHlp(matA, angDeg, 2, 0)
def RotateZ(matA, angDeg): return RotateHlp(matA, angDeg, 0, 1)
def RotateView(matA, angDeg): return RotateZ(RotateY(RotateX(matA, angDeg[0]), angDeg[1]), angDeg[2])

def Multiply(matA, matB):
    matC = np.copy(matA)
    for i0 in range(0, 4):
        for i1 in range(0, 4):
            matC[i0,i1] = matB[i0,0] * matA[0,i1] + matB[i0,1] * matA[1,i1] + matB[i0,2] * matA[2,i1] + matB[i0,3] * matA[3,i1]        
    return matC

def ToMat33(mat44):
    mat33 = np.matrix(np.identity(3), copy=False, dtype='float32')
    for i0 in range(0, 3):
        for i1 in range(0, 3): mat33[i0, i1] = mat44[i0, i1]
    return mat33

def TransformVec4(vecA,mat44):
    vecB = np.zeros(4, dtype='float32')
    for i0 in range(0, 4):
        vecB[i0] = vecA[0] * mat44[0,i0] + vecA[1] * mat44[1,i0] + vecA[2] * mat44[2,i0]  + vecA[3] * mat44[3,i0]
    return vecB

def Perspective(fov, aspectRatio, near, far):
    fn, f_n = far + near, far - near
    r, t = aspectRatio, 1.0 / math.tan( math.radians(fov) / 2.0 )
    return np.matrix( [ [t/r,0,0,0], [0,t,0,0], [0,0,-fn/f_n,-2.0*far*near/f_n], [0,0,-1,0] ] )

def AddToBuffer( buffer, data, count=1 ): 
    for inx_c in range(0, count):
        for inx_s in range(0, len(data)): buffer.append( data[inx_s] ) 

# initialize glut
glutInit()

# create window
wndW, wndH = 800, 600
glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_ALPHA | GLUT_DEPTH)
glutInitWindowPosition(0, 0)
glutInitWindowSize(wndW, wndH)
wndID = glutCreateWindow(b'OGL window') 
glutDisplayFunc(OnDraw) 
glutIdleFunc(OnDraw)

# define location vertex array opject
pointVAObj = CreateVAO( [ (3, [0.0, 0.0, 0.0] ), (3, [0.0, 0.0, 1.0]), (3, [1.0, 0.0, 0.0]) ] )

# create texture
texCX, texCY = 128, 128
texPlan = np.zeros( texCX * texCY * 4, dtype=np.uint8 )
for inx_x in range(0, texCX):
    for inx_y in range(0, texCY):
        val_x = math.sin( math.pi * 6.0 * inx_x / texCX )
        val_y = math.sin( math.pi * 6.0 * inx_y / texCY )
        inx_tex = inx_y * texCX * 4 + inx_x * 4
        texPlan[inx_tex + 0] = int( 128 + 127 * val_x )
        texPlan[inx_tex + 1] = 63
        texPlan[inx_tex + 2] = int( 128 + 127 * val_y )
        texPlan[inx_tex + 3] = 255
glActiveTexture( GL_TEXTURE0 )
texObj = glGenTextures( 1  )
glBindTexture( GL_TEXTURE_2D, texObj )
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, texCX, texCY, 0, GL_RGBA, GL_UNSIGNED_BYTE, texPlan)
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT)
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT) 

# load, compile and link shader
shaderProgram = LinkProgram( [
        CompileShader( 'python/ogl4subr/subr.vert', GL_VERTEX_SHADER ), 
        CompileShader( 'python/ogl4subr/subr.geo', GL_GEOMETRY_SHADER ), 
        CompileShader( 'python/ogl4subr/subr.frag', GL_FRAGMENT_SHADER )
    ] )
# get unifor locations
projectionMatLocation = glGetUniformLocation(shaderProgram, "u_projectionMat44")
viewMatLocation       = glGetUniformLocation(shaderProgram, "u_viewMat44")
modelMatLocation      = glGetUniformLocation(shaderProgram, "u_modelMat44")
textureMatLocation    = glGetUniformLocation(shaderProgram, "u_textureMat44")
textureLocation       = glGetUniformLocation(shaderProgram, "u_texture")
# linke uniform blocks
ubMaterial = UniformBlock(shaderProgram, "UB_material")
ubLightSource = UniformBlock(shaderProgram, "UB_lightSource")
ubMaterial.Link(1)
ubLightSource.Link(2)

# create uniform block buffers
lightSourceBuffer = UniformBlockBuffer(ubLightSource)
lightSourceBuffer.BindDataFloat(b'u_lightSource.ambient', [0.2, 0.2, 0.2, 1.0])
lightSourceBuffer.BindDataFloat(b'u_lightSource.diffuse', [0.2, 0.2, 0.2, 1.0])
lightSourceBuffer.BindDataFloat(b'u_lightSource.specular', [1.0, 1.0, 1.0, 1.0])

materialBuffer = [ UniformBlockBuffer(ubMaterial), UniformBlockBuffer(ubMaterial) ]

materialBuffer[0].BindDataFloat(b'u_roughness', [0.45])
materialBuffer[0].BindDataFloat(b'u_fresnel0', [0.45])
materialBuffer[0].BindDataFloat(b'u_color', [0.5, 0.7, 0.6, 1.0])
materialBuffer[0].BindDataFloat(b'u_specularTint',[1.0, 0.5, 0.5, 0.8])

materialBuffer[1].BindDataFloat(b'u_roughness', [0.4])
materialBuffer[1].BindDataFloat(b'u_fresnel0', [0.4])
materialBuffer[1].BindDataFloat(b'u_color', [0.7, 0.5, 0.6, 1.0])
materialBuffer[1].BindDataFloat(b'u_specularTint',[0.5, 1.0, 0.5, 0.8])

# start main loop
startTime = time()
glutMainLoop()

Изменение геометрии с помощью тесселяционных шейдеров в OGL 4.0 GLSL

Простая программа шейдеров шейдеров OGL 4.0 GLSL, которая показывает, как добавить детали с помощью тесселяционного шейдера в геометрию. Программа выполняется с помощью скрипта python. Для запуска скрипта необходимо установить PyOpenGL и NumPy.

Основной сеткой в ​​этом примере является икосаэдр, состоящий из 20 треугольников. Шейдер управления тесселяцией определяет, как каждый треугольник делится на множество мелких деталей. При тесселяции треугольника сгенерированные данные являются барицентрическими координатами на основе исходного треугольника. Шейдер оценки тесселяции генерирует новую геометрию из данных, полученных таким образом. В этом примере каждый треугольник получает пик посередине, который поднимается наружу от центра икосадера. Таким образом создается гораздо более сложная геометрия, чем исходный икосаэдр.

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

tess.vert

layout (location = 0) in vec3 inPos;
layout (location = 1) in vec3 inNV;

out TVertexData
{
    vec3 pos;
    vec3 nv;
} outData;

uniform mat4 u_projectionMat44;
uniform mat4 u_modelViewMat44;
uniform mat3 u_normalMat33;

void main()
{
    vec4 viewPos = u_modelViewMat44 * vec4( inPos, 1.0 );
    
    outData.pos = viewPos.xyz / viewPos.w;
    outData.nv  = u_normalMat33 * normalize( inNV );
    
    gl_Position = u_projectionMat44 * viewPos;
}

Шейдер управления тесселяцией

tess.tctrl

#version 400

layout( vertices=3 ) out;

in TVertexData
{
    vec3 pos;
    vec3 nv;
} inData[];

out TVertexData
{
    vec3 pos;
    vec3 nv;
} outData[];

void main()
{
    outData[gl_InvocationID].pos = inData[gl_InvocationID].pos;
    outData[gl_InvocationID].nv  = inData[gl_InvocationID].nv;
  
    if ( gl_InvocationID == 0 )
    {
        gl_TessLevelOuter[0] = 10.0;
        gl_TessLevelOuter[1] = 10.0;
        gl_TessLevelOuter[2] = 10.0;
        gl_TessLevelInner[0] = 10.0;
    }
}

Шейдер оценки оценки тесселяции

tess.teval

#version 400

layout(triangles, equal_spacing, ccw) in;

in TVertexData
{
    vec3 pos;
    vec3 nv;
} inData[];

out TTessData
{
    vec3  pos;
    vec3  nv;
    float height;
} outData;

uniform mat4 u_projectionMat44;

void main()
{
    float sideLen[3] = float[3]
    (
        length( inData[1].pos - inData[0].pos ),
        length( inData[2].pos - inData[1].pos ),
        length( inData[0].pos - inData[2].pos )
    );
    float s = ( sideLen[0] + sideLen[1] + sideLen[2] ) / 2.0;
    float rad = sqrt( (s - sideLen[0]) * (s - sideLen[1]) * (s - sideLen[2]) / s );

    vec3 cpt = ( inData[0].pos + inData[1].pos + inData[2].pos ) / 3.0;
    vec3 pos = inData[0].pos * gl_TessCoord.x + inData[1].pos * gl_TessCoord.y + inData[2].pos * gl_TessCoord.z;
    vec3 nv  = normalize( inData[0].nv * gl_TessCoord.x + inData[1].nv * gl_TessCoord.y + inData[2].nv * gl_TessCoord.z );

    float cptDist      = length( cpt - pos );
    float sizeRelation = 1.0 - min( rad, cptDist ) / rad; 
    float height       = pow( sizeRelation, 2.0 );

    outData.pos    = pos + nv * height * rad;
    outData.nv     = mix( nv, normalize( pos - cpt ), height );
    outData.height = height;

    gl_Position = u_projectionMat44 * vec4( outData.pos, 1.0 );
}

Фрагментный шейдер

tess.frag

#version 400

in TTessData
{
    vec3  pos;
    vec3  nv;
    float height;
} inData;

out vec4 fragColor;

uniform sampler2D u_texture;

uniform UB_material
{
    float u_roughness;
    float u_fresnel0;
    vec4  u_color;
    vec4  u_specularTint;
};

struct TLightSource
{
    vec4 ambient;
    vec4 diffuse;
    vec4 specular;
    vec4 dir;
};

uniform UB_lightSource
{
    TLightSource u_lightSource;
};

float Fresnel_Schlick( in float theta );
vec3 LightModel( in vec3 esPt, in vec3 esPtNV, in vec3 col, in vec4 specularTint, in float roughness, in float fresnel0 );

void main()
{
    vec3 col = mix( u_color.rgb, vec3( 1.0, 1.0, 1.0 ), inData.height );
    vec3 lightCol = LightModel( inData.pos, inData.nv, col, u_specularTint, u_roughness, u_fresnel0 );
    fragColor = vec4( clamp( lightCol, 0.0, 1.0 ), 1.0 );
}

float Fresnel_Schlick( in float theta )
{
    float m = clamp( 1.0 - theta, 0.0, 1.0 );
    float m2 = m * m;
    return m2 * m2 * m; // pow( m, 5.0 )
}

vec3 LightModel( in vec3 esPt, in vec3 esPtNV, in vec3 col, in vec4 specularTint, in float roughness, in float fresnel0 )
{
  vec3  esVLight      = normalize( -u_lightSource.dir.xyz );
  vec3  esVEye        = normalize( -esPt );
  vec3  halfVector    = normalize( esVEye + esVLight );
  float HdotL         = dot( halfVector, esVLight );
  float NdotL         = dot( esPtNV, esVLight );
  float NdotV         = dot( esPtNV, esVEye );
  float NdotH         = dot( esPtNV, halfVector );
  float NdotH2        = NdotH * NdotH;
  float NdotL_clamped = max( NdotL, 0.0 );
  float NdotV_clamped = max( NdotV, 0.0 );
  float m2            = roughness * roughness;
  
  // Lambertian diffuse
  float k_diffuse = NdotL_clamped;
  // Schlick approximation
  float fresnel = fresnel0 + ( 1.0 - fresnel0 ) * Fresnel_Schlick( HdotL );
  // Beckmann distribution
  float distribution = max( 0.0, exp( ( NdotH2 - 1.0 ) / ( m2 * NdotH2 ) ) / ( 3.14159265 * m2 * NdotH2 * NdotH2 ) );
  // Torrance-Sparrow geometric term
  float geometric_att = min( 1.0, min( 2.0 * NdotH * NdotV_clamped / HdotL, 2.0 * NdotH * NdotL_clamped / HdotL ) );
  // Microfacet bidirectional reflectance distribution function 
  float k_specular = fresnel * distribution * geometric_att / ( 4.0 * NdotL_clamped * NdotV_clamped );
  
  vec3 lightColor = col.rgb * u_lightSource.ambient.rgb +
                    max( 0.0, k_diffuse ) * col.rgb * u_lightSource.diffuse.rgb +
                    max( 0.0, k_specular ) * mix( col.rgb, specularTint.rgb, specularTint.a ) * u_lightSource.specular.rgb;
  return lightColor;
}

Скрипт Python

from OpenGL.GL import *
from OpenGL.GLUT import *
from OpenGL.GLU import *
import numpy as np
from time import time
import math
import sys

sin120 = 0.8660254
rotateCamera = False

# draw event
def OnDraw():
    dist = 3.0
    currentTime = time()
    comeraRotAng = CalcAng( currentTime, 10.0 ) 
    # set up projection matrix
    prjMat = Perspective(90.0, wndW/wndH, 0.5, 100.0) 
    # set up view matrix
    viewMat = np.matrix(np.identity(4), copy=False, dtype='float32')
    viewMat = Translate( viewMat, np.array( [0.0, 0.0, -12.0] ) )
    viewMat = RotateView( viewMat, [30.0, comeraRotAng if rotateCamera else 0.0, 0.0] )

    # set up light source
    lightSourceBuffer.BindDataFloat(b'u_lightSource.dir', TransformVec4([-1.0, -1.0, -5.0, 0.0], viewMat) )
    
    # set up icosahedron model matrix
    icoModelMat = np.matrix(np.identity(4), copy=False, dtype='float32')
    if not rotateCamera: icoModelMat = RotateY( icoModelMat, comeraRotAng ) 
    icoModelMat = Scale( icoModelMat, np.repeat( 5, 3 ) )
    icoModelMat = RotateY( icoModelMat, CalcAng( currentTime, 17.0 ) )
    icoModelMat = RotateX( icoModelMat, CalcAng( currentTime, 13.0 ) )

    # set up attributes and shader program
    glEnable( GL_DEPTH_TEST )
    glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT )
    glUseProgram( shaderProgram )
    glUniformMatrix4fv( projectionMatLocation, 1, GL_FALSE, prjMat )
    lightSourceBuffer.BindToTarget()
    
    # draw icosahedron
    icoMaterialBuffer.BindToTarget()
    modelViewMat = Multiply(viewMat, icoModelMat)
    glUniformMatrix4fv( modelViewMatLocation, 1, GL_FALSE, modelViewMat )
    glUniformMatrix3fv( normalMatLocation, 1, GL_FALSE, ToMat33(modelViewMat) )
    glBindVertexArray( icoVAObj )
    glPatchParameteri( GL_PATCH_VERTICES, 3 )
    glDrawArrays( GL_PATCHES, 0, len(icoPosData) )
    
    glutSwapBuffers()

def Fract(val): return val - math.trunc(val)
def CalcAng(currentTime, intervall): return Fract( (currentTime - startTime) / intervall ) * 360.0
def CalcMove(currentTime, intervall, range):
    pos = Fract( (currentTime - startTime) / intervall ) * 2.0
    pos = pos if pos < 1.0 else (2.0-pos)
    return range[0] + (range[1] - range[0]) * pos
    
# read shader program and compile shader
def CompileShader( sourceFileName, shaderStage ):
    with open( sourceFileName, 'r' ) as sourceFile:
        sourceCode = sourceFile.read()
    nameMap = { GL_VERTEX_SHADER: 'vertex', GL_GEOMETRY_SHADER: 'geometry', GL_FRAGMENT_SHADER: 'fragment' }    
    print( '\n%s shader code:' % nameMap.get(shaderStage, '') )
    print( sourceCode )
    shaderObj = glCreateShader( shaderStage )
    glShaderSource( shaderObj, sourceCode )
    glCompileShader( shaderObj )
    result = glGetShaderiv( shaderObj, GL_COMPILE_STATUS )
    if not (result):
        print( glGetShaderInfoLog( shaderObj ) )
        sys.exit()
    return shaderObj

# link shader objects to shader program
def LinkProgram( shaderObjs ):
    shaderProgram = glCreateProgram()
    for shObj in shaderObjs:
        glAttachShader( shaderProgram, shObj )
    glLinkProgram( shaderProgram )
    result = glGetProgramiv( shaderProgram, GL_LINK_STATUS )
    if not (result):
        print( 'link error:' )
        print( glGetProgramInfoLog( shaderProgram ) )
        sys.exit()
    return shaderProgram

# create vertex array object
def CreateVAO( dataArrays ):
    noOfBuffers = len(dataArrays)
    buffers = glGenBuffers(noOfBuffers)
    newVAObj = glGenVertexArrays( 1 )
    glBindVertexArray( newVAObj )
    for inx in range(0, noOfBuffers):
        vertexSize, dataArr = dataArrays[inx]
        arr = np.array( dataArr, dtype='float32' )
        glBindBuffer( GL_ARRAY_BUFFER, buffers[inx] )
        glBufferData( GL_ARRAY_BUFFER, arr, GL_STATIC_DRAW )
        glEnableVertexAttribArray( inx )
        glVertexAttribPointer( inx, vertexSize, GL_FLOAT, GL_FALSE, 0, None )
    return newVAObj 

# representation of a uniform block
class UniformBlock:
     def __init__(self, shaderProg, name):
        self.shaderProg = shaderProg 
        self.name = name
     def Link(self, bindingPoint):
        self.bindingPoint = bindingPoint
        self.noOfUniforms = glGetProgramiv(self.shaderProg, GL_ACTIVE_UNIFORMS)
        self.maxUniformNameLen = glGetProgramiv(self.shaderProg, GL_ACTIVE_UNIFORM_MAX_LENGTH)
        self.index = glGetUniformBlockIndex(self.shaderProg, self.name)
        intData = np.zeros(1, dtype=int)
        glGetActiveUniformBlockiv(self.shaderProg, self.index, GL_UNIFORM_BLOCK_ACTIVE_UNIFORMS, intData)
        self.count = intData[0]
        self.indices = np.zeros(self.count, dtype=int)
        glGetActiveUniformBlockiv(self.shaderProg, self.index, GL_UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES, self.indices)
        self.offsets = np.zeros(self.count, dtype=int)
        glGetActiveUniformsiv(self.shaderProg, self.count, self.indices, GL_UNIFORM_OFFSET, self.offsets)
        strLengthData = np.zeros(1, dtype=int)
        arraysizeData = np.zeros(1, dtype=int)
        typeData = np.zeros(1, dtype='uint32')
        nameData = np.chararray(self.maxUniformNameLen+1)
        self.namemap = {}
        self.dataSize = 0 
        for inx in range(0, len(self.indices)):
            glGetActiveUniform( self.shaderProg, self.indices[inx], self.maxUniformNameLen, strLengthData, arraysizeData,     typeData, nameData.data )
            name = nameData.tostring()[:strLengthData[0]]
            self.namemap[name] = inx
            self.dataSize = max(self.dataSize, self.offsets[inx] + arraysizeData * 16) 
        glUniformBlockBinding(self.shaderProg, self.index, self.bindingPoint)
        print('\nuniform block %s size:%4d' % (self.name, self.dataSize))
        for uName in self.namemap:
            print( '    %-40s index:%2d    offset:%4d' % (uName, self.indices[self.namemap[uName]], self.offsets[self.namemap    [uName]]) ) 

# representation of a uniform block buffer
class UniformBlockBuffer:
    def __init__(self, ub):
        self.namemap = ub.namemap
        self.offsets = ub.offsets
        self.bindingPoint = ub.bindingPoint
        self.object = glGenBuffers(1)
        self.dataSize = ub.dataSize
        glBindBuffer(GL_UNIFORM_BUFFER, self.object)
        dataArray = np.zeros(self.dataSize//4, dtype='float32')
        glBufferData(GL_UNIFORM_BUFFER, self.dataSize, dataArray, GL_DYNAMIC_DRAW)
    def BindToTarget(self):
        glBindBuffer(GL_UNIFORM_BUFFER, self.object)
        glBindBufferBase(GL_UNIFORM_BUFFER, self.bindingPoint, self.object)
    def BindDataFloat(self, name, dataArr):
        glBindBuffer(GL_UNIFORM_BUFFER, self.object)
        dataArray = np.array(dataArr, dtype='float32')
        glBufferSubData(GL_UNIFORM_BUFFER, self.offsets[self.namemap[name]], len(dataArr)*4, dataArray)

def Translate(matA, trans):
    matB = np.copy(matA)
    for i in range(0, 4): matB[3,i] = matA[0,i] * trans[0] + matA[1,i] * trans[1] + matA[2,i] * trans[2] + matA[3,i] 
    return matB

def Scale(matA, s):
    matB = np.copy(matA)
    for i0 in range(0, 3):
        for i1 in range(0, 4): matB[i0,i1] = matA[i0,i1] * s[i0] 
    return matB

def RotateHlp(matA, angDeg, a0, a1):
    matB = np.copy(matA)
    ang = math.radians(angDeg)
    sinAng, cosAng = math.sin(ang), math.cos(ang)
    for i in range(0, 4):
        matB[a0,i] = matA[a0,i] * cosAng + matA[a1,i] * sinAng
        matB[a1,i] = matA[a0,i] * -sinAng + matA[a1,i] * cosAng
    return matB

def RotateX(matA, angDeg): return RotateHlp(matA, angDeg, 1, 2)
def RotateY(matA, angDeg): return RotateHlp(matA, angDeg, 2, 0)
def RotateZ(matA, angDeg): return RotateHlp(matA, angDeg, 0, 1)
def RotateView(matA, angDeg): return RotateZ(RotateY(RotateX(matA, angDeg[0]), angDeg[1]), angDeg[2])

def Multiply(matA, matB):
    matC = np.copy(matA)
    for i0 in range(0, 4):
        for i1 in range(0, 4):
            matC[i0,i1] = matB[i0,0] * matA[0,i1] + matB[i0,1] * matA[1,i1] + matB[i0,2] * matA[2,i1] + matB[i0,3] * matA[3,i1]        
    return matC

def ToMat33(mat44):
    mat33 = np.matrix(np.identity(3), copy=False, dtype='float32')
    for i0 in range(0, 3):
        for i1 in range(0, 3): mat33[i0, i1] = mat44[i0, i1]
    return mat33

def TransformVec4(vecA,mat44):
    vecB = np.zeros(4, dtype='float32')
    for i0 in range(0, 4):
        vecB[i0] = vecA[0] * mat44[0,i0] + vecA[1] * mat44[1,i0] + vecA[2] * mat44[2,i0]  + vecA[3] * mat44[3,i0]
    return vecB

def Perspective(fov, aspectRatio, near, far):
    fn, f_n = far + near, far - near
    r, t = aspectRatio, 1.0 / math.tan( math.radians(fov) / 2.0 )
    return np.matrix( [ [t/r,0,0,0], [0,t,0,0], [0,0,-fn/f_n,-2.0*far*near/f_n], [0,0,-1,0] ] )

def AddToBuffer( buffer, data, count=1 ): 
    for inx_c in range(0, count):
        for inx_s in range(0, len(data)): buffer.append( data[inx_s] ) 

# initialize glut
glutInit()

# create window
wndW, wndH = 800, 600
glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_ALPHA | GLUT_DEPTH)
glutInitWindowPosition(0, 0)
glutInitWindowSize(wndW, wndH)
wndID = glutCreateWindow(b'OGL window') 
glutDisplayFunc(OnDraw) 
glutIdleFunc(OnDraw)

# define icosahedron vertex array opject
icoPts = [
    ( 0.000,  0.000,  1.000), ( 0.894,  0.000,  0.447), ( 0.276,  0.851,  0.447), (-0.724,  0.526,  0.447),
    (-0.724, -0.526,  0.447), ( 0.276, -0.851,  0.447), ( 0.724,  0.526, -0.447), (-0.276,  0.851, -0.447), 
    (-0.894,  0.000, -0.447), (-0.276, -0.851, -0.447), ( 0.724, -0.526, -0.447), ( 0.000,  0.000, -1.000) ]
icoCol = [ [1.0, 0.0, 0.0], [0.0, 0.0, 1.0], [1.0, 1.0, 0.0], [0.0, 1.0, 0.0], [1.0, 0.5, 0.0], [1.0, 0.0, 1.0] ]
icoIndices = [
  2,  0,  1,  3,  0,  2,  4,  0,  3,  5,  0,  4,  1,  0,  5, 11,  7,  6, 11,  8,  7, 11,  9,  8, 11, 10,  9, 11,  6, 10, 
  1,  6,  2,  2,  7,  3,  3,  8,  4,  4,  9,  5,  5, 10,  1,  2,  6,  7,  3,  7,  8,  4,  8,  9,  5,  9, 10,  1, 10,  6  ]
icoPosData = []
for inx in icoIndices: AddToBuffer( icoPosData, icoPts[inx] )
icoNVData = []
for inx_nv in range(0, len(icoIndices) // 3):
    nv = [0.0, 0.0, 0.0]
    for inx_p in range(0, 3): 
        for inx_s in range(0, 3): nv[inx_s] += icoPts[ icoIndices[inx_nv*3 + inx_p] ][inx_s]
    AddToBuffer( icoNVData, nv, 3 )
icoVAObj = CreateVAO( [ (3, icoPosData), (3, icoNVData) ] )

# load, compile and link shader
shaderProgram = LinkProgram( [
        CompileShader( 'tess.vert', GL_VERTEX_SHADER ),
        CompileShader( 'tess.tctrl', GL_TESS_CONTROL_SHADER ),
        CompileShader( 'tess.teval', GL_TESS_EVALUATION_SHADER ), 
        CompileShader( 'tess.frag', GL_FRAGMENT_SHADER )
    ] )
# get unifor locations
projectionMatLocation = glGetUniformLocation(shaderProgram, "u_projectionMat44")
modelViewMatLocation  = glGetUniformLocation(shaderProgram, "u_modelViewMat44")
normalMatLocation     = glGetUniformLocation(shaderProgram, "u_normalMat33")
# linke uniform blocks
ubMaterial = UniformBlock(shaderProgram, "UB_material")
ubLightSource = UniformBlock(shaderProgram, "UB_lightSource")
ubMaterial.Link(1)
ubLightSource.Link(2)

# create uniform block buffers
lightSourceBuffer = UniformBlockBuffer(ubLightSource)
lightSourceBuffer.BindDataFloat(b'u_lightSource.ambient', [0.2, 0.2, 0.2, 1.0])
lightSourceBuffer.BindDataFloat(b'u_lightSource.diffuse', [0.2, 0.2, 0.2, 1.0])
lightSourceBuffer.BindDataFloat(b'u_lightSource.specular', [1.0, 1.0, 1.0, 1.0])

icoMaterialBuffer = UniformBlockBuffer(ubMaterial)
icoMaterialBuffer.BindDataFloat(b'u_roughness', [0.45])
icoMaterialBuffer.BindDataFloat(b'u_fresnel0', [0.4])
icoMaterialBuffer.BindDataFloat(b'u_color', [0.6, 0.5, 0.8, 1.0])
icoMaterialBuffer.BindDataFloat(b'u_specularTint',[1.0, 0.5, 0.5, 0.8])

# start main loop
startTime = time()
glutMainLoop()


Modified text is an extract of the original Stack Overflow Documentation
Лицензировано согласно CC BY-SA 3.0
Не связан с Stack Overflow