netlib.narod.ru< Назад | Оглавление | Далее >

Класс AnimatedModel

В этом разделе вы создадите класс с именем AnimatedModel, используемый для поддержки скелетной анимации моделей во время выполнения. В этом классе есть методы для загрузки анимированных моделей, воспроизведения и обновления анимации и для рисования модели. Вы начнете конструирование класса AnimatedModel с объявления его атрибутов.

Анимированная модель загружается как объект XNA Model, содержащий словарь, где находится объект AnimatedModelData, к которому вы можете получить доступ через свойство Tag. Таким образом, класс Model содержит сетку модели и эффекты, а класс AnimatedModelData хранит скелет модели и анимации. Для хранения данных модели вы объявляете атрибут model типа Model и атрибут animatedModel типа AnimatedModelData, а преобразования модели (перемещение/вращение/масштабирование) вы храните отдельно в атрибуте типа Transformation.

Model             model;
AnimatedModelData animatedModelData;
Transformation    transformation;

Вам все еще надо определить несколько атрибутов для управления воспроизведением анимации. Вам надо хранить текущую воспроизводимую анимацию, а также текущий кадр и время этой анимации. Вы объявляете атрибут activeAnimation для хранения текущей анимации, воспроизводимой в данный момент, и атрибуты activeAnimationKeyframe и activeAnimationTime для хранения текущего кадра анимации и времени соответственно:

AnimationData activeAnimation;
int           activeAnimationKeyframe;
TimeSpan      activeAnimationTime;

Надо объявить еще два других атрибута, чтобы получить возможность настраивать скорость анимации и включать зацикливание анимации — это атрибуты enableAnimationLoop и animationSpeed:

bool  enableAnimationLoop;
float animationSpeed;

В процессе анимации модели вы используете несколько временных массивов матриц для вычисления финальной конфигурации костей скелета. Вы объявляете атрибут bones для хранения локальной конфигурации каждой кости, поскольку конфигурации костей модифицируются в ходе воспроизведения анимации. Вы также объявляете атрибут bonesAbsolute, чтобы хранить абсолютную конфигурацию каждой кости, вычисляемую с использованием массива bones и необходимую для анимации модели во время выполнения. Наконец вы объявляете атрибут bonesAnimation, для хранения итогового преобразования каждой кости, комбинирующего преобразования, необходимые для помещения вершин в координатную систему кости и их анимации с использованием абсолютной конфигурации каждой кости. Мы исследуем скелетную анимацию более подробно в разделе «Формулы скелетной анимации».

Matrix[] bones;
Matrix[] bonesAbsolute;
Matrix[] bonesAnimation;

Чтобы иметь возможность применять к костям произвольное преобразование, вы объявляете еще один массив матриц. Это произвольное преобразование вы используете для того, чтобы независимым образом модифицировать кости скелета во время воспроизведения анимации. Например, вы можете применить к скелету персонажа произвольное вращение относительно шейной кости:

Matrix[] bonesTransform;

И, наконец, вам потребуется объявить два атрибута для хранения эффекта анимированной модели и материала:

AnimatedModelEffect animatedModelEffect;
LightMaterial       lightMaterial;

Вы создаете класс AnimatedModelEffect для инкапсуляции эффекта анимированной модели, и применяете созданный в главе 8 класс LightMaterial для его конфигурирования.

Загрузка анимированной модели

Анимированная модель хранится как объект XNA Model, так что первый этап ее загрузки — это загрузка объекта XNA Model, с использованием диспетчера содержимого. Затем вам следует проверить, являетсяли загруженная модель правильной анимированной моделью — содержит ли она в свойстве Tag модели словарь с объектом AnimatedModelData:

model = Game.Content.Load<Model>(
               GameAssetsPath.MODELS_PATH + modelFileName);

// Получаем словарь
Dictionary<string, object> modelTag =
               (Dictionary<string, object>)model.Tag;
if (modelTag == null)
    throw new InvalidOperationException(
               "Неправильная анимированная модель.");

// Получаем AnimatedModelData из словаря
if (modelTag.ContainsKey("AnimatedModelData"))
    animatedModelData = (AnimatedModelData)
               modelTag["AnimatedModelData"];
else
    throw new InvalidOperationException(
               "Неправильная анимированная модель.");

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

if (animatedModelData.Animations.Length > 0)
    activeAnimation = animatedModelData.Animations[0];

Текущий ключевой кадр анимации и ее время сохраняются, соответственно, в атрибутах activeAnimationKeyframe и activeAnimationTime. Затем вы настраиваете скорость анимации через атрибут animationSpeed:

// Конфигурация анимации по умолчанию
animationSpeed = 1.0f;
activeAnimationKeyframe = 0;
activeAnimationTime = TimeSpan.Zero;

В процессе анимации модели используется несколько временных массивов матриц для вычисления финальной конфигурации каждой кости. Здесь вам необходимо создать эти массивы матриц, и их размер должен быть равен количеству костей в скелете модели. Вы должны инициализировать массив bones конфигурациями костей, хранящимися в AnimatedModelData, а массив bonesTransformation единичными матрицами:

// Временные матрицы, используемые для анимации костей
bones = new Matrix[animatedModelData.BonesBindPose.Length];
bonesAbsolute = new Matrix[animatedModelData.BonesBindPose.Length];
bonesAnimation = new Matrix[animatedModelData.BonesBindPose.Length];

// Используется для применения произвольного преобразования к костям 
bonesTransform = new Matrix[animatedModelData.BonesBindPose.Length];

for (int i = 0; i < bones.Length; i++)
{
    bones[i] = animatedModelData.BonesBindPose[i];
    bonesTransform[i] = Matrix.Identity;
}

В конце вы получаете из объекта модели эффект анимированной модели и инкапсулируете его в AnimatedModelEffect:

// Получаем эффект анимированной модели. Совместно используется всеми сетками
animatedModelEffect = new AnimatedModelEffect(model.Meshes[0].Effects[0]);

// Создаем материал по умолчанию
lightMaterial = new LightMaterial();

Заметьте, что эффект, применяемый при визуализации модели, совместно используется всеми сетками модели. Вот полный код метода Load класса AnimatedModel:

public void Load(string modelFileName)
{
    if (!isInitialized)
        Initialize();

    model = Game.Content.Load<Model>(
               GameAssetsPath.MODELS_PATH + modelFileName);

    // Получаем словарь
    Dictionary<string, object> modelTag =
              (Dictionary<string, object>)model.Tag;
    if (modelTag == null)
        throw new InvalidOperationException(
              "Неправильная анимированная модель.");

    // Получаем AnimatedModelData из словаря
    if (modelTag.ContainsKey("AnimatedModelData"))
        animatedModelData = (AnimatedModelData)
              modelTag["AnimatedModelData"];
    else
        throw new InvalidOperationException(
              "Неправильная анимированная модель.");

    // Параметры анимации по умолчанию
    animationSpeed = 1.0f;
    activeAnimationKeyframe = 0;
    activeAnimationTime = TimeSpan.Zero;
    if (animatedModelData.Animations.Length > 0)
        activeAnimation = animatedModelData.Animations[0];

    // Временные матрицы, используемые для анимации костей 
    bones = new Matrix[animatedModelData.BonesBindPose.Length];
    bonesAbsolute = new
              Matrix[animatedModelData.BonesBindPose.Length];
    bonesAnimation = new
              Matrix[animatedModelData.BonesBindPose.Length];

    // Используется для применения произвольного преобразования к костям
    bonesTransform = new
              Matrix[animatedModelData.BonesBindPose.Length];
    for (int i = 0; i < bones.Length; i++)
    {
        bones[i] = animatedModelData.BonesBindPose[i];
        bonesTransform[i] = Matrix.Identity;
    }

    // Получаем эффект анимированной модели.
    // Совместно используется всеми сетками
    animatedModelEffect = new
              AnimatedModelEffect(model.Meshes[0].Effects[0]);

    // Создаем материал по умолчанию
    lightMaterial = new LightMaterial();
}

Формулы скелетной анимации

В этом разделе вы изучите некоторые концепции и математические формулы, используемые в скелетной анимации. Скелетная анимация создается из множества ключевых кадров, где каждый ключевой кадр хранит конфигурацию кости (ее ориентацию и местоположение) и время кадра, в которое эта кость должна быть анимирована. В каждый момент времени вы используете один или несколько ключевых кадров для изменения конфигурации костей скелета. Рис. 11.7 иллюстрирует анимацию скелета, показанного на рис. 11.3, где изменилась ориентация кости левого плеча, что повлияло на другие кости.


Рис. 11.7. Анимация кости левого плеча скелета с рис. 11.3

Рис. 11.7. Анимация кости левого плеча скелета с рис. 11.3. Заметьте, что все последующие кости также изменили конфигурацию


Чтобы достичь результата, показанного на рис. 11.7, вам надо сохранить ключевой кадр анимации для кости левого плеча. Хотя конфигурация всех потомков левого плеча будет изменена, они продолжают оставаться в тех же самых позициях относительно левого плеча. Другими словами, вам не надо сохранять новые конфигурации потомков левого плеча, поскольку вы можете вычислить их, основываясь на новой конфигурации левого плеча. Итак, когда вам необходимо обновить модель, вы должны вычислить абсолютную конфигурацию каждой кости, и затем преобразовать вершины сетки, используя эти кости. В следующих разделах мы покажем некоторые математические формулы, используемые для преобразования сетки модели в процессе анимации модели. Затем вы реализуете и используете эти математические формулы для обновления и рисования модели. Чтобы использовать преимущества графического процессора видеокарты, некоторые из этих формул будут реализованы в эффекте анимированной модели.

Преобразование вершин сетки

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

Формула 1

В предыдущей формуле, PF — это итоговое местоположение вершины, P'0 — это начальная позиция вершины, Bone — это матрица, содержащая абсолютную конфигурацию кости, которая влияет на вершину, и W — это весовой коэффициент влияния кости на вершину. Поскольку на вершину влияет только одна кость, весовой коэффициент должен быть 1.0 (эквивалентно 100 процентам). Эта формула показывает, как вы должны вычислять итоговую позицию вершин: преобразуя начальную позицию вершины с помощью матрицы, содержащей абсолютную конфигурацию кости.

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

Формула 2

В показанной формуле P'0 — это исходная позиция вершины в системе координат позиции привязки кости, P0 — это местоположение вершины в системе координат объекта, и Bone-1BindPose — это инвертированная матрица абсолютной конфигурации кости в ее позиции привязки. Чтобы поместить вершину в позицию привязки кости вам достаточно умножить ее на инвертированную матрицу кости в позиции привязки. Используя две показанные выше формулы вы можете анимировать все вершины сетки модели, используя ее скелет.

Комбинирование преобразований костей

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

Формула 3

Заметьте, что сумма всех весовых коэффициентов, используемых для преобразования вершин в показанной выше формуле должна быть равна 1. И, наконец, следующая формула показывает все вычисления, используемые для трансформации вершин сетки:

Формула 4

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

Анимация модели

В ходе анимации модели коду необходимо постоянно обновлять кости скелета модели согласно ключевым кадрам анимации, где ключевые кадры содержат новую конфигурацию костей в их локальной системе координат относительно их предков. Вы обрабатываете анимацию модели, используя как центральный процессор (CPU), так и графический процессор (GPU), причем CPU отвечает за вычисление матрицы кости (матрицы [Bone-1BindPose * Bonei], показанной в последней формуле из предыдущего раздела), а GPU отвечает за вычисление итоговой матрицы (показанной в последней формуле) и преобразование вершин.

Для выполнения процесса обработки анимации на CPU вы создаете метод Update класса AnimatedModel, а для выполнения процесса обработки анимации на GPU вы создаете новый эффект для анимированных моделей.

Обновление анимированной модели

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

Вы начинаете первую часть процесса анимации с вычисления текущего времени анимации. Это выполняется путем увеличения времени анимации на время, прошедшее с момента последнего обновления, причем прошедшее время умножается на скорость анимации:

activeAnimationTime += new TimeSpan(
                (long)(time.ElapsedGameTime.Ticks * animationSpeed));

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

// Зацикливание анимации
if (activeAnimationTime > activeAnimation.Duration && enableAnimationLoop)
{
    long elapsedTicks = activeAnimationTime.Ticks % activeAnimation.Duration.Ticks;
    activeAnimationTime = new TimeSpan(elapsedTicks);
    activeAnimationKeyframe = 0;
}

Потом вы проверяете, являетсяли данное обновление первым в анимации. Если да, вам надо восстановить кости скелета в их позицию привязки:

// В начале анимации помещаем кости в позицию привязки
if (activeAnimationKeyframe == 0)
{
    for (int i = 0; i < bones.Length; i++)
        bones[i] = animatedModelData.BonesBindPose[i];
}

Чтобы воспроизвести анимацию вы перебираете в цикле ключевые кадры текущей анимации модели, обновляя кости скелета модели, когда activeAnimationTime больше, чем время ключевого кадра:

// Чтение всех ключевых кадров анимации, пока не достигнуто текущее время 
// Это возможно благодаря произведенной ранее сортировке ключевых кадров
int index = 0;
Keyframe[] keyframes = activeAnimation.Keyframes;
while (index < keyframes.Length && keyframes[index].Time <= activeAnimationTime)
{
    int boneIndex = keyframes[index].Bone;
    bones[boneIndex] = keyframes[index].Transform * bonesTransform[boneIndex];
    index++;
}
activeAnimationKeyframe = index - 1;

Во второй части процесса анимации вам необходимо перебрать все матрицы костей, вычисляя для каждой из них абсолютную конфигурацию. Поскольку массив костей скелета построен путем обхода в глубину, родитель кости в этом массиве не может иметь индекс больше, чем у потомка. Итак, вы можете по порядку перебирать элементы этого списка, вычисляя их финальные позиции, поскольку финальные позиции предков будут уже вычислены. Заметьте, что первая кость в массиве уже находится в абсолютной системе координат (поскольку у нее нет предка), но вы преобразуете ее с помощью произвольной пользовательской матрицы:

// Вычисляем абсолютные координаты костей
bonesAbsolute[0] = bones[0] * parent;
for (int i = 1; i < bonesAnimation.Length; i++)
{
    int boneParent = animatedModelData.BonesParent[i];

    // Преобразуем дочернюю кость, согласно ее предку
    bonesAbsolute[i] = bones[i] * bonesAbsolute[boneParent];
}

И, наконец, вы вычисляете итоговую позицию каждой кости, перемножая инвертированное преобразование кости в ее позиции привязки и текущую абсолютную позицию кости:

// Перед тем, как мы сможем преобразовать вершины сетки, используя
// вычисленную матрицу кости, необходимо поместить вершины в
// систему координат той кости, с которой они связаны 
for (int i = 0; i < bonesAnimation.Length; i++)
{
    bonesAnimation[i] = animatedModelData.BonesInverseBindPose[i] *
                                                     bonesAbsolute[i];
}

Вот полный код метода Update класса AnimatedModel:

private void UpdateAnimation(GameTime time, Matrix parent)
{
    activeAnimationTime += new TimeSpan(
                  (long)(time.ElapsedGameTime.Ticks * animationSpeed));

    if (activeAnimation != null)
    {
        // Зацикливание анимации
        if (activeAnimationTime >
                       activeAnimation.Duration && enableAnimationLoop)
        {
            long elapsedTicks = activeAnimationTime.Ticks %
                                        activeAnimation.Duration.Ticks;
            activeAnimationTime = new TimeSpan(elapsedTicks);
            activeAnimationKeyframe = 0;
        }

        // Каждый раз при старте анимации помещаем
        // локальные позиции привязки в массив костей 
        if (activeAnimationKeyframe == 0)
        {
            for (int i = 0; i < bones.Length; i++)
                bones[i] = animatedModelData.BonesBindPose[i];
        }

        // Воспроизводим все ключевые кадры анимации, пока не будет достигнуто
        // текущее время. Это возможно благодаря тому, что при обработке модели
        // ключевые кадры были отсортированы по времени 
        int index = 0;
        Keyframe[] keyframes = activeAnimation.Keyframes;

        while (index < keyframes.Length &&
                       keyframes[index].Time <= activeAnimationTime)
        {
            int boneIndex = keyframes[index].Bone;
            bones[boneIndex] = keyframes[index].Transform *
                                             bonesTransform[boneIndex];
            index++;
        }

        activeAnimationKeyframe = index - 1;
    }

    // Вычисляем абсолютные координаты костей 
    bonesAbsolute[0] = bones[0] * parent;
    for (int i = 1; i < bonesAnimation.Length; i++)
    {
        int boneParent = animatedModelData.BonesParent[i];

        // Преобразуем конфигурацию кости согласно конфигурации ее родителя 
        bonesAbsolute[i] = bones[i] * bonesAbsolute[boneParent];
    }

    // Перед тем, как мы сможем преобразовать вершины сетки, используя вычисленную
    // матрицу кости, необходимо поместить вершины в систему координат той кости,
    // с которой они связаны 
    for (int i = 0; i < bonesAnimation.Length; i++)
    {
        bonesAnimation[i] = animatedModelData.BonesInverseBindPose[i]
                                                        * bonesAbsolute[i];
    }
}

Эффект анимированной модели

В каждый временной интервал вам необходимо анимировать (преобразовывать) сетку модели согласно скелету модели, либо аппаратно (на GPU), либо программно (на CPU). Преимущество преобразования сетки модели GPU состоит в том, что такая обработка обычно намного быстрее, чем программная. В этом разделе вы создадите эффект для визуализации анимированной модели, преобразующий вершины сетки в своем вершинном шейдере. Этот эффект также поддерживает два всенаправленных источника света и текстурирование.

Обработка вершин анимированной модели

Давайте начнем конструирование эффекта с его вершинного шейдера. Вершинный шейдер получает структуру, содержащую местоположение вершины, нормаль, координаты текстуры, индекс кости и весовой коэффициент. У каждой вершины есть индексы костей, которые влияют на нее и весовые коэффициенты этого влияния.

Заметьте, что атрибуты индексов и весовых коэффициентов у вершины обрабатываются используемым по умолчанию обработчиком моделей XNA, классом ModelProcessor:

struct a2v
{
    float4 position : POSITION;
    float3 normal : NORMAL;
    float2 uv0 : TEXCOORD0;
    float4 boneIndex : BLENDINDICES0;
    float4 boneWeight : BLENDWEIGHT0;
};

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

struct v2f
{
    float4 hposition : POSITION;
    float2 uv0 : TEXCOORD0;
    float3 normal : TEXCOORD1;
    float3 lightVec1 : TEXCOORD2;
    float3 lightVec2 : TEXCOORD3;
    float3 eyeVec : TEXCOORD4;
};

Внутри обработчика вершин вы должны сперва вычислить итоговую матрицу кости, используемую для преобразования местоположения вершины и нормали:

// Вычисляем итоговую матрицу преобразования кости
float4x4 matTransform = matBones[IN.boneIndex.x] * IN.boneWeight.x;
matTransform += matBones[IN.boneIndex.y] * IN.boneWeight.y;
matTransform += matBones[IN.boneIndex.z] * IN.boneWeight.z;
float finalWeight = 1.0f - (IN.boneWeight.x + IN.boneWeight.y + IN.boneWeight.z);
matTransform += matBones[IN.boneIndex.w] * finalWeight;

Массив matBones это неизменные входные данные, содержащие матрицы с конфигурацией каждой кости, и его максимальный размер устанавливается в соответствии с числом свободных константных регистров GPU. В шейдере, который вы создаете, и который компилируется с использованием шейдерной модели 2.0, максимальный размер массива matBones равняется 58.

Затем вы преобразуете местоположение вершины и нормаль, используя итоговую матрицу кости. После этого вы преобразуете их еще раз, используя матрицу комбинирующую мировое преобразование, преобразование вида и преобразование проекции:

// Преобразование вершины и нормали
float4 position = mul(IN.position, matTransform);
float3 normal = mul(IN.normal, matTransform);
OUT.hposition = mul(position, matWVP);
OUT.normal = mul(normal, matWV);

В конце вы вычисляете вектор вида и два вектора освещения, используемые для освещения вершины:

// Вычисление векторов освещения и взгляда
float4 worldPosition = mul(position, matW);
OUT.eyeVec = mul(matVI[3].xyz - worldPosition, matV);
OUT.lightVec1 = mul(light1Position - worldPosition, matV);
OUT.lightVec2 = mul(light2Position - worldPosition, matV);
OUT.uv0 = IN.uv0;

Вот полный код обработки вершин:

v2f animatedModelVS(a2v IN)
{
    v2f OUT;

    // Вычисляем итоговую матрицу преобразования кости 
    float4x4 matTransform = matBones[IN.boneIndex.x] *
                                               IN.boneWeight.x;
    matTransform += matBones[IN.boneIndex.y] * IN.boneWeight.y;
    matTransform += matBones[IN.boneIndex.z] * IN.boneWeight.z;
    float finalWeight = 1.0f - (IN.boneWeight.x + IN.boneWeight.y +
                                                   IN.boneWeight.z);
    matTransform += matBones[IN.boneIndex.w] * finalWeight;

    // Преобразуем вершину и нормаль
    float4 position = mul(IN.position, matTransform);
    float3 normal = mul(IN.normal, matTransform);
    OUT.hposition = mul(position, matWVP);
    OUT.normal = mul(normal, matWV);

    // Вычисляем векторы взгляда и освещения
    float4 worldPosition = mul(position, matW);
    OUT.eyeVec = mul(matVI[3].xyz - worldPosition, matV);
    OUT.lightVec1 = mul(light1Position - worldPosition, matV);
    OUT.lightVec2 = mul(light2Position - worldPosition, matV);
    OUT.uv0 = IN.uv0;

    return OUT;
}

Обработка пикселей анимированной модели

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

// Нормализуем все входные векторы
float3 normal = normalize(IN.normal);
float3 eyeVec = normalize(IN.eyeVec);
float3 lightVec1 = normalize(IN.lightVec1);
float3 lightVec2 = normalize(IN.lightVec2);
float3 halfVec1 = normalize(lightVec1 + eyeVec);
float3 halfVec2 = normalize(lightVec2 + eyeVec);

К этому моменту у вас есть все необходимые векторы для расчета освещения. Вы выполняете расчет освещения, используя формулы Фонга, которые обычно реализуются в графическом API. Созданная функция phongShading реализует формулы Фонга и возвращает рассеиваемую и отражаемую компоненты для заданного источника света. Заметьте, что этот этап был выполнен в главе 10.

// Вычисление рассеиваемого и отражаемого света для каждого источника
float3 diffuseColor1, diffuseColor2;
float3 specularColor1, specularColor2;
phongShading(normal, lightVec1, halfwayVec1, light1Color,
             diffuseColor1, specularColor1);
phongShading(normal, lightVec2, halfwayVec2, light2Color,
             diffuseColor2, specularColor2);

Помимо освещения выполняется считывание цвета пикселя из текстуры:

float4 materialColor = tex2D(diffuse1Sampler, IN.uv0);

И, наконец, вы вычисляете итоговый цвет каждого пикселя, комбинируя его цвет с рассеиваемым и отражаемым компонентами из источников освещения:

float4 finalColor;
finalColor.a = 1.0f;
finalColor.rgb = materialColor *
            ((diffuseColor1+diffuseColor2) * diffuseColor +
            ambientLightColor) + (specularColor1 + specularColor2) *
            specularColor ;

Код функции phongShading показан в главе 10, а вот итоговый код пиксельного шейдера:

float4 animatedModelPS(v2f IN): COLOR0
{
    // Нормализуем все входные векторы
    float3 normal = normalize(IN.normal);
    float3 eyeVec = normalize(IN.eyeVec);
    float3 lightVec1 = normalize(IN.lightVec1);
    float3 lightVec2 = normalize(IN.lightVec2);
    float3 halfwayVec1 = normalize(lightVec1 + eyeVec);
    float3 halfwayVec2 = normalize(lightVec2 + eyeVec);

    // Вычисление рассеиваемого и отражаемого света для каждого источника
    float3 diffuseColor1, diffuseColor2;
    float3 specularColor1, specularColor2;
    phongShading(normal, lightVec1, halfwayVec1,
                 light1Color, diffuseColor1, specularColor1);
    phongShading(normal, lightVec2, halfwayVec2,
                 light2Color, diffuseColor2, specularColor2);

    // Читаем цвет из текстуры
    float4 materialColor = tex2D(diffuse1Sampler, IN.uv0);

    // Результат освещения по Фонгу
    float4 finalColor;
    finalColor.a = 1.0f;
    finalColor.rgb = materialColor *
                ((diffuseColor1+diffuseColor2) * diffuseColor +
                ambientLightColor) + (specularColor1+specularColor2) *
                specularColor ;

    return finalColor;
}

Далее представлен код для техники, использующей созданные выше вершинный и пиксельный шейдеры:

technique AnimatedModel
{
    pass p0
    {
        VertexShader = compile vs_2_0 animatedModelVS();
        PixelShader = compile ps_2_a animatedModelPS();
    }
}

Конвертация эффекта сетки

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

Метод ConvertMaterial получает в качестве параметра объект MaterialContent, который хранит содержимое используемого сеткой материала. Когда модель экспортируется без эффекта, у нее есть только некоторые базовые параметры материалов, такие как цвет и текстура. В таком случае полученный MaterialContent фактически является экземпляром класса BasicMaterialContent. Если модель экспортируется вместе с эффектом, полученный материал является экземпляром класса EffectMaterialContent.

Чтобы изменить используемый в модели материал, вам надо переопределить метод ConvertMaterial и преобразовать полученный BasicMaterialContent в EffectMaterialContent, содержащий эффект, который вы создали для анимированной модели. Ниже показан код метода ConvertMaterial, который вы должны добавить к классу обработчика модели.

protected override MaterialContent ConvertMaterial(
              MaterialContent material, ContentProcessorContext context)
{
    BasicMaterialContent basicMaterial = material
                                   as BasicMaterialContent;
    if (basicMaterial == null)
        context.Logger.LogImportantMessage(
                "This mesh doesn't have a valid basic material.");

    // Обрабатываем только сетки с базовым материалом
    // Иначе сетка должна использовать правильный эффект (AnimatedModel.fx)
    if (basicMaterial != null)
    {
        EffectMaterialContent effectMaterial =
                                   new EffectMaterialContent();
        effectMaterial.Effect =
                 new ExternalReference<EffectContent>(
                              SHADERS_PATH + SHADER_FILENAME);

        // Корректируем путь к текстуре
        if (basicMaterial.Texture != null)
        {
            string textureFileName = Path.GetFileName(
                              basicMaterial.Texture.Filename);
            effectMaterial.Textures.Add("diffuseTexture1",
                       new ExternalReference<TextureContent>(
                                TEXTURES_PATH + textureFileName));
        }

        return base.ConvertMaterial(effectMaterial, context);
    }
    else
        return base.ConvertMaterial(material, context);
}

Когда BasicMaterialContent конвертируется в EffectMaterialContent, используемая в материале по умолчанию текстура модели передается в только что созданный эффект.

Рисование модели

Поскольку анимированная модель это объект XNA Model, рисовать ее просто. Сперва вам надо сконфигурировать эффект анимированной модели, а затем вам только остается перебрать все сетки модели, вызывая их методы Draw. Вот код метода Draw класса AnimatedModel:

public override void Draw(GameTime gameTime)
{
    SetEffectMaterial();

    for (int i = 0; i < model.Meshes.Count; i++)
    {
        model.Meshes[i].Draw();
    }
}

netlib.narod.ru< Назад | Оглавление | Далее >

Сайт управляется системой uCoz