В прошлый раз мы говорили об инициализации системы OpenGL на Haskell и создали простенький модульный шаблон программы (мы используем биндинги GLUT).

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

Немного о VAO и VBO

Спецификация OpenGL определяет два объекта, которые мы уже использовали:

  • VAO (Vertex Array Object): инкапсулирует состояние системы, достаточное для запуска конвейера (то есть для поставки вершинных данных) и полного рендеринга объекта. Хранит кроме всего прочего:
    1. Для каждого атрибута(например позиции, цвета, и пр.):
      1. Флаг вкл/выкл, способ доступа: размер и тип атрибута, stride, смещение в буфере и пр.
      2. VBO, в котором содержатся эти данные (это тот буфер, который был забинден в глобальный стейт GL с bindBuffer ArrayBuffer на момент вызова vertexAttribPointer)
      3. Ещё что-то
    2. Буфер, забинденный сейчас как bindBuffer ElementArrayBuffer
  • VBO (Vertex Buffer Object): буфер в высокопроизводительной памяти

Например, рассмотрим последовательность загрузки атрибута “позиция” нашего кубика в GPU:

-- создание Program Object (PO) не зависит от других объектов
program <- loadShaders ...

...

vao <- genObjectName
bindVertexArrayObject $= Just vao
-- с этого момента вызовы *Attrib* меняют стэйт данного VAO

-- VBO, передача данных. Никак не связана с текущим VAO (пока)
posBuffer <- genObjectName
bindBuffer ArrayBuffer $= Just posBuffer
withArray pvs $ \ptr -> do
  let size = fromIntegral (numVertices pvs * vertexSize pvs)
  bufferData ArrayBuffer $= (size, ptr, StaticDraw)

-- получение локации _атрибута_ из GLSL-кода связано только с PO
posLoc <- get $ attribLocation program "vPosition"

-- вместо posLoc могли бы использовать просто цифры (но тогда
-- нужна location в "in vec3 vPosition" в шейдере)
vertexAttribPointer posLoc $=
  (ToFloat,
   VertexArrayDescriptor 3 Float 0 (bufferOffset 0))
-- код выше изменил стэйт прибинденного VAO
vertexAttribArray posLoc $= Enabled
-- стэйт VAO снова изменён

-- ...

-- получение uniform из микропрограммы зависит от PO
rotAngles <- get $ uniformLocation program "rotAngles"
uniform rotAngles $= Vector3 ax ay az

-- эти команды используют текущий VAO
clear [ColorBuffer]
drawArrays Triangles firsti (nvertices)
flush

-- можно быстро переключаться между несколькими VAO
bindVertexArrayObject $= Just vao2

drawArrays

Ключевая буква в названии данной функции – s. drawArrays:

drawArrays :: PrimitiveMode -> ArrayIndex -> NumArrayIndices -> IO ()

drawArrays mode first count использует count последовательных элементов параллельно из каждого включённого массива для отрисовки примитивов. Отсчёт количества параллельно выкладываемых на конвейер (подаваемых на соответствующие входы in GLSL-программы, начиная с вершинного шейдера) элементов начинается с общего индекса first. В нашей сегодняшней программе к одному VAO “подключено” два VBO, данные из которых ассоциированы с vPosition и vColor в вершинном шейдере. Работа конвейера OpenGL начинается именно с вершинного шейдера, поэтому мы должны передавать цвета от выхода к входу вплоть до фрагментного шейдера (где цвета и будут использованы).

Haskell-код

Очень коротко об изменениях в Haskell-коде.

Создаём список случайных цветов, буферизуем и подключаем attribute location аналогично vPosition из нашей прошлой статьи:

setupColorData :: Program -> [Vertex3 GLfloat] -> IO ()
setupColorData program cvs = do
  buff <- genObjectName
  bindBuffer ArrayBuffer $= Just buff
  withArray cvs $ \ptr -> do
    let size = fromIntegral (numVertices cvs * vertexSize cvs)
    bufferData ArrayBuffer $= (size, ptr, StaticDraw)
  atloc <- get $ attribLocation program "vColor"
  vertexAttribPointer atloc $=
    (ToFloat,
     VertexArrayDescriptor 3 Float 0 (bufferOffset 0))
  vertexAttribArray atloc $= Enabled

(конечно же весь такой код можно и нужно обобщить)

Остальной код и, что самое интересное, код непосредственно рисования на экране в обработчике display, – без изменения.

GLSL-код

Вертексный шейдер:

#version 430 core

in vec3 vPosition;
in vec3 vColor;

// слегка другое название: нужно просто протащить по конвейеру дальше
out vec3 VColor;

uniform vec3 rotAngles;


mat4 rotationMatrix(vec3 axis, float angle)
{
    ...
}


void
main()
{
    ...

  vec4 pos = trans * vec4(vPosition.xyz, 1);
  VColor = vColor;
  gl_Position = pos;
}

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

#version 430 core

in vec3 VColor;

out vec4 outColor;

void
main()
{
  outColor = vec4(VColor, 1);
}

Результат

Теперь у кажлого треугольника разноцветные вершины:

Странный кубик

Но что-то не так…

Z-buffer

А вот как мы включаем Depth-буфер и тест в GLUT:

glutPrepare :: IO ()
glutPrepare = do
...
  -- по умолчанию WithDepthBuffer не указан
  initialDisplayMode $= [ RGBAMode, WithDepthBuffer ]
...
  -- устанавливаем, как GL делать depth buffer test
  depthFunc $= Just Less
  displayCallback $= display descriptor
...

display :: Descriptor -> DisplayCallback
display d = do
  ...
  -- не забываем очищать и Z-буфер каждый раз
  clear [ ColorBuffer, DepthBuffer ]

  ...
  flush

Результат Depth-теста:

Не менее странный кубик