Список работ

OpenGL средствами pyglet

Содержание

Введение

Приводятся некоторые средства библиотеки pyglet для работы с OpenGL. Программирование выполняется на Python 3.6.6. Рассматривается вывод примитивов, заливка полигона с использованием растрового образа, применение материалов, наложение текстуры и употребление шейдеров.
Предполагается, что читатель знаком с основами компьютерной графики и OpenGL. По этой причине опускается рассмотрение вопросов проецирования, аффинных преобразований координат, систем цветов и пр., так же как и процедур и функций OpenGL.
Некоторые сведения об OpenGL можно найти, например, в [1-9].

Вывод прямоугольника и треугольников

Программирование вывода прямоугольника позволит привести код, обеспечивающий визуализацию на основе pyglet OpenGL.

import pyglet
from pyglet import app, graphics
from pyglet.window import Window
dx, dy = 25, 15 # Размеры прямоугольника
dx2 = dx / 2
dy2 = dy / 2
wx, wy = 1.5 * dx2, 1.5 * dy2 # Параметры области визуализации
width, height = int(20 * wx), int(20 * wy) # Размеры окна вывода
# Создаем окно визуализации
window = Window(visible = True, width = width, height = height,
                resizable = True, caption = 'Прямоугольник')
gl.glClearColor(0.1, 0.1, 0.1, 1.0) # Задаем почти черный цвет фона
gl.glClear(gl.GL_COLOR_BUFFER_BIT) # Заливка окна цветом фона
@window.event
def on_draw():
    # Проецирование
    gl.glMatrixMode(gl.GL_PROJECTION) # Теперь текущей является матрица проецирования
    gl.glLoadIdentity() # Инициализация матрицы проецирования
    gl.glOrtho(-wx, wx, -wy, wy, -1, 1) # Ортографическое проецирование
    gl.glColor3f(0, 1, 0) # Зеленый цвет
    gl.glBegin(gl.GL_QUADS) # Обход против часовой стрелки
    gl.glVertex3f(-dx2, -dy2, 0)
    gl.glVertex3f(dx2, -dy2, 0)
    gl.glVertex3f(dx2, dy2, 0)
    gl.glVertex3f(-dx2, dy2, 0)
    gl.glEnd()
app.run()

Результат показан на рис. 1.

Прямоугольник

Рис. 1. Прямоугольник

Тот же результат получим, применив graphics.draw [10] (приводится только процедура on_draw).

@window.event
def on_draw():
    gl.glMatrixMode(gl.GL_PROJECTION)
    gl.glLoadIdentity()
    gl.glOrtho(-wx, wx, -wy, wy, -1, 1)
    graphics.draw(4, gl.GL_QUADS,
                  ('v2f', (-dx2, -dy2, dx2, -dy2, dx2, dy2, -dx2, dy2)),
                  ('c3f', (0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0)))

Первый параметр – это число вершин, далее следуют кортежи с координатами вершин и их цветом.
Цвет можно задать перед graphics.draw:

    gl.glColor3f(0, 1, 0)
    graphics.draw(4, gl.GL_QUADS,
                 ('v2f', (-dx2, -dy2, dx2, -dy2, dx2, dy2, -dx2, dy2)))

Так же можно употребить graphics.vertex_list:

@window.event
def on_draw():
    gl.glMatrixMode(gl.GL_PROJECTION)
    gl.glLoadIdentity()
    gl.glOrtho(-wx, wx, -wy, wy, -1, 1)
    v_lst = graphics.vertex_list(4,
                                 ('v2f', (-dx2, -dy2, dx2, -dy2, dx2, dy2, -dx2, dy2)),
                                 ('c3f', (0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0)))
    v_lst.draw(gl.GL_QUADS)

При выводе нескольких примитивов, например треугольников, с общими вершинами более компактным будет применение graphics.draw_indexed, например:

@window.event
def on_draw():
    # Проецирование
    gl.glMatrixMode(gl.GL_PROJECTION)
    gl.glLoadIdentity()
    gl.glOrtho(-wx, wx, -wy, wy, -1, 1)
    graphics.draw_indexed(4, gl.GL_TRIANGLES,
                          [0, 1, 2, 1, 3, 2],
                          ('v2f', (-dx2, -dy2, 0, -dy / 4, 0, dy2, dx2, -dy2)),
                          ('c3f', (1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0)))

Всего для вывода двух смежных треугольников (рис. 2 ) достаточно задать 4 вершины.

Треугольники

Рис. 2. Треугольники

Чтобы отключить интерполяцию цветов, перед graphics.draw_indexed следует задать

gl.glShadeModel(gl.GL_FLAT) # По умолчанию употребляется GL_SMOOTH

Будет использован цвет вершины с индексом 2 – (0, 1, 0).

Вывод точек

Для точки можно задать ее размер:

gl.glPointSize(16)

и режим сглаживания:

gl.glEnable(gl.GL_POINT_SMOOTH)

Отобразим 8 точек: 4, используя glBegin / glEnd, и 4 сглаженные, применив draw, (рис. 3):

import pyglet
from pyglet import app, gl, graphics
from pyglet.window import Window
d, d2 = 12, 10
wx, wy = 1.5 * d, 1.2 * d # Параметры области визуализации
width, height = int(20 * wx), int(20 * wy) # Размеры окна вывода
window = Window(visible = True, width = width, height = height,
                resizable = True, caption = 'Точки')
gl.glClearColor(0.1, 0.1, 0.1, 1.0)
gl.glClear(gl.GL_COLOR_BUFFER_BIT)
@window.event
def on_draw():
    gl.glMatrixMode(gl.GL_PROJECTION)
    gl.glLoadIdentity()
    gl.glOrtho(-wx, wx, -wy, wy, -1, 1)
    gl.glPointSize(16)
    gl.glBegin(gl.GL_POINTS)
    gl.glColor3f(1, 0, 0)
    gl.glVertex3f(-d, -d, 0)
    gl.glColor3f(0, 1, 0)
    gl.glVertex3f(-d, d, 0)
    gl.glColor3f(0, 0, 1)
    gl.glVertex3f(d, d, 0)
    gl.glColor3f(1, 1, 0)
    gl.glVertex3f(d, -d, 0)
    gl.glEnd()
    gl.glEnable(gl.GL_POINT_SMOOTH)
    graphics.draw(4, gl.GL_POINTS,
                  ('v2f', (-d2, -d2, -d2, d2, d2, d2, d2, -d2)),
                  ('c3B', (0, 0, 255, 0, 255, 0, 255, 0, 0, 255, 255, 0)))
    gl.glDisable(gl.GL_POINT_SMOOTH)
app.run()

Точки

Рис. 3. Точки

Вывод линий

Для линии можно задать ее толщину:

gl.glLineWidth(4)

и образец (маску):

gl.glEnable(gl.GL_LINE_STIPPLE)
pattern = '0b1111100110011111' # '1111100110011111'
gl.glLineStipple(2, int(pattern, 2)) # Повторяем каждый бит образца 2 раза

Линии (рис. 4) выводятся после задания GL_LINES, или GL_LINE_STRIP, или GL_LINE_LOOP:

import pyglet
from pyglet import app, gl, graphics
from pyglet.window import Window
d, d2, d3 = 12, 8, 6
wx, wy = 1.5 * d, 1.2 * d # Параметры области визуализации
width, height = int(20 * wx), int(20 * wy) # Размеры окна вывода
window = Window(visible = True, width = width, height = height,
                resizable = True, caption = 'Линии')
gl.glClearColor(0.1, 0.1, 0.1, 1.0)
gl.glClear(gl.GL_COLOR_BUFFER_BIT)
@window.event
def on_draw():
    gl.glMatrixMode(gl.GL_PROJECTION)
    gl.glLoadIdentity()
    gl.glOrtho(-wx, wx, -wy, wy, -1, 1)
    gl.glLineWidth(4)
    gl.glBegin(gl.GL_LINES)
    gl.glColor3f(1, 0, 0)
    gl.glVertex3f(-d, -d, 0)
    gl.glVertex3f(-d, d, 0)
    gl.glColor3f(0, 1, 0)
    gl.glVertex3f(-d, d, 0)
    gl.glVertex3f(d, d, 0)
    gl.glColor3f(0, 0, 1)
    gl.glVertex3f(d, d, 0)
    gl.glVertex3f(d, -d, 0)
    gl.glColor3f(1, 1, 0)
    gl.glVertex3f(d, -d, 0)
    gl.glVertex3f(-d, -d, 0)
    gl.glEnd()
    graphics.draw(5, gl.GL_LINE_STRIP,
                  ('v2f', (-d, -d, -d, d, d, d, d, -d, -d, -d)),
                  ('c3f', (1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0)))
    gl.glEnable(gl.GL_LINE_STIPPLE)
    pattern = '0b1111100110011111' # '1111100110011111'
    gl.glLineStipple(2, int(pattern, 2)) # Повторяем каждый бит образца 2 раза
    gl.glColor3f(1, 1, 1)
    gl.glBegin(gl.GL_LINE_STRIP)
    gl.glVertex3f(-d2, -d2, 0)
    gl.glVertex3f(-d2, d2, 0)
    gl.glVertex3f(d2, d2, 0)
    gl.glVertex3f(d2, -d2, 0)
    gl.glVertex3f(-d2, -d2, 0)
    gl.glEnd()
    graphics.draw(4, gl.GL_LINE_LOOP,
                  ('v2f', (-d3, -d3, -d3, d3, d3, d3, d3, -d3)))
    gl.glDisable(gl.GL_LINE_STIPPLE)
app.run()

Линии

Рис. 4. Линии

Лицевая и нелицевая стороны полигона

Лицевая сторона будет показана, если при выводе многоугольника его вершины обходятся против часовой стрелки, в противном случае будет наблюдаться нелицевая сторона многоугольника. Каждая из сторон может быть показана либо в виде точек в вершинах многоугольника, либо в виде линий, либо залита одним цветом, либо залита с использованием интерполяции цветов, указанных в вершинах многоугольника, либо залита с использованием образца, текстуры или материала. Эти возможности, кроме трех последних, демонстрирует нижеприводимая программа, в которой первоначально левый прямоугольник выводится лицевой стороной, а правый – нелицевой, причем с интерполяцией цветов (рис. 5), поскольку вершины имеют разные цвета, а по умолчанию имеем glShadeModel(GL_SMOOTH).

GL_FRONT_AND_BACK: GL_FILL

Рис. 5. По умолчанию для GL_FRONT_AND_BACK установлено GL_FILL

Затем после нажатия на клавишу 1 лицевая сторона выводится в виде точек, а нелицевая – в виде линий (рис. 6).

После нажатия на 1

Рис. 6. После нажатия на 1

После нажатия на клавишу 2 картина меняется: лицевая сторона выводится в виде линий, а нелицевая – в виде точек (рис. 7).

После нажатия на 2

Рис. 7. После нажатия на 2

После нажатия на 3 оба прямоугольника заливаются без интерполяции цветов (рис. 8), а после нажатия на 4 получаем рис. 5.

После нажатия на 3

Рис. 8. После нажатия на 3

import pyglet
from pyglet import app, gl, graphics
from pyglet.window import Window, key
d = 12
d2 = 6
wx, wy = 1.5 * d, 1.2 * d2 # Параметры области визуализации
width, height = int(20 * wx), int(20 * wy) # Размеры окна вывода
window = Window(visible = True, width = width, height = height,
                resizable = True, caption = 'Способы вывода многоугольника')
gl.glClearColor(0.1, 0.1, 0.1, 1.0)
gl.glClear(gl.GL_COLOR_BUFFER_BIT)
gl.glEnable(gl.GL_POINT_SMOOTH)
gl.glPointSize(16)
gl.glLineWidth(4)
@window.event
def on_draw():
    window.clear()
    gl.glMatrixMode(gl.GL_PROJECTION)
    gl.glLoadIdentity()
    gl.glOrtho(-wx, wx, -wy, wy, -1, 1)
    gl.glBegin(gl.GL_QUADS)
    gl.glColor3f(0, 1, 0)
    gl.glVertex3f(-d, -d2, 0)
    gl.glVertex3f(-1, -d2, 0)
    gl.glVertex3f(-1, d2, 0)
    gl.glVertex3f(-d, d2, 0)
    gl.glEnd()
    graphics.draw(4, gl.GL_QUADS,
                  ('v2f', (1, -d2, 1, d2, d, d2, d, -d2)),
                  ('c3f', (1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1)))
@window.event
def on_key_press(symbol, modifiers):
    mode_f = mode_b = None
    c = chr(symbol)
    if c == '1': # c == key._1
        mode_f = gl.GL_POINT
        mode_b = gl.GL_LINE
        shade_model = gl.GL_SMOOTH
    elif symbol == key._2:
        mode_f = gl.GL_LINE
        mode_b = gl.GL_POINT
        shade_model = gl.GL_SMOOTH
    elif symbol == key._3:
        mode_f = mode_b = gl.GL_FILL
        shade_model = gl.GL_FLAT
    elif symbol == key._4:
        mode_f = mode_b = gl.GL_FILL
        shade_model = gl.GL_SMOOTH
    if mode_f is not None:
        gl.glPolygonMode(gl.GL_FRONT, mode_f)
        gl.glPolygonMode(gl.GL_BACK, mode_b)
        gl.glShadeModel(shade_model)
app.run()

Заливка полигона с использованием образца

Образец размера 32*32 бита приведен на рис. 9.

Образец

Рис. 9. Образец

Он задается массивом mask из 128 элементов. Тип массива __main__.c_ubyte_Array_128. Каждые 4 элемента массива содержат 32 бита и определяют одну строку образца. Образец используется после задания

gl.glEnable(gl.GL_POLYGON_STIPPLE)
gl.glPolygonStipple(mask)

Результат показан на рис. 10.

Заливка по образцу

Рис. 10. Заливка по образцу

import pyglet
from pyglet import app, gl, graphics
from pyglet.window import Window, key
d = 12
d2 = 6
wx, wy = 1.5 * d, 1.2 * d2 # Параметры области визуализации
width, height = int(20 * wx), int(20 * wy) # Размеры окна вывода
# Муха
mask0 = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x80, 0x01, 0xc0, 0x06, 0xc0, 0x03, 0x60,
        0x04, 0x60, 0x06, 0x20, 0x04, 0x30, 0x0c, 0x20, 0x04, 0x18, 0x18, 0x20, 0x04, 0x0c, 0x30, 0x20,
        0x04, 0x06, 0x60, 0x20, 0x44, 0x03, 0xc0, 0x22, 0x44, 0x01, 0x80, 0x22, 0x44, 0x01, 0x80, 0x22,
        0x44, 0x01, 0x80, 0x22, 0x44, 0x01, 0x80, 0x22, 0x44, 0x01, 0x80, 0x22, 0x44, 0x01, 0x80, 0x22,
        0x66, 0x01, 0x80, 0x66, 0x33, 0x01, 0x80, 0xcc, 0x19, 0x81, 0x81, 0x98, 0x0c, 0xc1, 0x83, 0x30,
        0x07, 0xe1, 0x87, 0xe0, 0x03, 0x3f, 0xfc, 0xc0, 0x03, 0x31, 0x8c, 0xc0, 0x03, 0x33, 0xcc, 0xc0,
        0x06, 0x64, 0x26, 0x60, 0x0c, 0xcc, 0x33, 0x30, 0x18, 0xcc, 0x33, 0x18, 0x10, 0xc4, 0x23, 0x08,
        0x10, 0x63, 0xc6, 0x08, 0x10, 0x30, 0x0c, 0x08, 0x10, 0x18, 0x18, 0x08, 0x10, 0x00, 0x00, 0x08]
dim = 128
mask = (gl.GLubyte * dim)()
for k in range(dim):
    mask[k] = mask0[k]
window = Window(visible = True, width = width, height = height,
                resizable = True, caption = 'Заливка по образцу')
gl.glClearColor(0.1, 0.1, 0.1, 1.0)
gl.glClear(gl.GL_COLOR_BUFFER_BIT)
@window.event
def on_draw():
    window.clear()
    gl.glMatrixMode(gl.GL_PROJECTION)
    gl.glLoadIdentity()
    gl.glOrtho(-wx, wx, -wy, wy, -1, 1)
    gl.glEnable(gl.GL_POLYGON_STIPPLE)
    gl.glPolygonStipple(mask)
    gl.glBegin(gl.GL_QUADS)
    gl.glColor3f(0, 1, 0)
    gl.glVertex3f(-d, -d2, 0)
    gl.glVertex3f(-1, -d2, 0)
    gl.glVertex3f(-1, d2, 0)
    gl.glVertex3f(-d, d2, 0)
    gl.glEnd()
    graphics.draw(4, gl.GL_QUADS,
                  ('v2f', (1, -d2, 1, d2, d, d2, d, -d2)),
                  ('c3f', (1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1)))
app.run()

Использование текстуры

Вывод текстуры регулируется ее координатами (glTexCoord2f) и параметрами (glTexParameterf). Цвет полигона определяется либо чисто текстурой, либо в результате смешения цвета полигона и текстуры (glTexEnvf).
Текстура обычно создается на основе растрового образа, загружаемого из файла (рис. 11), но может быть создана и программно (рис. 12).

Текстура

Рис. 11. Текстура на основе файла; tile_x = 2, tile_y = 1

tile_x, tile_y – соответственно число повторов текстуры по X и Y.

Текстура

Рис. 12. Программно сгенерированная текстура; tile_x = tile_y = 1

Оба результата получены следующим кодом:

from pyglet.gl import *
from pyglet import app
from pyglet.window import Window, key
d = 12
wx, wy = 1.5 * d, 1.1 * d # Параметры области визуализации
width, height = int(20 * wx), int(20 * wy) # Размеры окна вывода
texFromFile = True # True False
if texFromFile:
    tile_x = 2 # Число повторов текстуры по X
    tile_y = 1 # Число повторов текстуры по Y
else:
    tile_x = tile_y = 1
def to_c_float_Array(data): # Преобразование в си-массив
    return (GLfloat * len(data))(*data)
vld = to_c_float_Array([-d, -d, 0]) # Левая нижняя вершина
vrd = to_c_float_Array([d, -d, 0]) # Правая нижняя вершина
vru = to_c_float_Array([d, d, 0]) # Правая верхняя вершина
vlu = to_c_float_Array([-d, d, 0]) # Левая верхняя вершина
#
def texInit():
    if texFromFile:
        fn = 'G:\\python\\openGL\\кот.jpg'
        img = pyglet.image.load(fn)
        iWidth = img.width
        iHeight = img.height
        img = img.get_data('RGB', iWidth * 3)
    else:
        iWidth = iHeight = 64 # Размер текстуры равен iWidth * iHeight
        n = 3 * iWidth * iHeight
        # Каждый элемент текстуры содержит три компонента (формат текстуры GL_RGB)
        # GL_UNSIGNED_BYTE - это диапазон 0-255, поэтому для img задается GLuint
        img = np.zeros((3, iWidth, iHeight), dtype = 'uint8')
        for i in range(iHeight): # Генерация черно-белого образа, на основе которого создается текстура
         for j in range(iWidth):
            img[:, i, j] = ((i - 1) & 16 ^ (j - 1) & 16) * 255
        img = img.reshape(n)
        img = (GLubyte * n)(*img)
    p = GL_TEXTURE_2D
    r = GL_RGB
    # Задаем параметры текстуры
    glTexParameterf(p, GL_TEXTURE_WRAP_S, GL_REPEAT) # GL_CLAMP
    glTexParameterf(p, GL_TEXTURE_WRAP_T, GL_REPEAT)
    glTexParameterf(p, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
    glTexParameterf(p, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
    # Способ взаимодействия с текущим фрагментом изображения
    glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL)
    # Создаем 2d-текстуру на основе образа img
    glTexImage2D(p, 0, r, iWidth, iHeight, 0, r, GL_UNSIGNED_BYTE, img)
    glEnable(p)
window = Window(visible = True, width = width, height = height,
                resizable = True, caption = 'Текстура')
glClearColor(0.1, 0.1, 0.1, 1.0)
glClear(GL_COLOR_BUFFER_BIT)
texInit()
@window.event
def on_draw():
    window.clear()
    glMatrixMode(GL_PROJECTION)
    glLoadIdentity()
    glOrtho(-wx, wx, -wy, wy, -1, 1)
    glBegin(GL_QUADS)
    glTexCoord2f(0, 0);
    glVertex3fv(vld)
    glTexCoord2f(tile_x, 0)
    glVertex3fv(vrd)
    glTexCoord2f(tile_x, tile_y)
    glVertex3fv(vru)
    glTexCoord2f(0, tile_y)
    glVertex3fv(vlu)
    glEnd()
app.run()

Использование материала

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

gl.glEnable(gl.GL_LIGHTING)

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

Материал

Рис. 13. Выполнен расчет освещенности

import pyglet
from pyglet import app, gl, graphics
from pyglet.window import Window, key
import numpy as np
from sys import exit
d = 12
wx, wy = 1.5 * d, 1.1 * d # Параметры области визуализации
width, height = int(20 * wx), int(20 * wy) # Размеры окна вывода
#
mtClr0 = [1, 1, 0, 0] # Цвет материала
light_position0 = [0, 40, 40, 0] # Позиция источника света
lghtClr0 = [0.75, 0, 0, 0] # Цвет источника света
mtClr = (gl.GLfloat * 4)()
light_position = (gl.GLfloat * 4)()
lghtClr = (gl.GLfloat * 4)()
for k in range(4): mtClr[k] = mtClr0[k]
for k in range(4): light_position[k] = light_position0[k]
for k in range(4): lghtClr[k] = lghtClr0[k]
#
window = Window(visible = True, width = width, height = height,
                resizable = True, caption = 'Материал')
gl.glClearColor(0.1, 0.1, 0.1, 1.0)
gl.glClear(gl.GL_COLOR_BUFFER_BIT)
gl.glEnable(gl.GL_LIGHTING) # Активизируем использование материалов
@window.event
def on_draw():
    window.clear()
    gl.glMatrixMode(gl.GL_PROJECTION)
    gl.glLoadIdentity()
    gl.glOrtho(-wx, wx, -wy, wy, -1, 1)
    gl.glShadeModel(gl.GL_SMOOTH) # GL_FLAT - без интерполяции цветов
    gl.glMaterialfv(gl.GL_FRONT, gl.GL_SPECULAR, mtClr)
    gl.glLightfv(gl.GL_LIGHT0, gl.GL_POSITION, light_position)
    gl.glLightfv(gl.GL_LIGHT0, gl.GL_SPECULAR, lghtClr)
    gl.glEnable(gl.GL_LIGHT0) # Включаем в уравнение освещенности источник GL_LIGHT0
    gl.glColor3f(1, 0, 0)
    gl.glBegin(gl.GL_QUADS)
    gl.glNormal3f(0, 0, 1)
    gl.glVertex3f(-d, -d, 0)
    gl.glNormal3f(0, 0, 1)
    gl.glVertex3f(d, -d, 0)
    gl.glNormal3f(0, 0, 1)
    gl.glVertex3f(d, d, 0)
    gl.glNormal3f(0, 0, -1)
    gl.glVertex3f(-d, d, 0)
    gl.glEnd()
@window.event
def on_key_press(symbol, modifiers):
    if symbol == key._1:
        gl.glDisable(gl.GL_LIGHTING)
    elif symbol == key._2:
        gl.glEnable(gl.GL_LIGHTING)
app.run()

Цвет полигона будет определяться glColor3f, если отказаться от использования материала:

gl.glDisable(gl.GL_LIGHTING)

Это произойдет при нажатии на 1. После нажатия на 2 материал будет применен вновь.

Вывод параболоида

Выводится параболоид (рис. 14)

y = a*x2 + a*z2

Параболоид

Рис. 14. Параболоид

Нормали рассчитываются к граням в каждой вершине параболоида (гранью является трапеция).
Разницу между нормалью к грани и к вершине иллюстрирует рис. 14.

Нормаль

Рис. 15. Порядок вывода вершин, нормаль к грани и нормаль к вершине

Вычисление нормали:

import numpy as np
n = np.cross(a, b) # Векторное произведение
n = n / np.linalg.norm(n) # Нормализация
или
n = n / np.sqrt(np.sum(n**2)) # Нормализация

Замечание.OpenGL выполнит нормализацию самостоятельно, если задать:

glEnable(GL_NORMALIZE)

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

С нормалями

Рис. 16. Параболоид с нормалями

После отказа от сглаживания видны отдельные грани:

GL_FLAT

Рис. 17. Параболоид: gl.glShadeModel(gl.GL_FLAT)

При выводе текстуры (сгенерированной или из файла) имеем рис. 18.

Текстура

Рис. 18. Параболоид с текстурой

# Вывод параболоида
import pyglet
from pyglet import app, gl, graphics
from pyglet.window import Window, key
import numpy as np, math
# z = ac*x**2 + ac*y**2 (реально имеем: y = ac*x**2 + ac*z**2)
show_normals = True
kn = 2 # Коэффициент увеличения длины отображаемой нормали
ac = 2.5 # Коэффициент в уравнении параболоида
n = 24 # Число вершин в сечении параболоида
ch = 7 # Число сечений параболоида плоскостями y = const
h_cover = 20 # Высота параболоида
vrts = [] # Координаты вершин параболоида
nrms = [] # Координаты нормалей к граням (рассчитываются для каждой вершины)
textr = [] # Координаты текстуры (задаются для каждой вершины)
vrts_top = [] # Координаты вершин крышки
textr_top = [] # Координаты тектстуры крышки
dh = h_cover / ch # Расстояние между сечениями
dal = 2 * math.pi / n # dal - угол между вершинами сечения
hp0 = 0.1 # Низ параболоида
# Текстура
use_txtr = True
texFromFile = False # True False
if use_txtr: show_normals = False
# Координаты текстуры меняются в диапазоне 0-1
dnx = 1 / (n - 1) # Шаг изменения координат текстуры по x
dny = 1 / ch # Шаг изменения координат текстуры по y
hp = hp0
for i in range(ch): # Вершины
    al = 0 # Первая вершина лежит на оси Х
    s = ac * math.sqrt(hp)
    h0 = hp
    hp += dh
    s2 = ac * math.sqrt(hp)
    for j in range(n):
        co = math.cos(al)
        si = math.sin(al)
        al += dal
        co2 = math.cos(al)
        si2 = math.sin(al)
        # Координаты вершин очередной трапеции
        v0 = np.array([s * co, h0, -s * si])
        v1 = np.array([s * co2, h0, -s * si2])
        v2 = np.array([s2 * co2, hp, -s2 * si2])
        v3 = np.array([s2 * co, hp, -s2 * si])
        vrts.append([v0, v1, v2, v3])
        a = v1 - v0
        b = v3 - v0
        sab = np.cross(a, b) # Векторное произведение
        sab = sab / np.linalg.norm(sab) # Нормализация
        #sab = sab / np.sqrt(np.sum(sab**2))
        nrms.append(sab) # Координаты нормали
        textr.append([j * dnx, (h0 - hp0) / h_cover]) # Координаты текстуры
n_vrts = len(vrts)
# Крышка
al = 0
kt = 0.25
for j in range(n):
    co = math.cos(al); si = math.sin(al)
    al += dal
    vrts_top.append([s2 * co, hp, -s2 * si])
    nrms.append(nrms[n_vrts - n + j])
    textr.append([j * dnx, 1])
    if texFromFile:
        j2 = 2 * j * dnx
        if j <= n / 4:
            textr_top.append([j2, 0.5 - j2])
        elif j <= n / 2:
            textr_top.append([j2, j2 - 0.5])
        elif j <= 3 * n / 4:
            textr_top.append([2 - j2, j2 - 0.5])
        else:
            textr_top.append([2 - j2, 2.5 - j2])
    else:
        textr_top.append([kt * co, kt * si])
v0 = np.array(vrts_top[0])
v1 = np.array(vrts_top[1])
vn = np.array(vrts_top[n - 1])
nrm_top = np.cross(v1 - v0, vn - v0) # Нормаль к крышке
nrm_top = nrm_top / np.linalg.norm(sab)
#
mtClr0 = [1, 1, 0, 0]
light_position0 = [-80, 20, 90, 0]
lghtClr0 = [0.75, 0, 0, 0]
mtClr = (gl.GLfloat * 4)(*mtClr0)
light_position = (gl.GLfloat * 4)(*light_position0)
lghtClr = (gl.GLfloat * 4)(*lghtClr0)
#
def texInit():
    if texFromFile:
        fn = 'G:\\python\\openGL\\кот.jpg'
        img = pyglet.image.load(fn)
        iWidth = img.width
        iHeight = img.height
        img = img.get_data('RGB', iWidth * 3)
    else:
        iWidth = iHeight = 64
        n = 3 * iWidth * iHeight
        img = np.zeros((3, iWidth, iHeight), dtype = 'uint8')
        for i in range(iHeight): # Генерация черно-белого образа, на основе которого создается текстура
         for j in range(iWidth):
            img[:, i, j] = ((i - 1) & 16 ^ (j - 1) & 16) * 255
        img = img.reshape(n)
        img = (gl.GLubyte * n)(*img)
    p = gl.GL_TEXTURE_2D
    r = gl.GL_RGB
    gl.glTexParameterf(p, gl.GL_TEXTURE_WRAP_S, gl.GL_REPEAT)
    gl.glTexParameterf(p, gl.GL_TEXTURE_WRAP_T, gl.GL_REPEAT)
    gl.glTexParameterf(p, gl.GL_TEXTURE_MAG_FILTER, gl.GL_LINEAR)
    gl.glTexParameterf(p, gl.GL_TEXTURE_MIN_FILTER, gl.GL_LINEAR)
    gl.glTexEnvf(gl.GL_TEXTURE_ENV, gl.GL_TEXTURE_ENV_MODE, gl.GL_DECAL)
    gl.glTexImage2D(p, 0, r, iWidth, iHeight, 0, r, gl.GL_UNSIGNED_BYTE, img)
    if use_txtr:
        gl.glEnable(p)
w = h = h_cover
width, height = 20 * w, 10 * w
window = Window(visible = True, width = width, height = height,
                resizable = True, caption = 'Параболоид')
gl.glClearColor(1, 1, 1, 1) # Белый цвет фона
gl.glClear(gl.GL_COLOR_BUFFER_BIT|gl.GL_DEPTH_BUFFER_BIT)
gl.glPolygonMode(gl.GL_FRONT_AND_BACK, gl.GL_FILL)
gl.glLineWidth(2)
gl.glPointSize(4)
gl.glEnable(gl.GL_POINT_SMOOTH)
gl.glShadeModel(gl.GL_SMOOTH) # GL_SMOOTH, GL_FLAT - без интерполяции цветов
gl.glCullFace(gl.GL_BACK) # Запрещен вывод граней, показанных нелицевой стороной
gl.glEnable(gl.GL_CULL_FACE) # Активизируем режим GL_CULL_FACE
##gl.glEnable(gl.GL_NORMALIZE)
if show_normals:
    gl.glEnable(gl.GL_DEPTH_TEST) # Активизируем тест глубины
texInit()
@window.event
def on_draw():
    window.clear()
    gl.glMatrixMode(gl.GL_PROJECTION)
    gl.glLoadIdentity()
    gl.glOrtho(-w, w, -0.5 * h, h, -w, w) # Ортографическое проецирование
    gl.glRotatef(30, 1, 0, 0) # Поворот относительно оси X
    if use_txtr and texFromFile:
        gl.glRotatef(90, 0, 1, 0) # Поворот относительно оси Y
    gl.glTranslatef(0, -7, 0) # Перенос объекта вниз (вдоль оси Y)
    gl.glMaterialfv(gl.GL_FRONT, gl.GL_SPECULAR, mtClr)
    gl.glLightfv(gl.GL_LIGHT0, gl.GL_POSITION, light_position)
    gl.glLightfv(gl.GL_LIGHT0, gl.GL_SPECULAR, lghtClr)
    gl.glEnable(gl.GL_LIGHTING) # Активизируем заданные параметры освещенности
    gl.glEnable(gl.GL_LIGHT0)
    gl.glBegin(gl.GL_QUADS)
    k = -1
    for i in range(ch):
        for j in range(n):
            k += 1
            v = vrts[k]
            v0 = v[0]; v1 = v[1]; v2 = v[2]; v3 = v[3]
            sn = nrms[k] # Нормаль к вершине (она же нормаль к грани)
            gl.glNormal3f(sn[0], sn[1], sn[2])
            tc = textr[k] # Координаты текстуры
            gl.glTexCoord2f(tc[0], tc[1])
            gl.glVertex3f(v0[0], v0[1], v0[2])
            sn = nrms[k + 1]
            gl.glNormal3f(sn[0], sn[1], sn[2])
            gl.glTexCoord2f(tc[0] + dnx, tc[1])
            gl.glVertex3f(v1[0], v1[1], v1[2])
            sn = nrms[k + n]
            gl.glNormal3f(sn[0], sn[1], sn[2])
            gl.glTexCoord2f(tc[0] + dnx, tc[1] + dny)
            gl.glVertex3f(v2[0], v2[1], v2[2])
            sn = nrms[k + n - 1]
            gl.glNormal3f(sn[0], sn[1], sn[2])
            gl.glTexCoord2f(tc[0], tc[1] + dny)
            gl.glVertex3f(v3[0], v3[1], v3[2])
    gl.glEnd() # Заканчиваем вывод боковых граней
    gl.glBegin(gl.GL_POLYGON) # Вывод крышки
    gl.glNormal3f(nrm_top[0], nrm_top[1], nrm_top[2]) # Нормаль к крышке
    for j in range(n):
        v = vrts_top[j]
        tc = textr_top[j]
        gl.glTexCoord2f(tc[0], tc[1])
        gl.glVertex3f(v[0], v[1], v[2])
    gl.glEnd() # Заканчиваем вывод крышки
    if show_normals:
        # Вывод нормалей
        gl.glDisable(gl.GL_LIGHTING) # Отключаем расчет освещенности
        gl.glLineWidth(2) # Задание толщины линии
        gl.glColor3f(0, 0, 0) # Задание текущего цвета
        gl.glBegin(gl.GL_LINES)
        k = -1
        for i in range(ch):
            for j in range(n):
                k += 1
                v = vrts[k][0]
                gl.glVertex3f(v[0], v[1], v[2])
                sn = nrms[k]
                sx = kn * sn[0]
                sy = kn * sn[1]
                sz = kn * sn[2]
                gl.glVertex3f(v[0] + sx, v[1] + sy, v[2] + sz)
                # Нормали в верхнем сечении совпадают с нормалями в предыдущем сечении
                if i == ch - 1:
                    v = vrts[k][3]
                    gl.glVertex3f(v[0], v[1], v[2])
                    gl.glVertex3f(v[0] + sx, v[1] + sy, v[2] + sz)
        gl.glEnd()
        gl.glColor3f(1, 1, 1) # Текущий цвет
        gl.glBegin(gl.GL_LINES) # Нормаль к крышке
        gl.glVertex3f(0, hp, 0)
        gl.glVertex3f(kn * nrm_top[0], hp + kn * nrm_top[1], kn * nrm_top[2])
        gl.glEnd()
@window.event
def on_key_press(symbol, modifiers):
    if symbol == key._1:
        gl.glDisable(gl.GL_TEXTURE_2D)
        gl.glPolygonMode(gl.GL_FRONT, gl.GL_FILL)
        gl.glShadeModel(gl.GL_SMOOTH)
        gl.glEnable(gl.GL_CULL_FACE)
    elif symbol == key._2:
        gl.glPolygonMode(gl.GL_FRONT, gl.GL_FILL)
        gl.glShadeModel(gl.GL_FLAT)
        gl.glEnable(gl.GL_CULL_FACE)
    elif symbol == key._3:
        gl.glPolygonMode(gl.GL_FRONT, gl.GL_LINE) # Вывод ребер
    elif symbol == key._4:
        gl.glPolygonMode(gl.GL_FRONT, gl.GL_POINT)
    elif symbol == key._5:
        gl.glDisable(gl.GL_CULL_FACE)
app.run()

Использование нескольких текстур

Рассматривается на примере вывода игральной кости (рис. 19).

Кость

Рис. 19. Используется 6 текстур

Текстуры создаются на основе 6 файлов, содержащих приведенные на рис. 20 изображения.

Стороны кости

Рис. 20. Стороны игральной кости

Помимо куба с текстурами, приводимая ниже программа показывает после нажатия на клавиши 2-6 разные способы вывода куба (рис. 21).

Куб

Рис. 21. После нажатия на 2, 3, ..., 6

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

elif symbol == key._4:
    ps = (gl.GLfloat * 1)()
    ps_range = (gl.GLfloat * 2)()
    gl.glGetFloatv(gl.GL_POINT_SIZE, ps) # Получаем текущий размер точки
    # Допустимый диапазон изменения размеров точки
    gl.glGetFloatv(gl.GL_POINT_SIZE_RANGE, ps_range)
    ps = ps[0]
    ps = (ps + 2) if ps < ps_range[1] - 2 else 4
    gl.glPointSize(ps)
    gl.glPolygonMode(gl.GL_FRONT, gl.GL_POINT)

Программа вывода куба с текстурой и без нее:

# Игральная кость
import pyglet
from pyglet import app, gl, graphics
from pyglet.window import Window, key
h = 20 # Половины длины ребра куба
w = 2 * h # Для задания области вывода
width = height = 300 # Размер окна вывода
textureIDs = (gl.GLuint * 6)() # Массив идентификаторов (номеров) текстур
p = gl.GL_TEXTURE_2D
tc = 1 # Число повторов текстуры
rot_x = 15 # Углы поворота вокруг осей X, Y и Z
rot_y = 25 # (15, 25, 15) или (-25, 215, -15)
rot_z = 15
verts = ((h, -h, -h), # <class 'tuple'> Координаты вершин куба
         (h, h, -h),
         (-h, h, -h),
         (-h, -h, -h),
         (h, -h, h),
         (h, h, h),
         (-h, -h, h),
         (-h, h, h))
faces = ((0, 1, 2, 3), # Индексы вершин граней куба
         (3, 2, 7, 6),
         (6, 7, 5, 4),
         (4, 5, 1, 0),
         (1, 5, 7, 2),
         (4, 0, 3, 6))
clrs = ((1, 0, 0), (0, 1, 0), (0, 0, 1), (1, 1, 0),
        (0, 1, 1), (1, 1, 1), (1, 0, 0), (0, 1, 0),
        (0, 0, 1), (1, 1, 0), (0, 1, 1), (1, 1, 1))
t_coords = ((0, 0), (0, tc), (tc, tc), (tc, 0)) # Координаты текстуры
# Индексы ребер куба (используется при выводе линий вдоль ребер куба)
edges = ((0, 1), (0, 3), (0, 4), (2, 1), (2, 3), (2, 7),
         (6, 3), (6, 4), (6, 7), (5, 1), (5, 4), (5, 7))
def texInit(): # Формирование текстур
    gl.glGenTextures(6, textureIDs)
    r = gl.GL_RGB
    p3 = gl.GL_REPEAT # GL_REPEAT GL_CLAMP_TO_EDGE
    p4 = gl.GL_LINEAR
    for k in range(6):
        fn = 'dice' + str(k) + '.jpg'
        img = pyglet.image.load(fn)
        iWidth = img.width
        iHeight = img.height
        img = img.get_data('RGB', iWidth * 3)
        gl.glBindTexture(p, textureIDs[k])
        gl.glTexParameterf(p, gl.GL_TEXTURE_WRAP_S, p3)
        gl.glTexParameterf(p, gl.GL_TEXTURE_WRAP_T, p3)
        gl.glTexParameterf(p, gl.GL_TEXTURE_MAG_FILTER, p4)
        gl.glTexParameterf(p, gl.GL_TEXTURE_MIN_FILTER, p4)
        gl.glTexEnvf(gl.GL_TEXTURE_ENV, gl.GL_TEXTURE_ENV_MODE, gl.GL_DECAL)
        gl.glTexImage2D(p, 0, r, iWidth, iHeight, 0, r, gl.GL_UNSIGNED_BYTE, img)
    gl.glEnable(p)
def cube_draw():
    k = -1
    for face in faces:
        k += 1
        m = -1
        v4, c4, t4 = (), (), ()
        gl.glBindTexture(p, textureIDs[k])
        for v in face:
            m += 1
            c4 += clrs[k + m]
            t4 += t_coords[m]
            v4 += verts[v]
        graphics.draw(4, gl.GL_QUADS, ('v3f', v4), ('c3f', c4), ('t2f', t4))
##    gl.glColor3f(1, 0, 0)
##    for edge in edges:
##        v2 = ()
##        for v in edge:
##            v2 += verts[v]
##        graphics.draw(2, gl.GL_LINES, ('v3f', v2))
#
window = Window(visible = True, width = width, height = height,
                resizable = True, caption = 'Куб')
gl.glClearColor(0, 0, 0, 1) # Черный цвет фона
gl.glClear(gl.GL_COLOR_BUFFER_BIT|gl.GL_DEPTH_BUFFER_BIT)
gl.glPolygonMode(gl.GL_FRONT, gl.GL_FILL)
gl.glPolygonMode(gl.GL_BACK, gl.GL_LINE)
gl.glLineWidth(3)
gl.glPointSize(4)
gl.glEnable(gl.GL_POINT_SMOOTH)
gl.glShadeModel(gl.GL_SMOOTH) # GL_SMOOTH, GL_FLAT
gl.glCullFace(gl.GL_BACK) # GL_FRONT GL_BACK
gl.glEnable(gl.GL_CULL_FACE)
gl.glEnable(gl.GL_DEPTH_TEST)
gl.glDepthFunc(gl.GL_LESS) # GL_LESS GL_GREATER
#
texInit() # Создаем текстуры
#
@window.event
def on_draw():
    window.clear()
    gl.glMatrixMode(gl.GL_PROJECTION)
    gl.glLoadIdentity()
    gl.glOrtho(-w, w, -w, w, -w, w)
    gl.glRotatef(rot_x, 1, 0, 0) # Поворот относительно оси X
    gl.glRotatef(rot_y, 0, 1, 0) # Поворот относительно оси Y
    gl.glRotatef(rot_z, 0, 0, 1) # Поворот относительно оси Z
    cube_draw()
@window.event
def on_key_press(symbol, modifiers):
    if symbol == key._1:
        gl.glPolygonMode(gl.GL_FRONT, gl.GL_FILL)
        gl.glShadeModel(gl.GL_SMOOTH)
        gl.glEnable(gl.GL_CULL_FACE)
        gl.glEnable(p)
    elif symbol == key._2:
        gl.glPolygonMode(gl.GL_FRONT, gl.GL_FILL)
        gl.glShadeModel(gl.GL_FLAT)
        gl.glEnable(gl.GL_CULL_FACE)
        gl.glDisable(p)
    elif symbol == key._3:
        gl.glPolygonMode(gl.GL_FRONT, gl.GL_LINE) # Вывод ребер
    elif symbol == key._4:
        ps = (gl.GLfloat * 1)()
        ps_range = (gl.GLfloat * 2)()
        gl.glGetFloatv(gl.GL_POINT_SIZE, ps) # Получаем текущий размер точки
        # Допустимый диапазон изменения размеров точки
        gl.glGetFloatv(gl.GL_POINT_SIZE_RANGE, ps_range)
        ps = ps[0]
        ps = (ps + 2) if ps < ps_range[1] - 2 else 4
        gl.glPointSize(ps)
        gl.glPolygonMode(gl.GL_FRONT, gl.GL_POINT)
    elif symbol == key._5:
        gl.glDisable(gl.GL_CULL_FACE)
    elif symbol == key._6:
        gl.glPolygonMode(gl.GL_FRONT, gl.GL_FILL)
        gl.glShadeModel(gl.GL_SMOOTH)
        gl.glEnable(gl.GL_CULL_FACE)
        gl.glDisable(p)
app.run()

Вращающийся куб

Выводится вращающаяся игральная кость.

Шаг угла поворота 1°

Шаг угла поворота вокруг каждой оси равен либо da, либо -da.
Смена знака шага da выполняется после заданного числа поворотов (в программе – это 180).
Процедура cube_draw, выводящая куб, вызывается после каждого срабатывания таймера:

pyglet.clock.tick()

Вызов обеспечивает метод schedule:

pyglet.clock.schedule(cube_draw)

# Вращающаяся кость
import pyglet
from pyglet import app, gl, graphics
from pyglet.window import Window
n_rot, da = 0, 1
h = 20 # Половины длины ребра куба
w = 2 * h # Для задания области вывода
width = height = 300 # Размер окна вывода
textureIDs = (gl.GLuint * 6)() # Массив идентификаторов (номеров) текстур
p = gl.GL_TEXTURE_2D
tc = 1 # Число повторов текстуры
verts = ((h, -h, -h), # Координаты вершин куба
         (h, h, -h),
         (-h, h, -h),
         (-h, -h, -h),
         (h, -h, h),
         (h, h, h),
         (-h, -h, h),
         (-h, h, h))
faces = ((0, 1, 2, 3), # Индексы вершин граней куба
         (3, 2, 7, 6),
         (6, 7, 5, 4),
         (4, 5, 1, 0),
         (1, 5, 7, 2),
         (4, 0, 3, 6))
t_coords = ((0, 0), (0, tc), (tc, tc), (tc, 0)) # Координаты текстуры
def texInit(): # Формирование текстур
    gl.glGenTextures(6, textureIDs)
    r = gl.GL_RGB
    p3 = gl.GL_REPEAT
    p4 = gl.GL_LINEAR
    for k in range(6):
        fn = 'dice' + str(k) + '.jpg'
        img = pyglet.image.load(fn)
        iWidth = img.width
        iHeight = img.height
        img = img.get_data('RGB', iWidth * 3)
        gl.glBindTexture(p, textureIDs[k])
        gl.glTexParameterf(p, gl.GL_TEXTURE_WRAP_S, p3)
        gl.glTexParameterf(p, gl.GL_TEXTURE_WRAP_T, p3)
        gl.glTexParameterf(p, gl.GL_TEXTURE_MAG_FILTER, p4)
        gl.glTexParameterf(p, gl.GL_TEXTURE_MIN_FILTER, p4)
        gl.glTexEnvf(gl.GL_TEXTURE_ENV, gl.GL_TEXTURE_ENV_MODE, gl.GL_DECAL)
        gl.glTexImage2D(p, 0, r, iWidth, iHeight, 0, r, gl.GL_UNSIGNED_BYTE, img)
    gl.glEnable(p)
def cube_draw(dt):
    k = -1
    for face in faces:
        k += 1
        m = -1
        v4, t4 = (), ()
        gl.glBindTexture(p, textureIDs[k])
        for v in face:
            m += 1
            t4 += t_coords[m]
            v4 += verts[v]
        graphics.draw(4, gl.GL_QUADS, ('v3f', v4), ('t2f', t4))
window = Window(visible = True, width = width, height = height,
                resizable = True, caption = 'Куб')
gl.glClearColor(0, 0, 0, 1) # Черный цвет фона
gl.glClear(gl.GL_COLOR_BUFFER_BIT)
gl.glCullFace(gl.GL_BACK) # GL_FRONT GL_BACK
gl.glEnable(gl.GL_CULL_FACE)
#
texInit() # Создаем текстуры
#
@window.event
def on_draw():
    global n_rot, da
    window.clear()
    gl.glMatrixMode(gl.GL_PROJECTION)
    gl.glLoadIdentity()
    gl.glOrtho(-w, w, -w, w, -w, w)
    gl.glMatrixMode(gl.GL_MODELVIEW)
    if n_rot > 180:
        n_rot = 0
        da = -da
    n_rot += 1    
    gl.glRotatef(da, 1, 0, 0)
    gl.glRotatef(da, 0, 1, 0)
    gl.glRotatef(da, 0, 0, 1)
    pyglet.clock.tick()
#
pyglet.clock.schedule(cube_draw)
app.run()

Тест глубины

На рис. 22 показаны два прямоугольника с одинаковыми размерами, но разными z-координатами вершин: z-координата вершин красного прямоугольника (КП) равна 1, синего прямоугольника (СП) – 0.

Тест глубины

Рис. 22. Тест глубины отключен

СП перекрывает КП, хотя z-координата вершин КП больше, чем у СП (1 против 0). То есть должно быть ровно наоборот: КП перекрывает СП.
Однако этого не происходит, поскольку в приводимой ниже программе, выводящей СП и КП, первоначально отключен тест глубины:

glDisable(GL_DEPTH_TEST),

а СП выводится вслед за КП.
При нажатии на 1 включается тест глубины:

glEnable(GL_DEPTH_TEST),

и вывод осуществляется с учетом z-координат вершин СП и КП (рис. 23).

Тест глубины

Рис. 23. Выполняется тест глубины

При нажатии на 2 тест глубины отключается и получаем рис. 22.

# Тест глубины
import pyglet
from pyglet import app, gl, graphics
from pyglet.window import Window, key
d = 20 # Половина длины большой стороны прямоугольника
d2 = d / 2
z2 = 1 # z-координата вершин красного прямоугольника
wx, wy = 2 * d, 2 * d2 # Для задания области вывода
width, height = 300, 150 # Размеры окна вывода
def draw():
    gl.glMatrixMode(gl.GL_PROJECTION)
    gl.glLoadIdentity()
    gl.glOrtho(-wx, wx, -wy, wy, -2, 2)
    gl.glMatrixMode(gl.GL_MODELVIEW)
    gl.glLoadIdentity()
    gl.glTranslatef(-d/2, -d2/2, 0)
    gl.glColor3f(1, 0, 0) # Вывод красного прямоугольника (КП)
    graphics.draw(4, gl.GL_QUADS, ('v3f', (-d, -d2, z2, d, -d2, z2, d, d2, z2, -d, d2, z2)))
    gl.glTranslatef(d, d2, 0)
    gl.glColor3f(0, 0, 1) # Вывод синего прямоугольника (СП)
    graphics.draw(4, gl.GL_QUADS, ('v2f', (-d, -d2, d, -d2, d, d2, -d, d2)))
#
window = Window(width = width, height = height, caption = 'Тест глубины')
gl.glClearColor(0.75, 0.75, 0.75, 1) # Серый цвет фона
gl.glClear(gl.GL_COLOR_BUFFER_BIT|gl.GL_DEPTH_BUFFER_BIT)
gl.glDisable(gl.GL_DEPTH_TEST)
gl.glDepthFunc(gl.GL_LESS) # GL_LESS GL_GREATER
#
@window.event
def on_draw():
    window.clear()
    draw()
@window.event
def on_key_press(symbol, modifiers):
    if symbol == key._1:
        gl.glEnable(gl.GL_DEPTH_TEST)
    elif symbol == key._2:
        gl.glDisable(gl.GL_DEPTH_TEST)
app.run()

Два материала и источника света

На рис. 23 показан результат применения двух материалов и двух источников света.
Первые материал и источник света применяются к левому прямоугольнику, вторые – к правому.

Два материала

Рис. 23. Два материала и источника света. Нормали рассчитаны к граням

При левая грань выведена с применением первой пары материала и источника света:

gl.glMaterialfv(gl.GL_FRONT, gl.GL_SPECULAR, mtClr)
gl.glEnable(gl.GL_LIGHT0)
gl.glDisable(gl.GL_LIGHT1)

правая – с применением второй пары материала и источника света:

gl.glMaterialfv(gl.GL_FRONT, gl.GL_SPECULAR, mtClr2)
gl.glDisable(gl.GL_LIGHT0)
gl.glEnable(gl.GL_LIGHT1)

Цвета первых материала и света соответственно

[0.7, 0.9, 0, 0] и [1, 0.5, 0.5, 0],

а вторых

[0, 0.7, 0.9, 0] и [0.5, 0.5, 1.0, 0].

Координаты первого и второго источников света соответственно

[-100, 200, 400, 0] и [100, 200, 400, 0].

При одновременной работе обоих источников света получаем рис. 24.

Два света

Рис. 24. Два материала и источника света. Включены оба источника

Контрольные вопросы

ЛР 2

  1. Примитивы OpenGL.
  2. Способы вывода точки.
  3. Различие между GL_LINES, GL_LINE_LOOP и GL_LINE_STRIP.
  4. Как при выводе линии задать и использовать шаблон.
  5. Лицевая и нелицевая стороны полигона.
  6. Способы вывода полигона.
  7. Как задать вывод лицевой стороны в виде точек, нелицевой в виде линий.
  8. Как задать заливку лицевой стороны, а нелицевую вывести в виде точек.
  9. Как задать вывод с интерполяцией цветов и без нее.
  10. Параметры метода draw.
  11. Назначение glBegin / glEnd.

ЛР 3

  1. Виды проецирования.
  2. Как задать в OpenGL изометрическую проекцию 3d-объекта (разновидность аксонометрической проекции).
  3. Реализация аффинных преобразований координат в OpenGL.
  4. Расчет цвета пикселя отрезка и полигона при линейной интерполяции цветов, заданных в вершинах примитива.
  5. Как рассчитать нормали к граням и вершинам:

    Нормали

  6. Задание материалов и источников света.
  7. Компоненты света, задаваемые GL_AMBIENT, GL_DIFFUSE и GL_SPECULAR.
  8. Вывести в задаче 19 полигоны, используя draw.
  9. Задачи, решаемые с применением компьютерной графики.

ЛР 4

Наложение текстуры на грани призмы.

Текстура

Литература

1. OpenGL: преобразование координат. [Электронный ресурс] URL: http://100byte.ru/100btwrks/prjctn/prjctn.html. (Дата обращения: 01.09.2020).
2. Вывод граней OpenGL. [Электронный ресурс] URL: http://100byte.ru/stdntswrks/gl/gl.html. (Дата обращения: 01.09.2020).
3. C#, OpenGL: вывод стандартных примитивов. [Электронный ресурс] URL: http://100byte.ru/stdntswrks/cshrp/stdprmtvs/stdprmtvs.html. (Дата обращения: 01.09.2020).
4. Построение, текстурирование и тонирование сферы. [Электронный ресурс] URL: http://100byte.ru/stdntswrks/cshrp/sphTK/sphTK.html. (Дата обращения: 01.09.2020).
5. Шейдер с иллюзией выступов и впадин. [Электронный ресурс] URL: http://100byte.ru/stdntswrks/cshrp/mapTK/mapTK.html. (Дата обращения: 01.09.2020).
6. Шейдер с картой-кубом в задаче воспроизведения глобуса.Шейдер с картой-кубом в задаче воспроизведения глобуса. [Электронный ресурс] URL: http://100byte.ru/stdntswrks/cshrp/earthCubeMapTK/earthCubeMapTK.html. (Дата обращения: 01.09.2020).
7. Шейдерная реализация освещения по Блинну и Фонгу. [Электронный ресурс] URL: http://100byte.ru/stdntswrks/cshrp/blinnPhongPart/blinnPhong.html. (Дата обращения: 01.09.2020).
8. Вывод текста.. [Электронный ресурс] URL: http://100byte.ru/stdntswrks/cshrp/txt.html. (Дата обращения: 01.09.2020).
9. Вывод частиц. [Электронный ресурс] URL: http://100byte.ru/stdntswrks/cshrp/pf.html. (Дата обращения: 01.09.2020).
10. Руководство по программированию. [Электронный ресурс] URL: https://pyglet.readthedocs.io/en/latest/programming_guide/graphics.html. (Дата обращения: 01.09.2020).

Список работ

Рейтинг@Mail.ru