- •Введение
- •Глава 1. Подготовка к изучению книги
- •Установка DirectX SDK
- •Выбор отладочных или рабочих версий библиотек
- •Настройка вашего компилятора
- •Установка директорий DirectX SDK
- •Привязывание к библиотекам DirectX
- •Установка используемого по умолчанию состояния символа
- •Использование вспомогательного кода книги
- •Использование вспомогательных объектов
- •Проверка вспомогательных функций
- •Двигаясь дальше по книге
- •Глава 2. Синхронизация анимации и движения
- •Использование движения, синхронизированного по времени
- •Считывание времени в Windows
- •Анимирование с использованием временных меток
- •Перемещение, синхронизированное со временем
- •Движение вдоль траекторий
- •Создание анализатора маршрутов .X файла
- •Создание внутриигровых кинематографических последовательностей
- •Посмотрите демонстрационные программы
- •TimedAnim
- •TimedMovement
- •Route
- •Cinematic
- •Глава 3. Использование формата файла .X
- •Работа с .X шаблонами и объектами данных
- •Определение шаблонов
- •Работа со стандартными шаблонами DirectX
- •Открытие .X файла
- •Перечисление объектов данных
- •Получение данных объекта
- •Создание класса .X анализатора
- •Загрузка мешей с использованием D3DX
- •Загрузка мешей, используя анализатор .X
- •Загрузка скелетных мешей
- •Загрузка анимации из .X
- •Загрузка специализированных данных из .X
- •Посмотрите демонстрационные программы
- •ParseFrame
- •Глава 4. Работа со скелетной анимацией
- •Начало скелетной анимации
- •Использование структур скелетов и иерархий костей
- •Использование скелетной структуры и скелетного меша
- •Загрузка иерархий из .X
- •Изменение положения костей
- •Обновление иерархии
- •Работа со скелетными мешами
- •Загрузка скелетных мешей из .X
- •Создание контейнера вторичного меша
- •Сопоставление костей фреймам
- •Обновление скелетного меша
- •Визуализация скелетных мешей
- •Глава 5. Использование скелетной анимации, основанной на ключевых кадрах
- •Использование наборов скелетных анимаций, основанных на ключевых кадрах
- •Использование ключей при анимации
- •Работа с четырьмя типами ключей
- •Считывание данных анимации из .X файлов
- •Прикрепление анимации к костям
- •Обновление анимации
- •Посмотрите демонстрационные программы
- •Глава 6. Комбинирование скелетных анимаций
- •Комбинирование скелетных анимаций
- •Соединение преобразований
- •Улучшение объектов скелетной анимации
- •Посмотрите демонстрационные программы
- •Глава 7. Создание кукольной анимации
- •Работа с физикой твердого тела
- •Создание твердого тела
- •Расположение и ориентирование твердых тел
- •Обработка движения твердых тел
- •Использование сил для создания движения
- •Соединение твердых тел с помощью пружин
- •Обеспечение обнаружения столкновений и ответной реакции
- •Создание систем кукольной анимации
- •Определение состояния твердого тела
- •Хранение костей
- •Создание класса управления куклой
- •Создание данных костей
- •Вычисление ограничивающего параллелепипеда кости
- •Установка сил
- •Объединение костей
- •Обработка столкновений
- •Восстановление соединений костей
- •Перестроение иерархии
- •Посмотрите демонстрационные программы
- •Глава 8. Работа с морфирующей анимацией
- •Морфинг в действии
- •Определение исходного и целевого меша
- •Морфинг мешей
- •Создание морфированного меша при помощи обработки
- •Визуализация морфированных мешей
- •Расчленение наборов
- •Создание морфирующего вершинного шейдера
- •Посмотрите демонстрационные программы
- •Глава 9. Использование морфирующей анимации, основанной на ключевых кадрах
- •Использование наборов морфируемой анимации
- •Создание шаблонов .X для морфируемой анимации
- •Загрузка данных морфируемой анимации
- •Визуализации морфированного меша
- •Получение данных морфируемого меша из альтернативных источников
- •Посмотрите демонстрационные программы
- •Глава 10. Комбинирование морфированных анимаций
- •Комбинирование морфированных анимаций
- •Использование базового меша в комбинированных морфированных анимациях
- •Вычисление разностей
- •Комбинирование разностей
- •Создание вершинных шейдеров комбинированного морфирования
- •Использование вершинного шейдера морфируемого комбинирования
- •Посмотрите демонстрационные программы
- •Глава 11. Морфируемая лицевая анимация
- •Основы лицевой анимации
- •Использование комбинированного морфирования
- •Использования фонем для речи
- •Создание лицевых мешей
- •Создание базового меша
- •Создание выражений лица
- •Создание мешей визем
- •Создание анимационных последовательностей
- •Создание последовательностей фонем
- •Использование анализатора файлов .X для последовательностей
- •Проигрывание лицевых последовательностей со звуком
- •Использование DirectShow для звука
- •Синхронизация анимации со звуком
- •Зацикливание воспроизведения звуков
- •Посмотрите демонстрационные программы
- •Глава 12. Использование частиц в анимации
- •Работа с частицами
- •Основы
- •Рисование частиц с помощью квадратных полигонов
- •Работа с точечными спрайтами
- •Улучшения визуализации частиц при помощи вершинных шейдеров
- •Оживление частиц
- •Передвижение частиц при помощи скорости
- •Использование интеллекта при обработке
- •Создание и уничтожение частиц
- •Управление частицами с помощью класса
- •Использование излучателей в проектах
- •Создание движков частиц в вершинных шейдерах
- •Посмотрите демонстрационные программы
- •Глава 13. Имитирование одежды и анимация мешей мягких тел
- •Имитация одежды в ваших проектах
- •Получение данных одежды из мешей
- •Приложение сил для создания движения
- •Воссоздание и визуализация меша одежды
- •Восстановление исходного меша
- •Добавление дополнительных пружин
- •Загрузка данных масс и пружин из .X файла
- •Создание анализатора .X данных одежды
- •Работа с обнаружением столкновений и реакцией на них
- •Определение объектов столкновений
- •Обнаружение и реакция на столкновения
- •Создание класса меша одежды
- •Использование мешей мягких тел
- •Восстановление мешей мягких тел
- •Посмотрите демонстрационные программы
- •Глава 14. Использование анимированных текстур
- •Использование анимации текстур в ваших проектах
- •Работа с преобразованиями текстур
- •Создание преобразования текстур
- •Установка матриц преобразования текстуры
- •Использование преобразования текстур в проектах
- •Использование файлов видео в качестве текстур
- •Импорт видео при помощи DirectShow
- •Создание специализированного фильтра
- •Работа со специализированным фильтром
- •Создание менеджера анимированных текстур
- •Окончание современной анимации
- •Веб-сайты
- •Рекомендуемые книги
- •DirectX 9.0 SDK
- •GoldWave Demo
- •Paint Shop Pro Trial Version
- •TrueSpace Demo
- •Microsoft Agent and LISET
- •Предметный указатель
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;
}