Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
DirectX. Продвинутая Анимация (2004) [rus].pdf
Скачиваний:
335
Добавлен:
16.08.2013
Размер:
8.39 Mб
Скачать

318

Так что тяжелая работа была проделана не зря, вы имеете возможность сохранять, а потом перезагружать переприсвоенные значения. Это особенно удобно, если вы работаете со множеством различных последовательностей, используя один и те же значения преобразования. Для сохранения базы данных преобразования щелкните Save Database (Сохранить базу данных) и введите имя файла. Все базы данных преобразований сохраняются в файле с расширением .IPA. Для загрузки базы данных преобразования щелкните кнопку Load Database (Загрузить базу данных), укажите загружаемый .IPA файл и нажмите Load.

Последними двумя кнопками программы ConvLWV являются Clear Database (Очистить базу данных) и Reset Database (Сбросить базу данных). Кнопка Clear Database устанавливает все значения преобразования в 0x0000, означая, что всем Unicode значениям IPA переприсваиваются нули. Щелкнув на кнопку Reset Database вы устанавливаете все значения преобразования в соответствующие Unicode значения IPA (таким образом 0х025b преобразуется в 0х025b, 0x0075 в 0x0075 и т. д.)

Вот и все об использовании программы ConvLWV. Смелее, попробуйте все - записывать, обрабатывать, конвертировать файлы .LWV в .X. Когда вы будете готовы, можно переходить к загрузке последовательностей фонем в ваши программы при помощи специализированного анализатора файлов .X.

Использование анализатора файлов .X для последовательностей

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

typedef struct {

DWORD Code; // Номер меша фонемы

DWORD StartTime; // Начальное время морфирования DWORD EndTime; // Конечное время морфирования

} sPhoneme;

При анализе файла .X, содержащего последовательность фонем, создайте массив структур sPhoneme в соответствии с количеством ключевых кадров, используемых в анимации. Предположим, имеется следующий объект PhonemeSequence (содержащий последовательность фонем):

Морфируемая лицевая анимация

PhonemeSequence PSEQ { 5; 2; 0; 30;,

3; 30; 200;, 6; 200; 230;, 2; 230; 270;, 0; 270; 468;;

}

Этот объект содержит пять ключевых кадров анимации, определяемых первым числом объекта. По мере анализа объекта PSEQ необходимо будет создать пять соответствующих структур sPhoneme. После того как вы создали эти структуры, вы можете просматривать список ключевых кадров.

Первый ключевой кадр использует меш фонем номер два и полностью комбинируется за 30 миллисекунд (начало в 0 миллисекундах, а конец в 30). Второй ключевой кадр использует меш номер 3 и полностью комбинируется за 170 миллисекунд (начало в 30 миллисекунд, а конец в 200). То же самое справедливо и для оставшихся ключевых кадров.

Одной из тем, обсуждения которой я избегал до теперешнего момента, является фактический принцип действия комбинирования в лицевой анимации. В главе 8 вы видели, как комбинировать несколько мешей. Для лицевой анимации моргания глаз и движения бровей комбинирование соответствующих мешей на разные доли является превосходным решением. Затруднения появляются при попытках морфировать один меш фонемы в другой. Морфирование базового меша в меш фонемы не вызывает никаких трудностей. Однако попытка морфирования одного меша фонемы в другой невозможна. Если базовый меш является исходным при операции морфирования, а меш фонемы является целевым, то как мы можем комбинировать меши фонем, не задав нового базового меша?

Конечно же, всегда можно задать новый базовый меш для комбинирования, однако проблема в том, что все анимации созданы в соответствии с начальным базовым мешем (в котором рот меша закрыт, а глаза открыты). Использование меша фонемы в качестве нового базового меша вызовет только новые трудности!

В чем же решение? На самом деле оно достаточно просто. Комбинируя базовый меш с двумя различными мешами фонем одновременно, можно сделать, чтобы меш морфировал бы из одного фонемного меша в другой. Все, что необходимо сделать, — это медленно уменьшать значения комбинирования исходного меша фонемы от 1 до 0 и медленно увеличивать значения комбинирования целевого меша фонем от 0 до 1.

320

Я вернусь к этому решению комбинирования немного позже. А пока, давайте перейдем к анализатору .X и посмотрим на объявление класса.

class cXPhonemeParser : public cXParser

{

public:

char *m_Name; // Имя последовательности

DWORD m_NumPhonemes; // Количество фонем в последовательности sPhoneme *m_Phonemes; // Массив фонем

DWORD m_Length; // Длина (в миллисекундах) последовательности

protected:

BOOL ParseObject(IDirectXFileData *pDataObj, \ IDirectXFileData*pParentDataObj,\ DWORD Depth, \

void **Data, BOOL Reference);

public:

cXPhonemeParser();

~cXPhonemeParser();

//Освободить загруженные ресурсы void Free();

//Найти фонему по заданному времени DWORD FindPhoneme(DWORD Time);

//Получить номер меша и значения масштабирования времени void GetAnimData(DWORD Time, \

DWORD *Phoneme1, float *Phoneme1Time, \

DWORD *Phoneme2, float *Phoneme2Time);

};

Я хочу рассмотреть класс cXPhonemeParser по частям, чтобы вы могли лучше понять, что в нем происходит. Первое, что вам необходимо заметить в классе, это объявление переменных. Каждая анимация последовательности фонем хранится в объекте PhonemeSequence. Помните, что каждому экземпляру объекта может быть присвоено имя. В этом и есть предназначение переменной m_Name - хранить имя экземпляра объекта.

Замечание. В данном случае, класс cXPhonemeParser хранит данные только одного объекта PhonemeSequence, так что имеется только один буфер, содержащий имя и остальную информацию о фонемах. Если вас это беспокоит, я рекомендую расширить класс, чтобы он содержал множество последовательностей фонем.

За m_Name следует количество ключевых кадров, содержащихся в последовательности (хранящихся в .X файле), и массив структур sPhoneme, содержащих информацию о последовательности фонем. Переменная mLength представляет собой продолжительность всей последовательности анимации в миллисекундах. Она полезна для проверки временных значений на превышения длины анимации.

Морфируемаялицеваяанимация

После данных в классе cXPhonemeParser располагаются функции, первой из которых является типичная функция ParseObject, которую вы уже должны знать и любить. Помните, функция ParseObject вызывается каждый раз при перечислении объекта данных из файла .X, так что данные последовательности фонем загружаются

вней.

Т.к. мы ищем только объекты PhonemeSequence, функция ParseObject на данный момент будет небольшой. Начав с объявления GUID шаблона PhonemeSequence, можно переходить к фактическому коду ParseTemplate, чтобы увидеть что происходит.

// Определить GUID шаблона PhonemeSequence DEFINE_GUID(PhonemeSequence,

0x918dee50, 0x657c, 0x48b0,

0x94, 0xa5, 0x15, 0xec, 0x23, 0хе6, 0х3b, 0хс9);

BOOL cXPhonemeParser::ParseObject( \ IDirectXFileData *pDataObj, \ IDirectXFileData *pParentDataObj, \ DWORD Depth, \

void **Data, BOOL Reference)

{

const GUID *Type = GetObjectGUID(pDataObj);

// Обработать только объекты последовательности фонем if(*Type == PhonemeSequence) {

// Освободить текущую загруженную последовательность Free();

//Получить имя и указатель на данные m_Name = GetObjectName(pDataObj);

DWORD *DataPtr = (DWORD*)GetObjectData(pDataObj, NULL);

//Получить количество фонем,создать структуры и загрузить данные m_NumPhonemes = *DataPtr++;

m_Phonemes = new sPhoneme[m_NumPhonemes];

На данный момент мы получили имена анализируемых экземпляров объектов

иколичество ключевых кадров фонем последовательности. Далее создается массив структур sPhoneme, и обработка продолжается, просматривая каждый ключевой кадр в последовательности, получая номера мешей фонем, начального

иконечного времени.

for(DWORD i=0;i<m_NumPhonemes;i++) { m_Phonemes[i].Code = *DataPtr++; m_Phonemes[i].StartTime = *DataPtr++; m_Phonemes[i].EndTime = *DataPtr++;

}

m_Length = m_Phonemes[m_NumPhonemes-1].EndTime + 1;

}

322

// Анализировать дочерние объекты

return ParseChildTemplates(pDataObj, Depth, Data, Reference);

}

По мере создания функции ParseObject вы можете видеть, что сохраняется продолжительность пбследовательности анимации и перечисляются все дочерние объекты. Помните, что анализируется только первый объект последовательности фонем; любой объект, загружаемый после первого, уничтожает данные предыдущего. Опять же, для своего проекта вы можете захотеть добавить возможность загрузки более чем одной последовательности.

Далее в классе cXPhonemeParser следует функция Free. Эта функция освобождает данные класса, такие как массив ключевых кадров фонем. Я пропущу код этой функции и перейду к следующей, FindPhoneme, которая ответственна за нахождение номера меша фонемы среди ключевых кадров по заданному времени.

DWORD cXPhonemeParser::FindPhoneme(DWORD Time)

{

if(m_NumPhonemes) { // Искать по времени

for(DWORD i=0;i<m_NumPhonemes;i++) { if(Time >= m_Phonemes[i].StartTime && \

Time <= m_Phonemes [i].EndTime)

return i;

}

}

return 0;

}

Функция FindPhoneme просто просматривает массив ключевых кадров фонем, ища тот, в который попадает заданное время. Функция FindPhoneme обычно вызывается функцией GetAnimData, которую вы используете для получения двух мешей фонем, необходимых для комбинирования, и значений комбинирования для них (находящихся в диапазоне от 0 до 1).

void cXPhonemeParser::GetAnimData( \ DWORD Time, \

DWORD *Phoneme1, float *Phoneme1Time, \ DWORD *Phoneme2, float *Phoneme2Time)

{

// Быстрая проверка на окончание анимации if(Time >= m_Length) {

*Phoneme1 = m_Phonemes[m NumPhonemes-1].Code; *Phoneme2 = 0;

*Phoneme1Time = 1.0f; *Phoneme2Time = 0.0f; return;

}

Морфируемая лицевая анимация

Как вы можете видеть из последнего кусочка кода, необходимо вставить специальный случай проверки, минула ли анимация свою продолжительность. Если да, только последний ключевой кадр используется, при этом первый меш комбинируется на 100 процентов, а второй на 0.

Двигаясь далее, функция GetAnimData просматривает список фонем в поисках той, которая совпадает с заданным временем.

// Найти ключ, используемый в последовательности фонем DWORD Index1 = FindPhoneme(Time);

DWORD Index2 = Index1+1; if(Index2 >= m_NumPhonemes) Index2 = Index1;

// Установить номера индексов фонем *Phoneme1 = m_Phonemes[Index1].Code; *Phoneme2 = m_Phonemes[Index2].Code;

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

Вот здесь мы и возвращаемся к использованию трех мешей для морфирования от одной фонемы к другой. По мере того как последовательность анимируется от первого меша фонемы ко второму, значение комбинирования первого меша медленно уменьшается от 1 до 0. Второй меш комбинируется при начальном значении комбинирования, равном 0 и медленно увеличивающем до 1.

Для вычисления коэффициентов смешивания двух мешей необходимо добавить в конец функции GetAnimData несколько вычислений, которые масшта-

бируют текущее время

между временами двух используемых ключевых кадров

и сохраняют значения

комбинирования в качестве коэффициентов масшта-

бированного времени.

 

// Вычислить значения синхронизации

DWORD Time1 = m_Phonemes[Index1].StartTime; DWORD Time2 = m_Phonemes[Index1].EndTime; DWORD TimeDiff = Time2 - Time1;

Time -= Time1;

float TimeFactor = 1.0f / (float)TimeDiff; float Timing = (float)Time * TimeFactor; // Установить времена фонем

*Phoneme1Time = 1.0f - Timing; *Phoneme2Time = Timing;

}

Соседние файлы в предмете Программирование на C++