Объекты OpenGL в Haskell: VAO и VBO
В прошлый раз мы говорили об инициализации системы OpenGL на Haskell и создали простенький модульный шаблон программы (мы используем биндинги GLUT).
В этот раз мы немного дополним наш кубик, раскрасив вершины треугольников, из которых он собран, случайными цветами.
Немного о VAO и VBO⌗
Спецификация OpenGL определяет два объекта, которые мы уже использовали:
- VAO (Vertex Array Object): инкапсулирует состояние системы, достаточное для запуска конвейера (то есть для поставки вершинных данных) и полного рендеринга объекта. Хранит кроме всего прочего:
- Для каждого атрибута(например позиции, цвета, и пр.):
- Флаг вкл/выкл, способ доступа: размер и тип атрибута, stride, смещение в буфере и пр.
- VBO, в котором содержатся эти данные (это тот буфер, который был забинден в глобальный стейт GL с
bindBuffer ArrayBuffer
на момент вызоваvertexAttribPointer
) - Ещё что-то
- Буфер, забинденный сейчас как
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-теста: