(Продолжение, начало см. в МК № 36, 40 (207, 211)). В данной части сериала речь пойдет о поточечном выводе на экран и о том, что есть в библиотеке OpenGL для работы непосредственно с пикселами. Хотя эта библиотека известна именно по работе с трехмерной графикой, в ней есть много функций для плоскостных построений (2d), а также для работы с пикселами. И именно при создании двухмерной (плоской) графики точечный (попиксельный) вывод и все, что с ним связано, наиболее часто используется. Сюда относятся различные алгоритмы, с помощью которых генерируются эффекты огня, линзы, звездного коридора, сглаживания изображения и т.д. Необходимость в непосредственной работе с пикселами может возникнуть и тогда, например, когда потребуется узнать цвет в определенной позиции области вывода (в некоторых случаях этим можно пользоваться для выбора объектов сцены). Короче говоря, знания в области 2d никому не повредят.
Теперь, после небольшого вступления, перейдем к практике. И, как всегда, сначала пример, а затем детальное описание работы с пикселами в OpenGL. Пример сегодня будет довольно занимательный. Он иллюстрирует одну из разновидностей эффекта морфинга плавный переход одного изображения в другое. Существует довольно сложный морфинг, при котором происходит плавное изменение контуров одного изображения до получения другого, но мы рассмотрим более простой, основанный на смешении цветов. В этом случае результирующий цвет определяется по формуле C=C1+(C2-C1)*T/M, где С1 цвет пиксела первого изображения; С2 цвет пиксела второго изображения с теми же координатами, что и первого; M задает количество кадров морфинга; T текущий кадр. Надеюсь, с этим все ясно. А теперь как это выглядит на практике. Создайте форму, разместите на ней кнопку и два изображения, каждое размером 50х50 точек (потом, конечно, можете в программе поменять размерности массивов и длину циклов, чтобы использовать другие картинки). Объявите глобальные переменные и константы и не забудьте объявить dc:HDC и hrc:HGLRC:
Затем в текст модуля добавьте процедуру, осуществляющую смешение цветов по формуле. Вот как она выглядит:
Теперь обработчик события OnClick кнопки приведите к следующему виду:
В обработчике события OnDestroy формы разместите текст, корректно завершающий работу приложения:
И, наконец, заключительный кусок кода обработчик события OnCreate формы, имеющий следующий вид (текст процедуры SetDCpixelFormat я не привожу вы можете взять его из примеров в предыдущих статьях):
Уже можно запустить и посмотреть, как пример работает. Понажимайте кнопку и увидите, как при каждом нажатии в результирующем изображении происходят изменения. Так как же все это работает?! Сначала, при создании формы, в обработчике события OnCreate формы в цикле заполняются массивы img и img1. Это делается таким образом: берется цвет изображения, стандартными командами getRvalue, getGvalue, getBvalue из полученного цвета извлекаются красная, зеленая и синяя составляющие, их значения заносятся в массивы img и img1. Обратите внимание на описание этих массивов речь идет о трехмерных массивах. Первые два индекса можно считать размерами помещаемого в них изображения, третий может иметь три значения (0,1,2), определяющие ячейки массива, в которых хранятся составляющие цвета RGB (соответственно значениям третьего индекса). Если в составляющих цвета имеется alfa, то используется формат gl_rgba, тогда третий индекс может иметь значения 0,1,2,3 (Red, Green, Blue, Alfa). Это один из способов описания массивов изображений, используемых функциями считывания и вывода пикселов в OpenGL. После того как картинки считаны в массивы, по нажатию кнопки выполнятся следующие действия: командой glClear очистятся необходимые буферы (чтобы начисто рисовать новый кадр), затем командой glPixelStorei задастся выравнивание пикселов, используемое командами чтения/вывода для корректного чтения массива (или в массив) пикселов из памяти и вывода на экран (в данном случае используется байтовое выравнивание, т.к. массив байтов). Далее вызывается ранее описанная пользовательская процедура morph. Она, как уже говорилось, смешивает цвета двух изображений и результаты помещает в результирующий массив res. Уже знакомая вам команда glRasterPos, о которой говорилось в предыдущих статьях, устанавливает позицию вывода блока пикселов (координаты его левого нижнего угла). Наконец, glDrawPixels выводит собственно блок пикселов результирующий массив res. В ее параметрах указывается ширина, высота блока, формат данных, хранимых в нем, их тип и указатель на этот блок (массив). Остается лишь отобразить содержимое буфера на форме и перерисовать ее.
В принципе, на этом можно было бы и закончить. Но так как, судя по письмам, эта тема интересна многим читателям, а не только мне, то не могу не удержаться от того, чтобы привести еще один занимательный пример и еще немного кое о чем порассказать :-).
Еще немного о пикселах
К сожалению, как бы красиво не выглядели все пиксельные эффекты, они очень медленны. Конечно, на тех компьютерах, что есть сегодня у большинства пользователей и геймеров, вывод относительно большого блока пикселов не особо сильно скажется на работе приложения. Но на компьютерах с процессорами ниже PII и со слабыми видеокартами может получиться глобальный слайд-шоу :-). А ведь такого рода компьютеров еще достаточно много, и их владельцам тоже хочется играть, хоть и в простые, но в 3D-игры. Лично мне в плане скоростных показателей нравится подход фирмы Sega разнообразие настроек вывода 3D-графики (глубина цвета, разрешения и др.) позволяет более-менее нормально играть в их игры практически на любых компьютерах, чего не скажешь об играх некоторых других производителей. В общем, вам решать, насколько активно использовать прямую работу с пикселами в ваших программах, использовать ее вообще или, может, искать альтернативу. Например, во многих играх и демках отдано предпочтение текстурам. Ну да ладно немного отвлекся от темы. Вернемся к командам OpenGL для работы с пикселами.
В рассмотренном выше примере описывалась только работа с выводом. Но ведь есть и функция для чтения блока пикселов glReadPixels. Пример ее использования вы можете получить, слегка модифицировав предыдущий. Для этого обязательно установите параметры ClientWidth и ClientHeight формы, равными 220 и 180 соответственно. Почему поймете позже. И поменяйте текст процедуры-обработчика события OnClick кнопки на следующий:
Добавьте в объявление переменных переменную fl типа byte это будет флаг, разрешающий или запрещающий чтение пикселов. После этого допишите в модуль процедуру smooth (можно оставить процедуру morph, но она использоваться не будет):
Запустите и посмотрите, что происходит. На форме случайным образом выводятся линии, после этого считываются цвета пикселов в области вывода линий. Команда glReadPixels, осуществляющая считывание, очень схожа со стандартной низкоуровневой функцией getPixel, она тоже считывает цвета пикселов с контекста устройства (с формы). В параметрах glReadPixels указываются: координаты нижнего левого угла считываемого блока, размеры считываемого блока, формат считываемых значений, их тип и указатель на массив, куда будет произведено чтение. Обратите внимание на то, что команда glPixelStorei здесь не используется чтение и вывод производятся с одинаковым выравниванием и поэтому в выводимом изображении нет никаких нежелательных смещений. Затем, когда пикселы считаны, вызывается наша процедура smooth. Она иллюстрирует еще один простой эффект smooth (размытие). Этот эффект делается так: цвет пиксела в данной позиции определяется средним арифметическим, найденным от суммы составляющих цветов пикселов, расположенных выше, ниже, слева и справа от пиксела в данной позиции. Вообще, такой эффект делается также с помощью локальной фильтрации (матрицами). Это очень распространенный способ. Детально его описывать не буду. Если интересно, поищите в Интернете по фразе Image filtering. Далее в примере идут уже знакомые строки, описанные выше. Но в OpenGL есть еще одна интересная пиксельная команда glPixelZoom. Она позволяет масштабировать выводимые блоки пикселов (в аргументах задаются масштабные коэффициенты во сколько раз увеличить или уменьшить по x и по y). Попробуйте ее вставить перед выводом пикселов в этом и предыдущем примерах. Еще можно получить интересную картинку, если выводить массив с помощью glVertex (glBegin с аргументом gl_points). Например, есть массив 50х50. Запускается цикл вывода двумерного массива, в котором некоторые переменные i и k изменяются от 1 до 50. При выводе в аргументах glVertex указывается: glVertex2f (i/25, k/25). Устанавливая разные делители, можно получить эффектную разновидность масштабирования.
На этот раз все. Программируйте, экспериментируйте, рассматривайте приведенные здесь картинки, читайте МК, в общем, развлекайтесь по полной программе :-) и не пропустите следующую статью!