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

252

Глава 8

Визуализация морфированных мешей

Если вы создаете морфируемый меш, непосредственно работая с буфером вершин результирующего меша, как показано в предыдущем разделе, то его можно визуализировать также как и используемые вами объекты ID3DXMesh. Например, вы можете перебрать все материалы в меше, установить материал и текстуру и нарисовать текущий поднабор. Нет никакой нужды приводить код здесь - это простая визуализация меша.

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

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

Расчленение наборов

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

Подождите-ка! Как визуализировать меш самостоятельно? Повторив то, что делает функция ID3DXMesh::DrawSubset, конечно же! Функция DrawSubset работает, используя один из двух методов. В первом режиме, применяемом, если меш не оптимизирован для использования таблицы атрибутов, просматривается весь список атрибутов и визуализируются те многоугольники, которые принадлежат одному поднабору. Этот метод может быть немного медленным, потому что он визуализирует меши, использующие множество материалов, маленькими наборами многоугольников.

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

Работа сморфирующейанимацией

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

// pMesh = только что загруженный меш pMesh->OptimizeInPlace(D3DXMESHOPT_ATTRSORT, \

NULL, NULL, NULL, NULL);

После оптимизации меша вы можете получить таблицу атрибутов, используемую объектом ID3DXMesh. Таблица атрибутов имеет тип D3DXATTRIBUTERANGE, который определен так:

typedef struct _D3DXATTRIBUTERANGE { DWORD Attribld;

DWORD FaceStart;

DWORD FaceCount; DWORD VertexStart; DWORD VertexCount;

} D3DXATTRIBUTERANGE;

Первая переменная Attribld является номером поднабора, который представляет структура. Каждый материал меша ассоциирован с одной структурой D3DXATTRIBUTERANGE, Attribld которой указывает номер поднабора.

Далее идет FaceStart и FaceCount. Эти две переменные используются для определения, какие грани принадлежат поднабору. Вот где и появляется оптимизация - все грани, принадлежащие одному и тому же поднабору, группируются

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

вданном поднаборе.

Наконец вы обнаружите VertexStart и VertexCount, которые совсем как FaceStart и FaceCount определяют, какие вершины используются при визуализации многоугольников. VertexStart представляет собой первую вершину из буфера вершин, используемую в поднаборе, a VertexCount содержит количество вершин, визуализируемых за один раз. Когда вы оптимизируете меш, основываясь на вершинах, вы заметите, что все вершины запакованы в буфере для уменьшения количества вершин, используемых при визуализации поднабора.

Для каждого поднабора вашего меша существует соответствующая структура D3DXATTRIBUTERANGE. Таким образом, меш, использующий три материала, имеет три структуры атрибутов. После оптимизации меша (используя ID3DXMesh::OptimizeInPlace) вы можете получить таблицу атрибутов, сначала

254

взяв из объекта меша количество структур атрибутов, используя функцию ID3DXMesh::GetArtribute-Table, как показано тут:

// Получить количество атрибутов в таблице DWORD NumAttributes; pMesh->GetAttributeTable(NULL, &NumAttributes);

Теперь вам необходимо просто создать необходимое количество объектов D3DXATTRIBUTERANGE и вызвать функцию GetAttributeTable, указав, на этот раз, указатель на массив объектов атрибутов.

// Выделить память под таблицу атрибутов и получить ее данные D3DXATTRIBUTERANGE *pAttributes;

pAttributes = new D3DXATTRIBUTERANGE[NumAttributes]; pMesh->GetAttributeTable(pAttributes, NumAttributes);

Замечательно! После того как вы получили данные атрибутов, вы можете визуализировать поднаборы, просматривая каждый объект таблицы атрибутов и используя данные каждого из них при вызове DrawIndexedPrimitive. На самом деле вам необходимо сначала получить указатели на буферы вершин и индексов меша.

//Получить интерфейс буфера вершин IDirect3DVertexBuffer9 *pVB; pMesh->GetVertexBuffer(&pVB);

//Получить интерфейс буфера индексов IDirect3DIndexBuffer9 *pIB; pMesh->GetIndexBuffer(&pIB);

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

//Установить вершинный шейдер и объявления pDevice->SetFVF(NULL); // Clear FVF usage pDevice->SetVertexShader(pShader); pDevice->SetVertexDeclaration(pDecl);

//Установить потоки

pDevice->SetStreamSource(0, pVB, \

0, pMesh->GetNumBytesPerVertex()); pDevice->SetIndices(pIB);

// Просмотреть каждый поднабор for(DWORD i=0;i<NumAttributes;i++) {

//Получить номер материала DWORD MatID = pAttributes[i];

//Установить текстуру поднабора

pDevice->SetTexture(0, pTexture[AttribID]);

Работа сморфирующейанимацией

// Визуализировать многоугольник, используя таблицу pDevice->DrawIndexedPrimitive(D3DPT_TRIANGLELIST, 0, \

pAttributes[i].VertexStart, \ pAttributes[i].VertexCount, \ pAttributes[i].FaceStart * 3, \ pAttributes[i].FaceCount);

}

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

pVB->Release(); pVB = NULL;

pIB->Release(); pIB = NULL;

Когда вы закончите работу с таблицей атрибутов, убедитесь, что тоже освободили ее.

delete [] pAttributes; pAttributes = NULL;

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

Создание морфирующего вершинного шейдера

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

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

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

256

Глава 8

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

Давайте не будем больше тянуть и перейдем к вершинным шейдерам. Начнем с объявления элементов вершин, которые ассоциируют данные вершин с регистрами шейдера.

D3DVERTEXELEMENT9 MorphMeshDecl[] =

{

// первый поток используется для исходного меша

{0 , 0, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0 },

{0, 12, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_NORMAL,0 } ,

{0, 24, D3DDECLTYPE_FLOAT2, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 0 },

// второй поток используется для целевого меша

{1, 0, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 1 },

{1, 12, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_NORMAL, 1 },

{1, 24, D3DDECLTYPE_FLOAT2, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 1 },

D3DDECL_END() };

Как вы можете видеть из объявления элементов вершин, используются два потока. Каждый поток использует набор положений, нормалей и текстурных координат. Первый поток имеет индекс использования 0, в то время как второй - 1.

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

Явернусь к этим потокам чуть позже; а пока я хочу перейти к коду шейдера.

Вначале вершинного шейдера вы обнаружите версию и привязочные объявления.

vы.1.0

; declare mapping dсl position v0

Работа сморфирующейанимацией

dcl_normal v1 dcl_texcoord v2 dcl_position1 v3 dcl_normal1 v4

Вы можете видеть, что используются только пять вершинных регистров, а где же шестой? Т. к. оба меша используют одни и те же текстурные координаты, второй набор может быть полностью опущен. Все к лучшему, меньше надо будет делать. Используемые регистры непосредственно привязываются к объявлениям элементов вершин. В объявлении находятся координаты положения исходного меша (v0), нормаль (v), текстурные координаты (v2), координаты положения целевого меша (v3) и его нормаль (v4).

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

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

; применить значения скаляра и обратного скаляра к координатам mul r0, v0, c4.x

mad r0, v3, c4.y, r0

Заметьте, что вы сначала умножаете v0 на обратный скаляр, после чего умножаете v3 на скаляр. Оба полученных результата складываются и сохраняются во временном регистре, который нужен для проецирования, используя транспонированное комбинированное преобразование мир*вид*проекция. Я вернусь к этому чуть позже; а пока, необходимо обработать значения нормалей так же, как вы делали это с координатами вершин.

; применить значения скаляра и обратного скаляра к нормалям mul r1, v1, c4.x

mad r1, v4, c4.y, r1

Все что осталось сделать, это преобразовать координаты при помощи транспонированной матрицы преобразования мир*вид*проекция (хранимой в константах вершинного шейдера с с0 до с3), векторно умножить нормаль на обратное направление света (то же самое направление, которое вы используете в структуре D3DLIGHT9), хранимое в константе вершинного шейдера с5, и сохранить текстурные координаты (из регистра v2).

258

Глава 8

;Спроецировать положение m4x4 oPos, r0, с0

;Векторно умножить нормаль на обратное направление света, для

;получения рассеянного света

dp3 oD0, r1, -c5

; сохранить текстурные координаты mov oT0.xy, v2

Видите, я же говорил, что вершинный шейдер прост. Предположив, что у вас уже есть код загрузки вершинного шейдера, перейдем к тому, как визуализировать меш, используя вершинный шейдер.

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

//matWorld = матрица преобразования мира (D3DXMATRIX)

//matView = матрица преобразования вида (D3DXMATRIX)

//matProj = матрица преобразования проекции(D3DXMATRIX)

Вслучае, если вы не хотите следить за этими тремя матрицами, вы можете получить их при помощи функции IDirect3DDevice9::GetTransform, как показано тут.

pDevice->GetTransform(D3DTS_WORLD, &matWorld); pDevice->GetTransform(D3DTS_VIEW, &matView); pDevice->GetTransform(D3DTS_PROJECTION, &matProj);

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

D3DXMATRIX matWVP = matWorld * matView * matProj; D3DXMatrixTranspose(&matWVP, &matWVP); pDDevice->SetVertexShaderConstantF(0, (float*)&matWVP, 4);

После этого установите значение обратного скаляра морфинга в с4.х и значения скаляра в с4.у.

// Установить используемые значения скаляра и обратного скаляра D3DXVECTOR4 vecScalar = D3DXVECTOR4(1.0f - Scalar, \

Scalar, \ 0.0, 0.0);

pDevice->SetVertexShaderConstantF(4, (float)*&Scalar, 1);

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