Дата:   17.11.2017 г.
Время:
 
 
Профессионалам и любителям
ПРОСТО * ДОСТУПНО * ИНТЕРЕСНО
01796
Подписной
индекс
Опрос
У вас есть ноутбук?
Погода
 
Архив - Многослойное GL-текстурирование - Журнал «Компьютер»
Многослойное GL-текстурирование
№ 3-4'2006     Владислав Демьянишин   сайт автора    Тема: Программирование     ( Прочитано 6831 раз )
 
Мне на глаза попадалось не мало статей на тему OpenGL для Delphi, в которых снова и снова разжёвывались основы программирования 3D-графики на основе использования библиотеки OpenGL. Снова и снова меня учили создавать уже до тошноты знакомые GL-окна, рисовать на них GL-примитивы и ограничивались этим. Иногда мне везло, и я мог прочесть в статье о том, как натянуть одну текстуру на поверхность. Но и в этом случае автор статьи не утруждал себя разъяснениями о том, как можно сместить текстуру по поверхности или масштабировать её в произвольном масштабе, а не просто повторять её стандартными средствами. Тем более, нигде я не мог прочесть, как всё-таки применить этот пресловутый и легендарный Bump-mapping. А между тем, ещё где-то таились секреты наложения нескольких текстур на одну поверхность, при условии, что конвейер имеющегося видео акселератора позволяет делать это.
Тогда, научившись всему этому самостоятельно, я решил сам написать статью. Сев за написание статьи, я решил опустить все подробности создания всяких там окон, контекстов устройств и прочих атрибутов, необходимых для того, чтобы вся эта штуковина завертелась у читателя на экране монитора. Следовательно, я буду ориентироваться уже на опытного программиста, который умеет делать все эти приготовления.
Какое у меня получилось длинное вступление. Вероятно, у кого-то из вас уже руки чешутся опробовать всё это на практике. Тогда приступим. Сначала следует привести хотя бы приблизительное объявление класса текстуры
 
type TGLTexture = class
TexRepeatS, TexRepeatT : TGLTexRepeat;
TexSmooth : boolean;
TexEnvMode, Textured, TexID : GLint;
TexWidthScale, TexHeightScale, TexWidthOffset, TexHeightOffset : GLFloat;
procedure Init;
procedure BindTexture;
end;
 
Тогда метод инициализации класса должен быть таким
 
procedure TGLTexture.Init;
begin
// Настройки по умолчанию
TexWidthScale := 1;
TexHeightScale := 1;
TexWidthOffset := 0;
TexHeightOffset := 0;
TexRepeatS := repRepeat;
TexRepeatT := repRepeat;
TexEnvMode := 1;
TexSmooth := true;
Textured := texNone;
TexID := -1; // текстура ещё не загружена
end;
 
Из метода рисования поверхности для активации текстуры следует вызывать метод BindTexture экземпляра её класса. Итак, рассмотрим код метода BindTexture и параллельно обоснуем необходимость параметров текстуры.
Без параметра TexID, где должен храниться индекс зарегистрированной текстуры в GL-контексте, вообще никуда. Это тот самый индекс, указанный в вызове команды glBindTexture при загрузке текстуры при помощи команды glTexImage2D или gluBuild2DMipmaps. При этом TexID должен содержать номер больше нуля, поскольку 0 зарезервирован для старых версий, когда была возможна только одна текстура.
 
procedure TGLTexture. BindTexture;
var Tmp, aTexRepS, aTexRepT, aTexSmooth, aTexModulate : GLint;
begin
if (Textured>texNone) and (TexID>0) then begin
// Активизируем 2D-текстуру
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, TexID);
 
Поскольку мне бы хотелось, чтобы на поверхность накладывались несколько текстур, то должен быть список текстур для каждой поверхности. А помимо этого любую текстуру можно включить или исключить из наложения в какой-то момент, не удаляя из списка текстур для данной поверхности. Но глупо так просто расходовать память для параметра, который будет всего лишь иметь два значения false/true. Тогда пускай этот параметр дополнительно хранит информацию о способе генерации текстурных координат наложения и называется Textured, где значение texNone означало бы, что текстура выключена, значение texFixed – что текстура имеет чёткий список координат для текстурирования в некотором массиве, а значения texObjLinear, texEyeLinear и texSphereMap означали бы, что текстурные координаты должен генерировать акселератор самостоятельно по соответствующим методам. Для задания вида текстурного натяжения на поверхность объявляем константы texNone, texFixed, texObjLinear, texEyeLinear и texSphereMap со значениями от 0 до 4 соответственно. Если координаты текстуры не фиксированы, то включаем режим авто-генерации текстурных координат акселератором
 
glEnable( GL_TEXTURE_GEN_S );
glEnable( GL_TEXTURE_GEN_T );
 
и выбираем метод генерирования координат
 
case Textured of
texObjLinear: Tmp := GL_OBJECT_LINEAR;
texEyeLinear: Tmp := GL_EYE_LINEAR;
else Tmp:=GL_SPHERE_MAP; // для texSphereMap
end; // case
glTexGeni( GL_S, GL_TEXTURE_GEN_MODE, Tmp );
glTexGeni( GL_T, GL_TEXTURE_GEN_MODE, Tmp );
 
Для начала следует сказать, что вопреки тому, что в разных статьях меня учили, что поверхность имеет некоторые свойства, на практике оказалось, что на самом деле это как раз текстура может иметь те самые свойства, такие как повторяемость, или не повторяемость изображения текстуры по поверхности, то есть если бы я создавал класс TGLTexture, а мы сейчас как раз этим и заняты ;), то он бы у меня обязательно содержал параметры TexRepeatS, TexRepeatT соответственно по ширине и высоте типа TGLTexRepeat с описанием
 
type TGLTexRepeat = ( repClamp, repRepeat, repClampToEdge,
repClampToBorder, repMirroredRepeat );
 
Устанавливаем режим повторяемости текстуры для авто-генерации
 
case TexRepeatS of
repClamp : NowTexRepS := GL_CLAMP;
repRepeat : NowTexRepS := GL_REPEAT;
repClampToEdge : NowTexRepS := GL_CLAMP_TO_EDGE;
end;
 
case TexRepeatT of
end;
 
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, NowTexRepS );
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, NowTexRepT );
 
А вот и первая хитрость – это пары параметров TexWidthScale, TexHeightScale и TexWidthOffset, TexHeightOffset, где первая содержит коэффициенты масштабирования текстуры при наложении, а вторая пара хранит смещение текстуры по поверхности. Причём величины эти должны быть заданы в количестве текстуры, то есть, TexWidthScale=2 означает, что по ширине текстуру следует удвоить, то бишь повторить дважды, а TexWidthScale=0.5 – текстуру наложить только первой половиной её изображения. Аналогично для TexWidthOffset=0.5, только в данном случае текстура будет не масштабирована, а сдвинута по поверхности на половину своего рисунка по ширине. При этом значения параметров обоих пар могут быть как положительными, так и отрицательными. За счёт этих параметров достигается эффект окна в пределах текстуры, и какая часть изображения попадает в это окно, та и накладывается на поверхность. Умело используя эти четыре параметра, можно с лёгкостью добиться анимации изображения на поверхности, применив текстуру, состоящую из нескольких картинок, объединённых в одну единую ленту по ширине, либо по высоте и затем сдвигая текстурное окно по такой ленте. Пример подобной текстуры показан на рис. 4
 
3D моделирование, OpenGL, Delphi
 
В данном случае для формирования текстурного окна следует параметр TexWidthScale установить в значение 0.125, поскольку в ленте текстуры содержаться 8 фрагментов анимации пламени и, стало быть, чтобы каждый раз на поверхности был виден только один из них, следует текстурное окно по ширине уменьшить до 1/8. А чтобы оживить пламя, достаточно периодически сдвигать окно на величину 0.125, то есть постоянно наращивать параметр TexWidthOffset на это значение.
Если координаты текстуры фиксированы, то отключаем режим авто-генерации текстурных координат следующим кодом
 
glDisable( GL_TEXTURE_GEN_S );
glDisable( GL_TEXTURE_GEN_T );
 
и применяем сдвиг и масштабирование координат текстуры
 
glMatrixMode( GL_TEXTURE );
glLoadIdentity;
glTranslatef( TexWidthOffset, TexHeightOffset, 0);
glScalef( TexWidthScale, TexHeightScale, 1);
glMatrixMode( GL_MODELVIEW );
 
Поскольку текстуру можно накладывать со сглаживанием, или без него, то параметру TexSmooth тоже быть. Тогда режим фильтрации текстуры устанавливаем так
 
if TexSmooth then NowTexSmooth := GL_LINEAR
else NowTexSmooth := GL_NEAREST;
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_ MAG_FILTER, NowTexSmooth );
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_ MIN_FILTER, NowTexSmooth );
 
Помимо этого текстуру можно накладывать со смешением цветов, и прочими эффектами наподобие Bump-mapping, то параметр TexEnvMode просто необходим.
Хочу обратить внимание на то, что в случае применения к поверхности не одной, а двух и более текстур, где одна является обычной окрашивающей текстурой, а другая Bump-текстурой, то первой следует указывать (активизировать) Bump-текстуру, иначе эффект Bump-mapping не сработает или сработает скорее всего как эффект Light-mapping. Будучи второй Bump-текстура будет интерпретирована акселератором как обычная Light-map текстура, поскольку для этих двух эффектов применяется одна и та же команда glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_ADD ).
 
3D моделирование, OpenGL, Delphi
 
 
3D моделирование, OpenGL, Delphi
 
На рис. 1 и 2 видно, какого эффекта можно достичь, используя Light-mapping.
 
3D моделирование, OpenGL, Delphi
 
На рис. 3 можно увидеть, какого эффекта можно достичь, используя Bump-mapping. При этом эффект будет ощутим в том случае, когда между лучом источника света и плоскостью поверхности будет минимальный угол, то есть менее 90 градусов. Лишь в таком случае абсолютно плоская поверхность может выглядеть как бы рельефной, то бишь объёмной и такая сцена будет казаться более детальной, чем, если бы на текстуре поверхности просто были нарисованы шероховатости. Учитывая выше сказанное, устанавливаем режим смешивания цветов
 
case TexEnvMode of
0: NowTexModulate := GL_DECAL;
1: NowTexModulate := GL_MODULATE;
2: NowTexModulate := GL_BLEND;
3: NowTexModulate := GL_REPLACE;
else NowTexModulate := GL_ADD; // для Bump-mapping и Light-mapping
end; // case
glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, NowTexModulate );
 
Теперь немного пофантазируем над классом поверхности, и предположим, что класс содержит списки всех составляющих треугольников и их нормалей, входящих вершин и, если нужно, сглаживающих нормалей для каждой вершины, а также списки текстурных треугольников и текстурных вершин. Причём каждый треугольник хранится в виде триады индексов вершин, входящих в него, а индекс вершины – это ни что иное, как порядковый номер вершины в списке вершин, то бишь в массиве. При этом количество треугольников поверхности всегда ровняется количеству текстурированных треугольников.
 
 
 
 
Для составления метода рисования поверхности составим схему действий:
1 В некоторую локальную переменную, скажем NowTextur, заносим количество текстур со значением не равным texNone параметра Textured, то есть, по сути – количество активных текстур, которые следует наложить на поверхность.
2 Проверяем доступность мультитекстурирования, прочитав значение параметра GL_ARB_multitexture среды OpenGL. Если допустимо наложение только одной текстуры, то количество активизируемых текстур в NowTextur и максимальное допустимое количество текстур в NowMaxTextur устанавливаем в один. После чего следует вызвать метод BindTexture первой активной текстуры по списку.
3 Если мультитекстурирование возможно, то в случае, когда количество активизируемых текстур в NowTextur превышает максимально допустимое количество текстур в GLMaxTextureUnits (параметр среды OpenGL) для одновременного наложения на данном акселераторе, то параметр NowMaxTextur устанавливаем в допустимый максимум для акселератора, иначе заносим значение NowTextur в локальную переменную NowMaxTextur.
После чего перебираем по списку все текстуры, и кодом
 
glActiveTextureARB( GL_TEXTURE0_ARB + k );
Texture[ j ].BindTexture;
 
активизируем только активные, причём переменная j индексирует текстуры по списку от нуля, а переменная k ведёт счёт подключаемых текстур от нуля.
Прежде, чем перейти непосредственно к рендерингу, необходимо составить вспомогательную процедуру procedure TexCoord2f ( U, V : GLFloat ), которая должна будет заносить на обработку координаты очередной текстурной вершины. При мультитекстурировании в случае одной активной текстуры достаточно вызывать команду glMultiTexCoord2fARB( GL_TEXTURE0_ARB, U, V ). Если же активных текстур несколько, то перебирая их по списку и отыскивая активные с фиксированными координатами (когда Texture[ j ].Textured = texFixed) вызывать следует команду glMultiTexCoord2fARB( GL_TEXTURE0_ARB + k, U, V ), где k ведёт учёт активных текстур.
Следует помнить, что в случае недоступности мультитекстурирования, во избежание сбоя, задавать координаты вершин текстуры следует только старой командой glTexCoord2f.
Теперь можно приступить непосредственно к рендерингу.
 
glBegin( GL_TRIANGLES );
// Идём по списку треугольников
for j := 0 to FaceCount-1 do begin
glNormal3fv(…);
if NowTextur > 0 then begin
// Получаем триаду индексов текстурных вершин очередного треугольника
NowTFace := TexFace[j];
TexCoord2f( TexVertex[NowTFace[0>.u, TexVertex[NowTFace[0>.v );
end;
glVertex3fv(…);
if NowTextur > 0 then TexCoord2f( TexVertex[NowTFace[1>.u, TexVertex[NowTFace[1>.v );
glVertex3fv(…);
if NowTextur > 0 then TexCoord2f( TexVertex[NowTFace[2>.u, TexVertex[NowTFace[2>.v );
glVertex3fv(…);
end;
glEnd;
 
После завершения рисования очередной поверхности следует отключить режим текстурирования, чтобы при рисовании следующей поверхности данный режим не влиял. С одной текстурой всё просто
 
glActiveTextureARB( GL_TEXTURE0_ARB );
glDisable( GL_TEXTURE_2D );
 
Иначе необходимо отключить все включённые текстуры
 
for j := 0 to NowTextur-1 do begin
glActiveTextureARB( GL_TEXTURE0_ARB + j );
glDisable( GL_TEXTURE_2D );
end;
 
Параметры среды OpenGL
 
GLMaxTextureUnits - глобальная переменная типа Integer (GLint) и её значение получаем один раз при инициализации программы командой glGetIntegerv( GL_MAX_TEXTURE_UNITS_ARB, @GLMaxTextureUnits ). Её значение показывает количество текстур, которые можно нанести на поверхность одновременно. Например, для акселератора TNT2 Pro 32MB этот параметр ровняется двум.
Помимо этого следует получить адреса команд:
glMultiTexCoord1fARB := wglGetProcAddress('glMultiTexCoord1fARB');
glMultiTexCoord2fARB := wglGetProcAddress('glMultiTexCoord2fARB');
glActiveTextureARB := wglGetProcAddress('glActiveTextureARB');
где они описаны как
var
glMultiTexCoord1fARB : procedure(target: GLenum; s: GLfloat); stdcall;
glMultiTexCoord2fARB : procedure(target: GLenum; s, t: GLfloat); stdcall;
glMultiTexCoord3fARB : procedure(target: GLenum; s, t, r: GLfloat); stdcall;
glMultiTexCoord4fARB : procedure(target: GLenum; s, t, r, q: GLfloat); stdcall;
glActiveTextureARB : procedure(target: GLenum); stdcall;
Параметр GL_ARB_multitexture типа boolean можно получить при помощи следующего кода, благодаря чему можно выяснить, поддерживается ли мультитекстурирование данным акселератором:
 
function CheckExtension( const Extension : String ) : boolean;
begin
Result := Pos( Extension, Buffer ) > 0;
end;
 
procedure ReadAllExtensions;
var Buffer : String;
begin
Buffer := glGetString( GL_EXTENSIONS );
GL_ARB_multitexture := CheckExtension( 'GL_ARB_multitexture' );
GL_EXT_fog_coord := CheckExtension( 'GL_EXT_fog_coord' );
end;
 
Прошу обратить внимание на строку GL_EXT_fog_coord := CheckExtension( 'GL_EXT_fog_coord' ), которая позволяет определить поддерживается ли объёмный туман данным акселератором. Скоро она нам понадобится.
 
Объёмный GL-туман
 
Я не буду вдаваться в подробное описание настройки параметров тумана, о которых и так можно прочесть в любой статье для начинающих, но должен сказать, что по умолчанию включён обычный глобальный туман, в сомнительной реалистичности которого и так все уже могли убедиться. Чтобы использовать объёмный туман следует заранее удостовериться, что оный поддерживается, то есть проверить переменную GL_EXT_fog_coord, и лишь затем выполнить следующую команду
 
glFogi( GL_FOG_COORDINATE_SOURCE_EXT, GL_FOG_COORDINATE_EXT );
 
а чтобы при необходимости вернуться снова к обычному туману, то достаточно выполнить команду
 
glFogi( GL_FOG_COORDINATE_SOURCE_EXT, GL_FRAGMENT_DEPTH_EXT );
 
Чтобы работать с объёмным туманом, следует просчитать величину его интенсивности для каждой вершины каждого треугольника объекта, который предположительно находится в тумане или за ним, то есть как бы в зоне тумана. Вычисления эти можно делать как угодно, из расчёта, например, что объект будет всегда находиться на своём месте и, значит, интенсивность тумана для каждой вершины достаточно просчитать всего один раз. Иначе, если, допустим, туман стелется по горизонтальной поверхности, и на этой поверхности лежит объект, который может перемещаться вверх и вниз, и вероятно может выйти из области тумана, то, следовательно, необходимо при каждом перемещении вычислять новую интенсивность тумана для каждой вершины объекта. Помимо этого можно создать эффект такого тумана, который бы расступался вблизи камеры на определённый радиус, тогда для вычисления интенсивности такого тумана необходим другой алгоритмический подход, но это уже чистая математика.
Следует отметить, что чем больше треугольников содержит в себе объект, помещаемый в туман, тем реалистичнее будет сам туман. Иначе обыкновенный квадрат, состоящий всего из четырёх вершин ни в малейшей мере не создаст эффект тумана, поскольку четыре вершины дают слишком малую “пищу” для вычислений в акселераторе. И совсем другое дело, если бы этот квадрат состоял из сетки квадратов хотя бы 10x10, то эффект объёмного тумана стал бы ощутимее.
 
3D моделирование, OpenGL, Delphi
 
3D моделирование, OpenGL, Delphi
 
 
На рисунках 5 и 6 показана сцена без тумана и с туманом в помещении (ну и пылищи там ;) ). Где, на ваш взгляд, сцена выглядит лучше?
 
3D моделирование, OpenGL, Delphi
 
 
3D моделирование, OpenGL, Delphi
 
На рисунках 7 и 8 представлена сцена без тумана и с туманом на открытом пространстве. На рис. 7 воздушная атмосфера визуально практически не улавливается, а на рисунке 8 туман делает сцену мягче и воздух насыщенным. Где, на ваш взгляд, сцена выглядит естественнее?
Итак мы пришли к выводу, что для класса поверхности необходим параметр Foging : boolean, отвечающий за влияние тумана на объект и список интенсивности тумана в каждой вершине объекта, то бишь массив элементов вещественного типа. Тогда фрагмент кода, реализующего всё выше сказанное, выглядит так
 
// Включаем влияние тумана на рендеринг данного объекта
if Foging then glEnable( GL_FOG );
glBegin( GL_TRIANGLES );
// Идём по списку треугольников
for j := 0 to FaceCount-1 do begin
glNormal3fv(…);
if Foging then glFogCoordfEXT(…);
glVertex3fv(…);
if Foging then glFogCoordfEXT(…);
glVertex3fv(…);
if Foging then glFogCoordfEXT(…);
glVertex3fv(…);
end;
glEnd;
 
Если туман был применён, то следует исключить несанкционированное влияние тумана на следующие объекты
 
if Foging then glDisable(GL_FOG);
 
Предварительно при инициализации программы следует не забыть получить адрес команды glFogCoordfEXT := wglGetProcAddress( 'glFogCoordfEXT' ), где она описана как var glFogCoordfEXT : procedure ( coord : single ); stdcall;
 
Думаю, теперь туман недостатка информации по OpenGL немного рассеется ;)
Вот собственно и всё, что я имел сказать.
 
P.S.
Иллюстрации созданы в 3D-аниматоре “3D Draw” ver. 2.1.1001.
 

 
 
На главную страницу На предыдущую страницу На начало страницы
 
 
 
 
 
2009 - 2017 © СПД Зайцев А.Б.
Сайт является средством массовой информации.
При перепечатке и цитировании в печатных СМИ ссылка на журнал "Компьютер" обязательна.
При перепечатке и цитировании в Интернете обязательна активная гиперссылка на сайт Comput.com.ua, не закрытая для индексирования.
Украина онлайн Рейтинг@Mail.ru Рейтинг Сайтов YandeG