(Окончание, начало см. в МК № 36, 40, 43 (207, 211, 214)). Текстуры — одна из самых эффектных и интересных возможностей библиотеки OpenGL. Ведь именно они позволяют придать объектам соответствующий вид и сделать сцену более реалистичной. Кроме того, в большинстве существующих сегодня 3D-играх текстуры используются для создания эффектов взрывов, огня, воды, магии и проч. Текстуры обеспечивают самый быстрый вывод как обычного растрового изображения, так и изображения с прозрачностью, с деформацией. В этой статье речь пойдет только лишь об основах и приемах работы с текстурами. Но кроме того, для создания действительно сложных и впечатляющих эффектов вам понадобятся еще и знания различных алгоритмов, реализующих их. Если вы действительно интересуетесь программированием 3D-графики, не ленитесь и побольше экспериментируйте. В крайнем случае, для нахождения ответов на свои вопросы используйте Интернет. Кстати, на сайтах http://www.scene.orgи http://www.demoscene.ruразмещены различные демо-программы, наглядно иллюстрирующие возможности программирования динамической графики с помощью OpenGL или Direct3D. Советую посмотреть. Ну а теперь перейдем все же непосредственно к текстурам.
Наложение текстуры
В библиотеке OpenGL предусмотрены возможности работы с несколькими типами текстур: одномерной и двумерной. Как правило, одномерные текстуры используются для нанесения штриховки или узоров в виде полосок (Рис. 1). Источником изображения для текстуры в данном случае является одномерный массив со значениями составляющих цвета (RGB) для каждой полоски. В двумерной же текстуре для этих целей используется соответственно двумерный массив, что позволяет наносить на объекты прямоугольные образы, в том числе, например, фотографии и прочее. Давайте все же ограничимся рассмотрением основ работы именно с двумерной текстурой, потому как, изучив материал по этой теме, вы без особых усилий сможете самостоятельно научиться использовать и одномерные текстуры (изменив буквально несколько параметров в функциях (см. хелп). Итак, начнем с самого начала, а именно с простого покрытия графического примитива текстурой. Как вы уже, наверное, знаете, текстура представляет собой некий графический образ, наносящийся на графический объект и располагающийся соответственно его форме (рельефу). Для использования текстуры потребуется создать и заполнить массив образа — для хранения текстурной картинки, а затем выполнить ряд действий по ее нанесению. При этом следует учитывать тот факт, что массив должен иметь размерности, кратные степени двойки (2, 4, 16, 32 и т.д.) Благо, в OpenGL есть механизм уменьшения/увеличения текстуры с коррекцией качества (см. статью Davert’a «Алгоритмы текстурирования», МК №47(218)). Вот пример нанесения текстуры на полигональную поверхность (текст процедуры SetDCpixelFormat см. в предыдущих статьях) — в качестве
текстуры используется картинка 128х128 (сделано в PhotoShop :-)); разместите на форме компонент TImage с картинкой размером 128х128 и кнопку — компонент TButton:
{глобальные переменные} Var image:array[0..127,0..127,0..2]of glubyte; i,k:integer; … {пользовательские процедуры — для создания массива текстуры и для рисования полигона с текстурой соответственно} procedure createTexture; var color:integer; begin For k := 0 to 127 do For i := 0 to 127 do begin color:=form1.image1.picture.bitmap.Canvas.Pixels[i,k]; image[i][k][0] := GetRValue(color); image[i][k][1] := GetGValue(color); image[i][k][2] := GetBValue(color); end; glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA,128,128,0,GL_RGB,GL_UNSIGNED_BYTE,@image); glTexEnvi(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_DECAL); glEnable(GL_TEXTURE_2D); end; procedure drawpoly; begin glbegin(gl_polygon); gltexcoord2f(0,1); glvertex2f(1,1); gltexcoord2f(0,0); glvertex2f(-1,1); gltexcoord2f(1,0); glvertex2f(-1,-1); gltexcoord2f(1,1); glvertex2f(1,-1); glend; end; {обработчик события OnCreate формы; в разделе private переменная DC имеет тип HDC, а HRC — тип HGLRC} procedure TForm1.FormCreate(Sender: TObject); begin DC := GetDC (Handle); SetDCPixelFormat(DC); hrc := wglCreateContext(DC); wglMakeCurrent(DC, hrc); glClearColor (0, 0, 0, 0); glMatrixMode (GL_PROJECTION); glLoadIdentity; glFrustum (-1, 1, -1, 1, 2, 20); glMatrixMode (GL_MODELVIEW); glLoadIdentity; glTranslatef(0.0, 0.0, -6.0); glEnable (GL_LIGHTING); glEnable (GL_LIGHT0); glEnable (GL_DEPTH_TEST); end; {обработчик события OnDestroy формы} procedure TForm1.FormDestroy(Sender: TObject); begin wglMakeCurrent(0, 0); wglDeleteContext(hrc); ReleaseDC (Handle, DC); DeleteDC (DC); end; {обработчик события OnClick кнопки} procedure TForm1.Button1Click(Sender: TObject); begin glClear (GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT); createTexture; glrotatef(-25,1,1,0); drawpoly; gltranslatef(0,0,2); drawpoly; swapbuffers(dc); end;
Давайте посмотрим, как все это работает. Сначала при запуске программы происходит обработка события OnCreate формы — с помощью уже знакомых вам команд OpenGL устанавливаются начальные настройки (получение контекстов, настройка рабочей области и пр.) После этого при нажатии кнопки выполняется пользовательская процедура CreateTexture. В ней первым делом заполняется массив образа текстуры. Для этого из помещенной ранее в компонент Timage картинки считываются значения RGB-составляющих каждого пиксела и помещаются в трехмерный массив image. Затем выполняется минимальный необходимый набор команд, обеспечивающий нанесение текстуры. Командой glTexParameteri задаются параметры нанесения текстуры на объект: GL_TEXTURE_2D — указывает, что используется именно двухмерная текстура (GL_TEXTURE_1D — для одномерной), далее следует какой-либо параметр текстуры и его значение. В нашем случае двумя вызовами команды glTexParameteri задаются правила нанесения текстуры на объект для случаев, когда: поверхность, на которую будет помещаться текстура, меньше размера самой текстуры (GL_TEXTURE_MAG_FILTER) (по количеству пикселов) и наоборот (GL_TEXTURE_MIN_FILTER). Параметр GL_NEAREST определяет метод расчета соответствия пикселов текстуры пикселам текстурированной поверхности, в данном случае не очень точный, но
более быстрый, чем при GL_LINEAR (с большей точностью) Следующей командой (gl_TexImage2D) выбирается картинка (вернее, массив, содержащий ее) для текстуры, после чего команда glTexEnvi определяет характер взаимодействия цвета примитива с цветом текстуры. Если указан параметр GL_DECAL, цвет примитива не влияет на цвет текстуры, если же GL_MODULATE — цветовое содержание текстуры будет зависеть от яркости и составляющих цвета примитива (например, если примитив будет иметь красный цвет, то в текстуре красный уберется; если же примитив окрасить, к примеру, в 50%-й серый, то тогда яркость пикселов текстуры уменьшится на 50%); GL_BLEND выполняет функцию, противоположную GL_MODULATE. По завершению всех этих действий командой glEnable разрешается использование двумерной текстуры. Но это еще не все. Для корректного нанесения текстуры надо определить ее координаты. Их следует рассматривать с одной стороны как некие масштабные коэффициенты, с помощью которых можно определить количество повторений текстуры на поверхности объекта, а с другой стороны, как параметры, определяющие расположение текстуры на объекте. В нашем случае работа происходит только с двумя координатами (текстура кладется на плоскость, при том что сам объект расположен в трехмерном пространстве), хотя их всего 4 (называются s,t,r,q). В процедуре drawpoly после указания каждой вершины примитива также указываются и координаты текстуры (glTexCoord2f) — они существуют не сами по себе, а имеют некоторую связь с координатами примитива. Попробуйте в процедуре в параметрах команды glTexCoord заменить единицы, например, на двойки — вместо одной большой картинки текстуры на примитиве появятся 4 маленькие. Также можно, расставив координаты текстуры соответствующим образом, повернуть текстуру на 90 или перевернуть. К примеру, поменяйте параметры команд glTexCoord на соответствующие —(0,0),(1,0),(1,1),(0,1). Вот и все. Результат работы программы — на Рис. 2 (две текстурированные поверхности). Теперь поговорим о некоторых эффектах.
Прозрачность текстуры
Это один из самых распространенных эффектов. Большинство (если не все) сегодняшних трехмерных игр так или иначе используют его для создания красивых и впечатляющих эффектов. Суть эффекта заключается в использовании так называемого альфа-канала — четвертой составляющей цвета (Red, Green, Blue, Alfa). С помощью него можно определять степень прозрачности цвета. Давайте рассмотрим пример на эту тему. Для этого измените в предыдущем примере объявление массива image: var image:array[0..127,0..127,0..3]of glubyte — теперь в нем будет присутствовать 4 цветовые составляющие. Кроме того, приведите процедуру CreateTexture к следующему виду:
procedure createTexture; var color:integer; begin For k := 0 to 127 do For i := 0 to 127 do begin color:=form1.image1.picture.bitmap.Canvas.Pixels[i,k]; image[i][k][0] := GetRValue(color); image[i][k][1] := GetGValue(color); image[i][k][2] := GetBValue(color); if (image[i][k][0]<160)and {если цвет изображения} (image[i][k][1]<160)and {близок к черному,} (image[i][k][2]<160) then {то тогда} image[i][k][3]:=1 else {делаем его прозрачным} image[i][k][3]:=255; {в другом случае — цвет делаем непрозрачным} end; glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexImage2D(GL_TEXTURE_2D, 0,GL_RGBA,128,128,0,GL_RGBA,GL_UNSIGNED_BYTE,@image); glEnable(GL_TEXTURE_2D); glenable(gl_blend); glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); end;
Обратите внимание: здесь исчезла команда glTexEnv, также в параметрах команды glTexImage2D присутствует только формат GL_RGBA и, наконец появились две новые строчки, завершающие код процедуры. Первая (glenable(gL_blend)) включает режим смешения цветов объектов сцены (не только текстурированных). Вторая (glBlendFunc) определяет характер этого смешения. Все цвета, близкие к черному, делаются прозрачными, а остальные — нет. Думаю, здесь все понятно. Результат на Рис. 3 — через одну текстурированную поверхность видна часть другой.
Эффект зеркальной поверхности
К сожалению, используемая в данной статье плоскость не дает возможности реализовать этот эффект во всей его красе. Если хотите, можете скачать модуль dglut.pas по адресу — в нем есть процедура рисования чайника (не подумайте чего лишнего :-) — просто чайник считается сложным объектом и удобен для различных демонстраций, особенно в данном случае). Вы сможете легко заменить плоскость на этот чайник и вдоволь полюбоваться эффектом (Рис. 4). И все же я приведу строки, реализующие этот эффект на плоскости (допишите их в конец вышеприведенной процедуры CreateTexture):
gltexgeni(gl_s, gl_texture_gen_mode,GL_SPHERE_MAP); gltexgeni(gl_t, gl_texture_gen_mode,GL_SPHERE_MAP); glenable(gl_texture_gen_s); glenable(gl_texture_gen_t);
Попробуйте самостоятельно добавить анимацию поворота — будет видно, что плоскость, подобно зеркалу, отражает картинку текстуры так, как если бы та была размещена перед ней. С поворотом плоскости текстурный рисунок на ней изменяется (как бы «съезжает»). Качество эффекта почти не зависит от используемой текстуры, а достигается путем использования так называемых команд генерации текстурных координат (gltexgeni) с параметром GL_SPHERE_MAP.
В данном материале рассмотрены лишь базовые принципы работы с текстурами, т.к. эта тема довольно обширна. Но надеюсь, это поможет вам понять суть и в дальнейшем расширять свои знания, имея уже хоть какую-нибудь основу и практику.


