Добавляем загрузку текстур.
главная страница статьи файлы о сайте ссылки
Добавляем загрузку текстур.

Георгий Мошкин
tmtlib@narod.ru

Для загрузки и наложении текстур при использовании формата Half-Life smd достаточно лишь немного дополнить написанный ранее загрузчик 3D моделей (читайте здесь).

Итак, воспользуемся утилитой mdldec.exe и распакуем модель RE_STARS_Jill.mdl:

C:\models\jill>mdldec.exe RE_STARS_Jill.mdl

В директории jill сразу появилось огромное количество smd файлов. Один из этих файлов содержит модель, а все остальные - скелетную анимацию. Поищем основной smd файл, в котором содержится модель. Обычно этот файл занимает больше всего места на диске. В данном случае это оказался файл c_marine.smd.

Если мы заглянем внутрь этого файла, то увидим всё то же, что я описывал в статье про скелетную анимацию. Каждый треугольник модели описывается следующим образом:

SM_4B.bmp
6 -9.572851 0.463093 21.991520 -0.698505 0.532904 0.477603 0.406000 0.383698
6 -9.595107 0.464483 21.707262 -0.821391 0.501495 -0.271697 0.422000 0.383698
6 -9.631591 -0.089407 21.959997 -0.781629 -0.392314 0.484918 0.406000 0.405567

Напомню смысл этих записей. В первой строчке стоит название текстуры, которая должна накладыватсья на этот треугольник - SM_4B.bmp. Следующие за ней три строки соответствуют трём вершинам треугольников. Например, в строчке

6 -9.572851 0.463093 21.991520 -0.698505 0.532904 0.477603 0.406000 0.383698

записаны следующие данные:

6 - номер сустава, к которому прикреплена вершина
-9,572851 - X вершины
0.463093 - Y вершины
21.991520 - Z вершины
-0.698505 - X нормали
0.532904 - Y нормали
0.477603 - Z нормали
0.406000 - текстурная координата U
0.383698 - текстурная координата V

Нас здесь больше всего интересуют текстурные координаты U,V. Именно эти значения необходимо подавать в glTexCoord2f при выводе вершины в OpenGL.

Как вы помните, в написанном ранее примере я уже сделал считывание текстурных координат и имени файла текстуры. Для этого были введены соответствующие типы хранения данных:

type TPoligon=record
tekstura:string;
vershini:array[0..2] of TVershina;
end;

и

type TVershina=record
sustav:integer;
koordinata:array[0..2] of single;
normal:array[0..2] of single;
textkoordinata:array[0..1] of single;
end;

Поэтому в самой программе у нас уже есть все необходимые данные для загрузки и наложения текстур на модель. Имя текстуры i-того полигона определяется через обращение к poligon[i].tekstura. Текстурные координаты j-той вершины i-того полигона определяются через обращение к записям poligon[i].vershini[j].textkoordinata[0] и poligon[i].vershini[j].textkoordinata[1]. Таким образом, все необходимые нам данные определяются так:

имя текстуры:=poligon[i].tekstura
координата U:=poligon[i].vershini[j].textkoordinata[0]
координата V:=poligon[i].vershini[j].textkoordinata[1]

Сделаем загрузку текстур. Так как разные полигоны имеют общие текстуры, то целесообразно сделать внешний массив для хранения идентификаторов OpenGL текстур:

teksturi:array of GLUINT;

В элементе k массива teksturi будет храниться идентификатор, подаваемый в glBindTexture:

glBindTexture(GL_TEXTURE_2D, teksturi[k]);

Но откуда мы узнаем, какой элемент массива teksturi необходим полигону i? То есть как мы определим число k по номеру полигона i? Для этого нужно дополнить тип TPoligon следующим образом (новая строчка выделена красным цветом):

type TPoligon=record
tekstura:string;
teksGL:integer;
vershini:array[0..2] of TVershina;
end;

Теперь понятно, что при установке текущего номера текстуры мы будем подавать в OpenGL следующую команду:

glBindTexture(GL_TEXTURE_2D, teksturi[poligon[i].teksGL]);

Осталось только найти общие имена текстур, загрузить неповторяющиеся текстуры и установить соответствующие teksnomer для каждого полигона. В своё время я уже реализовывал это в версии загрузчика скелетной анимации для TMT Pascal, поэтому сделать это не составит большого труда.

Итак, после загрузки модели (событие по кнопке "1.Загрузить модель и анимацию"), нам необходимо произвести некий анализ всех имён poligon[i].tekstura. Это необходимо сделать после строчки, в которой происходит загрузка модели:

monster.zagruz('c_marine.smd');
... < добавлять сюда
anim.zagruz('jump.smd');

Помимо общего списка идентификаторов текстур нам понадобятся их имена, поэтому добавим:

imenatektur:array of STRING;

В этих массивов одинаковое число элементов:

teksturi:array of GLUINT;
imenatektur:array of STRING;

Для загрузки текстур будем использовать модуль BMP.PAS, который есть во многих демках на sulaco.co.za, поэтому в раздел USES добавляем модуль "bmp":

uses BMP;

Использование массивов, заданных выше, будет заключаться в следующем:

LoadTexture(imenatekstur[j], teksturi[j])

Таким образом, мы загружаем текстуру с именем imenatekstur[j] и получаем число teksturi[j]:

LoadTexture('текстура.bmp', идентификатор)

Обратите внимание, что imenatekstur[j] задаём мы, а значение идентификатора teksturi[j] получаем после срабатывания функции LoadTexture.

Выше мы дополнили тип TPolygon записью teksGL:

type TPoligon=record
tekstura:string;
teksGL:integer;
vershini:array[0..2] of TVershina;
end;

В переменной teksGL будет храниться индекс нужного элемента массива teksturi. Использоваться teksGL будет так:

glBindTexture(GL_TEXTURE_2D, teksturi[poligon[i].teksGL]);

Вот как будет выглядеть дополненный процесс загрузки текстур:

for i:=0 to length(monster.poligon)-1 do - пройдёмся по всем полигонам
begin
nashli:=false; // пока совпадений не найдено

for j:=0 to length(imenatekstur)-1 do - пройдёмся по списку загруженных текстур
if imenatekstur[j]=monster.poligon[i].tekstura then
begin
nashli:=true; - уже загружена, поэтому LoadTexture не нужно будет вызывать
nashliNomer:=j;
end;

if (nashli=false) then - выше мы не нашли имя в списке - значит будем загружать
begin
setLength(imenatekstur, length(imenatekstur)+1);
setLength(teksturi, length(teksturi)+1);

k:=length(teksturi)-1;

imenatekstur[k]:=monster.poligon[i].tekstura;
LoadTexture('.\teksturi\'+imenatekstur[k],teksturi[k]);
monster.poligon[i].teksGL:=k;
end
else
begin
monster.poligon[i].teksGL:=nashliNomer; - присваиваем загруженный ранее номер
end;

end;

Осталось сделать совсем немного - добавить команды OpenGL для наложения текстур. Для этого отыщем процедуру procedure TForm1.RenderModel. Вот она:

procedure TForm1.RenderModel;
var i,j:integer;
begin
glBegin(GL_TRIANGLES);

for i:=0 to length(monster.poligon)-1 do
for j:=0 to 2 do
glVertex3f(monster.poligon[i].vershini[j].koordinata[0],
monster.poligon[i].vershini[j].koordinata[1],
monster.poligon[i].vershini[j].koordinata[2]);

glEnd;
end;

Для кнопок визуализации скелета добавляем glDisable(GL_TEXTURE_2D), а для отображения скелета - glEnable(GL_TEXTURE_2D). Сама процедура вывода модели с наложением текстур будет выглядеть следующим образом:

var tekushtex:integer; // текущая текстура

procedure TForm1.RenderModel;
var i,j:integer;
begin


glBegin(GL_TRIANGLES);
for i:=0 to length(monster.poligon)-1 do
for j:=0 to 2 do
begin

if tekushtex<>monster.poligon[i].teksGL then
begin
glEnd;
tekushtex:=monster.poligon[i].teksGL;
glBindTexture(GL_TEXTURE_2D,teksturi[tekushtex]);
glBegin(GL_TRIANGLES);
end;


glTexCoord2f(monster.poligon[i].vershini[j].textkoordinata[0],
monster.poligon[i].vershini[j].textkoordinata[1]);
glVertex3f(monster.poligon[i].vershini[j].koordinata[0],
monster.poligon[i].vershini[j].koordinata[1],
monster.poligon[i].vershini[j].koordinata[2]);
end;
glEnd;
end;

Как видите, появилась команда glBindTexture и glTexCoord2f. Процедура glBindTexture служит для выбора текущей текстуры, а glTexCoord2f - для установки текстурной координаты. Стоит отметить, что вызывать glBindTexture можно только вне блока glBegin - glEnd. Так как вызов данной процедуры занимает определённое время, то для оптимизации я ввёл переменную tekushtex. Если ранее мы уже сделали вызов кода:

glBindTexture(GL_TEXTURE_2D,teksturi[tekushtex])

то вызывать её вновь следует лишь в том случае, если у последующего треугольника номер текстуры не равен текущему:

if tekushtex<>monster.poligon[i].teksGL then

Такая оптимизация позволяет уменьшить время, затрачиваемое на вывод 3D модели. Оптимизация получается за счёт уменьшения количества вызовов glBindTexture и уменьшения количества блоков glBegin - glEnd.

В заключении стоит отметить ещё несколько моментов: текстуры сконвертировал в формат 24bit BMP. Размеры текстур такие, чтобы не было проблем с OpenGL. Вместо mdldec можете использовать программу MilkShape3D (в меню там есть Decompile Half-Life MDL).