Как известно, функция glDrawPixels является устаревшей (deprecated), но иногда необходимо просто вывести набор пикселей на экран. Например в целях отладки или там, где не важна скорость вывода. В этом сообщении я приведу аналог данной функции, принимающей в качестве аргументов все тоже самое, что и оригинальная glDrawPixels. Данное сообщение также поможет новичкам в OpenGL поверхностно ознакомиться с тем, как создаются текстуры, шейдеры, вершинный буфер (Vertex Buffer Object) и VertexArrayObject. Не смотря на свою простоту код, описанный ниже охватывает фундаментальные основы визуализации на OpenGL 3.x и выше.
Далее построчно будет рассмотрено, что, как и зачем в этой функции.
Вершинный шейдер принимает на вход двухмерные координаты (position) в диапазоне -1,+1,
описывающие Screen-aligned quad в однородных координатах. На выходе - текстурные координаты изображения.
Фрагментный шейдер принимает на вход текстурные координаты, полученные в вершинном шейдере и использует их для выборки пикселей изображения из текстуры.
Вершинный и фрагментный шейдеры компилируются и исполняются на GPU. Теперь перейдем непосредственно к вызовам функций OpenGL.
Для справки: существуют также, не рассмотренные здесь: compute shader, geometry shader, tessellation control shader, tessellation evaluation shader.
2. Создание текстуры
Создание текстуры в нашем примере не отличается от создания текстур в ранних версиях OpenGL. Итак, для того чтобы создать текстуру, необходимо сгенерировать ее идентификатор:
glGenTextures(1, &texid );
Далее необходимо указать OpenGL-ю, с какой текстурой мы в данный момент работаем. Для этого служит glBindTexture:
glBindTexture( GL_TEXTURE_2D, texid );
Здесь первый аргумент тип нашей текстуры (GL_TEXTURE_2D), второй - идентификатор, полученный на предыдущем шаге.
Следующая строчка не обязательна, если ширина исходного изображения (точнее размер в байтах ширины исходного изображения) выровнена по 4-ем байтам. В нашем случае мы указываем выравнивание по 1-му байту, т.к. изображение не выравнено.
glPixelStorei( GL_UNPACK_ALIGNMENT, 1); // set 1-byte alignment
Теперь необходимо загрузить массив пикселей изображения в текстуру. В нашем примере для этого подходит функция glTexImage2D.
Здесь нас интересует второй, третий и шестой аргумент. Второй аргумент - это mip-уровень текстуры (у нас он нулевой и единственный), третий аргумент - это в каком формате изображение будет храниться в видео памяти, шестой - бордер. Аргументы _Width, _Height, _Format, _Type и _Pixels полностью соответствуют спецификации glDrawPixels.
Ну и, наконец, устанавливаем фильтрацию текстуры. Так как мы пишем аналог glDrawPixels, мы устанавливаем фильтрацию GL_NEAREST (округление к ближайшему пикселю при выборке из текстуры в шейдере).
Создание шейдерной программы состоит из нескольких этапов:
- создание идентификаторов шейдерной программы, а также идентификаторов самих шейдеров (в нашем случае только два - вершинный и фрагментный);
- задание исходного кода шейдерам;
- компиляция шейдеров;
- присоединение шейдеров к шейдерной программе;
- удаление шейдеров;
- линковка и валидация шейдерной программы.
Желательно на этапах компиляции, линковки и валидирования проверять на соответствующие ошибки. В данном примере для чистоты и ясности кода эти моменты опущены.
Чтобы установить шейдерную программу, как используемую (текущую), необходимо сделать следующим образом:
glUseProgram( shaderProgram );
4. Привязка текстуры к шейдеру
Для получения доступа к юниформам (uniform) шейдера существует ф-ия glGetUniformLocation, которая возвращает расположение (грубо говоря, адрес) юниформа.
Здесь мы активируем нулевой текстурный юнит (GL_TEXTURE0) и указываем, что данный uniform ссылается на него.
Если вы обратили внимание, порядок вершин задан в соответствии с GL_TRIANGLE_STRIP. На мой взгляд, это оптимальный способ задать Screen-Aligned Quad (наименьшее количество вершин и без необходимости в индексном буфере).
Сгенерируем идентификатор для вершинного буфера.
glGenBuffers(1, &vertBuffer );
Биндим полученный индентификтор, чтобы OpenGL знал с каким из вершинных буферов он работает в данный момент.