Такая простая вещь как морфирование мешей требует использования вершинных шейдеров. На самом деле, как только вы увидите насколько просто использовать морфируемые вершинные шейдеры, вы никогда не будете вручную изменять буферы вершин!
Помните, что по сути морфируемый меш состоит из интерполированных координат положения и нормалей, которые вычисляются из исходного и целевого меша. Т. к. координаты этих положений и нормалей являются частью потока вершин, вы можете создать вершинный шейдер, который бы одновременно использовал эти два потока вершин и то же самое значение скаляра, что и раньше, и вычислял бы значения вершин при помощи простых команд.
Все правильно, больше никаких блокировок или перестроений морфируемого меша в каждом кадре. Все происходит в вершинном шейдере! Вам просто необходимо установить источники потоков вершин. Используя методы визуализации, рассмотренные в предыдущем разделе, вы можете визуализировать группы многоугольников, принадлежащих мешу.
Замечание. Во вспомогательных функциях первой главы вы могли обнаружить функцию 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. Убедитесь, что исходный и целевой меши используют одинаковый порядок индексов и вершин, иначе морфируемая анимация может искажаться.
Я вернусь к этим потокам чуть позже; а пока я хочу перейти к коду шейдера. В начале вершинного шейдера вы обнаружите версию и привязочные объявления.
vs.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 означает, что ее координаты совпадают с координатами целевого меша DirectX
Далее вам необходимо умножить координаты вершины, хранящейся в 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).
; Спроецировать положение 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, & m a t V i e w ) ; pDevice->GetTransform(D3DTS_PROJECTION, & m a t P r o j ) ;
{mospagebreak}После того как вы получили эти преобразования, вы можете скомбинировать их и установить транспонированную матрицу в константы от с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);
Наконец, установите нормализованное направление света в регистр констант с5 вершинного шейдера. Это значение направления является направленным вектором, хранимым в структуре D3DLIGHT, которую вы уже должны были использовать, хотя этот вектор приводится к типу D3DXVECTOR4.
// Установить направление света в регистр констант с5 D3DXVECTOR4 vecLight(0.0f, -1.0f, 0.0f, 0.0f); pDevice->SetVertexShaderConstantF(5, (float*)&vecLight, 1);
Заметьте, что координаты света хранятся в пространстве объекта, означая, что свет вращается вместе с объектом. Это необходимо для того, чтобы объект освещался корректно, независимо от направления просмотра. Если вы хотите использовать координаты света в пространстве вида, а не объекта, тогда вам необходимо преобразовать направление света на обратное преобразование вида.
Гмм! Небольшая тема растянулась на много, и теперь вы готовы визуализировать меш при помощи вершинных шейдеров. На данный момент, остается только установить потоки вершин, текстуру, шейдер, объявление вершин и вызвать функцию рисования элементарных объектов. Конечно вам необходимо использовать методы из предыдущего раздела для визуализации отдельных поднаборов исходного меша. Вместо того чтобы повторять код, который вы уже видели, я посоветую вам посмотреть код демонстрационной программы вершинного шейдера для этой главы. Счастливого морфинга!
27 августа 2010