Рендеринг меха короткошерстных животных в режиме реального времени

Создать новую статью

Дата последнего изменения :   14.08.2009 04:05
Рейтинг
 


В статье рассматриваются современные методы рендеринга меха в режиме реального времени. Представлены примеры моделей, созданных с использованием контуров и оболочек в режиме реального времени с использованием библиотеки Intel® Integrated Performance Primitives (Intel® IPP) в системах с графическими адаптерами на базе набора микросхем Intel® 915G Express

Введение

Основной задачей разработчиков, занимающихся компьютерной графикой, является придание создаваемым персонажам наибольшей реалистичности. Эта задача имеет несколько аспектов: анимация персонажа, лицевая анимация, затенение и освещение, физика, и множество других эффектов, которые изучаются уже более 30 лет. Одним из важнейших и самых трудных аспектов является моделирование и рендеринг шерстяного покрова (меха). Разработчики решают данную задачу с переменным успехом. Когда-то их усилия были направлены на придание реализма персонажам с помощью автономного рендеринга, а в последнее время они трудятся над высокопроизводительным рендерингом в режиме реального времени. В настоящей статье рассматриваются современные методы рендеринга шерсти животных в режиме реального времени. Представлены примеры моделей, созданных на основе метода контуров и оболочек с использованием библиотеки Intel® Integrated Performance Primitives (Intel® IPP) в системе с интегрированным графическим адаптером на базе набора микросхем Intel® 915G Express, который поддерживает DirectX* 9, а также технологии затенения Vertex Shader 3.0 и Pixel Shader 2.0. Все параметры представленного метода рендеринга (такие как цвет и толщина материалов, а также количество используемых контуров и оболочек) можно изменять.

Рис. 1. Пример модели, рассчитанной с помощью рендеринга в режиме реального времени по методу, описанному в данной статье

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

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

Опубликованные работы о рендеринге

Как отмечалось во Введении, вопросами рендеринга шерстяного покрова занимались многие разработчики. Одна из первых работ на эту тему, “Rendering Fur with Three Dimensional Models” (“Рендеринг шерстяного покрова трехмерных моделей”), принадлежит Джеймсу Каджийя (James Kajiya). Джеймс Каджийя ввел в употребление акроним "текстел" (элемент текстуры). Интересен тот факт, что Джеймс Каджийя использовал этот термин в значении, отличном от используемого сейчас: автор определяет его не как любой элемент текстуры, а как "трехмерную карту текстуры, в которой структура поверхности, заданная нормалью, тангенсом и бинормалью, а также параметры освещения, свободно распределены по всему объему". В данной статье метод трассировки лучей (ray tracing), описанный Каджийей, используется применительно к рендерингу шерстяного покрова в режиме реального времени.

В другой, недавно опубликованной работе, “Fake Fur Rendering” (“Рендеринг искусственного меха") Дэна Голдмэна (Dan Goldman) рассмотрены методы автономного рендеринга, использованные компанией Industrial Light and Magic при моделировании шерсти животных в фильме "101 далматинец"*. Алгоритм, описанный в этой статье, показывает более высокую производительность, чем метод моделирования "настоящего меха" (“real fur”), однако он подходит только для рендеринга шерсти животных, достаточно удаленных от камеры. Результаты моделирования по методу Голдмэна вполне удовлетворительны, но они не совсем подходят к современным методам рендеринга в режиме реального времени.

Краткое описание алгоритма

Рис. 2. Двумерная иллюстрация метода создания волосков из отдельных сегментов

На приведенном выше рисунке дана двумерная иллюстрация метода создания волосков из отдельных сегментов. В левой части рисунка изображены увеличенные волоски, которые нужно смоделировать, в середине – волоски, созданные из отдельных сегментов-оболочек, справа – конечный набор оболочек. Утолщения над отрезками, обозначающими оболочки (см. правую часть Рис. 2) соответствуют участкам шерстяного покрова. Для них будут установлены такие свойства, как освещение и затенение, а альфа-канал будет либо непрозрачным, либо полупрозрачным. Участки без шерстяного покрова будут прозрачными (значение альфа-канала будет равно 0).

Метод, описанный в данной статье, основан на создании текстур, оболочек и котуров, описанных в работе Джерома Ленджайла “Рендеринг шерстяного покрова произвольных поверхностей в режиме реального времени”. Вначале мы создадим текстуры для оболочек и контуров с помощью библиотек Intel IPP. Затем создадим контуры и выполним рендеринг изображения в кадровом буфере (пока в кадровом буфере выполняется рендеринг, вершинный шейдер произведет расчет координат вершин оболочек). Метод использования оболочек и контуров в данном случае демонстрирует хорошие результаты при моделировании короткого и прямого меха так как в сочетании с методом трассировки лучей он дает довольно достоверный визуальный результат.

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

  1. создание текстур для оболочек с помощью библиотек Intel® IPP
  2. создание текстур для контуров
  3. создание геометрии контуров
  4. создание геометрии оболочек
  5. рендеринг с использованием полученных компонентов.

Текстуры оболочек

Рис. 3. Пять оболочек, созданных по закону нормального распределения

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

Текстуры контуров

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

Рис. 4. Применение текстур оболочек

На рис. 4 продемонстрирован результат применения текстур оболочек (см. Рис. 2). При моделировании тела не использовались контуры.

Геометрия оболочек

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

Геометрия контуров

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

Run-time

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

Шумовая функция

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

Дальнейшие улучшения

Существует несколько путей развития рендеринга в будущем. Среди них, например, применение алгоритма наложения текстур, описанного в статье Джерома Ленджайла “Рендеринг шерстяного покрова произвольных поверхностей в режиме реального времени”. Или расчет модели шерстяного покрова при влиянии на него каких-либо физических сил, например, ветра. Среди интересных проблем можно также выделить создание вьющегося шерстяного покрова и расчет затенения влажной шерсти.

Дополнительные ресурсы

  • Дж. С. Чонг. Fur Rendering and Dynamics Using Discrete Shells (Рендеринг и динамика шерстяного покрова с использованием отдельных оболочек). Университет Симона Фрейзера: CMPT 461 Окончательный проект.
  • Джером Ленджайл (Jerome Lengyel), Эмиль Прон (Emil Praun), Адам Финкельстайн (Adam Finkelstein) и Хьюго Хоппе (Hugues Hoppe). Real-Time Fur over Arbitrary Surfaces (Рендеринг шерстяного покрова произвольных поверхностей в режиме реального времени). Симпозиум по интерактивной трехмерной графике, 2001 г. Стр. 227-232.
  • Дэн Голдмэн. Fake Fur Rendering (Рендеринг шерстяного покрова методом "искусственного меха"). SIGGRAPH, 1997 г. Стр. 127-134.
  • Джеймс Каджийя и Тимоти Кэй (Timothy Kay). Rendering Fur with Three Dimensional Models (Рендеринг шерстяного покрова трехмерных моделей). ACM SIGGRAPH, 1989 г. Стр. 271-280.
  • Эмиль Прон, Адам Финкельстайн и Хьюго Хоппе. Lapped Textures (Наложение текстур). SIGGRAPH, 2000 г. Стр. 465-470.
  • Библиотека Intel Performance Primitives для архитектур Intel®.

Приложений A. Пример кода для создания текстур оболочек.

HRESULT generateShellTextures()

{
              HRESULT hr;
              IDirect3DDevice9* pd3dDevice = g_pd3dDevice;
WCHAR str[MAX_PATH];
 

              //generate textures
              ippStaticInit();
              IDirect3DSurface9* pSurface;
              D3DLOCKED_RECT lockedRect;
              BYTE* pBData;
              const int w = 200;
              const int h = 200;
              IppiSize roi = {w, h};
              lockedRect.Pitch = w*3;
              Ipp8u bTem[h][w];
              Ipp32u n = 7;

              memset(bTem, 0, w*h);
              ippiAddRandGauss_Direct_8u_C1IR(&bTem[0][0], w, roi, 0, 100, &n);
              //ippiAddRandUniform_Direct_8u_C1IR(&bTem[0][0], w, roi, 0, 256, &n);
pd3dDevice->CreateOffscreenPlainSurface(w, h, 
D3DFMT_A8R8G8B8, D3DPOOL_SCRATCH, &pSurface, NULL);
              pSurface->LockRect(&lockedRect, NULL, D3DLOCK_NOOVERWRITE);
              pBData = (BYTE*)lockedRect.pBits;
          
     for (int j=0; j<h; j++)
              {
                            for (int k=0; k<w; k++)
                            {
                                          if(bTem[j][k]==0)
                                          {
                                                   *pBData++ = 0;
                                                   *pBData++ = 0;
                                                   *pBData++ = 0;
                                                   *pBData++ = 255;

                                          }
                                          else
                                          {
                                                  *pBData++ = g_shellTextureColor[0];
                                                  *pBData++ = g_shellTextureColor[1];
                                                  *pBData++ = g_shellTextureColor[2];
                                                  *pBData++ = g_shellTextureColor[3];
                                          }
                            }
              }
              pSurface->UnlockRect();
              WCHAR sTem1[8];
              WCHAR sTem2[128];
              wcscpy(sTem2, L"Media	est");
              wcscat(sTem2, (WCHAR*)_itow(0, sTem1, 10));
              wcscat(sTem2, L".bmp");
              D3DXSaveSurfaceToFile(sTem2, D3DXIFF_BMP, pSurface, NULL, NULL);

              for (int i=1; i<g_nNumShellLayers; i++)
              {
                            memset(bTem, 0, w*h);
                            ippiAddRandGauss_Direct_8u_C1IR(&bTem[0][0], roi.width, roi, 0, 100, &n);
                            pSurface->LockRect(&lockedRect, NULL, D3DLOCK_NOOVERWRITE);
                            pBData = (BYTE*)lockedRect.pBits;
        
                   for (int j=0; j<h; j++)
                            {
                   		for (int k=0; k<w; k++)
                            	{
                                	if(bTem[j][k]>150)
                                          {
                                          	*pBData++ = 0;
                                          	*pBData++ = 0;
                                          	*pBData++ = 0;
                                          	*pBData++ = 0;
                                          }
                                    else
                                          {
                                            if (pBData[0]==0&&pBData[1]==0&&pBData[2]==0)
                                          		{
                                          			pBData++;
                                           			pBData++;
                                                    pBData++;
                                                    *pBData++ = 0;
                                          		}
                                    		else
                                          		{
                                                    pBData++;
                                                    pBData++;
                                                    pBData++;
                                                    pBData++;
     									  		}
                                          }
                                 }
                            }

                            pSurface->UnlockRect();
                            WCHAR sTem1[8];
                            WCHAR sTem2[128];
                            wcscpy(sTem2, L"Media	est");
                            wcscat(sTem2, (WCHAR*)_itow(i, sTem1, 10));
                            wcscat(sTem2, L".bmp");
                            D3DXSaveSurfaceToFile(sTem2, D3DXIFF_BMP, pSurface, NULL, NULL);
              }
              SAFE_RELEASE(pSurface);
}

Приложение B: Пример кода для создания текстур контуров и оболочек

void generateFinsAndShells()
{
              IDirect3DDevice9* pd3dDevice = g_pd3dDevice;

              generateTextures();

              // Create vertex and index buffers for fins
              pd3dDevice->CreateVertexBuffer(sizeof(finVertex)*g_pMesh->GetNumFaces()*3*(g_nNumFinLayers+1), 0, D3DFVF_XYZ|D3DFVF_TEX1|D3DFVF_TEXCOORDSIZE2(0), D3DPOOL_DEFAULT, &g_pFinsVB, NULL);

              pd3dDevice->CreateIndexBuffer(sizeof(SHORT)*g_pMesh->GetNumFaces()*3*(6*g_nNumFinLayers), 0, D3DFMT_INDEX16, D3DPOOL_DEFAULT, &g_pFinsIB, NULL);
              for (int i=0; i<g_nNumShellLayers; i++)
              {
                            pd3dDevice->CreateVertexBuffer(sizeof(shellVertex)*g_pMesh->GetNumFaces()*3, 0, D3DFVF_XYZ|D3DFVF_NORMAL|D3DFVF_TEX1|D3DFVF_TEXCOORDSIZE2(0), D3DPOOL_DEFAULT, &g_pShellsVB[i], NULL);

                            pd3dDevice->CreateIndexBuffer(sizeof(SHORT)*g_pMesh->GetNumFaces()*3, 0, D3DFMT_INDEX16, D3DPOOL_DEFAULT, &g_pShellsIB[i], NULL);

              }

              // Get vertex buffer from mesh
              // Compute face normals
              // Extrude shells and fins
              SHORT* p_IB;
              struct vertexData
              {
                            D3DXVECTOR3 pos;
                            D3DXVECTOR3 nor;
                            FLOAT tu, tv;
              };

              vertexData* p_VB;

              finVertex* p_finsVB = new finVertex[g_pMesh->GetNumFaces()*3*(g_nNumFinLayers+1)];
              SHORT* p_finsIB = new SHORT[g_pMesh->GetNumFaces()*3*(6*g_nNumFinLayers)];

              shellVertex* p_shellsVB[MAX_SHELLS];
              SHORT* p_shellsIB[MAX_SHELLS];
              for (int i=0; i<g_nNumShellLayers; i++)

              {
                            p_shellsVB[i] = new shellVertex[g_pMesh->GetNumFaces()*3];
                            p_shellsIB[i] = new SHORT[g_pMesh->GetNumFaces()*3];
              }

 

              g_pMesh->LockIndexBuffer(D3DLOCK_READONLY, (void**) &p_IB);
              g_pMesh->LockVertexBuffer(D3DLOCK_NOOVERWRITE, (void**) &p_VB);
              for (int i=0; i<g_pMesh->GetNumFaces()*3; i+=3)
              {
                            // compute face normals
                            D3DXVECTOR3 faceNormal;
                            D3DXVECTOR3 v0, v1;
                            v0.x = p_VB[p_IB[i+1]].pos.x - p_VB[p_IB[i]].pos.x;
                            v0.y = p_VB[p_IB[i+1]].pos.y - p_VB[p_IB[i]].pos.y;
                            v0.z = p_VB[p_IB[i+1]].pos.z - p_VB[p_IB[i]].pos.z;
                            v1.x = p_VB[p_IB[i+2]].pos.x - p_VB[p_IB[i]].pos.x;
                            v1.y = p_VB[p_IB[i+2]].pos.y - p_VB[p_IB[i]].pos.y;
                            v1.z = p_VB[p_IB[i+2]].pos.z - p_VB[p_IB[i]].pos.z;
                            D3DXVec3Cross(&faceNormal, &v0, &v1);
                            D3DXVec3Normalize(&faceNormal, &faceNormal);
                            p_finsVB[i*(g_nNumFinLayers+1)].position = p_VB[p_IB[i]].pos;
                            p_finsVB[i*(g_nNumFinLayers+1)].tu = 0.0;
                            p_finsVB[i*(g_nNumFinLayers+1)].tv = 0.0;
                            p_finsVB[i*(g_nNumFinLayers+1)+1].position = p_VB[p_IB[i+1]].pos;
                            p_finsVB[i*(g_nNumFinLayers+1)+1].tu = 0.0;
                            p_finsVB[i*(g_nNumFinLayers+1)+1].tv = 1.0;
                            p_finsVB[i*(g_nNumFinLayers+1)+2].position = p_VB[p_IB[i+2]].pos;
                            p_finsVB[i*(g_nNumFinLayers+1)+2].tu = 1.0;
                            p_finsVB[i*(g_nNumFinLayers+1)+2].tv = 0.0;

                            for (int j=0; j<g_nNumFinLayers; j++)
                            {
                                          // extrude and store in vertex buffer
p_finsVB[i*(g_nNumFinLayers+1)+(j+1)*3].position = p_VB[p_IB[i]].pos + (float)(j+1)*g_fFinHeight*faceNormal;
                                          p_finsVB[i*(g_nNumFinLayers+1)+(j+1)*3].tu = 0.0;
                                          p_finsVB[i*(g_nNumFinLayers+1)+(j+1)*3].tv = 0.0;
p_finsVB[i*(g_nNumFinLayers+1)+(j+1)*3+1].position = p_VB[p_IB[i+1]].pos + (float)(j+1)*g_fFinHeight*faceNormal;
                                          p_finsVB[i*(g_nNumFinLayers+1)+(j+1)*3+1].tu = 0.0;
                                          p_finsVB[i*(g_nNumFinLayers+1)+(j+1)*3+1].tv = 1.0;
p_finsVB[i*(g_nNumFinLayers+1)+(j+1)*3+2].position = p_VB[p_IB[i+2]].pos + (float)(j+1)*g_fFinHeight*faceNormal;
                                          p_finsVB[i*(g_nNumFinLayers+1)+(j+1)*3+2].tu = 1.0;
                                          p_finsVB[i*(g_nNumFinLayers+1)+(j+1)*3+2].tv = 0.0;

                                          // generate indices in the fins index buffer
                                          to create fins
                                          p_finsIB[i*6*g_nNumFinLayers+(j*18)] = i*(g_nNumFinLayers+1);
                                          p_finsIB[i*6*g_nNumFinLayers+(j*18)+1] = i*(g_nNumFinLayers+1)+1;
                                          p_finsIB[i*6*g_nNumFinLayers+(j*18)+2] = i*(g_nNumFinLayers+1)+(j+1)*3;
                                          p_finsIB[i*6*g_nNumFinLayers+(j*18)+3] = i*(g_nNumFinLayers+1)+(j+1)*3;
                                          p_finsIB[i*6*g_nNumFinLayers+(j*18)+4] = i*(g_nNumFinLayers+1)+1;
                                          p_finsIB[i*6*g_nNumFinLayers+(j*18)+5] = i*(g_nNumFinLayers+1)+(j+1)*3+1;
                                          p_finsIB[i*6*g_nNumFinLayers+(j*18)+6] = i*(g_nNumFinLayers+1)+(j+1)*3+1;
                         				  p_finsIB[i*6*g_nNumFinLayers+(j*18)+7] = i*(g_nNumFinLayers+1)+1;
                                          p_finsIB[i*6*g_nNumFinLayers+(j*18)+8] = i*(g_nNumFinLayers+1)+2;
                                          p_finsIB[i*6*g_nNumFinLayers+(j*18)+9] = i*(g_nNumFinLayers+1)+2;
                                          p_finsIB[i*6*g_nNumFinLayers+(j*18)+10] = i*(g_nNumFinLayers+1)+(j+1)*3+2;
                                          p_finsIB[i*6*g_nNumFinLayers+(j*18)+11] = i*(g_nNumFinLayers+1)+(j+1)*3+1;
                                          p_finsIB[i*6*g_nNumFinLayers+(j*18)+12] = i*(g_nNumFinLayers+1)+2;
                                          p_finsIB[i*6*g_nNumFinLayers+(j*18)+13] = i*(g_nNumFinLayers+1);
                                          p_finsIB[i*6*g_nNumFinLayers+(j*18)+14] = i*(g_nNumFinLayers+1)+(j+1)*3;
                                          p_finsIB[i*6*g_nNumFinLayers+(j*18)+15] = i*(g_nNumFinLayers+1)+(j+1)*3;
                                          p_finsIB[i*6*g_nNumFinLayers+(j*18)+16] = i*(g_nNumFinLayers+1)+(j+1)*3+2;
                                          p_finsIB[i*6*g_nNumFinLayers+(j*18)+17] = i*(g_nNumFinLayers+1)+2;
        }

                            for (int j=0; j<g_nNumShellLayers; j++)
                            {
                                          // extrude and store in vertex buffer
                                          p_shellsVB[j][i].position   = p_VB[p_IB[i]].pos + (float)(j+1)*g_fShellHeight*faceNormal;
                                          p_shellsVB[j][i].normal = faceNormal;
                                          p_shellsVB[j][i].tu = (i+1)%2;
                                          p_shellsVB[j][i].tv = i%2;
                                          p_shellsVB[j][i+1].position = p_VB[p_IB[i+1]].pos + (float)(j+1)*g_fShellHeight*faceNormal;
                                          p_shellsVB[j][i+1].normal = faceNormal;
                                          p_shellsVB[j][i+1].tu = (i+1)%2;
                                          p_shellsVB[j][i+1].tv = (i+1)%2;
                                          p_shellsVB[j][i+2].position = p_VB[p_IB[i+2]].pos + (float)(j+1)*g_fShellHeight*faceNormal;
                      					  p_shellsVB[j][i+2].normal = faceNormal;
                                          p_shellsVB[j][i+2].tu = i%2;
                                          p_shellsVB[j][i+2].tv = (i+1)%2;

                                          // generate indices in the shells index buffer
                                          to create shells
                                          p_shellsIB[j][i] = i;
                                          p_shellsIB[j][i+1] = i+1;
                                          p_shellsIB[j][i+2] = i+2;
                            }
              }

              g_pMesh->UnlockVertexBuffer();
              g_pMesh->UnlockIndexBuffer();

              // copy to vertex and index buffers

              D3DXVECTOR3* p_VBa;
              SHORT* p_IBa;
              if (g_nNumFinLayers>0)
              {
                            g_pFinsVB->Lock(0, 0, (void**) &p_VBa, 0);
                            memcpy(p_VBa, p_finsVB, sizeof(finVertex)*g_pMesh->GetNumFaces()*3*(g_nNumFinLayers+1));
                            g_pFinsVB->Unlock();
                            g_pFinsIB->Lock(0, 0, (void**) &p_IBa, 0);
                            memcpy(p_IBa, p_finsIB, sizeof(SHORT)*g_pMesh->GetNumFaces()*3*2*3*g_nNumFinLayers);
                            g_pFinsIB->Unlock();
              }

              for (int i=0; i<g_nNumShellLayers; i++)
              {
                            g_pShellsVB[i]->Lock(0, 0, (void**) &p_VBa, 0);
                            memcpy(p_VBa, p_shellsVB[i], sizeof(shellVertex)*g_pMesh->GetNumFaces()*3);
                            g_pShellsVB[i]->Unlock();
                            g_pShellsIB[i]->Lock(0, 0, (void**) &p_IBa, 0);
                            memcpy(p_IBa, p_shellsIB[i], sizeof(SHORT)*g_pMesh->GetNumFaces()*3);
                            g_pShellsIB[i]->Unlock();
              }

              delete[] p_finsVB;
              delete[] p

Об авторах

Адам Лэйк (Adam Lake) – ведущий разработчик программного обеспечения группы программного обеспечения и решений корпорации Intel, возглавляющий проект "Современные игровые технологии". Адам Лэйк специализируется на архитектурах и алгоритмах компьютерной графики нового поколения. До работы в Intel он получил степень магистра вычислительной техники Университета штата Северная Каролина (Чэпел Хилл, штат Сев. Каролина), изучал компьютерную графику и системы виртуальной реальности. Перед учебой в университете он работал в Национальной лаборатории в Лос-Аламосе, в группе прикладной и теоретической физики и вычислительных научных методов, где работал над САПР для физиков. Он опубликовал несколько статей, посвященных компьютерной графике, рецензировал статьи для Специальной группы по компьютерной графике Института инженеров по электротехнике и электронике. В свободное время он занимается велосипедным спортом, туризмом, сноубордингом, любит читать и немного умеет водить машину.