Особенности работы с Direct 3D и основные сведения об OpenGL
Direct 3D Retained Mode (RM) как набор интерфейсов для работы с трехмерной графикой. Его использование для загрузки сеток, расположения источников освещения и оживления полученной сцены. Инициализация указателей и устройств, моделирование объектов.
Рубрика | Программирование, компьютеры и кибернетика |
Вид | курс лекций |
Язык | русский |
Дата добавления | 10.01.2014 |
Размер файла | 349,1 K |
Отправить свою хорошую работу в базу знаний просто. Используйте форму, расположенную ниже
Студенты, аспиранты, молодые ученые, использующие базу знаний в своей учебе и работе, будут вам очень благодарны.
Размещено на http://www.allbest.ru
Размещено на http://www.allbest.ru
§1. Абстрактный Direct 3D
Direct 3D Retained Mode (Абстрактный режим) - это набор интерфейсов для работы с трехмерной графикой. С помощью этого набора интерфейсов можно манипулировать сетками (это другое название моделей и объектов), источниками освещения, камерами, а также фреймами. Последние обеспечивают мощный механизм для быстрого построения и манипулирования сценами. Поэтому, Retained Mode (далее просто RM) предназначен для работы непосредственно со сценой, а Непосредственный Режим (Immediate Mode) предназначен для вывода текстурированных треугольников. Используя, RM Вы сможете загрузить сетки, расположить источники освещения и оживить полученную сцену.
RM очень распространенный набор интерфейсов, скорее всего он есть на всех компьютерах с Windows 9x, NT, Me, 2000, так как RM входил в состав DirectX до версии 7. RM не модернизировался фирмой Microsoft после выхода DirectX v6.1, но был в составе DirectX v7, как часть DirectX Media. Предполагалось, его заменит компонент Fahrenheit Scene Graph (FSG) в составе графической библиотеки Fahrenheit. Но этого не произошло. Тем не менее, RM не потерял привлекательности, так как в составе DirectX пока нет другого набора высокоуровневых интерфейсов для работы с трехмерной графикой на уровне сцены.
Описание основных интерфейсов RM.
Надеюсь, Вы знаете, как программировать на Си с помощью Microsoft Visual C++ v4 или выше. Если нет, то Вам будет трудно понять эту статью. Также я предполагаю, что Вы знаете, как работают COM-объекты, так как все интерфейсы RM реализованы на этой основе. Начальные знания по трехмерной графике тоже будут не лишними.
Главный интерфейс RM называется Direct3DRM. Он позволяет создавать другие интерфейсы RM и выполнять ряд сервисных функций. В частности связывать RM с устройствами для вывода графики.
Интерфейс для работы с устройствами Direct3DRMDevice. Устройства - это фактически драйвера видеокарт. Есть два основных типа устройств RM: программные и аппаратные. Первые выполняют трехмерные расчеты на центральном процессоре компьютера, а вторые используют специализированный графический процессор на видеокарте, что обеспечивает несравненно более быстрый вывод графики.
Помимо этого, программные и аппаратные устройства поддерживают две цветовых модели Ramp и RGB. Ramp - обеспечивает только монохромное освещение, RGB - цветное. Поэтому устройства Ramp всегда быстрее, чем RGB. Обычно имеется не менее трех устройств: Ramp, RGB программные устройства и аппаратное устройство.
Интерфейс Direct3DRMWinDevice позволяет RM сообщаться с Windows. Он проделывает «непонятную», но необходимую работу.
Интерфейс Direct3DRMViewport позволяет создавать области просмотра, т. е. по-другому говоря камеры, посредством которых мы, и созерцаем трехмерный мир.
Интерфейс Direct3DRMFrame позволяет работать с фреймами. Фреймы - это точки првязки объектов в пространстве, а не кадры в анимациию. Direct3DRMFrameосновной интерфейс RM. Именно он позволяет создавать сцены и эффективно с ними работать. С помощью фреймов можно связывать группы объектов и легко ими манипулировать. Фрейм позволяет привязать сетку или источник освещения к определенному месту в пространстве. К фрейму можно привязать другие фреймы и перемещая главный фрейм, будут перемещаться и связанные фреймы. Т. е. фреймы позволяют обеспечить прямую кинематику, но, к сожалению, инверсной кинематики фреймы не обеспечивают.
Интерфейс конструктора сеток Direct3DRMMeshBuilder еще один очень важный и полезный интерфейс. Он позволяет загружать и сохранять сетки, задавать параметры визуализации, перемещать и масштабировать сетки. Но, к сожалению, этот интерфейс несколько медлителен, и не годиться для быстродействующих приложений.
Зато интерфейс сеток Direct3DRMMesh предназначен для увеличения быстродействия, но очень не прост в «общении». Он позволяет изменять внешний вид сетки (форму и закраску). Для этого используется группа граней внутри сетки. С помощью этого интерфейса можно создавать сетку непосредственно в приложении и манипулировать ее формой, например, для морфинга.
Сетки состоят из граней. Интерфейс для работы с ними Direct3DRMFace. Это не высокоуровневый интерфейс, он предназначен для «тонкой» работы над сеткой (на уровне вершин граней). Рассмотренные три последних интерфейса позволяют эффективно оперировать с сетками на любом уровне работы с сеткой.
Следующие три интерфейса позволяют «одевать» сетки, иначе бы сетка была просто набором цветных граней. Под «одеждой» я подразумеваю текстуры.
Интерфейс текстуры Direct3DRMTextureпозволяет назначить текстуры на сетку. Текстуры могут быть загружены из BMP или PPM файлов. Они могут находиться в ресурсах программы, в файлах или в памяти. Также этот интерфейс позволяет работать с декалами. Декал - это спрайт, который Вы можете добавить к сцене. С помощью них можно эмулировать дождь, снег, взрывы. Но их видно только с лицевой стороны.
Интерфейс покрытия текстурой Direct3DRMWrap необходим, для того чтобы задать метод наложения текстуры на сетку. Это не очень просто. Но без этого сетка будет выглядеть уродливо. Если Вы разработали модель в программе трехмерного моделирования и конвертировали ее в формат сетки RM (файлы с расширением “x”), то покрытие уже задано, и Вам не надо об этом беспокоиться. Есть три типа покрытия: плоское, сферическое и цилиндрическое. Покрытие назначается на всю сетку, т.е. расположение текстуры на гранях вычисляется автоматически.
Интерфейс материала Direct3DRMMaterial позволяет определить, как свет отражается от объекта и как сетка излучает свет. Т.е. можно имитировать металлические или пластиковые объекты. А также объекты, испускающие свет.
Если не использовать интерфейс Direct3DRMLight Вы ничего не увидите на сцене. Там будет царить ночь. Есть пять типов источников света: рассеянный, точечный, направленный, параллельный и прожектор.
Интерфейс для анимации Direct3DRMAnimation позволяет двигать и вращать объекты на сцене. Этот интерфейс позволяет создавать ключи. В ключах задается перемещение, вращение и масштабирование объекта. Последовательность, основанная на ключах, позволяет создавать анимацию в целом.
Для загрузки подготовленных сцен с анимацией служит интерфейс анимационного набора Direct3DRMAnimationSet. Он поддерживает совокупность анимированных объектов.
RM позволяет эффективно работать с сетками, фреймами, источниками освещения, камерами и анимацией. Данный набор интерфейсов обеспечивает представление трехмерной сцены и позволяет осветить и оживить ее. RM не накладывает ограничений на сцену, Вы свободны в своем творчестве!
Типы данных RM.
Есть несколько типов данных, которые широко используются в RM.
D3DVALUE имеет тип float. Если Вы хотите, чтобы Ваша программа была переносимой на другие платформы (вдруг, это случиться), то используйте макрос D3DVALUE. Например, D3DVALUE(3) будет равно 3.0f.
СтруктураD3DVECTOR определена так:
typedef struct _D3DVECTOR {
union {
D3DVALUE x;
D3DVALUE dvX;
};
union {
D3DVALUE y;
D3DVALUE dvY;
};
union {
D3DVALUE z;
D3DVALUE dvZ;
};
} D3DVECTOR, *LPD3DVECTOR;
Эта структура нужна для представления вектора или точки.
Для представления цвета в RM используется типD3DCOLOR(длина 4 байта). Для приведения в тип D3DCOLOR можно использовать макросы D3DRGB или D3DRGBA.
D3DCOLOR color=D3DRGB(1,1,1);
// Белый цвет
D3DCOLOR color=D3DRGBA(1,1,1,0);
// Белый цвет с нулевой прозрачностью
Для задания размера объекта используется структураD3DRMBOX.
typedef struct _D3DRMBOX
{
D3DVECTOR min, max;
} D3DRMBOX;
Переменные min, max описывают параллелепипед, который ограничивает объект (оболочка объекта).
Большинство функций RM возвращает в качестве кода ошибки HRESULT. Если код равен константе D3DRM_OK, то вызов функции был успешен. Иначе, вы получите код ошибки.
Инициализация Direct3D Retained Mode.
Для использования RM необходимо провести ряд подготовительных операций. В зависимости от конкретных требований количество и последовательность этих операций может быть разной. Но в основном необходимо сделать все тоже, что и при использовании любой другой графической библиотеки: нужно инициализировать графический режим и выполнить ряд настроек.
Чтобы максимально упростить пример приложения с RM я написал его без применения классов, но на C++. Я не стал создавать пример с помощью MFC или другой библиотеки классов. Если это необходимо, то Вы сможете сделать это сами. Я также не буду обсуждать текст программы непосредственно связанный с работой Windows (в частности, обработку сообщений). Только укажу, в каком обработчике сообщений надо вызывать ту или иную функцию примера. Я постарался максимально упростить сам пример, но все же не в ущерб основным понятиям.
Вначале приведем объявления переменных, которые мы будем использовать в примере. Пока без обсуждения.
#include <windows.h>
#include <windowsx.h>
#include "ddraw.h"
#include "d3drm.h"
#include "d3drmwin.h"
BOOL CreateScene();
void DestroyScene();
void ScaleMesh(LP DIRECT3DRMMESHBUILDER mesh, D3DVALUE dim);
int yes_full, yes_render, yes_hardware;
HWND hwnd;
LP DIRECT3DRM rm=0;
LP DIRECTDRAWCLIPPER clip=0;
LP DIRECT3DRMDEVICE device=0;
LP DIRECT3DRMFRAME scene=0;
LP DIRECTDRAW ddraw=0;
LP DIRECTDRAWSURFACE primsurf=0;
LP DIRECTDRAWSURFACE backsurf=0;
LP DIRECTDRAWSURFACE zbufsurf=0;
LP DIRECT3DRMFRAME camera;
LP DIRECT3DRMVIEWPORT viewport;
LP DIRECT3DRMMESHBUILDER meshbuilder;
D3DDEVICEDESC DrvDesc;
RECT rect_win;
int bpp;
GUID* ptr_guid=0;
Функцию инициализации RM лучше всего вызывать, после того как создано окно. Т. е. после получения сообщения WM_CREATE функцией обработки сообщений окна. Функция инициализации RM должна быть вызвана, прежде всего. У нее шесть параметров.
BOOL InitD3DRM
(HWND hWnd, int full_win, int wid, int hei, int bit, int yes_hw)
{
hwnd=hWnd;
yes_full=full_win;
yes_hardware=yes_hw;
hWnd - указатель на окно, если full_win равно единице, то предполагается использовать RM на полноэкранном режиме, иначе в окне на рабочем столе. Wid, hei,bit задают ширину, высоту и цветность полноэкранного режима. yes_hw равно 1, значит надо попытаться использовать аппаратное устройство, иначе программное.
RM можно использовать либо в окне на рабочем столе Windows или на полный экран. Причем, видеорежим полного экрана может и не совпадать с видеорежимом рабочего стола. В случае окна wid, hei, bit не имеют смысла, так как окно всегда располагается на рабочем столе и его размер задается при инициализации. И по этому нам придется запросить их отдельно:
GetClientRect(hwnd,&rect_win);
HDC hdc=GetDC(hwnd);
bpp=GetDeviceCaps(hdc,BITSPIXEL);
ReleaseDC(hwnd,hdc);
Затем мы находим указатель на устройстве, которое поддерживает или аппаратную (yes_hw равен 1), или программную (yes_hw равен 0) работу. Обсудим эту функцию позже.
ptr_guid=FindBestGUID (yes_full==1?bit:bpp, yes_hardware, &DrvDesc);
if (ptr_guid==0) yes_hardware=0;
Создадим главный интерфейс Direct3DRM.
HRESULT rc;
rc= Direct3DRMCreate(&rm);
if (rc!=D3DRM_OK) return FALSE;
Порядок выполнения нашего кода зависит от переменной yes_full. Если она равна нулю, значит надо инициализировать RM в окне.
if (yes_full==0)
{
rc= DirectDrawCreateClipper(0,&clip,NULL);
if (rc!=D3DRM_OK) return FALSE;
Здесь мы инициализировали переменную clip - указатель на интерфейс DirectDrawClipper. Так как мы работаем в окне, то необходимо использовать область отсечения для управления обновлением в окне. Предположим мы ввели сцену в окне и сверху на это изображение наложили несколько окон, мы вправе ожидать, что RM не будет затирать содержимое чужих окон. Вот это и будет обеспечивать интерфейс DirectDrawClipper, который ведает сложными операциями отсечения частей окна, где не должен быть идти графический вывод.
clip->SetHWnd(NULL,hwnd);
if (rc!=DD_OK) return FALSE;
Здесь мы назначаем объекту отсечения окно, в котором он и будет работать.
Создадим устройство RM.
rc=rm->CreateDeviceFromClipper(clip, ptr_guid, rect_win.right, rect_win.bottom, &device);
if (rc!=D3DRM_OK)
{
if (clip) { clip->Release(); clip=0; }
if (rm) { rm->Release(); rm=0; }
return FALSE;
}
}
Здесь инициализируется указатель на устройство device. Собственно говоря, все! Мы инициализировали RM для работы в окне! Далее надо выполнить ряд настроек и можно создавать сцену.
Инициализировать RM для работы на полный экран значительно сложнее.
Вначале погасим курсор мыши.
else
{
ShowCursor(FALSE);
Затем инициализируем указатель на интерфейс DirectDraw. Здесь уже не нужен объект отсечения.
DirectDrawCreate(0,&ddraw,0);
Переключим видеорежим.
ddraw->SetCooperativeLevel(hwnd, DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN);
ddraw->SetDisplayMode(wid, hei, bit);
rect_win.right=wid;
rect_win.bottom=hei;
Но это далеко не все! Надо создать три поверхности! Первичную, вторичную и Z-буфер. Спросите, зачем это?! Дело в том, что полноэкранное приложение игнорирует рабочий стол Windows и поэтому мы должны управлять видеоадаптером сами. В награду Вам будет плавная анимация, так как, используя полноэкранное приложение, Вы можете подготовить сцену во вторичной поверхности, а затем сделать ее первичной (выполнить переключение поверхности). Тем самым поэтапная прорисовка сцены не будет видна зрителю.
Создадим первичную поверхность.
DDSURFACEDESC desc;
desc.dwSize = sizeof(desc);
desc.dwFlags = DDSD_BACKBUFFERCOUNT | DDSD_CAPS;
desc.dwBackBufferCount = 1;
desc.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE | DDSCAPS_3DDEVICE |
DDSCAPS_FLIP | DDSCAPS_COMPLEX;
ddraw->CreateSurface(&desc,&primsurf,0);
Затем добавим к ней вторичную.
DDSCAPS ddscaps;
ddscaps.dwCaps = DDSCAPS_BACKBUFFER;
primsurf->GetAttachedSurface(&ddscaps,&backsurf);
Теперь создадим Z-буфер.
memset(&desc,0,sizeof(desc));
desc.dwSize = sizeof(DDSURFACEDESC);
desc.dwFlags = DDSD_WIDTH | DDSD_HEIGHT
| DDSD_CAPS | DDSD_ZBUFFERBITDEPTH;
desc.dwWidth = wid;
desc.dwHeight = hei;
int bit_z;
f (yes_hardware)
{
int devDepth = DrvDesc.dwDeviceZBufferBitDepth;
if (devDepth & DDBD_32)
bit_z = 32;
else if (devDepth & DDBD_24)
bit_z = 24;
else if (devDepth & DDBD_16)
bit_z = 16;
else if (devDepth & DDBD_8)
bit_z = 8;
}
else bit_z=16;
desc.dwZBufferBitDepth = bit_z;
esc.ddsCaps.dwCaps = DDSCAPS_ZBUFFER;
if (yes_hardware)
desc.ddsCaps.dwCaps |= DDSCAPS_VIDEOMEMORY;
else
desc.ddsCaps.dwCaps |= DDSCAPS_SYSTEMMEMORY;
ddraw->CreateSurface(&desc,&zbufsurf,0);
backsurf->AddAttachedSurface(zbufsurf);
Помимо настроек самого Z-буфера, мы использовали настройки устройства (DrvDesc), а именно значение которое определяет глубину Z-буфера. Как правило, программные устройства поддерживают любую глубину Z-буфера и 8 и 16 и 24 и 32 разрядную. Но в случае аппаратного устройства это не всегда так. Надо четко определить максимальную разрядность и использовать именно ее, иначе при выборе несуществующей разрядности Z-буфера RM невозможно будет инициализировать.
Кроме этого при использовании аппаратного устройства можно использовать аппаратный Z-буфер, а не в основной памяти компьютера.
Тем самым все готово для инициализации устройства RM.
rc=rm->CreateDeviceFromSurface(ptr_guid,ddraw,backsurf,&device);
if (rc!=D3DRM_OK)
{
ShowCursor(TRUE);
if (zbufsurf) { zbufsurf->Release(); zbufsurf=0; }
if (backsurf) { backsurf->Release(); backsurf=0; }
if (primsurf) { primsurf->Release(); primsurf=0; }
if (ddraw) { ddraw->Release(); ddraw=0; }
if (rm) { rm->Release(); rm=0; }
return FALSE;
}
}
Мы создаем устройство для вторичной поверхности (т. е. вывод будет идти на вторичную поверхность) и инициализируем device. Довольно сложно? Вы правы, я видел много разных вариантов подобного кода, и они не всегда работали корректно, в основном по причине неправильного выбора глубины Z-буфера.
Теперь настроим наше устройство, которое и будет визуализировать сцену.
device->SetQuality(D3DRMRENDER_GOURAUD);
Установим режим закрашивания (освещения) по методу Гуро. RM также поддерживает режимы закрашивания каркаса (только вершины без граней), неосвещенный метод, равномерная закраска. Закраска по методу Фонга поддерживается, как правило, только программными устройствами.
Далее следует настройка устройства и Direct3DRM в зависимости от цветности.
switch (yes_full==1?bit:bpp)
{
case 16:
device->SetShades(32);
rm->SetDefaultTextureColors(64);
rm->SetDefaultTextureShades(32);
device->SetDither(FALSE);
break;
case 24:
case 32:
device->SetShades(256);
rm->SetDefaultTextureColors(64);
rm->SetDefaultTextureShades(256);
device->SetDither(FALSE);
break;
}
Как Вы видите, я не предполагаю, что Вы будете использовать 8-битные палитровые режимы. Так как они видимо устарели. Так же я не обсуждаю эти установки, т. к. не играют большой роли. Таким образом, устройство RM создано, настроено, и можно создавать сцену.
Сначала создадим головной фрейм.
rc=rm->CreateFrame(NULL,&scene);
if (rc!=D3DRM_OK) return FALSE;
Это простой, но крайне важный шаг. Это центр нашего мира! Без этого шага наше приложение работать не будет. К головному фрейму привязываются все объекты на сцене. Головных фреймов может быть несколько, но в этом нет необходимости.
Теперь создадим сцену.
rc=CreateScene();
if (rc!=TRUE) return FALSE;
yes_render=1;
return TRUE;
}
А теперь вернемся к функции FindBestGUID.
#define MAX_DRV 5
struct {
GUID Guid;
char Name[50];
char About[50];
BOOL isHardware;
D3DDEVICEDESC Desc;
} DrvInfo[MAX_DRV];
int ptr_drv=0;
static DWORD BPPToDDBD(int bpp)
{
switch(bpp)
{
case 1: return DDBD_1; case 2: return DDBD_2;
case 4: return DDBD_4; case 8: return DDBD_8;
case 16: return DDBD_16; case 24: return DDBD_24;
case 32: return DDBD_32; default: return 0;
}
}
static HRESULT WINAPI enumDeviceFunc(
LPGUID lpGuid, LPSTR lpDeviceDescription,
LPSTR lpDeviceName, LPD3DDEVICEDESC lpHWDesc,
LPD3DDEVICEDESC lpHELDesc, LPVOID lpContext)
{
if (ptr_drv>=MAX_DRV) return D3DENUMRET_CANCEL;
memcpy(&DrvInfo[ptr_drv].Guid, lpGuid, sizeof(GUID));
lstrcpy(DrvInfo[ptr_drv].About, lpDeviceDescription);
lstrcpy(DrvInfo[ptr_drv].Name, lpDeviceName);
if (lpHWDesc->dcmColorModel)
{
DrvInfo[ptr_drv].isHardware=1;
memcpy(&DrvInfo[ptr_drv].Desc, lpHWDesc,sizeof(D3DDEVICEDESC));
}
else
{
DrvInfo[ptr_drv].isHardware=0;
memcpy(&DrvInfo[ptr_drv].Desc,lpHELDesc,sizeof(D3DDEVICEDESC));
}
ptr_drv++;
return D3DENUMRET_OK;
}
GUID* FindBestGUID(int ptr_bpp, int hw, LPD3DDEVICEDESC lpDesc)
{
GUID* lpguid=0;
LP DIRECTDRAW ddraw;
LP DIRECT3D d3d;
HRESULT rc;
int i;
rc = DirectDrawCreate(0, &ddraw, 0);
if (rc!=DD_OK)
return lpguid;
rc = ddraw->QueryInterface(IID_I Direct3D, (void**)&d3d);
if (rc != D3DRM_OK)
return lpguid;
rc=d3d->EnumDevices(&enumDeviceFunc, 0);
if (rc != D3D_OK)
return lpguid;
d3d->Release();
ddraw->Release();
for (i=0; i<ptr_drv; i++)
{
if (DrvInfo[i].isHardware==hw)
if (DrvInfo[i].Desc.dwDeviceRenderBitDepth & BPPToDDBD(ptr_bpp))
if (DrvInfo[i].Desc.dcmColorModel==D3DCOLOR_RGB)
{
lpDesc=&DrvInfo[i].Desc;
return &DrvInfo[i].Guid;
}
}
return lpguid;
}
Я привел весь код сразу, в надежде, что Вы сами во всем разберетесь (если не поймете, то ничего плохого в этом нет - это вспомогательный код). Отмечу только, что здесь используется функция перечисления устройств Direct3D. Именно их использует RM внутри себя для непосредственной визуализации полигонов. Все данные, полученные при перечислении, сохраняются в массив структур DrvInfo. Затем мы выбираем наилучшее устройство. Оно должно быть или аппаратным или программным устройством, поддерживать необходимую цветность, и цветное освещение.
Повторюсь, обычно всего три устройства, но бывает и четвертое устройство от производителя видеокарты, и раньше встречалось пятое устройство с поддержкой технологии MMX. Оно хоть и наиболее быстрое, но не всегда поддерживает все возможности RM. За этим надо следить с помощью описания Desc.
Мне кажется, что инициализация работы самая сложная и рутинная часть работы с RM. Если я помог Вам разобраться с этим, то я выполнил свою задачу.
Следующие три функции необходимы для корректной работы RM в окне. После получения сообщения WM_ACTIVATE от Windows необходимо известить об этом RM.
void ActivateD3DRM(WPARAM wParam)
{
if (yes_render==0) return;
if (yes_full==1) return;
LP DIRECT3DRMWINDEVICE win_dev;
if (device==0) return;
if (device->QueryInterface(IID_I Direct3DRMWinDevice,
void **)&win_dev)!=D3DRM_OK) return;
win_dev->HandleActivate((WORD)wParam);
win_dev->Release();
}
Вначале надо запросить интерфейс Direct3DRMWinDevice и затем вызвать функцию его HandleActivate.
Посмотрим на функцию извещения RM о получении сообщения WM_PAINT.
void PaintD3DRM()
{
if (yes_render==0) return;
if (yes_full==1) return;
HDC hdc=GetDC(hwnd);
int bpp_ptr=GetDeviceCaps(hdc,BITSPIXEL);
ReleaseDC(hwnd,hdc);
if (bpp!=bpp_ptr)
{
DestroyD3DRM();
InitD3DRM(hwnd,yes_full,0,0,0,yes_hardware);
}
RECT rect;
PAINTSTRUCT ps;
LP DIRECT3DRMWINDEVICE win_dev;
if (GetUpdateRect(hwnd,&rect,FALSE)==FALSE) return;
BeginPaint(hwnd,&ps);
if (device->QueryInterface(IID_I Direct3DRMWinDevice,(void **)&win_dev)!=D3DRM_OK)
return;
win_dev->HandlePaint(ps.hdc);
win_dev->Release();
EndPaint(hwnd,&ps);
}
В этой функции надо повторно инициализировать RM, если изменена текущая цветность. Далее запрашивается интерфейс Direct3DRMWinDevice и вызывается его функция HandlePaint.
Если изменились размеры окна, то об этом также необходимо известить RM.
void SizeD3DRM(WPARAM wParam,LPARAM lParam)
{
if (yes_render==0) return;
if (yes_full==1) return;
int w = LOWORD(lParam);
int h = HIWORD(lParam);
if ((w==rect_win.right) && (h==rect_win.bottom)) return;
DestroyD3DRM();
InitD3DRM(hwnd,yes_full,0,0,0,yes_hardware);
}
Но это извещение носит несколько странный характер! Мы вынуждены повторно инициализировать RM?! Что делать - по-другому никак нельзя.
Теперь рассмотрим, как надо производить сброс всех интерфейсов.
void DestroyD3DRM()
{
if (yes_render==0) return;
yes_render=0;
DestroyScene();
if (scene) { scene->Release(); scene=0; }
if (yes_full==1)
{
ShowCursor(TRUE);
if (device) { device->Release(); device=0; }
if (zbufsurf) { zbufsurf->Release(); zbufsurf=0; }
if (backsurf) { backsurf->Release(); backsurf=0; }
if (primsurf) { primsurf->Release(); primsurf=0; }
if (ddraw) { ddraw->Release(); ddraw=0; }
if (rm) { rm->Release(); rm=0; }
}
else
{
if (device) { device->Release(); device=0; }
if (clip) { clip->Release(); clip=0; }
if (rm) { rm->Release(); rm=0; }
}
}
При этом если приложение работало в полноэкранном режиме, то происходит возврат в видеорежим рабочего стола.
Все эти рассмотренные функции не делают визуализации, т. е. после их работы на экране ничего нет. Необходима функция визуализации.
void RenderD3DRM()
{
if (yes_render==0) return;
if (yes_full==0)
{
rm->Tick(D3DVALUE(1));
}
else
{
if (primsurf->IsLost() == DDERR_SURFACELOST)
primsurf->Restore();
scene->Move(D3DVALUE(1.0));
viewport->Clear();
viewport->ForceUpdate(0,0,rect_win.right-1,rect_win.bottom-1);
viewport->Render(scene);
evice->Update();
primsurf->Flip(0,DDFLIP_WAIT);
}
}
Функцию Render необходимо вызывать максимально часто. Обычно это делается в цикле обработки сообщений, когда нет сообщений, подлежащих обработке. Например, в MFC вызов Render можно сделать в функции OnIdle.
Что же делает функция Render? Вначале рассмотрим, как осуществляется визуализация в режиме окна. Вызывается только одна функция Tick. С помощью этой функции производится передвижение всех анимированных объектов (функция Move), очистка экрана (Clear), собственно визуализация (Render) и обновление устройства (Update). Таким образом, если для полноэкранного режима приходиться вызывать все четыре функции отдельно, то для оконного режима хватило только одной.
У функции Tick один аргумент и тот же аргумент у функции Move. Он управляет скоростью анимации. Если задать ему значение 0.5, то, например, если задано вращение у какого-то объекта, он будет вращаться в два раза медленнее, чем при значении 1.0. Если значение скорости анимации 2.0, то в два раза быстрее. Тем самым можно добиться одинаковой скорости анимации на разных компьютерах. Т.е. на медленных компьютерах надо увеличить значение скорости анимации, на быстрых компьютерах уменьшить, и тогда время, за которое происходит анимация, будет постоянным. Но на практике все сложнее. Фактически, не стоит менять это значение 1.0 на другое. Особенно не стоит подстраивать скорость анимации на лету (вычисляя как надо уменьшить или увеличить скорость анимации в зависимости от скорости выполнения программы), т.к. анимация уже не будет плавной. Она то будет очень плавной, то отдельные фазы движений будут проскакивать. Самое простое решением исходить из скорости обновления экрана. Выставить скорость анимации в 1.0 и ограничить скорость обновления экрана, например, 75 раз в секунду.
Вернемся к функции Render и посмотрим, что делается, когда приложение работает в полноэкранном режиме.
Необходимо восстановить первичную поверхность, если она потеряна (Restore). Отмечалось выше, надо выполнить анимацию (Move) и очистку экрана (Clear). Кроме этого, укажем устройству RM, что надо обновить все изображение в области просмотра (ForceUpdate). Визуализируем сцену (Render), обновим вторичную поверхность (Flip). Теперь изображение создано на вторичной поверхности, и надо только переключить первичную поверхность, чтобы увидеть его. По-моему мнению, все это могла сделать функция Tick, но она не делает этого.
И сейчас мы перейдем к рассмотрению основной функции нашего примера CreateScene. С помощью этой функции будет загружена сетка, мы заставим ее вращаться, и осветим ее коричневым цветом. До этого момента мы только и делали, что готовились к созданию сцены.
BOOL CreateScene()
{
LPCSTR searchpath = "C:\\mesh";
rm->AddSearchPath(searchpath);
Зададим директорию, где будет находиться наша сетка. Но она может находиться и в текущей директории.
HRESULT rc;
rm->CreateMeshBuilder(&meshbuilder);
rc=meshbuilder>Load("test.x",NULL,D3DRMLOAD_FROMFILE,NULL,NULL);
if (rc!=D3DRM_OK) return FALSE;
ScaleMesh(meshbuilder,D3DVALUE(25));
Создадим сетку (CreateMeshBuilder), с помощью интерфейса конструктора сеток загрузим ее из файла (Load). После этого мы масштабируем ее (ScaleMesh). Как видите это довольно просто. Масштабирование сетки необходимо, но необязательно. Просто разные сетки могут иметь разные размеры, и соответственно после загрузки и отображения, они могут быть или слишком маленькими, или слишком большими.
Недостаточно только загрузить сетку, ее не будет видно на экране. Необходимо для нее создать фрейм. Так как все объекты на экране даже камера должны иметь свой фрейм. Все на сцене RM происходит посредством фреймов.
LP DIRECT3DRMFRAME meshframe;
rm->CreateFrame(scene,&meshframe);
Фрейм meshframe создается относительно фрейма scene. Как указано выше фрейм scene был создан как корневой фрейм сцены. Можно создать несколько корневых фреймов. Для этого надо в первый аргумент функции CreateFrame поместить NULL. Но все корневые фреймы будут находиться в начале координат.
Здесь мы относительно фрейма scene сделали фрейм meshframe. Он будет, по умолчанию, находиться в местоположении фрейма scene, т.е. сетка будет в центре сцены. Если конечно центр сетки, совпадает с его геометрическим центром.
Теперь зададим вращение сетки.
meshframe->SetRotation(scene,
D3DVALUE(0), D3DVALUE(1), D3DVALUE(0),
D3DVALUE(.1));
Вращение задается фрейму, а не сетке! Если вращается фрейм, то вращается и сетка. Если, например, задать вращение корневого фрейма, то будет вращаться вся сцена. Первый аргумент функции SetRotation указатель scene, т.е. meshframe дочерний фрейм по отношению к фрейму scene. Затем, три аргумента задают вектор относительно, которого происходит вращение (x=0, y=1, z=0). Этот вектор направлен вверх, и вращение будет происходить вокруг оси Y. Пятый аргумент задает угол вращения 0.1 радиан, т.е. при каждом обновлении экрана (вызов функции Move) сетка повернется на 0.1 радиан.
meshframe->AddVisual(meshbuilder);
meshframe->Release();meshframe=0;
Вызовом функции AddVisual мы привяжем сетку к фрейму (сделаем его видимым). Очень важно это делать только после всех настроек фрейма и сетки. Так как указатель meshframe нам больше не нужен, то освободим его. Но он не пропадет - указатель на него храниться в головном фрейме.
LP DIRECT3DRMLIGHT slight;
rm->CreateLightRGB(D3DRMLIGHT_SPOT,
D3DVALUE(1.0),D3DVALUE(0.7),D3DVALUE(0.5),&slight);
Для того чтобы на сцене появился свет, создадим прожектор (D3DRMLIGHT_SPOT). Создадим цветной, не монохромный свет. Три значения задают коричневый цвет источника света. В результате получим указатель slight. Теперь создадим фрейм для него.
LP DIRECT3DRMFRAME slightframe;
rm->CreateFrame(scene,&slightframe);
Сдвинем фрейм источника света.
До сих пор мы не говорили о том, какая система координат используется в RM, настало время это выяснить: левосторонняя система координат. В этой системе значения по оси Z увеличиваются по мере удаления от зрителя и уменьшаются по мере приближения к нему. Оси X и Y ведут себя обычным образом.
slightframe->SetPosition(scene, D3DVALUE(0.),D3DVALUE(0.),D3DVALUE(-100.));
Мы сдвигаем источник света далеко за камеру. При этом не смещаем его ни вверх, ни вниз, ни влево, ни вправо.
slightframe->AddLight(slight);
slightframe->Release();slightframe=0;
slight->Release();slight=0;
Но в любом случае надо присоединить прожектор к фрейму и освободить указатели на прожектор и его фрейм.
Последнее, что нам осталось сделать создать камеру. Камеру можно было создать и значительно раньше, у нее нет привязок к объектам сцены.
rm->CreateFrame(scene,&camera);
camera>SetPosition(scene,D3DVALUE(0),D3DVALUE(30),D3DVALUE(-50));
Создадим фрейм (CreateFrame) и зададим ему позицию (SetPosition). “Оттащим” камеру подальше от центра координат и вверх, чтобы видеть весь объект.
camera->SetOrientation(scene,
D3DVALUE(0.),D3DVALUE(-0.7),D3DVALUE(1.),
D3DVALUE(0.),D3DVALUE(1.),D3DVALUE(0.));
Зададим камере ориентацию. Для этого надо задать два вектора. Один вектор лицевой, другой головной. Фактически, все это очень похоже на голову. Представьте себе, что Вы смотрите на сцену. При этом нос будет ассоциироваться с лицевым вектором, а макушка с головным. Тогда вы сразу поймете, что лицевой вектор направлен вдоль оси Z от зрителя, но при этом немного смещен вниз (Вы как бы опустили нос и он смотрит на точку с координатами x=0, y=-0.7, z=1), а головной строго вверх. Т.е. камера будет поднята вверх, и мы будем смотреть на объект сверху вниз.
rm->CreateViewport(device,camera,0,0,
device->GetWidth(),device->GetHeight(),&viewport);
return TRUE;
}
Помимо этого надо создать область просмотра. Для ее инициализации надо использовать устройство (device) и фрейм камеры. В результате мы получаем указатель viewport.
Мы указали все, что хотели. Дальше сетка будет вращаться, свет освещать ее и камера наблюдать за этим сверху. Пока вызывается функция Render.
В завершение приведем вспомогательную функцию для масштабирования.
void ScaleMesh(LP DIRECT3DRMMESHBUILDER mesh, D3DVALUE dim)
{
D3DRMBOX box;
mesh->GetBox(&box);
D3DVALUE sizex = box.max.x - box.min.x;
D3DVALUE sizey = box.max.y - box.min.y;
D3DVALUE sizez = box.max.z - box.min.z;
D3DVALUE largedim = D3DVALUE(0);
if (sizex>largedim) largedim=sizex;
if (sizey>largedim) largedim=sizey;
if (sizez>largedim) largedim=sizez;
D3DVALUE scalefactor = dim/largedim;
mesh->Scale(scalefactor,scalefactor,scalefactor);
}
И функцию для уничтожения сцены.
void DestroyScene()
{
if (camera) camera->Release();camera=0;
if (viewport) viewport->Release();viewport=0;
if (meshbuilder) meshbuilder->Release();meshbuilder=0;
}
Теперь у Вас есть весь код примера инициализации RM и построения простейшей сцены. Нет только код создания окна и обработки сообщений. Его можно найти по адресу в Интернете:rm01.zip.
Если у Вас нет DirectX Media (SDK или Runtime), то по этому адресу его можно найти: DirectX Media
На мой взгляд, приведенный пример можно существенно улучшить. Во-первых, в нем используются устаревшие интерфейсы RM, можно использовать более свежие. Это позволит использовать, в дальнейшем при работе, все возможности RM. Во-вторых, можно провести инициализацию устройства RM в окне также с помощью поверхности. Создать первичную поверхность окна, и без привязки к ней внеэкранную поверхность. При этом вывод графики направлять на внеэкранную поверхность, а потом копировать результат на поверхность окна. Это позволит реализовать и в окне плавную анимацию.
В завершение не большое замечание: для отладки полноэкранных приложений лучше всего использовать видеорежим, совпадающий с видеорежимом рабочего стола Windows.
§2. Моделирование объектов с помощью Direct3D Retained Mode
Мы рассмотрели порядок инициализации RM, а также функции для обеспечения визуализации трехмерной сцены с помощью RM. В этой статье будут рассмотрены методы создания простейших объектов с помощью интерфейса MeshBuilder (Построитель Сетки).
Но вначале об изменениях, которые были внесены в код примера первой статьи. Во-первых, перевод на новые интерфейсные методы RM, которые соответствуют версии DirectX 7.0. Во-вторых, реализация движения камерой, для просмотра сцены. Кроме этого, в пример добавлен выбор устройств, видеорежимов. Все эти сервисные функции не будут рассмотрены в этой статье, так как они принципиально не отличаются от предыдущей реализации.
Исходный код примера к этой статье можно найти по адресу:rm02.zip. Исходный код примера Вам будет необходим, так как полностью в статье он не приводится.
Непосредственно сам демонстрационный пример создается в функции CreateScene (файл scene.cpp):
// Создание сцены
BOOL CreateScene()
{
static int first=0;
if (first==0)
{
AddPath();
CreateLight();
SetCamera();
if (CreateBoard()==FALSE) return FALSE;
first=1;
}
rm_info.rm->CreateFrame(rm_info.scene, &main_frame);
switch (n_scene)
{
case 0: if (DemoCubes()==FALSE) return FALSE; break;
case 1: if (DemoShapesRotate()==FALSE) return FALSE; break;
case 2: if (DemoMaterial()==FALSE) return FALSE; break;
case 3: if (DemoTexture()==FALSE) return FALSE; break;
}
return TRUE;
}
Вначале добавляем пути для поиска файлов (AddPath), затем создаем свет на сцене (CreateLight), камеру (SetCamera) и пол похожий на шахматную доску (CreateBoard). Эти функции выполняются всего один раз при старте приложения. Затем создается фрейм main_frame. Это главный фрейм всех демонстрационных сцен примера, относительно него и будут добавляться объекты. Пример позволяет создавать несколько демонстрационных сцен и переключать их с помощью переменной n_scene. Смена номера сцены осуществляется клавишами `1', `2', `3', `4'.
В зависимости от номера n_scene создается соответствующая сцена. В этой статье будет рассмотрена сцена под номером 0 (выбор клавишей `1'), в которой иллюстрировано создание простейших кубов (функция DemoCubes).
Для уничтожения сцены вызывается функция:
// Удаление сцены
void DestroyScene()
{
rm_info.scene->DeleteVisual(main_frame);
rm_info.scene->DeleteChild(main_frame);
DD_RELEASE(main_frame);
}
Вначале удаляются все визуальные объекты, связанные с main_frame, затем уничтожается main_frame, как дочерний фрейм главного фрейма rm_info.scene. Структура rm_info содержит указатели на основные интерфейсы созданные при инициализации RM, а также часть вспомогательных переменных (полное описание структуры приведено в файле rm.h).
После удаления main_frame, со сцены пропадают все созданные объекты за исключением света, камеры и шахматной доски, так как они привязаны к фрейму rm_info.scene. Это в дальнейшем и позволяет менять сцену без повторного создания света, камеры и пола.
В первой демонстрационной сцене создаются три куба и одна плоская четырехгранная грань. При запуске примера камера (и зритель соответственно) сдвинута относительно центра координат на -50 единиц по оси Z. Вы можете передвигать камеру с помощью клавиш управления курсором и менять наклон камеры с помощью мыши, но везде в статье считается, что камера находится в первоначальном положении. Напомню, что в RM ось Z идет от зрителя, ось Y вверх, ось X вправо (положительные части осей).
Рис. 1. Сцена с кубами
Интерфейс Построителя Сетки позволяет моделировать сетки только из полигонов. В его составе нет функций для работы с NURBS или с поверхностями разделения. При полигональнальном моделировании объекты создаются из вершин (точки в пространстве), которые объединяются в грани. Грань это и есть полигон, т.е. грани непосредственно отображаются на экране. Грань помимо вершин имеет ребра, это отрезки соединяющие вершины.
Рис. 2. Грани, ребра и вершины
Каждая грань имеет свой цвет или свою текстуру, иначе ее не будет видно. Вершина также может иметь свой цвет и к вершине можно назначить точку на текстуре. Цвет и текстура задают окраску грани, и именно поэтому мы видим ее на экране. Кроме этого, на каждую грань и каждую вершину можно назначить нормаль, которая также влияет на закраску грани и объекта в целом.
Данные о закраски граней используются по-разному в зависимости от метода закраски объекта, режима освещения и режима заполнения. По умолчанию, в примере включена закраска по методу Гуро (при этом вычисляется цвет всех точек текстуры отдельно), включен свет и сплошное заполнение.
Довольно сложно вначале разобраться со всеми этими режимами и их комбинациями, по этому для простоты используется наиболее часто используемая комбинация всевозможных режимов.
Перейдем непосредственно к моделированию. Создадим простейший куб из 6 граней (сторон). Это будет красный куб в середине сцены. Все функции для моделирования находятся в файле cubes.cpp.
// Создание демонстрационных кубов
BOOL DemoCubes()
{
LP DIRECT3DRMFRAME3 frame=0;
LP DIRECT3DRMMESHBUILDER3 builder_faces=0;
LP DIRECT3DRMMESHBUILDER3 builder_triangles=0;
LP DIRECT3DRMMESHBUILDER3 builder_clone=0;
LP DIRECT3DRMMESH mesh=0;
HRESULT rc;
// Создание куба из граней
// Фрейм
rc=rm_info.rm->CreateFrame(main_frame, &frame);
DD_CHK(rc,_gError);
// Позиция
frame->SetPosition(main_frame, 0.0f, 0.0f, 0.0f);
// Построитель сетки
rc=rm_info.rm->CreateMeshBuilder(&builder_faces);
DD_CHK(rc,_gError);
// Создание
build_cube_faces(builder_faces,DEGREES_0);
// Красный цвет
builder_faces->SetColor(RGBA_MAKE(255,0,0,255));
// Масштабирование
ScaleMesh(builder_faces,D3DVAL(10));
// Выделим сетку
builder_faces->CreateMesh(&mesh);
// Сделаем ее видимой
frame->AddVisual((LP DIRECT3DRMVISUAL)mesh);
DD_RELEASE(frame);
DD_RELEASE(mesh);
Создадим фрейм для куба (CreateFrame), зададим его позицию (SetPosition), создадим интерфейс для Построителя Сетки (CreateMeshBuilder) и с помощью его создадим куб (build_cube_faces). Закрасим весь куб, все 6 граней (SetColor), масштабируем (ScaleMesh). Для ускорения вывода на экран создадим интерфейс Сетки (CreateMesh) и сделаем его видимым на экране (AddVisual).
Т. е. для того, чтобы любой объект был видим, на экране в RM необходимо создать сам объект, закрасить его и придать ему объем (чтобы он занимал место на сцене). Кроме этого, надо не забыть создать фрейм для объекта и сделать объект видимым в этом фрейме. Если что-то упустить объект не будет виден на сцене для зрителя.
Теперь посмотрим, как происходит моделирование простейшего куба из четырехугольных граней.
// Вершины куба
D3DVECTOR verts[] = {
1.0f, 1.0f, -1.0f, 1.0f, -1.0f, -1.0f,
-1.0f, -1.0f, -1.0f, -1.0f, 1.0f, -1.0f,
1.0f, 1.0f, 1.0f, 1.0f, -1.0f, 1.0f,
-1.0f, -1.0f, 1.0f, -1.0f, 1.0f, 1.0f };
// Грани куба
DWORD face_data[] = {
4, 0, 1, 2, 3,
// 4, 3, 2, 1, 0,
4, 7, 6, 5, 4,
4, 4, 5, 1, 0,
4, 3, 2, 6, 7,
4, 4, 0, 3, 7,
4, 1, 5, 6, 2,
0};
// Построение куба
BOOL build_cube_faces(LP DIRECT3DRMMESHBUILDER3 mb, float angle)
{
// Добавление граней
mb->AddFaces(16, verts, 0, 0, face_data, NULL);
// Генерация нормалей
mb->GenerateNormals(angle,
D3DRMGENERATENORMALS_USECREASEANGLE);
return TRUE;
}
Для того чтобы создать куб, воспользуемся функцией AddFaces. Она имеет 6 параметров, из них используются три. Первый задает число вершин (их у куба 8), второй задает массив с вершинами, пятый параметр задает массив, в котором каждой грани сопоставлены свои вершины.
В массиве verts заданы значения вершин. Каждая вершина задается тремя числами - это координаты точки в пространстве (x, y, z). Здесь мы строим куб относительно центра координат, размером две единицы по любой из осей. Порядок расположения вершин в массиве verts показан на рисунке 3.
Рис. 3. Расположение вершин в кубе
Но вершины сами по себе не задают грани. Какие грани мы создадим, определяет массив face_data. Первое значение массива задает, сколько точек в первой грани, затем идут индексы вершин в массиве verts, затем также задается вторая вершина и так далее. Последнее значение в этом массиве должно быть ноль. Номера вершин указаны в таком порядке, чтобы в каждой грани они добавлялись по часовой стрелке. Тем самым задается направление вектора нормали. У всех граней вектора нормалей направлены от куба, а не внутрь его. Поэтому они видны снаружи куба, а не изнутри. Если добавлять вершины в грани против часовой стрелки, то грани будут видны изнутри куба, а не снаружи. А если добавлять в беспорядке, то, скорее всего она будет отображена некорректно. Для того чтобы можно было посмотреть на неправильный способ создания граней, уберите комментарий в массиве verts и посмотри на результат.
Вершины куба заданы таким образом, чтобы куб был создан в начале координат. Грани куба создаются в таком порядке: передняя, задняя, правая, левая, верхняя и нижняя. Например, левая грань сначала включает ближние точки к зрителю, потом дальние. Нижняя грань включает правую ближнюю вершину и правую дальнюю вершину, а потом уже левые вершины. Нужно обязательно соблюдать порядок добавления граней. В частности, грань из вершин 0, 1, 6, 7 будет разрезать куб по диагонали. Если Вы поместите камеру вглубь куба, то куб пропадет, Вы не увидите ни одной из его граней, так как RM не будет их отображать.
В этом примере у граней нет общих вершин, и мы не задали ни одной нормали. Для этого, кстати, и служат два неиспользуемых параметра в функции AddFaces. Последний параметр ее возвращает массив получившихся граней.
Мы можем попросить интерфейс Построителя Сетки сгенерировать нормали для вершин и граней автоматически, с помощью функции GenerateNormals. Так как направление нормали грани уже задано (порядком задания вершин в гранях). С помощью этих нормалей можно вычислить нормали в вершинах, усредняя нормали граней прилегающих к вершине. Новая нормаль к вершине создается, если угол между гранями, прилегающими к вершине больше угла, который мы указали (angle). В данном случае 0 градусов, т.е. нормаль, будет сгенерирована всегда. С помощью этой функции можно также объединить все одинаковые вершины, а потом уже вычислить нормали, это делается с помощью указания флага D3DRMGENERATENORMALS_PRECOMPACT.
Мы создали куб из выпуклых четырехугольных граней. Как известно, грани бывают и вогнутые и могут состоять из любого числа точек. Вы можете создавать только выпуклые и планарные грани, т.е. любая прямая линия, проведенная между двумя вершинами должна проходить внутри грани и не пересекать ее ребра, и вершины граней должны лежать в одной плоскости. RM самостоятельно разбивает не треугольные грани на треугольные, так как именно текстурированные треугольники быстро отображаются трехмерными графическими акселераторами.
Рис. 4. Грани
Поэтому давайте рассмотрим, как создать куб из треугольников. Это будет правый зеленый куб на сцене.
// Создание куба из треугольников
// Фрейм
rc=rm_info.rm->CreateFrame(main_frame, &frame);
DD_CHK(rc,_gError);
// Позиция
frame->SetPosition(main_frame, 15.0f, 0.0f, 0.0f);
// Построитель сетки
rc=rm_info.rm->CreateMeshBuilder(&builder_triangles);
DD_CHK(rc,_gError);
// Создание
build_cube_triangles(builder_triangles,DEGREES_0);
// закраска
builder_triangles->SetColor(RGBA_MAKE(0,255,0,128));
// Масштабирование
ScaleMesh(builder_triangles,D3DVAL(10));
// Выделим сетку
builder_triangles->CreateMesh(&mesh);
// Сделаем ее видимой
frame->AddVisual((LP DIRECT3DRMVISUAL)mesh);
DD_RELEASE(mesh);
DD_RELEASE(frame);
DD_RELEASE(builder_triangles);
Создание фрейма куба и его визуализация выполнена также как для предыдущего куба. Построение куба из треугольников выполнено в функции build_cube_triangles.
#define MAX_TRI 6*2*3
VertexTriangle_t v_tri[MAX_TRI];
// Грани куба из треугольников
DWORD tri_data[MAX_TRI] = {
0, 1, 2, 2, 3, 0,
7, 6, 5, 5, 4, 7,
4, 5, 1, 1, 0, 4,
3, 2, 6, 6, 7, 3,
4, 0, 3, 3, 7, 4,
1, 5, 6, 6, 2, 1,
}
// Построение куба из треугольников
BOOL build_cube_triangles(LP DIRECT3DRMMESHBUILDER3 mb, float angle)
{
for (int i=0; i<MAX_TRI; i++)
{
v_tri[i].type=D3DRMVERTEX_LIST;
memcpy(&v_tri[i].pos,&verts[tri_data[i]],sizeof(D3DVECTOR));
}
// Добавление граней
mb->AddTriangles(0, D3DRMFVF_TYPE | D3DRMFVF_NORMAL, MAX_TRI,
(LPVOID) &v_tri);
// Генерация нормалей
mb->GenerateNormals(angle,
D3DRMGENERATENORMALS_USECREASEANGLE);
return TRUE;
}
Структура VertexTriangle определена:
// Вершины для AddTriangles
typedef struct
{
DWORD type;
D3DVECTOR pos;
D3DVECTOR normal;
} VertexTriangle_t;
С помощью нее задаются вершины треугольников. Переменная type указывает тип вершины, пока воспользуемся типом D3DRMVERTEX_LIST, pos - позиция вершины, normal - вектор нормали вершины. Нормали по-прежнему мы сгенерируем функцией GenerateNormals. Тем самым надо задать только позиции вершин. Позиции, как и в предыдущей функции, берутся из массива verts, при этом номера вершин из массива tri_data.
Тип D3DRMVERTEX_LIST позволяет задавать треугольники из 3 вершин, т. е. на передней грани куба будет 2 треугольника из 3 вершин - всего 6 вершин. Тем самым всего на 6 гранях куба будет 36 вершин (#define MAX_TRI 6*2*3). Порядок добавления вершин опять же по часовой стрелке и порядок создания сторон тот же, что и для предыдущего примера.
Как можно заметить, каждая первая и шестая вершины одинаковы из шести подряд идущих вершин (задающие сторону куба), одинаковы также третья и четвертая из шести. Т. е. такой способ задания куба несколько избыточен.
В отличие от предыдущего, здесь не надо задавать грани, треугольник это и есть грань. Вы задаете три точки и получаете грань.
Добавление треугольников делается функцией AddTriangles. У нее 4 параметра. Первый всегда 0, второй задает тип структуры для хранения вершин треугольника, третий количество вершин, а последний указатель на массив структур, где хранятся вершины.
Структура для хранения вершин обязательно должна содержать поле позиции вершины. Наличие или отсутствие других полей задается флагами. Помимо типа вершины и нормали, можно указать цвет (D3DRMFVF_COLOR) и координаты текстуры (D3DRMFVF_TEXTURECOORDS) для вершины.
Если Вы заметили, то это куб полупрозрачный. Прозрачность задается четвертым параметром в макросе RGBA_MAKE:
// закраска
builder_triangles->SetColor(RGBA_MAKE(0,255,0,128));
Иногда бывает необходимо повторить (клонировать) объект, давайте посмотрим, как это делается. Кроме этого, мы рассмотрим, как окрасить грани в разные цвета. В результате получится разноцветный куб слева на демонстрационной сцене.
// Закраска куба с клонированием
// Фрейм
rc=rm_info.rm->CreateFrame(main_frame, &frame);
DD_CHK(rc,_gError);
// Позиция
frame->SetPosition(main_frame, -15.0f, 0.0f, 0.0f);
// Создание клона
rc=builder_faces->Clone(0, IID_I Direct3DRMVisual,
(LPVOID *)&builder_clone);
DD_CHK(rc,_gError);
// Закраска граней
color_cube_faces(builder_clone);
// Выделим сетку
builder_clone->CreateMesh(&mesh);
// Визуализация сетки
frame->AddVisual((LP DIRECT3DRMVISUAL)mesh);
DD_RELEASE(frame);
DD_RELEASE(mesh);
DD_RELEASE(builder_clone);
DD_RELEASE(builder_faces);
Возьмем первый куб, который мы создали из 6 четырехугольных граней. Клонирование его производится функцией Clone. Фактически, это создание и заполнение нового интерфейса Построителя Сетки. Далее надо его раскрасить в функции color_cube_faces.
Для закраски граней куба необходимо каждой грани назначить свой цвет. Для этого нужно получить интерфейс Грани (Direct3DRMFace2). В этом интерфейсе есть функции, которые позволяют назначить цвет грани, назначить текстуру на грань, также функции манипулирования с вершинами грани. Мы используем только функцию назначения цвета на грань.
// закраска граней куба
void color_cube_faces(LP DIRECT3DRMMESHBUILDER3 mb)
{
D3DCOLOR colors[6] ={
RGBA_MAKE(255,0,0,128),RGBA_MAKE(0,255,0,128),RGBA_MAKE(0,0,255,128),
RGBA_MAKE(255,255,0,128),RGBA_MAKE(255,0,255,128),RGBA_MAKE(0,255,255,128),
};
int i;
LP DIRECT3DRMFACE2 face;
for (i=0; i<mb->GetFaceCount(); i++)
{
if (GetFace(mb,i,&face)==FALSE) break;
face->SetColor(colors[i%6]);
DD_RELEASE(face);
}
}
В этой функции вначале задается массив цветов с помощью макроса RGBA_MAKE, причем у всех граней цвет разный и все грани полупрозрачные. Далее, мы получаем число граней (GetFaceCount), получаем очередную грань (GetFace) по номеру, и назначаем ей цвет.
Функция GetFace несколько сложнее.
// Получить грань
BOOL GetFace(
LP DIRECT3DRMMESHBUILDER3 mb,
int n, LP DIRECT3DRMFACE2* face)
{
if (n>= mb->GetFaceCount()) return FALSE;
// Список граней
I Direct3DRMFaceArray* arr_faces = NULL;
mb->GetFaces(&arr_faces);
// Нужная грань
I Direct3DRMFace* tmp_face = NULL;
arr_faces->GetElement(n, &tmp_face);
tmp_face->QueryInterface(IID_I Direct3DRMFace2,
(LPVOID *)face);
DD_RELEASE(tmp_face);
DD_RELEASE(arr_faces);
return TRUE;
}
Для того, что получить нужную грань, надо вначале создать интерфейс Массива Граней (I Direct3DRMFaceArray), затем по номеру запросить нужную грань (GetElement). Но интерфейс Массива Граней не поддерживает версию 2 интерфейса Граней, поэтому надо запросить у старого интерфейса I Direct3DRMFace новый интерфейс I Direct3DRMFace2 (QueryInterface), после этого мы получаем указатель на нужную грань.
Визуализация клонированного и раскрашенного куба производиться как ранее.
Мы создали три куба, теперь создадим обычную четырехугольную грань, но с цветовым градиентом и с помощью функции AddFacesIndexed. Грань расположена за кубами на демонстрационной сцене.
// Создание плоской грани
// Фрейм
rc=rm_info.rm->CreateFrame(main_frame, &frame);
DD_CHK(rc,_gError);
// Позиция
frame->SetPosition(main_frame, 0.0f, 10.0f, 55.0f);
// Построитель сетки
rc=rm_info.rm->CreateMeshBuilder(&builder_faces);
DD_CHK(rc,_gError);
// Создание
build_face(builder_faces);
// Масштабирование
builder_faces->Scale(D3DVAL(55),D3DVAL(20),D3DVAL(0));
// Выделим сетку
builder_faces->CreateMesh(&mesh);
// Сделаем ее видимой
frame->AddVisual((LP DIRECT3DRMVISUAL)mesh);
DD_RELEASE(frame);
DD_RELEASE(mesh);
DD_RELEASE(builder_faces);
Для создания грани потребуются четыре массива. Первый массив вершин позиций граней, затем массив нормалей (все нормали направлены на зрителя), затем массив, который задает порядок вершин и нормалей в грани. В отличие от первого примера, мы зададим нормали непосредственно. Массив colors_face задает цвета вершин грани. Значения получены с помощью Adobe PhotoShop (взяты крайние точки с одного из срезов цветового куба).
...Подобные документы
Программирование приложения с использованием библиотеки OpenGL и функции для рисования геометрических объектов. Разработка процедуры визуализации трехмерной сцены и интерфейса пользователя. Логическая структура и функциональная декомпозиция проекта.
курсовая работа [1,1 M], добавлен 23.06.2011Общие сведения о OpenGL и его использование для разработки логотипа. Разработка программы: функции, их использование в программе. Построение модели и возможность перемещения объектов. Задание освещения объектов моделирования и проработка элементов фона.
курсовая работа [447,7 K], добавлен 14.07.2012Построение динамической трехмерной сцены, включающей заданные тело и поверхность определенного вида средствами графической библиотеки. Наложение текстур на тела, поверхности с помощью функции SetupTextures. Графическое представление тела с текстурой.
курсовая работа [582,9 K], добавлен 24.12.2010Ознакомление с интерфейсом, основными возможностями и преимуществами использования программы OpenGL - популярной библиотекой для работы с 2D и 3D графикой. Рассмотрение назначения, базовых компонент и правил инициализации программного движка DirectX.
презентация [19,4 K], добавлен 14.08.2013Программный код OpenGL. Синтаксис команд OpenGL. OpenGL как конечный автомат. Конвейер визуализации OpenGL. Библиотеки, относящиеся к OpenGL. Библиотека OpenGL. Подключаемые файлы. GLUT, инструментарий утилит библиотеки OpenGL.
курсовая работа [304,9 K], добавлен 01.06.2004Общие сведения о системе Компас 3D, предназначенной для графического ввода и редактирования чертежей на ПК. Ее основные функции, типы объектов, единицы измерения. Принципы работы в Компас-График LT. Пример создания файла трехмерной модели сборки детали.
курсовая работа [1,1 M], добавлен 03.11.2014Основы работы с графиков средствами OpenGL в C#. Ее спецификации, принципы и возможности. Direct3D как самостоятельная часть библиотеки Microsoft DirectX, которая отвечает за графику и вывод графической информации. Независимость от языка программирования.
курсовая работа [2,1 M], добавлен 17.02.2013Функциональные возможности библиотеки OpenGL. Разработка процедуры визуализации трехмерной сцены, интерфейса пользователя и подсистемы управления событиями с целью создания приложения для построения динамического изображения 3D-модели объекта "Самолет".
курсовая работа [1,7 M], добавлен 28.06.2011Назначение и стандарты реализации OpenGL для Windows, порядок подключения графической библиотеки. Основные функции и синтаксис команд. Рисование примитивов, видовые и аффинные преобразования. Моделирование двумерных графических объектов и анимации.
лабораторная работа [35,0 K], добавлен 04.07.2009Возможности интегрированного объектно-ориентированного пакета программ CorelDraw для работы с векторной графикой. Элементы графического интерфейса программы, панель задач, рабочие инструменты, специальные эффекты м приемы для работы с векторной графикой.
статья [528,6 K], добавлен 01.05.2010Определение понятия трехмерной компьютерной графики. Особенности создания 3D-объектов при помощи булевых операций, редактируемых поверхностей, на основе примитивов. Моделирование трехмерных объектов при помощи программного пакета Autodesk 3ds Max.
дипломная работа [4,2 M], добавлен 13.04.2014Точность чертежей и документации. Использование собственного математического ядра и параметрических технологий как ключевая особенность "Компас-3D". Основной инструментарий трехмерного моделирования. Моделирование деталей из листового материала.
реферат [16,4 K], добавлен 20.06.2013Исследование особенностей системного и прикладного программного обеспечения. Обзор языков программирования Pascal, Delphi и Assembler. Проектирование динамической трехмерной сцены в нестандартном графическом режиме. Составление математической модели.
курсовая работа [1,0 M], добавлен 17.02.2013Формулы поверхностей, матрицы основных и перспективных преобразований. Этапы проектирования трехмерной сцены в нестандартном графическом режиме 320х200 точек на 256 цветов. Блок-схема головной программы, процедуры отрисовки линии и поворота всей фигуры.
курсовая работа [565,5 K], добавлен 21.12.2012Суть программирования с использованием библиотеки OpenGL, его назначение, архитектура, преимущества и базовые возможности. Разработка приложения для построения динамического изображения трехмерной модели объекта "Компьютер", руководство пользователя.
курсовая работа [866,8 K], добавлен 22.06.2011Моделирование работы мастерской с использованием языка GPSS Wоrld. Определение основныx xарактеристик моделируемой системы: средней длины очереди неисправныx аппаратов; коэффициента загрузки мастеров. Описание машинной программы решения задачи.
курсовая работа [380,6 K], добавлен 28.06.2011Разработка трехмерной модели приложения "Гоночный автомобиль" на языке С++ с использованием библиотеки OpenGL и MFC, создание программы в среде Visual Studio 6.0. Информационное обеспечение, логическая структура и функциональная декомпозиция проекта.
курсовая работа [3,9 M], добавлен 29.06.2011Общие понятия о гироскопах, их классификация и применение. Механические гироскопы, свойства трехстепенного роторного гироскопа. Создание проекта "Гироскоп Фуко" средствами OpenGL и начальная настройка среды разработки. Инициализация объекта вывода и Glut.
курсовая работа [491,9 K], добавлен 18.11.2013Базовые приемы работы при создании трехмерной модели в пакете Компас. Абсолютная система координат, координатные плоскости. Управление изображением, цветом и свойствами поверхности объектов. Этапы процесса разработки трехмерной модели "Форма для льда".
курсовая работа [963,3 K], добавлен 11.06.2012Моделирование заданных команд, внутренних функциональных устройств и объектов ввода-вывода микроконтроллера. Разработка программа для демонстрации совместной работы микроконтроллера и моделируемого внешнего устройства. Компоненты архитектуры ATMega128.
курсовая работа [3,6 M], добавлен 12.06.2013