Откровенно говоря, рассеянный свет — довольно скучная вещь. Он полезен, особенно когда применяется совместно с источниками света других типов, но сам по себе рассеянный свет весьма невыразителен. После нескольких безуспешных попыток создать демонстрационную программу, в которой использовался бы только рассеянный свет, я отступил и решил, что рассеянный свет не заслуживает собственного примера. Вместо этого давайте применим мастер Direct3D AppWizard чтобы создать приложение, в котором рассеянный свет будет использоваться для освещения выбранного вами объекта.
Запустите программу Visual C++ Developer Studio, и выберите команду New в меню File. На экран будет выведено диалоговое окно New. Выберите в списке пункт Project Workspace. Будет открыто диалоговое окно New Project Workspace. В списке Type выберите Direct3D AppWizard, введите имя проекта, например AmbientLight, в поле Name, и щелкните по кнопке Create.
На экран будет выведено первое окно мастера Direct3D AppWizard. Щелкните по кнопке Next. В следующем диалоговом окне вам будет предложено выбрать объект, который будет отображать новое приложение. По умолчанию используется объект Swirl (завиток). Выберите переключатель Let me choose an object и введите имя объекта в текстовом поле Object, либо щелкните по кнопке Browse и воспользуйтесь диалоговым окном выбора файла. На рис. 6.1 показано диалоговое окно Object Selection в котором выбран файл sphere1.x из DirectX SDK.
Рис. 6.1. Диалоговое окно выбора объекта мастера Direct3D AppWizard
СОВЕТ | Содержимое X-файлов. Помните, что вы должны выбрать X-файл, содержащий единственную сетку. X-файлы, содержащие анимации или иерархию фреймов, будут отображаться неправильно. Большинство X-файлов на сопроводительном CD-ROM содержат единственную сетку. |
После того, как вы выбрали сетку, которая будет отображаться приложением, щелкните по кнопке Next.
На экран будет выведено диалоговое окно, позволяющее выбрать используемые в приложении источники света. Внешний вид этого окна показан на рис. 6.2.
Рис. 6.2. Окно выбора источников света мастера Direct3D AppWizard
По умолчанию используется источник направленного света. Снимите флажок Directional и установите флажок Ambient. Кроме того, в группе Color Model установите переключатель RGB. Это позволит использовать цветное освещение. После всех изменений диалоговое окно Light Selection должно выглядеть так, как показано на рис. 6.2.
В следующих диалоговых окнах оставьте предлагаемые по умолчанию параметры, и в завершающем окне щелкните по кнопке Finish. Мастер AppWizard выведет окно с просьбой подтвердить правильность заданных параметров. Щелкните по кнопке OK. Developer Studio создаст новое приложение в соответствии с указанными вами параметрами. Когда Developer Studio завершит работу (это не займет много времени), скомпилируйте и запустите новое приложение. Окно нового приложения должно выглядеть аналогично рис. 6.3 (особенно, если вы выбрали в качестве объекта файл sphere1.x).
Рис. 6.3. Приложение, созданное с помощью мастера Direct3D AppWizard
СОВЕТ | Предупреждения о включаемых файлах. Когда вы в первый раз компилируете новый проект, или когда в меню Build выбрана команда Update All Dependencies Visual C++ часто выводит несколько предупреждений. Это происходит потому что компилятор пытается обнаружить все указанные в директивах #include включаемые файлы и выводит предупреждение всякий раз, когда не может обнаружить требуемый файл. К сожалению, поиск включаемых файлов выполняется до обработки программы препроцессором. Это означает, что включаемые файлы, которые не используются в проекте из-за директив условной компиляции #ifdef все равно будут искаться среди включаемых файлов проекта. Предупреждения об их отсутствии можно игнорировать. |
Независимо от того, какой объект вы выбрали, он, скорее всего, выглядит не очень хорошо. Это связано с тем, что источник рассеянного света освещает каждую часть сетки с одинаковой интенсивностью. В результате мы видим силуэт объекта. Если наложить на сетку текстуру, результат будет чуть лучше, но изображение все равно останется скучным и плоским.
Мы используем только что созданный проект для демонстрации следующих технологий:
Использование источника рассеянного света Использование интерфейса Direct3DRMMeshBuilder Изменение метода визуализации сетки во время работы программы.Приложение Spotlight предоставляет пользователю меню Beam, позволяющее изменить угол светового пятна и угол зоны освещенности прожектора. Шесть функций, реализующих эту функциональность, показаны ниже:
void SpotlightWin::OnBeamNormal() { spotlight->SetUmbra(D3DVALUE(0.2)); spotlight->SetPenumbra(D3DVALUE(0.4)); beamwidth = BEAM_NORMAL; } void SpotlightWin::OnBeamNarrow() { spotlight->SetUmbra(D3DVALUE(0.1)); spotlight->SetPenumbra(D3DVALUE(0.2)); beamwidth = BEAM_NARROW; } void SpotlightWin::OnBeamWide() { spotlight->SetUmbra(D3DVALUE(0.4)); spotlight->SetPenumbra(D3DVALUE(0.8)); beamwidth = BEAM_WIDE; } void SpotlightWin::OnUpdateBeamNormal(CCmdUI* pCmdUI) { pCmdUI->SetCheck(beamwidth == BEAM_NORMAL); } void SpotlightWin::OnUpdateBeamNarrow(CCmdUI* pCmdUI) { pCmdUI->SetCheck(beamwidth == BEAM_NARROW); } void SpotlightWin::OnUpdateBeamWide(CCmdUI* pCmdUI) { pCmdUI->SetCheck(beamwidth == BEAM_WIDE); }Одна из первых трех функций вызывается, когда пользователь выбирает один из пунктов меню Beam. Функции SetUmbra() и SetPenumbra() применяются для модификации характеристик прожектора. Кроме того, переменной класса beamwidth присваивается значение, соответствующее установленным в данный момент параметрам прожектора. Эта переменная используется следующими тремя функциями, которые предназначены для отображения метки возле активного пункта меню.
Одна из возможностей, которую мастер Direct3D AppWizard добавляет в проект (независимо от того, просите вы об этом или нет) — это меню Render, позволяющее изменить метод визуализации сетки во время работы программы. Взглянув на определение класса AmbientLightWin, вы увидите объявление шести функций, реализующих эту функциональность. Функции OnRenderWireframe(), OnRenderFlat() и OnRenderGouraud() являются обработчиками событий, которые вызываются при выборе одной из команд меню Render. Эти функции выглядят следующим образом:
void AmbientLightWin::OnRenderWireframe() { if (meshbuilder) meshbuilder->SetQuality(D3DRMRENDER_WIREFRAME); } void AmbientLightWin::OnRenderFlat() { if (meshbuilder) meshbuilder->SetQuality(D3DRMRENDER_FLAT); } void AmbientLightWin::OnRenderGouraud() { if (meshbuilder) meshbuilder->SetQuality(D3DRMRENDER_GOURAUD); }Каждая функция проверяет значение указателя meshbuilder. Если указатель был инициализирован, вызывается функция SetQuality() интерфейса Direct3DRMMeshBuilder для изменения метода визуализации сетки.
Оставшиеся три функции, OnUpdateRenderFlat(), OnUpdateRenderGouraud() и OnUpdateRenderWireframe(), вызываются Windows каждый раз при отображении на экране меню Render. Они применяются для вывода отметки рядом с активным в данный момент пунктом меню. Ниже приведен код этих функций:
void AmbientLightWin::OnUpdateRenderWireframe(CCmdUI* pCmdUI) { if (meshbuilder) { D3DRMRENDERQUALITY meshquality = meshbuilder->GetQuality(); pCmdUI->SetCheck(meshquality == D3DRMRENDER_WIREFRAME); } } void AmbientLightWin::OnUpdateRenderFlat(CCmdUI* pCmdUI) { if (meshbuilder) { D3DRMRENDERQUALITY meshquality = meshbuilder->GetQuality(); pCmdUI->SetCheck(meshquality == D3DRMRENDER_FLAT); } } void AmbientLightWin::OnUpdateRenderGouraud(CCmdUI* pCmdUI) { if (meshbuilder) { D3DRMRENDERQUALITY meshquality = meshbuilder->GetQuality(); pCmdUI->SetCheck(meshquality == D3DRMRENDER_GOURAUD); } }Каждая из трех функций вызывает функцию GetQuality() интерфейса Direct3DRMMeshBuilder, чтобы определить используемый в данный момент метод визуализации сетки. Затем выполняется сравнение полученного метода визуализации, чтобы определить должна ли быть проставлена отметка рядом с данным пунктом меню. Результат сравнения используется в качестве аргумента функции SetCheck(), если он равен TRUE, рядом с пунктом меню ставится флажок.
Рассмотренные шесть функций в той или иной форме присутствуют в большинстве демонстрационных приложений, помещенных на CD-ROM.
Осталось обратить внимание еще на одну особенность приложения Shadow. Обработчики событий меню Render должны изменять параметры двух конструкторов сеток, а не одного, как в других приложениях. Ниже показан код обработчика события для пункта Wireframe меню Render:
void ShadowWin::OnRenderWireframe() { if (floorbuilder) floorbuilder->SetQuality(D3DRMRENDER_WIREFRAME); if (forkbuilder) forkbuilder->SetQuality(D3DRMRENDER_WIREFRAME); }Ранее в этой главе, когда мы изучали источник рассеянного света и создавали приложение AmbientLight, мы рассмотрели шесть функций, необходимых для реализации меню Render. В них мы использовали функции GetQuality() и SetQuality() интерфейса Direct3DRMMeshBuilder. В приложении Spotlight такая стратегия не работает, поскольку здесь мы используем интерфейс Direct3DRMMesh.
Интерфейс Direct3DRMMesh практически полностью посвящен работе с группами. Группой называется набор граней сетки, который может модифицироваться как единая сущность. Для контроля и назначения цвета группы граней интерфейс Direct3DRMMesh предоставляет функции GetGroupColor() и SetGroupColor(). Существует только одна проблема: эти функции требуют идентификатор, который указывает, какая группа граней сетки нас интересует.
Как мы увидим в главе8, идентификаторы групп важны, когда мы используем сетку, содержащую несколько групп. Однако сейчас все грани каждой из трех сеток принадлежат одной и той же группе. К счастью для нас, если сетка содержит только одну группу, то в качестве идентификатора группы можно использовать ноль. Этот идентификатор группы по умолчанию и используется в следующих функциях приложения Spotlight:
void SpotlightWin::OnRenderWireframe() { mesh1->SetGroupQuality(0, D3DRMRENDER_WIREFRAME); mesh2->SetGroupQuality(0, D3DRMRENDER_WIREFRAME); mesh3->SetGroupQuality(0, D3DRMRENDER_WIREFRAME); } void SpotlightWin::OnRenderFlat() { mesh1->SetGroupQuality(0, D3DRMRENDER_FLAT); mesh2->SetGroupQuality(0, D3DRMRENDER_FLAT); mesh3->SetGroupQuality(0, D3DRMRENDER_FLAT); } void SpotlightWin::OnRenderGouraud() { mesh1->SetGroupQuality(0, D3DRMRENDER_GOURAUD); mesh2->SetGroupQuality(0, D3DRMRENDER_GOURAUD); mesh3->SetGroupQuality(0, D3DRMRENDER_GOURAUD); } void SpotlightWin::OnUpdateRenderWireframe(CCmdUI* pCmdUI) { D3DRMRENDERQUALITY quality = mesh1->GetGroupQuality(0); pCmdUI->SetCheck(quality == D3DRMRENDER_WIREFRAME); } void SpotlightWin::OnUpdateRenderFlat(CCmdUI* pCmdUI) { D3DRMRENDERQUALITY quality = mesh1->GetGroupQuality(0); pCmdUI->SetCheck(quality == D3DRMRENDER_FLAT); } void SpotlightWin::OnUpdateRenderGouraud(CCmdUI* pCmdUI) { D3DRMRENDERQUALITY quality = mesh1->GetGroupQuality(0); pCmdUI->SetCheck(quality == D3DRMRENDER_GOURAUD); }Одна из первых трех функций вызывается, когда пользователь выбирает один из пунктов в меню Render приложения. Функция SetGroupQuality() используется чтобы изменить метод визуализации сетки. Следующие три функции вызываются MFC перед выводом на экран меню Render. Функция SetCheck() используется для установки флажка слева от пункта меню, соответствующего используемому в данный момент методу визуализации.
Сцена создается в функции CreateScene(). Если вы назвали проект AmbientLight, функция CreateScene() будет выглядеть подобно показанной в листинге 6.1.
Листинг 6.1. Функция AmbientLightWin::CreateScene() |
BOOL AmbientLightWin::CreateScene() { HRESULT r; // ------ СЕТКА -------- d3drm->CreateMeshBuilder(&meshbuilder); r = meshbuilder->Load(meshname, NULL, D3DRMLOAD_FROMFILE, NULL, NULL); if (r != D3DRM_OK) { CString msg; msg.Format("Failed to load file '%s'\n", meshname); AfxMessageBox(msg); return FALSE; } ScaleMesh(meshbuilder, D3DVALUE(25)); //------- ФРЕЙМ СЕТКИ -------- LPDIRECT3DRMFRAME meshframe; d3drm->CreateFrame(scene, &meshframe); meshframe->AddVisual(meshbuilder); meshframe->SetRotation(scene, D3DVALUE(0), D3DVALUE(1), D3DVALUE(0), D3DVALUE(.1)); meshframe->Release(); meshframe = 0; // -------- РАССЕЯННЫЙ СВЕТ -------- LPDIRECT3DRMLIGHT alight; d3drm->CreateLightRGB(D3DRMLIGHT_AMBIENT, D3DVALUE(1.00), D3DVALUE(1.00), D3DVALUE(1.00), &alight); scene->AddLight(alight); alight->Release(); alight = 0; //------ КАМЕРА ---------- d3drm->CreateFrame(scene, &camera); camera->SetPosition(scene, D3DVALUE(0), D3DVALUE(0), D3DVALUE(-50)); d3drm->CreateViewport(device, camera, 0, 0, device->GetWidth(), device->GetHeight(), &viewport); return TRUE; } |
Функция CreateScene() выполняет следующие действия:
Создание и загрузка сетки. Создание фрейма для сетки. Создание источника рассеянного света. Создание порта просмотра.На первом этапе для загрузки сетки из файла используется интерфейс Direct3DRMMeshBuilder. Рассмотрим этот фрагмент более пристально:
d3drm->CreateMeshBuilder(&meshbuilder); r = meshbuilder->Load(meshname, NULL, D3DRMLOAD_FROMFILE, NULL, NULL); if (r != D3DRM_OK) { CString msg; msg.Format("Failed to load file '%s'\n", meshname); AfxMessageBox(msg); return FALSE; } ScaleMesh(meshbuilder, D3DVALUE(25));Сначала с помощью функции CreateMeshBuilder() интерфейса Direct3DRM инициализируется переменная meshbuilder. Затем для загрузки файла сетки с диска вызывается функция Load() (переменная meshname хранит имя файла сетки, которое было указано при создании приложения с помощью мастера AppWizard). Если выполнение функции Load() завершается неудачей, выводится окно сообщения и возвращается значение FALSE. Если функция Load() завершилась успешно, вызывается функция ScaleMesh() для задания оптимального размера сетки.
На втором этапе работы функции CreateScene() выполняется создание фрейма к которому будет присоединена сетка. Эта часть кода выглядит так:
LPDIRECT3DRMFRAME meshframe; d3drm->CreateFrame(scene, &meshframe); meshframe->AddVisual(meshbuilder); meshframe->SetRotation(scene, D3DVALUE(0), D3DVALUE(1), D3DVALUE(0), D3DVALUE(.1)); meshframe->Release(); meshframe = 0;Указатель meshframe объявлен как указатель на интерфейс Direct3DRMFrame. Для создания нового фрейма применяется функция CreateFrame() интерфейса Direct3DRM. Конструктор сеток meshbuilder, созданный на предыдущем этапе, присоединяется к новому фрейму с помощью функции AddVisual().
Далее производится вызов функции SetRotation(), чтобы назначить фрейму атрибуты вращения. Фрейм будет вращаться вокруг оси Y (на это указывают первые три аргумента функции). Последний аргумент функции SetRotation() указывает, что фрейм будет поворачиваться при каждом обновлении изображения на 0.1радиан.
На третьем этапе работы функции CreateScene() создается источник рассеянного света:
LPDIRECT3DRMLIGHT alight; d3drm->CreateLightRGB(D3DRMLIGHT_AMBIENT, D3DVALUE(1.00), D3DVALUE(1.00), D3DVALUE(1.00), &alight); scene->AddLight(alight); alight->Release(); alight = 0;Переменная alight объявляется как указатель на интерфейс Direct3DRMLight. Функция CreateLightRGB() используется для создания источника света и инициализации указателя alight. Первый аргумент функции CreateLightRGB() — это константа, которая определяет тип создаваемого источника света. Используемая здесь константа, D3DRMLIGHT_AMBIENT, является одной из пяти возможных:
D3DRMLIGHT_AMBIENT D3DRMLIGHT_POINT D3DRMLIGHT_SPOT D3DRMLIGHT_DIRECTIONAL D3DRMLIGHT_PARALLELPOINTСледующие три аргумента функции CreateLightRGB() определяют цвет источника света. Использованные значения (1, 1, 1) указывают, что будет создан источник белого света, поскольку для красной, зеленой и синей составляющих заданы одинаковые, максимально возможные значения. Ниже показаны некоторые альтернативные варианты.
d3drm->CreateLightRGB(D3DRMLIGHT_AMBIENT, D3DVALUE(1.00), D3DVALUE(0.00), D3DVALUE(0.00), &alight); // создает источник красного света d3drm->CreateLightRGB(D3DRMLIGHT_AMBIENT, D3DVALUE(0.00), D3DVALUE(1.00), D3DVALUE(0.00), &alight); // создает источник зеленого света d3drm->CreateLightRGB(D3DRMLIGHT_AMBIENT, D3DVALUE(0.00), D3DVALUE(0.00), D3DVALUE(1.00), &alight); // создает источник синего света d3drm->CreateLightRGB(D3DRMLIGHT_AMBIENT, D3DVALUE(1.00), D3DVALUE(0.00), D3DVALUE(1.00), &alight); // создает источник пурпурного света d3drm->CreateLightRGB(D3DRMLIGHT_AMBIENT, D3DVALUE(0.30), D3DVALUE(0.30), D3DVALUE(0.30), &alight); // создает источник темно-серого светаЕсли в сцене присутствует только один источник рассеянного света (что справедливо для нашей сцены), все грани в сцене будут визуализированы с использованием цвета этого источника света. Последним аргументом функции CreateLightRGB() является адрес указателя alight.
Затем новый источник света присоединяется к фрейму сцены:
scene->AddLight(alight);Как вы увидите далее в этой главе, когда мы будем рассматривать другие типы источников света, остальные источники света не будут присоединены непосредственно к корневому фрейму сцены. Источник рассеянного света может быть присоединен к коревому фрейму потому, что на него не оказывает влияние местоположение и ориентация фрейма. Фактически, источник рассеянного света может быть присоединен к любому фрейму сцены и результат от этого не изменится.
После этого мы освобождаем указатель alight и обнуляем его:
alight->Release(); alight = 0;Помните, что вызов функции Release() освобождает указатель, а не сам объект. Объект будет уничтожен только когда не останется ни одного указывающего на него указателя. В данном случае источник света не будет уничтожен, поскольку он был добавлен к сцене.
Четвертым и последним этапом работы функции CreateScene() является создание порта просмотра:
d3drm->CreateFrame(scene, &camera); camera->SetPosition(scene, D3DVALUE(0), D3DVALUE(0), D3DVALUE(-50)); d3drm->CreateViewport(device, camera, 0, 0, device->GetWidth(), device->GetHeight(), &viewport);В этом фрагменте кода выполняется инициализация двух указателей: camera и viewport. Как говорилось в главе 4, эти указатели являются членами класса RMWin. Наша функция CreateScene() должна инициализировать эти указатели. Если мы не сделаем этого, выполнение приложения будет остановлено, и будет выведено сообщение об ошибке.
Указатель camera — это указатель на интерфейс Direct3DRMFrame, который инициализируется функцией CreateFrame() интерфейса Direct3DRM. Затем новый фрейм перемешается на 50 единиц от начала координат по направлению к зрителю. Это делается из-за того, что сетка, созданная на первом этапе, была помещена в начало координат (по умолчанию). Мы смещаем камеру от начала координат, чтобы объект попал в область просмотра. Затем для инициализации указателя viewport вызывается функция CreateViewport() интерфейса Direct3DRM.
Завершая работу, функция CreateScene() возвращает TRUE, чтобы сообщить об успешном выполнении всех действий. Если функция CreateScene() возвращает FALSE, работа приложения прекращается и выводится сообщение об ошибке.
В приложении Firefly создание сцены осуществляется в функции CreateScene(). Функция отвечает за создание двух сеток: одной для чаши и одной для светлячка. Кроме того, в ней создается один точечный источник света и один порт просмотра. Для анимации источника света и сферической сетки применяются пустые фреймы. За дополнительной информацией о пустых фреймах можно обратиться к описанию приложения Decal в главе 5. Код функции CreateScene() приложения Firefly приведен в листинге 6.2.
Листинг 6.2. Функция FireflyWin::CreateScene() |
BOOL FireflyWin::CreateScene() { //-------- СЕТКА ЧАШИ -------- D3DRMLOADRESOURCE resinfo; resinfo.hModule = NULL; resinfo.lpName = MAKEINTRESOURCE(IDR_CHALICEMESH); resinfo.lpType = "MESH"; d3drm->CreateMeshBuilder(&chalicebuilder); chalicebuilder->Load(&resinfo, NULL, D3DRMLOAD_FROMRESOURCE, NULL, NULL); ScaleMesh(chalicebuilder, D3DVALUE(25)); //------- ФРЕЙМ ЧАШИ ------ LPDIRECT3DRMFRAME chaliceframe; d3drm->CreateFrame(scene, &chaliceframe); chaliceframe->AddVisual(chalicebuilder); chaliceframe->Release(); chaliceframe = 0; //-------- ТОЧЕЧНЫЙ СВЕТ -------- LPDIRECT3DRMLIGHT pointlight; d3drm->CreateLightRGB(D3DRMLIGHT_POINT, D3DVALUE(1.0),D3DVALUE(1.0), D3DVALUE(1.0), &pointlight); //-------- СЕТКА СВЕТЛЯЧКА ------ resinfo.hModule = NULL; resinfo.lpName = MAKEINTRESOURCE(IDR_SPHEREMESH); resinfo.lpType = "MESH"; LPDIRECT3DRMMESHBUILDER flybuilder; d3drm->CreateMeshBuilder(&flybuilder); flybuilder->Load(&resinfo, NULL, D3DRMLOAD_FROMRESOURCE, NULL, NULL); flybuilder->SetQuality(D3DRMRENDER_WIREFRAME); ScaleMesh(flybuilder, D3DVALUE(0.3)); //-------- ФРЕЙМЫ ИСТОЧНИКА СВЕТА -------- LPDIRECT3DRMFRAME dummyframe; d3drm->CreateFrame(scene, &dummyframe); dummyframe->SetRotation(scene, D3DVALUE(0), D3DVALUE(1), D3DVALUE(0), D3DVALUE(.08)); LPDIRECT3DRMFRAME lightframe; d3drm->CreateFrame(dummyframe, &lightframe); lightframe->SetPosition(dummyframe, D3DVAL(15), D3DVAL(6), D3DVAL(0)); lightframe->AddLight(pointlight); lightframe->AddVisual(flybuilder); flybuilder->Release(); flybuilder = 0; lightframe->Release(); lightframe = 0; dummyframe->Release(); dummyframe = 0; pointlight->Release(); pointlight = 0; //-------- ПОРТ ПРОСМОТРА ------------ d3drm->CreateFrame(scene, &camera); camera->SetPosition(scene, D3DVALUE(0.0), D3DVALUE(20.0), D3DVALUE(-50.0)); camera->SetOrientation(scene, D3DVALUE(0), D3DVALUE(-20), D3DVALUE(50), D3DVALUE(0), D3DVALUE(1), D3DVALUE(0)); d3drm->CreateViewport(device, camera, 0, 0, device->GetWidth(), device->GetHeight(), &viewport); return TRUE; } |
Функция CreateScene() выполняет следующие действия:
Создание и загрузка сетки чаши. Создание фрейма для сетки чаши. Создание точечного источника света. Создание сетки, представляющей светлячка. Создание фреймов для источника света и сетки светлячка. Создание порта просмотра.Сначала создаем сетку чаши:
D3DRMLOADRESOURCE resinfo; resinfo.hModule = NULL; resinfo.lpName = MAKEINTRESOURCE(IDR_CHALICEMESH); resinfo.lpType = "MESH"; d3drm->CreateMeshBuilder(&chalicebuilder); chalicebuilder->Load(&resinfo, NULL, D3DRMLOAD_FROMRESOURCE, NULL, NULL); ScaleMesh(chalicebuilder, D3DVALUE(25));Интерфейс Direct3DRMMeshBuilder используется для загрузки сетки из ресурсов программы. Затем, для изменения размеров сетки применяется функция ScaleMesh().
Новая сетка присоединяется к фрейму с именем chaliceframe:
LPDIRECT3DRMFRAME chaliceframe; d3drm->CreateFrame(scene, &chaliceframe); chaliceframe->AddVisual(chalicebuilder); chaliceframe->Release(); chaliceframe = 0;Для присоединения сетки чаши к новому фрейму вызывается функция AddVisual(). Перемещение фрейма не выполняется, поэтому сетка располагается в начале координат. Обратите внимание, что указатель chalicebuilder не объявлен в функции CreateScene(), а является членом класса FireflyWin. Это сделано для того, чтобы с помощью меню Render можно было изменять используемый метод визуализации. Также обратите внимание, что указатель chalicebuilder не освобождается в функции CreateScene(). Если мы освободим указатель chalicebuilder, то у нас не останется способа изменить метод визуализации во время работы программы.
Следующий шаг — создание точечного источника света. Сначала для создания источника света применим функцию CreateLightRGB() интерфейса Direct3DRM:
LPDIRECT3DRMLIGHT pointlight; d3drm->CreateLightRGB(D3DRMLIGHT_POINT, D3DVALUE(1.0), D3DVALUE(1.0), D3DVALUE(1.0), &pointlight);Константа D3DRMLIGHT_POINT используется для указания типа создаваемого источника света. Следующие три числовых аргумента сообщают, что мы создаем источник излучающий белый свет. Последний аргумент — это адрес указателя, который будет указывать на новый интерфейс Direct3DRMLight.
Теперь создается сетка, представляющая светлячка:
resinfo.hModule = NULL; resinfo.lpName = MAKEINTRESOURCE(IDR_SPHEREMESH); resinfo.lpType = "MESH"; LPDIRECT3DRMMESHBUILDER flybuilder; d3drm->CreateMeshBuilder(&flybuilder); flybuilder->Load(&resinfo, NULL, D3DRMLOAD_FROMRESOURCE, NULL, NULL); flybuilder->SetQuality(D3DRMRENDER_WIREFRAME); ScaleMesh(flybuilder, D3DVALUE(0.3));Фактически в программе нет сетки реального светлячка, а для представления светлячка используется просто сетка сферы. Идентификатор ресурса сетки сферы используется при подготовке структуры resinfo. Конструктор сеток создается с помощью функции CreateMeshBuilder() интерфейса Direct3DRM, а затем функция Load() интерфейса Direct3DRMMeshBuilder загружает сетку. Функция SetQuality() используется, чтобы включить для сетки каркасный метод визуализации. Это сделано для того, чтобы сетка была ясно видна на сцене. Вспомните, что каркасный метод визуализации использует при изображения сетки только ее цвет и игнорирует любые, включенные в сцену источники света. Использование каркасного метода приводит к тому, что сетка будет точно того цвета, который указан для нее, и никакие дополнительные вычисления цвета не требуются. Потом конструктор сеток масштабируется с помощью функции ScaleMesh().
На пятом этапе создаются два фрейма, которые мы будем использовать для анимации источника света и сферической сетки:
LPDIRECT3DRMFRAME dummyframe; d3drm->CreateFrame(scene, &dummyframe); dummyframe->SetRotation(scene, D3DVALUE(0), D3DVALUE(1), D3DVALUE(0), D3DVALUE(.08)); LPDIRECT3DRMFRAME lightframe; d3drm->CreateFrame(dummyframe, &lightframe); lightframe->SetPosition(dummyframe, D3DVAL(15), D3DVAL(6), D3DVAL(0)); lightframe->AddLight(pointlight); lightframe->AddVisual(flybuilder); flybuilder->Release(); flybuilder = 0; lightframe->Release(); lightframe = 0; dummyframe->Release(); dummyframe = 0; pointlight->Release(); pointlight = 0;Указатель dummyframe инициализируется функцией CreateFrame() интерфейса Direct3DRM и является прямым потомком корневого фрейма сцены (scene). Новому фрейму назначается вращение вокруг оси Y. Это приведет к тому, что его дочерние фреймы (например, тот, который мы создадим чуть позже) тоже будут вращаться вокруг оси Y.
Второй фрейм (lightframe) создается как потомок фрейма dummyframe. Потом вызывается функция SetPosition(), чтобы переместить новый фрейм от начала координат. Если мы не переместим этот фрейм, он будет вращаться на одном месте, вместо того, чтобы перемещаться по орбите вокруг пустого фрейма.
Теперь к фрейму lightframe присоединяются сферическая сетка и источник света. Это гарантирует, что источник света и светлячок будут находиться в одной и той же точке пространства.
В конце все четыре указателя (два указателя на фреймы, указатель flybuilder и указатель pointlight) освобождаются и обнуляются.
Оставшаяся часть функции FireflyWin::CreateScene() (см. листинг 6.2) создает для приложения порт просмотра. Мы подробно изучим порты просмотра в главе 9.
Функция AdjustSpin() является функцией обратного вызова, которая периодически изменяет атрибуты вращения фрейма вилки. Для генерации случайного вектора в ней используется функция D3DRMVectorRandom().
void ShadowWin::AdjustSpin(LPDIRECT3DRMFRAME frame, void*, D3DVALUE) { static UINT delay; if (++delay < 11) return; delay = 0; LPDIRECT3DRMFRAME scene; frame->GetScene(&scene); D3DVECTOR spinvect; D3DRMVectorRandom(&spinvect); D3DVALUE spin = D3DDivide(rand() % 100 + 1, 200); frame->SetRotation(scene, spinvect.x, spinvect.y, spinvect.z, spin); }Такая же функция (с другим названием) присутствует в приложении OrbStar. Вы найдете ее подробное описание в главе 5.
Сцена создается функцией CreateScene(), код которой представлен в листинге6.6.
Листинг 6.6. Функция ShadowWin::CreateScene() |
//------------- СЕТКА ПОЛА -------- D3DRMLOADRESOURCE resinfo; resinfo.hModule = NULL; resinfo.lpName = MAKEINTRESOURCE(IDR_CUBEMESH); resinfo.lpType = "MESH"; d3drm->CreateMeshBuilder(&floorbuilder); floorbuilder->Load(&resinfo, NULL, D3DRMLOAD_FROMRESOURCE, NULL, NULL); floorbuilder->Scale(D3DVALUE(5), D3DVALUE(.05), D3DVALUE(5)); floorbuilder->SetPerspective(TRUE); floorbuilder->SetQuality(D3DRMRENDER_FLAT); //------------ ТЕКСТУРА ПОЛА -------------- LPDIRECT3DRMTEXTURE texture; HRSRC texture_id = FindResource(NULL, MAKEINTRESOURCE(IDR_FLOORTEXTURE), "TEXTURE"); d3drm->LoadTextureFromResource(texture_id, &texture); floorbuilder->SetTexture(texture); texture->Release(); texture = 0; //------------ НАЛОЖЕНИЕ ТЕКСТУРЫ ПОЛА ------------ D3DRMBOX box; floorbuilder->GetBox(&box); D3DVALUE w = box.max.x - box.min.x; D3DVALUE h = box.max.z - box.min.z; LPDIRECT3DRMWRAP wrap; d3drm->CreateWrap( D3DRMWRAP_FLAT, NULL, D3DVALUE(0.0), D3DVALUE(0.0), D3DVALUE(0.0), // начало координат наложения D3DVALUE(0.0), D3DVALUE(1.0), D3DVALUE(0.0), // ось z наложения D3DVALUE(0.0), D3DVALUE(0.0), D3DVALUE(1.0), // ось y наложения D3DVALUE(.5) ,D3DVALUE(0.5), // начало координат текстуры D3DDivide(1,w), D3DDivide(1,h), // масштаб текстуры &wrap); wrap->Apply(floorbuilder); wrap->Release(); wrap = 0; //------------- ФРЕЙМ ПОЛА ---------- LPDIRECT3DRMFRAME floorframe; d3drm->CreateFrame(scene, &floorframe); floorframe->AddVisual(floorbuilder); floorframe->Release(); floorframe = 0; //---------------- СЕТКА ВИЛКИ -------- resinfo.hModule = NULL; resinfo.lpName = MAKEINTRESOURCE(IDR_FORKMESH); resinfo.lpType = "MESH"; d3drm->CreateMeshBuilder(&forkbuilder); forkbuilder->Load(&resinfo, NULL, D3DRMLOAD_FROMRESOURCE, NULL, NULL); forkbuilder->SetQuality(D3DRMRENDER_FLAT); //--------------- ФРЕЙМ ВИЛКИ ---------- LPDIRECT3DRMFRAME forkframe; d3drm->CreateFrame(scene, &forkframe); forkframe->SetRotation(scene, D3DVALUE(1), D3DVALUE(1), D3DVALUE(1), D3DVALUE(0.4)); forkframe->SetPosition(scene, D3DVALUE(0), D3DVALUE(6), D3DVALUE(0)); forkframe->AddVisual(forkbuilder); forkframe->AddMoveCallback(AdjustSpin, NULL); //-------------- РАССЕЯННЫЙ СВЕТ -------- LPDIRECT3DRMLIGHT ambientlight; d3drm->CreateLightRGB(D3DRMLIGHT_AMBIENT, D3DVALUE(1),D3DVALUE(1), D3DVALUE(1), &ambientlight); scene->AddLight(ambientlight); ambientlight->Release(); ambientlight = 0; //-------------- ТОЧЕЧНЫЙ СВЕТ ---------- LPDIRECT3DRMLIGHT pointlight; d3drm->CreateLightRGB(D3DRMLIGHT_POINT, D3DVALUE(1),D3DVALUE(1), D3DVALUE(1), &pointlight); LPDIRECT3DRMFRAME lightframe; d3drm->CreateFrame(scene, &lightframe); lightframe->SetPosition(scene, D3DVALUE(0), D3DVALUE(30), D3DVALUE(0)); lightframe->AddLight(pointlight); lightframe->Release(); lightframe = 0; //-------------- ТЕНЬ ---------- LPDIRECT3DRMVISUAL shadow; d3drm->CreateShadow(forkbuilder, pointlight, D3DVALUE(0), box.max.y+D3DVALUE(0.1), D3DVALUE(0), D3DVALUE(0), box.max.y+D3DVALUE(1.0), D3DVALUE(0), (LPDIRECT3DRMVISUAL*)&shadow); forkframe->AddVisual(shadow); shadow->Release(); shadow = 0; forkframe->Release(); forkframe = 0; pointlight->Release(); pointlight = 0; //------------- ПОРТ ПРОСМОТРА -------- d3drm->CreateFrame(scene, &camera); camera->SetPosition(scene, D3DVALUE(0.0), D3DVALUE(25.0), D3DVALUE(-20.0)); camera->SetOrientation(scene, D3DVALUE(0), D3DVALUE(-25), D3DVALUE(20), D3DVALUE(.1), D3DVALUE(1), D3DVALUE(0)); d3drm->CreateViewport(device, camera, 0, 0, device->GetWidth(), device->GetHeight(), &viewport); return TRUE; } |
Функция CreateScene() выполняет следующие десять действий:
Создание сетки, представляющей платформу или пол. Создание текстуры для сетки пола. Создание и применение наложения текстуры для сетки пола. Размещение сетки пола и ее фрейма. Создание сетки вилки. Размещение сетки вилки и ее фрейма. Создание источника рассеянного света. Создание точечного источника света. Создание тени. Создание порта просмотра.Давайте начнем изучение кода с шага 1, создания сетки пола:
D3DRMLOADRESOURCE resinfo; resinfo.hModule = NULL; resinfo.lpName = MAKEINTRESOURCE(IDR_CUBEMESH); resinfo.lpType = "MESH"; d3drm->CreateMeshBuilder(&floorbuilder); floorbuilder->Load(&resinfo, NULL, D3DRMLOAD_FROMRESOURCE, NULL, NULL); floorbuilder->Scale(D3DVALUE(5), D3DVALUE(.05), D3DVALUE(5)); floorbuilder->SetPerspective(TRUE); floorbuilder->SetQuality(D3DRMRENDER_FLAT);Для представления пола используется сетка кубической формы. Она загружается с помощью функции Load() интерфейса Direct3DRMMeshBuilder, а затем делается плоской с помощью функции Scale(). Для сетки разрешается перспективная коррекция и устанавливается плоский метод визуализации.
На следующем этапе осуществляется загрузка текстуры, которая будет наложена на сетку пола:
LPDIRECT3DRMTEXTURE texture; HRSRC texture_id = FindResource(NULL, MAKEINTRESOURCE(IDR_FLOORTEXTURE), "TEXTURE"); d3drm->LoadTextureFromResource(texture_id, &texture); floorbuilder->SetTexture(texture); texture->Release(); texture = 0;Значение, идентифицирующее текстуру, получается с помощью функции FindResource(), входящей в Win32 API. Затем текстура загружается с помощью функции LoadTextureFromResource() и связывается с созданным ранее конструктором сеток с помощью функции SetTexture(). После выполнения этих действий указатель texture освобождается.
На третьем этапе происходит создание и применение наложения текстуры для сетки пола:
D3DRMBOX box; floorbuilder->GetBox(&box); D3DVALUE w = box.max.x - box.min.x; D3DVALUE h = box.max.z - box.min.z; LPDIRECT3DRMWRAP wrap; d3drm->CreateWrap(D3DRMWRAP_FLAT, NULL, D3DVALUE(0.0), D3DVALUE(0.0), D3DVALUE(0.0), // начало координат наложения D3DVALUE(0.0), D3DVALUE(1.0), D3DVALUE(0.0), // ось z наложения D3DVALUE(0.0), D3DVALUE(0.0), D3DVALUE(1.0), // ось y наложения D3DVALUE(.5) ,D3DVALUE(0.5), // начало координат текстуры D3DDivide(1,w), D3DDivide(1,h), // масштаб текстуры &wrap); wrap->Apply(floorbuilder); wrap->Release(); wrap = 0;Функция GetBox() интерфейса Direct3DRMMeshBuilder используется для получения размеров сетки пола. Размеры сетки (и ряд других значений) используются при создании плоского наложения текстуры. Наложение текстуры используется чтобы применить привязанную ранее текстуру к сетке пола с помощью функции Apply() интерфейса Direct3DRMWrap. Затем указатель wrap освобождается. Подробное описание функции CreateWrap() приведено в разделе главы 5, посвященном описанию приложения Wraps.
Теперь создадим фрейм для сетки пола:
LPDIRECT3DRMFRAME floorframe; d3drm->CreateFrame(scene, &floorframe); floorframe->AddVisual(floorbuilder); floorframe->Release(); floorframe = 0;Фрейм создается функцией CreateFrame() интерфейса Direct3DRM. Функция AddVisual() применяется для присоединения конструктора сеток к новому фрейму. После этого указатель floorframe освобожлается поскольку он больше нам не потребуется.
На пятом этапе создается сетка вилки:
resinfo.hModule = NULL; resinfo.lpName = MAKEINTRESOURCE( IDR_FORKMESH ); resinfo.lpType = "MESH"; d3drm->CreateMeshBuilder(&forkbuilder); forkbuilder->Load(&resinfo, NULL, D3DRMLOAD_FROMRESOURCE, NULL, NULL); forkbuilder->SetQuality(D3DRMRENDER_FLAT);Файл сетки вилки является частью ресурсов программы (также как и сетка пола и текстура пола). Сетка загружается функцией Load() интерфейса Direct3DRMMeshBuilder. Для сетки задается плоский метод визуализации. Обратите внимание, что мы не включаем перспективную коррекцию, поскольку на сетку вилки текстура не накладывается.
Теперь пришло время создать фрейм для сетки вилки и настроить его параметры:
LPDIRECT3DRMFRAME forkframe; d3drm->CreateFrame(scene, &forkframe); forkframe->SetRotation(scene, D3DVALUE(1), D3DVALUE(1), D3DVALUE(1), D3DVALUE(0.4)); forkframe->SetPosition(scene, D3DVALUE(0), D3DVALUE(6), D3DVALUE(0)); forkframe->AddVisual(forkbuilder); forkframe->AddMoveCallback(AdjustSpin, NULL);Новому фрейму присваиваются атрибуты вращения, но это является временной мерой, поскольку вскоре функция обратного вызова AdjustSpin() перезапишет эти параметры. Фрейм вилки размещается на шесть единиц выше начала координат (сетка пола размещена в начале координат).
Сетка вилки присоединяется к своему фрейму с помощью функции AddVisual(). Затем с помощью функции AddMoveCallback() интерфейса Direct3DRMFrame устанавливается функция обратного вызова AdjustSpin().
На седьмом этапе создается источник рассеянного света:
LPDIRECT3DRMLIGHT ambientlight; d3drm->CreateLightRGB(D3DRMLIGHT_AMBIENT, D3DVALUE(1),D3DVALUE(1), D3DVALUE(1), &ambientlight); scene->AddLight(ambientlight); ambientlight->Release(); ambientlight = 0;Ранее в этой главе, когда мы изучали способы работы с источником рассеянного света, мы присоединяли источник света непосредственно к корневому фрейму сцены. Здесь мы тоже не будем тратить силы на создание фреймов и воспользуемся корневым фреймом сцены (scene).
Восьмым шагом является создание точечного источника света. Это тот источник света, который будет создавать тень от вилки. Код его создания выглядит так:
LPDIRECT3DRMLIGHT pointlight; d3drm->CreateLightRGB(D3DRMLIGHT_POINT, D3DVALUE(1),D3DVALUE(1), D3DVALUE(1), &pointlight); LPDIRECT3DRMFRAME lightframe; d3drm->CreateFrame(scene, &lightframe); lightframe->SetPosition(scene, D3DVALUE(0), D3DVALUE(30), D3DVALUE(0)); lightframe->AddLight(pointlight); lightframe->Release(); lightframe = 0;Сразу после создания источника света, создается фрейм для его размещения. Фрейм перемещается от начала координат на 30 единиц вверх. Для присоединения источника света к новому фрейму используется функция AddLight().
На следующем этапе выполняется создание объекта Direct3DRMShadow:
LPDIRECT3DRMSHADOW shadow; d3drm->CreateShadow(forkbuilder, pointlight, D3DVALUE(0), box.max.y+D3DVALUE(0.1), D3DVALUE(0), D3DVALUE(0), box.max.y+D3DVALUE(1.0), D3DVALUE(0), (LPDIRECT3DRMVISUAL*)&shadow); forkframe->AddVisual(shadow); shadow->Release(); shadow = 0; forkframe->Release(); forkframe = 0; pointlight->Release(); pointlight = 0;Тень создается с помощью функции CreateShadow() интерфейса Direct3DRM, которая получает девять аргументов. Первый аргумент — это указатель на объект, который будет отбрасывать тень. Второй аргумент — указатель на источник света, который будет использоваться при вычислении формы тени. Следующие шесть аргументов определяют плоскость, на которую будет отбрасываться тень (первые три аргумента задают точку плоскости, а следующие три определяют перпендикулярный плоскости вектор). Последний аргумент, передаваемый функции CreateShadow() интерфейса Direct3DRM, — адрес инициализируемого функцией указателя. По непонятным причинам в качестве последнего аргумента функция CreateShadow() ожидает указатель на интерфейс Direct3DRMVisual, а не указатель на интерфейс Direct3DRMShadow. Поэтому для корректной компиляции программы необходимо указать явное преобразование типа указателя.
На заключительном этапе работы функция CreateScene() создает порт просмотра:
d3drm->CreateFrame(scene, &camera); camera->SetPosition(scene, D3DVALUE(0.0), D3DVALUE(25.0), D3DVALUE(-20.0)); camera->SetOrientation(scene, D3DVALUE(0), D3DVALUE(-25), D3DVALUE(20), D3DVALUE(.1), D3DVALUE(1), D3DVALUE(0)); d3drm->CreateViewport(device, camera, 0, 0, device->GetWidth(), device->GetHeight(), &viewport);Фрейм camera инициализируется функцией CreateFrame() интерфейса Direct3DRM, перемещается вверх и вперед от начала координат, и направляется на начало координат. Функция CreateViewport() применяется для инициализации указателя viewport.
Завершая свою работу, функция CreateScene() возвращает TRUE, чтобы сообщить об успешном завершении создания сцены.
Сцена приложения SpaceDonut создается функцией CreateScene(), код которой приведен в листинге 6.4.
Листинг 6.4. Функция SpaceDonutWin::CreateScene() |
BOOL SpaceDonutWin::CreateScene() { // ------- СЕТКА ПОНЧИКА -------- D3DRMLOADRESOURCE resinfo; resinfo.hModule = NULL; resinfo.lpName = MAKEINTRESOURCE(IDR_DONUTMESH); resinfo.lpType = "MESH"; d3drm->CreateMeshBuilder(&meshbuilder); meshbuilder->Load(&resinfo, NULL, D3DRMLOAD_FROMRESOURCE, NULL, NULL); meshbuilder->SetPerspective(TRUE); meshbuilder->SetColorRGB(D3DVALUE(1), D3DVALUE(1), D3DVALUE(1)); ScaleMesh(meshbuilder, D3DVALUE(20)); //------ ТЕКСТУРА ГЛАЗУРИ -------- LPDIRECT3DRMTEXTURE texture; HRSRC texture_id = FindResource(NULL, MAKEINTRESOURCE(IDR_FROSTINGTEXTURE), "TEXTURE"); d3drm->LoadTextureFromResource(texture_id, &texture); meshbuilder->SetTexture(texture); texture->Release(); texture = 0; //------- НАЛОЖЕНИЕ ---------- D3DRMBOX box; meshbuilder->GetBox(&box); D3DVALUE w = box.max.x - box.min.x; D3DVALUE h = box.max.y - box.min.y; LPDIRECT3DRMWRAP wrap; d3drm->CreateWrap(D3DRMWRAP_FLAT, scene, D3DVALUE(0.0), D3DVALUE(0.0), D3DVALUE(0.0), // начало координат наложения D3DVALUE(0.0), D3DVALUE(1.0), D3DVALUE(0.0), // ось Z наложения D3DVALUE(0.0), D3DVALUE(0.0), D3DVALUE(1.0), // ось Y наложения D3DVALUE(0.5), D3DVALUE(0.5), // начало координат текстуры D3DDivide(1,w), D3DDivide(1,h), // масштаб текстуры &wrap); wrap->Apply(meshbuilder); wrap->Release(); wrap = 0; //------- ФРЕЙМЫ ДЛЯ ПОНЧИКА -------- LPDIRECT3DRMFRAME leftframe; d3drm->CreateFrame(scene, &leftframe); leftframe->SetPosition(scene, D3DVALUE(-12), D3DVALUE(0), D3DVALUE(0)); leftframe->SetOrientation(scene, D3DVALUE(0), D3DVALUE(1), D3DVALUE(0), D3DVALUE(0), D3DVALUE(0), D3DVALUE(1)); leftframe->SetRotation(scene, D3DVALUE(0), D3DVALUE(1), D3DVALUE(0), D3DVALUE(0.1)); leftframe->AddVisual(meshbuilder); leftframe->Release(); leftframe = 0; LPDIRECT3DRMFRAME rightframe; d3drm->CreateFrame(scene, &rightframe); rightframe->SetPosition(scene, D3DVALUE(12), D3DVALUE(0), D3DVALUE(0)); rightframe->SetOrientation(scene, D3DVALUE(0), D3DVALUE(1), D3DVALUE(0), D3DVALUE(0), D3DVALUE(0), D3DVALUE(1)); rightframe->SetRotation(scene, D3DVALUE(0), D3DVALUE(1), D3DVALUE(0), D3DVALUE(-0.1)); rightframe->AddVisual(meshbuilder); rightframe->Release(); rightframe = 0; // --------- ПАРАЛЛЕЛЬНО-ТОЧЕЧНЫЙ СВЕТ -------- LPDIRECT3DRMLIGHT light; d3drm->CreateLightRGB(D3DRMLIGHT_PARALLELPOINT, D3DVALUE(1.0), D3DVALUE(1.0), D3DVALUE(1.0), &light); LPDIRECT3DRMFRAME lightframe; d3drm->CreateFrame(scene, &lightframe); lightframe->AddLight(light); lightframe->Release(); lightframe = 0; light->Release(); light = 0; //------ КАМЕРА ---------- d3drm->CreateFrame(scene, &camera); camera->SetPosition(scene, D3DVALUE(0), D3DVALUE(0), D3DVALUE(-50)); d3drm->CreateViewport(device, camera, 0, 0, device->GetWidth(), device->GetHeight(), &viewport); return TRUE; } |
Функция CreateScene() выполняет следующие действия:
Создает сетку пончика. Создает и загружает текстуру, изображающую глазурь на пончике. Накладывает текстуру глазури на сетку пончика. Создает два фрейма и размещает их с двух сторон от начала координат, присоединяя к каждому фрейму сетку пончика. Создает параллельно точечный источник света и фрейм для него. Создает порт просмотра.Давайте рассмотрим код, выполняющий каждое из перечисленных действий. Мы начнем с первого этапа: создания и загрузки сетки пончика:
D3DRMLOADRESOURCE resinfo; resinfo.hModule = NULL; resinfo.lpName = MAKEINTRESOURCE(IDR_DONUTMESH); resinfo.lpType = "MESH"; d3drm->CreateMeshBuilder(&meshbuilder); meshbuilder->Load(&resinfo, NULL, D3DRMLOAD_FROMRESOURCE, NULL, NULL); meshbuilder->SetPerspective(TRUE); meshbuilder->SetColorRGB(D3DVALUE(1), D3DVALUE(1), D3DVALUE(1)); ScaleMesh(meshbuilder, D3DVALUE(20));Приведенный код аналогичен коду для объекта meshbuilder в других демонстрационных программах. Загружаемая сетка идентифицируется с помощью структуры resinfo. Константа IDR_DONUTMESH — это идентификатор ресурса для импортированного в проект файла сетки. Строка "MESH" определяет категорию ресурса. Указатель meshbuilder инициализируется с помощью функции CreateMeshBuilder() интерфейса Direct3DRM, после чего, для загрузки сетки применяется функция Load(). Вызов функции SetPerspective() разрешает перспективную коррекцию текстур. Последнее действие является необязательным, и выполняется только для того, чтобы улучшить вид сетки после наложения текстуры.
Затем вызывается функция SetColorRGB() интерфейса Direct3DRMMeshBuilder, чтобы окрасить сетку в белый цвет. Это сделано потому, что используемая в данном примере сетка пончика имеет грани различных цветов. Чтобы одновременно установить цвет для всех граней сетки и используется функция SetColorRGB(). После этого вызывается функция ScaleMesh() чтобы задать размер сетки.
Код следующего этапа — создания и загрузка текстуры, изображающей глазурь на пончике — выглядит так:
LPDIRECT3DRMTEXTURE texture; HRSRC texture_id = FindResource(NULL, MAKEINTRESOURCE(IDR_FROSTINGTEXTURE), "TEXTURE"); d3drm->LoadTextureFromResource(texture_id, &texture); meshbuilder->SetTexture(texture); texture->Release(); texture = 0;Переменная texture_id идентифицирует загружаемую текстуру. Функция FindResource() применяется для задания идентификатора ресурса (IDR_FROSTINGTEXTURE) и типа ресурса ("TEXTURE").
Указатель texture инициализируется функцией LoadTextureFromResource(). Новая текстура связывается с объектом meshbuilder с помощью функции SetTexture(). После выполнения описанных действий указатель texture освобождается.
На третьем этапе работы функция SpaceDonutWin::CreateScene() создает и применяет наложение текстуры. Вот как выглядит предназначенный для этого код:
D3DRMBOX box; meshbuilder->GetBox(&box); D3DVALUE w = box.max.x - box.min.x; D3DVALUE h = box.max.y - box.min.y; LPDIRECT3DRMWRAP wrap; d3drm->CreateWrap(D3DRMWRAP_FLAT, scene, D3DVALUE(0.0), D3DVALUE(0.0), D3DVALUE(0.0), // начало координат наложения D3DVALUE(0.0), D3DVALUE(1.0), D3DVALUE(0.0), // ось z наложения D3DVALUE(0.0), D3DVALUE(0.0), D3DVALUE(1.0), // ось y наложения D3DVALUE(0.5), D3DVALUE(0.5), // начало координат текстуры D3DDivide(1,w), D3DDivide(1,h), // масштаб текстуры &wrap); wrap->Apply(meshbuilder); wrap->Release(); wrap = 0;Функция CreateWrap() интерфейса Direct3DRM подробно рассматривалась в главе 5 (в разделе, посвященном приложению Wraps), поэтому здесь данный код не обсуждается. Если говорить коротко, создается плоское наложение текстуры, растягивающее текстуру по размеру сетки. Затем наложение текстуры применяется к сетке с помощью функции Apply() интерфейса Direct3DRMWrap.
На четвертом этапе программа создает и размещает два фрейма. Для идентификации этих фреймов в функции CreateScene() используются переменные leftframe и rightframe. Ниже приведен код для указателя leftframe.
LPDIRECT3DRMFRAME leftframe; d3drm->CreateFrame(scene, &leftframe); leftframe->SetPosition(scene, D3DVALUE(-12), D3DVALUE(0), D3DVALUE(0)); leftframe->SetOrientation(scene, D3DVALUE(0), D3DVALUE(1), D3DVALUE(0), D3DVALUE(0), D3DVALUE(0), D3DVALUE(1)); leftframe->SetRotation(scene, D3DVALUE(0), D3DVALUE(1), D3DVALUE(0), D3DVALUE(0.1)); leftframe->AddVisual(meshbuilder); leftframe->Release(); leftframe = 0;Указатель leftframe инициализируется функцией CreateFrame(). Функция SetPosition() применяется, чтобы переместить фрейм на 12 единиц влево от его местоположения по умолчанию (начала координат).
Расположенный следом вызов функции SetOrientation(), необходим из-за ориентации сетки пончика в файле. Файл сетки создан таким образом, что пончик расположен вдоль оси Y (лежит лицом вниз). Мы же хотим, чтобы пончик был выровнен вдоль оси Z (стоял на боку). Чтобы сделать это, нам необходимо знать, где у сетки находятся лицевой и верхний векторы.
Значение по умолчанию для лицевого вектора объекта равно <0, 0, 1>, а значит лицевая грань объекта расположена в направлении положительных значений по оси Z. Значение по умолчанию для верхнего вектора объекта равно <0, 1, 0>. Из вышесказанного следует, что для того, чтобы сориентировать пончик желаемым образом, достаточно просто поменять местами лицевой и верхний векторы. Благодаря этому лицевая поверхность кольца будет направлена в ту сторону, куда раньше был направлен его верх, и наоборот.
Затем фрейму назначаются атрибуты вращения. Вращение осуществляется вокруг оси Y на 0.1 радиана за каждое обновление экрана. После завершения этих действий сетка пончика присоединяется к фрейму с помощью функции AddVisual() и указатель на фрейм освобождается.
Код для создания и настройки указателя rightframe выглядит аналогично коду для leftframe, поэтому здесь мы отметим только различия:
Фрейм rightframe располагается справа от начала координат. Фрейму присваивается отрицательная величина угла поворота, чтобы два фрейма вращались в противоположных направлениях.Обратите внимание, что в данном приложении один и тот же конструктор сеток присоединяется к разным фреймам. Это весьма полезная техника для отображения и анимации объектов. Однако не забывайте, что хотя на сцене будут присутствовать несколько объектов, в действительности существует только один объект. Если вы измените текстуру или цвет конструктора сеток, это изменение повлияет на все экземпляры данного объекта. Фактически, если вы запустите приложение SpaceDonut и измените параметры визуализации с помощью меню Render, вы увидите, что изменится изображение обоих экземпляров сетки.
На пятом этапе работы функции CreateScene() выполняется создание параллельно-точечного источника света и фрейма для его размещения. Код этой части выглядит так:
LPDIRECT3DRMLIGHT light; d3drm->CreateLightRGB(D3DRMLIGHT_PARALLELPOINT, D3DVALUE(1.0), D3DVALUE(1.0), D3DVALUE(1.0), &light); LPDIRECT3DRMFRAME lightframe; d3drm->CreateFrame(scene, &lightframe); lightframe->AddLight(light); lightframe->Release(); lightframe = 0; light->Release(); light = 0;Функция CreateLightRGB() интерфейса Direct3DRM используется для инициализации указателя light. Константа D3DRMLIGHT_PARALLELPOINT задает тип источника света, а три числовых значения определяют красную, зеленую и синюю составляющие цвета источника света (мы создаем источник белого света).
Далее указатель lightframe инициализируется с помощью функции CreateFrame() интерфейса Direct3DRM. Созданный ранее источник света присоединяется к новому фрейму с помощью функции AddLight(). Затем указатели light и lightframe освобождаются.
На заключительном, шестом этапе работы функции CreateScene() выполняется создание и размещение порта просмотра:
d3drm->CreateFrame(scene, &camera); camera->SetPosition(scene, D3DVALUE(0), D3DVALUE(0), D3DVALUE(-50)); d3drm->CreateViewport(device, camera, 0, 0, device->GetWidth(), device->GetHeight(), &viewport);Также, как и в других демонстрационных программах выполняется инициализация и размещение фрейма camera. В данном случае камера смещается на 50 единиц от начала координат по направлению к зрителю. Указатель viewport инициализируется функцией CreateViewport() интерфейса Direct3DRM.
Приложение SpaceStation — это одна из самых простых демонстрационных программ на CD-ROM. В приложении используется одна сетка и один источник света, и нет никаких функций обратного вызова или текстур. Сцена приложения SpaceStation конструируется функцией SpaceStationWin::CreateScene(), код которой показан в листинге 6.3.
Листинг 6.3. Функция SpaceStationWin::CreateScene() |
BOOL SpaceStationWin::CreateScene() { // ------- СЕТКА -------- D3DRMLOADRESOURCE resinfo; resinfo.hModule = NULL; resinfo.lpName = MAKEINTRESOURCE(IDR_STATIONMESH); resinfo.lpType = "MESH"; d3drm->CreateMeshBuilder(&meshbuilder); meshbuilder->Load(&resinfo, NULL, D3DRMLOAD_FROMRESOURCE, NULL, NULL); ScaleMesh(meshbuilder, D3DVALUE(32)); // ------ ФРЕЙМ СЕТКИ -------- LPDIRECT3DRMFRAME meshframe; d3drm->CreateFrame(scene, &meshframe); meshframe->AddVisual(meshbuilder); meshframe->SetRotation(scene, D3DVALUE(0), D3DVALUE(1), D3DVALUE(0), D3DVALUE(.05)); meshframe->Release(); meshframe = 0; // --------- ИСТОЧНИК СВЕТА И ЕГО ФРЕЙМ -------- LPDIRECT3DRMLIGHT light; d3drm->CreateLightRGB(D3DRMLIGHT_DIRECTIONAL, D3DVALUE(1.00), D3DVALUE(1.00), D3DVALUE(1.00), &light); LPDIRECT3DRMFRAME lightframe; d3drm->CreateFrame(scene, &lightframe); lightframe->AddLight(light); lightframe->SetOrientation(scene, D3DVALUE(-1), D3DVALUE(0), D3DVALUE(1), D3DVALUE(0), D3DVALUE(1), D3DVALUE(0)); light->Release(); light = 0; lightframe->Release(); lightframe = 0; //------ КАМЕРА ---------- d3drm->CreateFrame(scene, &camera); camera->SetPosition(scene, D3DVALUE(0), D3DVALUE(25), D3DVALUE(-50)); camera->SetOrientation(scene, D3DVALUE(0), D3DVALUE(-23), D3DVALUE(50), D3DVALUE(0.7), D3DVALUE(1), D3DVALUE(0)); d3drm->CreateViewport(device, camera, 0, 0, device->GetWidth(), device->GetHeight(), &viewport); return TRUE; } |
Функция CreateScene() выполняет следующие действия:
Создает сетку космической станции. Создает фрейм для сетки космической станции. Создает источник направленного света и фрейм для него. Создает порт просмотра.Код, выполняющий первый из перечисленных этапов, показан ниже:
D3DRMLOADRESOURCE resinfo; resinfo.hModule = NULL; resinfo.lpName = MAKEINTRESOURCE(IDR_STATIONMESH); resinfo.lpType = "MESH"; d3drm->CreateMeshBuilder(&meshbuilder); meshbuilder->Load(&resinfo, NULL, D3DRMLOAD_FROMRESOURCE, NULL, NULL); ScaleMesh(meshbuilder, D3DVALUE(32));Сначала объявляется экземпляр структуры D3DRMLOADRESOURCE, используемой для хранения данных о местоположении сетки космической станции в ресурсах программы. Затем для инициализации указателя meshbuilder вызывается функция CreateMeshBuilder() интерфейса Direct3DRM. Указатель meshbuilder является членом класса SpaceStationWin и поэтому не объявлен в функции CreateScene(). Потом функция Load() интерфейса Direct3DRMMeshBuilder используется для загрузки сетки космической станции. Здесь не осуществляется никакой проверки ошибок, поскольку сетка является частью исполняемого файла программы. Для прошедшей тестирование программы (как данный пример), успешное выполнение функции Load() гарантировано. После возврата из функции Load() вызывается функция ScaleMesh() чтобы придать сетке наиболее подходящий размер.
На следующем (втором) этапе создается фрейм для сетки космической станции:
LPDIRECT3DRMFRAME meshframe; d3drm->CreateFrame(scene, &meshframe); meshframe->AddVisual(meshbuilder); meshframe->SetRotation(scene, D3DVALUE(0), D3DVALUE(1), D3DVALUE(0), D3DVALUE(.05)); meshframe->Release(); meshframe = 0;Как только фрейм создан, представляющий космическую станцию объект meshbuilder присоедияется к новому фрейму (meshframe) с помощью функции AddVisual() интерфейса Direct3DRMFrame. Затем вызывается функция SetRotation() для назначения фрейму атрибутов вращения. Аргументы, передаваемые функции SetRotation() задают поворот сетки вокруг оси Y на 0,5 радиана при каждом обновлении изображения сцены. После выполнения всех этих действий указатель meshframe освобождается и обнуляется.
Далее функция CreateScene() создает источник направленного света и присоединяет его к фрейму:
LPDIRECT3DRMLIGHT light; d3drm->CreateLightRGB(D3DRMLIGHT_DIRECTIONAL, D3DVALUE(1.00), D3DVALUE(1.00), D3DVALUE(1.00), &light); LPDIRECT3DRMFRAME lightframe; d3drm->CreateFrame(scene, &lightframe); lightframe->AddLight(light); lightframe->SetOrientation(scene, D3DVALUE(-1), D3DVALUE(0), D3DVALUE(1), D3DVALUE(0), D3DVALUE(1), D3DVALUE(0)); light->Release(); light = 0; lightframe->Release(); lightframe = 0;Здесь в коде объявлен указатель на интерфейс Direct3DRMLight с именем light, который инициализируется функцией CreateLightRGB() интерфейса Direct3DRM. Константа D3DRMLIGHT_DIRECTIONAL задает тип создаваемого источника света. Следующие три аргумента определяют цвет источника света (в данном случае — белый), а последний аргумент является адресом инициализируемого указателя light.
Теперь пора создать фрейм для источника света. Указатель lightframe инициализируется функцией CreateFrame(), после чего источник света присоединяется к новому фрейму с помощью функции AddLight() интерфейса Direct3DRMFrame.
Для ориентации источника света применяется функция SetOrientation(). Обратите внимание, что лицевой вектор (вектор, определенный первыми тремя аргументами) направлен от начала координат вдоль осей X и Z. Отрицательное значение по оси X указывает, что свет будет падать слева направо (с точки зрения зрителя), а положительное значение по оси Z указывает, что свет будет падать от зрителя в глубину сцены. На рис. 6.6 показано направление света по отношению к сетке космической станции (вид сверху).
Рис. 6.6. Вид сверху на сцену приложения SpaceStation
После выполнения ориентации источника света указатели light и lightframe освобождаются.
Четвертый, и последний фрагмент кода функции SpaceStationWin::CreateScene() создает порт просмотра и настраивает его параметры. Взгляните еще раз на приложение SpaceStation (рис. 6.5). Обратите внимание, что сетка космической станции наклонена. Это не вызвано тем, что мы наклонили фрейм к которому присоединена сетка космической станции. Фактически, мы только лишь назначили фрейму атрибуты вращения, чтобы он поворачивался вокруг оси Y. И все же, если вы запустите приложение SpaceStation, космическая станция будет наклонена и вращаться в том направлении, в котором наклонена.
Это вызвано тем, что вместо того, чтобы наклонять и сетку и вектор вращения, мы наклонили порт просмотра. Код создания порта просмотра для этого приложения выглядит так:
d3drm->CreateFrame(scene, &camera); camera->SetPosition(scene, D3DVALUE(0), D3DVALUE(25), D3DVALUE(-50)); camera->SetOrientation(scene, D3DVALUE(0), D3DVALUE(-23), D3DVALUE(50), D3DVALUE(0.7), D3DVALUE(1), D3DVALUE(0)); d3drm->CreateViewport(device, camera, 0, 0, device->GetWidth(), device->GetHeight(), &viewport);Эта часть кода отличается от других приложений, рассмотренных нами к этому моменту. Во-первых, здесь по-другому позиционируется фрейм camera. Обычно мы перемещали камеру от начала координат в направлении зрителя по прямой, передавая функции SetPosition() в качестве аргументов значения <0, 0, –50>. В этот раз мы перемещаем фрейм camera от начала координат на 50 единиц к зрителю и на 25 единиц вверх. По умолчанию фрейм направлен вдоль оси Z. Это значит, что если мы не изменим ориентацию фрейма camera, то, скорее всего, вообще не увидим сетку космической станции, поскольку поле зрения камеры будет расположено выше сетки.
Вызов функции SetOrientation(), размещенный вслед за вызовом функции SetPosition(), используется для двух целей. Во-первых, он необходим, чтобы направить камеру на сетку. Во вторых, он применяется, чтобы наклонить камеру. Давайте внимательно посмотрим на вызов функции SetOrientation():
camera->SetOrientation(scene, D3DVALUE(0), D3DVALUE(-23), D3DVALUE(50), D3DVALUE(0.7), D3DVALUE(1), D3DVALUE(0));Лицевой вектор (определяемый первыми тремя числовыми аргументами) направлен от начала координат к точке, расположенной на 23 единицы ниже и на 50 единиц дальше. Обратите внимание, что этот вектор почти полностью противоположен вектору, который был использован при позиционировании фрейма камеры. Это не случайность. Фактически, вы всегда можете добиться, чтобы камера была направлена на начало координат, указав в качестве лицевого вектора вектор, противоположный тому, который использовался при позиционировании камеры. Для создания дополнительного визуального эффекта лицевой вектор фрейма камеры был слегка изменен (методом проб и ошибок), поэтому два вектора в программе не являются в точности противоположными.
Теперь давайте взглянем на верхний, или небесный, вектор (определенный последними тремя числовыми аргументами функции SetOrientation()). В других демонстрационных программах мы использовали значения <0, 1, 0>, чтобы задать вектор, направленный вверх вдоль оси Y (отсюда и название вектора). На этот раз мы используем значения <0.7, 1, 0>. Задав вектор, который указывает почти настолько же далеко вправо, насколько вверх, мы наклоняем фрейм камеры вправо.
Последней функцией, вызываемой из CreateScene(), является функция CreateViewport() интерфейса Direct3DRM. Этот вызов функции ничем не отличается от других программ.
Функция CreateScene() приложения Spotlight отвечает за создание сеток и прожектора, а также устанавливает функцию обратного вызова, использующуюся для анимации прожектора. Код функции SpotlightWin::CreateScene() представлен в листинге6.5.
Листинг 6.5. Функция SpotlightWin::CreateScene() |
BOOL SpotlightWin::CreateScene() { //----- СЕТКИ -------- D3DRMLOADRESOURCE resinfo; resinfo.hModule = NULL; resinfo.lpName = MAKEINTRESOURCE(IDR_SPHEREMESH); resinfo.lpType = "MESH"; LPDIRECT3DRMMESHBUILDER builder; d3drm->CreateMeshBuilder(&builder); builder->Load(&resinfo, NULL, D3DRMLOAD_FROMRESOURCE, NULL, NULL); builder->SetColorRGB(D3DVALUE(1.0), D3DVALUE(0.0), D3DVALUE(0.0)); builder->CreateMesh(&mesh1); builder->SetColorRGB(D3DVALUE(0.0), D3DVALUE(1.0), D3DVALUE(0.0)); builder->CreateMesh(&mesh2); builder->SetColorRGB(D3DVALUE(0.0), D3DVALUE(0.0), D3DVALUE(1.0)); builder->CreateMesh(&mesh3); builder->Release(); builder = 0; //----- ФРЕЙМЫ ДЛЯ СЕТОК -------- LPDIRECT3DRMFRAME frame1; d3drm->CreateFrame(scene, &frame1); frame1->SetPosition(scene, D3DVALUE(-2), D3DVALUE(0), D3DVALUE(0)); frame1->AddVisual(mesh1); frame1->Release(); frame1 = 0; LPDIRECT3DRMFRAME frame2; d3drm->CreateFrame(scene, &frame2); frame2->SetPosition(scene, D3DVALUE(2), D3DVALUE(0), D3DVALUE(0)); frame2->AddVisual(mesh2); frame2->Release(); frame2 = 0; LPDIRECT3DRMFRAME frame3; d3drm->CreateFrame(scene, &frame3); frame3->SetPosition(scene, D3DVALUE(0), D3DVALUE(0), D3DVALUE(2)); frame3->AddVisual(mesh3); frame3->Release(); frame3 = 0; //------- ПРОЖЕКТОР ------ d3drm->CreateLightRGB(D3DRMLIGHT_SPOT, D3DVALUE(0.8), D3DVALUE(0.8), D3DVALUE(0.8), &spotlight); OnBeamNormal(); //------ ФРЕЙМ ПРОЖЕКТОРА -------- LPDIRECT3DRMFRAME lightframe; d3drm->CreateFrame(scene, &lightframe); lightframe->SetPosition(scene, D3DVALUE(0), D3DVALUE(10), D3DVALUE(-10)); lightframe->SetOrientation(scene, D3DVALUE(0), D3DVALUE(-1), D3DVALUE(1), D3DVALUE(0), D3DVALUE(1), D3DVALUE(0)); lightframe->AddLight(spotlight); lightframe->AddMoveCallback(MoveLight, NULL); lightframe->Release(); lightframe = 0; //------ КАМЕРА ------ d3drm->CreateFrame(scene, &camera); camera->SetPosition(scene, D3DVALUE(0), D3DVALUE(6), D3DVALUE(-6)); camera->SetOrientation(scene, D3DVALUE(0), D3DVALUE(-1), D3DVALUE(1.1), D3DVALUE(0), D3DVALUE(1), D3DVALUE(0)); d3drm->CreateViewport(device, camera, 0, 0, device->GetWidth(), device->GetHeight(), &viewport); return TRUE; } |
Функция CreateScene() выполняет следующие действия:
Использует интерфейс Direct3DRMMeshBuilder для загрузки сферической сетки и создает три указателя на интерфейс Direct3DRMMesh. Создает и размещает на сцене фрейм для каждой из трех сеток. Создает и настраивает прожектор. Создает фрейм для прожектора. Создает и настраивает порт просмотра.Давайте взглянем на код, реализующий первый этап:
D3DRMLOADRESOURCE resinfo; resinfo.hModule = NULL; resinfo.lpName = MAKEINTRESOURCE(IDR_SPHEREMESH); resinfo.lpType = "MESH"; LPDIRECT3DRMMESHBUILDER builder; d3drm->CreateMeshBuilder(&builder); builder->Load(&resinfo, NULL, D3DRMLOAD_FROMRESOURCE, NULL, NULL); builder->SetColorRGB(D3DVALUE(1.0), D3DVALUE(0.0), D3DVALUE(0.0)); builder->CreateMesh(&mesh1); builder->SetColorRGB(D3DVALUE(0.0), D3DVALUE(1.0), D3DVALUE(0.0)); builder->CreateMesh(&mesh2); builder->SetColorRGB(D3DVALUE(0.0), D3DVALUE(0.0), D3DVALUE(1.0)); builder->CreateMesh(&mesh3); builder->Release(); builder = 0;В самом начале выполняется объявление и инициализация экземпляра структуры D3DRMLOADRESOURCE. Структура resinfo идентифицирует сферическую сетку, которую мы будем использовать.
Далее объявляется указатель на интерфейс Direct3DRMMeshBuilder с именем builder. Для инициализации этого указателя вызывается функция CreateMeshBuilder() интерфейса Direct3DRM. Функция Load() применяется для загрузки сетки. Адрес подготовленной ранее структуры resinfo передается функции Load() в ее первом аргументе.
Затем инициализируются три указателя на интерфейс Direct3DRMMesh. Каждый из них инициализируется с помощью функции CreateMesh() интерфейса Direct3DRMMeshBuilder. После каждого вызова функции CreateMesh() конструктору сеток назначается новый цвет. В результате будут созданы три сетки различных цветов, каждая из которых представлена интерфейсом Direct3DRMMesh. Сразу после создания этих трех сеток указатель builder освобождается.
На втором этапе работы функции CreateScene() выполняется создание и размещение трех фреймов, по одному для каждой из сеток:
LPDIRECT3DRMFRAME frame1; d3drm->CreateFrame(scene, &frame1); frame1->SetPosition(scene, D3DVALUE(-2), D3DVALUE(0), D3DVALUE(0)); frame1->AddVisual(mesh1); frame1->Release(); frame1 = 0; LPDIRECT3DRMFRAME frame2; d3drm->CreateFrame(scene, &frame2); frame2->SetPosition(scene, D3DVALUE(2), D3DVALUE(0), D3DVALUE(0)); frame2->AddVisual(mesh2); frame2->Release(); frame2 = 0; LPDIRECT3DRMFRAME frame3; d3drm->CreateFrame(scene, &frame3); frame3->SetPosition(scene, D3DVALUE(0), D3DVALUE(0), D3DVALUE(2)); frame3->AddVisual(mesh3); frame3->Release(); frame3 = 0;Каждый фрейм создается функцией CreateFrame() интерфейса Direct3DRM, после чего помещается на предназначенное ему место сцены. Третий фрейм помещается за первыми двумя. Каждая из созданных ранее сеток присоединяется к одному из фреймов с помощью функции AddVisual(). После того, как сетка присоединена к фрейму, указатель на фрейм освобождается.
На третьем этапе, код которого показан ниже, создается прожектор:
d3drm->CreateLightRGB(D3DRMLIGHT_SPOT, D3DVALUE(0.8), D3DVALUE(0.8), D3DVALUE(0.8), &spotlight); OnBeamNormal();Вначале для инициализации указателя spotlight вызывается функция CreateLightRGB() интерфейса Direct3DRM. Константа D3DRMLIGHT_SPOT указывает, что мы создаем прожектор. Для красной, зеленой и синей составляющих цвета мы указываем значение 0.8, чтобы цвет нашего источника света был светло-серым.
Затем вызывается функция OnBeamNormal(), являющаяся обработчиком сообщения для пункта Normal меню Beam. Здесь мы вызываем ее для инициализации значений угла светового пятна и угла области освещенности. Чуть позже код функции OnBeamNormal() будет рассмотрен более подробно.
На четвертом этапе создается фрейм к которому будет присоединен прожектор:
LPDIRECT3DRMFRAME lightframe; d3drm->CreateFrame(scene, &lightframe); lightframe->SetPosition(scene, D3DVALUE(0), D3DVALUE(10), D3DVALUE(-10)); lightframe->SetOrientation(scene, D3DVALUE(0), D3DVALUE(-1), D3DVALUE(1), D3DVALUE(0), D3DVALUE(1), D3DVALUE(0)); lightframe->AddLight(spotlight); lightframe->AddMoveCallback(MoveLight, NULL); lightframe->Release(); lightframe = 0;Указатель lightframe инициализируется функцией CreateFrame() интерфейса Direct3DRM. Функция SetPosition() применяется, чтобы поместить фрейм в точку, расположенную на 10 единиц выше и на 10 единиц дальше начала координат. Затем вызывается функция SetOrientation(), чтобы направить фрейм на начало координат.
После этого созданный ранее прожектор присоединяется к новому фрейму с помощью функции AddLight(). Кроме того, с помощью функции AddMoveCallback() устанавливается функция обратного вызова MoveLight(). После выполнения всех описанных действий указатель lightframe освобождается.
На заключительном, пятом, этапе работы функции CreateScene() осуществляется создание порта просмотра:
d3drm->CreateFrame(scene, &camera); camera->SetPosition(scene, D3DVALUE(0), D3DVALUE(6), D3DVALUE(-6)); camera->SetOrientation(scene, D3DVALUE(0), D3DVALUE(-1), D3DVALUE(1.1), D3DVALUE(0), D3DVALUE(1), D3DVALUE(0)); d3drm->CreateViewport(device, camera, 0, 0, device->GetWidth(), device->GetHeight(), &viewport);Сначала инициализируется фрейм camera. Затем этот фрейм перемещается от начала координат на шесть единиц вверх и на шесть единиц ближе к зрителю. Функция SetOrientation() применяется, чтобы направить лицевую грань фрейма на начало координат. И, в самом конце, вызывается функция CreateViewport() интерфейса Direct3DRM для инициализации указателя viewport.
В функции CreateScene() приложения Spotlight выполняется установка функции обратного вызова MoveLight(), осуществляющей изменение ориентации фрейма прожектора при каждом обновлении изображения на экране. Код функции MoveLight() выглядит следующим образом:
void SpotlightWin::MoveLight(LPDIRECT3DRMFRAME frame, void*, D3DVALUE) { static const D3DVALUE LIM = D3DVALUE(0.3); static D3DVALUE xi = D3DVALUE(0.03); static D3DVALUE yi = D3DVALUE(0.04); static D3DVALUE x, y; if (x < -LIM || x > LIM) xi = -xi; if (y < -LIM || y > LIM) yi = -yi; x += xi; y += yi; frame->SetOrientation(NULL, x, y-1, D3DVALUE(1), D3DVALUE(0), D3DVALUE(1), D3DVALUE(0)); }Так же, как в приложении Jade из главы 5, в приложении Spotlight для выполнения анимации используется простой алгоритм «подскакивающего мяча» (более сложные приемы анимации мы изучим в главе 7). Объявленные в функции MoveLight() статические переменные применяются для отслеживания и ограничения изменений ориентации фрейма. После вычисления нового значения ориентации фрейма, оно устанавливается с помощью функции SetOrientation().
Вы уже получили немного информации об источниках света в главах 4 и 5. В этой главе мы детально обсудим каждый, поддерживаемый в Direct3D, тип источника света. Кроме того, мы поговорим о тенях.
При обсуждении большинства тем будут использоваться демонстрационные программы с CD-ROM. В данной главе мы изучим следующие приложения:
Firefly SpaceStation SpaceDonut Spotlight ShadowЛьвиная доля функциональности нашего проекта сосредоточена в классе AmbientLightWin. Определение этого класса выглядит следующим образом:
class AmbientLightWin : public RMWin { public: AmbientLightWin(); BOOL CreateScene(); protected: //{{AFX_MSG(AmbientLightWin) afx_msg void OnRenderWireframe(); afx_msg void OnRenderFlat(); afx_msg void OnRenderGouraud(); afx_msg void OnUpdateRenderFlat(CCmdUI* pCmdUI); afx_msg void OnUpdateRenderGouraud(CCmdUI* pCmdUI); afx_msg void OnUpdateRenderWireframe(CCmdUI* pCmdUI); //}}AFX_MSG DECLARE_MESSAGE_MAP() private: LPDIRECT3DRMMESHBUILDER meshbuilder; };Класс AmbientLightWin наследуется от класса RMWin. В нем объявлены две открытые функции: конструктор и функция CreateScene(). Конструктор инициализирует единственную переменную класса:
AmbientLightWin::AmbientLightWin() { meshbuilder=0; }Переменная meshbuilder является указателем на интерфейс Direct3DRMMeshBuilder и будет использоваться в приложении в качестве указателя на сетку. Указатель meshbuilder инициализируется в функции CreateScene(), которую мы рассмотрим чуть позже.
Кроме того, в классе объявлены шесть защищенных функций, которые используются в качестве обработчиков событий. Первые три функции, OnRenderWireframe(), OnRenderFlat() и OnRenderGouraud(), вызываются, когда пользователь выбирает одну из команд в меню Render. Оставшиеся три функции вызываются MFC каждый раз перед отображением меню. Эти функции применяются для отображения в меню флажка рядом с используемым в данный момент методом визуализации. Эти функции также будут рассмотрены чуть позже.
Основная функциональность приложения Firefly реализована в классе FireflyWin, наследуемом от класса RMWin. Определение класса выглядит следующим образом:
class FireflyWin : public RMWin { public: FireflyWin(); BOOL CreateScene(); protected: //{{AFX_MSG(FireflyWin) afx_msg void OnRenderWireframe(); afx_msg void OnRenderFlat(); afx_msg void OnRenderGouraud(); afx_msg void OnUpdateRenderFlat(CCmdUI* pCmdUI); afx_msg void OnUpdateRenderGouraud(CCmdUI* pCmdUI); afx_msg void OnUpdateRenderWireframe(CCmdUI* pCmdUI); //}}AFX_MSG DECLARE_MESSAGE_MAP() private: LPDIRECT3DRMMESHBUILDER chalicebuilder; };В классе FireflyWin объявлены две открытых функции: конструктор и функция CreateScene(). Конструктор обнуляет переменную класса chalicebuilder:
FireflyWin::FireflyWin() { chalicebuilder = 0; }Шесть защищенных функций обеспечивают в приложении Firefly поддержку меню Render.
Основная функциональность приложения Shadow предоставляется классом ShadowWin:
class ShadowWin : public RMWin { public: ShadowWin(); BOOL CreateScene(); protected: //{{AFX_MSG(ShadowWin) afx_msg void OnRenderWireframe(); afx_msg void OnRenderFlat(); afx_msg void OnRenderGouraud(); afx_msg void OnUpdateRenderFlat(CCmdUI* pCmdUI); afx_msg void OnUpdateRenderGouraud(CCmdUI* pCmdUI); afx_msg void OnUpdateRenderWireframe(CCmdUI* pCmdUI); //}}AFX_MSG DECLARE_MESSAGE_MAP() private: static void AdjustSpin(LPDIRECT3DRMFRAME frame, void*, D3DVALUE); private: LPDIRECT3DRMMESHBUILDER floorbuilder; LPDIRECT3DRMMESHBUILDER forkbuilder; };Класс предоставляет две открытых функции: конструктор и функцию CreateScene(). Кроме того, объявлены шесть функций для реализации меню Render, которые уже рассматривались нами при изучении других приложений.
Функция AdjustSpin() является функцией обратного вызова, используемой для изменения параметров вращения вилки во время работы приложения. Функция обратного вызова устанавливается в функции CreateScene().
Также объявлены две переменных класса. Обе они являются указателями на интерфейс Direct3DRMMeshBuilder. Эти указатели используются при создании сцены, а также в обработчиках событий меню Render.
Основная функциональность приложения SpaceDonut сосредоточена в классе SpaceDonutWin, объявление которого выглядит следующим образом:
class SpaceDonutWin : public RMWin { public: SpaceDonutWin(); BOOL CreateScene(); protected: //{{AFX_MSG(SpaceDonutWin) afx_msg void OnRenderWireframe(); afx_msg void OnRenderFlat(); afx_msg void OnRenderGouraud(); afx_msg void OnUpdateRenderFlat(CCmdUI* pCmdUI); afx_msg void OnUpdateRenderGouraud(CCmdUI* pCmdUI); afx_msg void OnUpdateRenderWireframe(CCmdUI* pCmdUI); //}}AFX_MSG DECLARE_MESSAGE_MAP() private: LPDIRECT3DRMMESHBUILDER meshbuilder; };Подобно всем демонстрационным приложениям на CD-ROM, программа SpaceDonut наследует свой класс окна от класса RMWin. Класс SpaceDonutWin предоставляет две открытых функции: конструктор и функцию CreateScene(). Конструктор отвечает за инициализацию любых членов данных, определенных в классе (в нашем случае, единственной переменной). Функция CreateScene() создает сцену приложения.
Шесть защищенных функций реализуют поддержку приложением меню Render. Для изменения параметров сетки эти функции используют переменную meshbuilder.
Основная функциональность приложения SpaceStation сосредоточена в классе SpaceStationWin. Определение этого класса выглядит следующим образом:
class SpaceStationWin : public RMWin { public: SpaceStationWin(); BOOL CreateScene(); protected: //{{AFX_MSG(SpaceStationWin) afx_msg void OnRenderWireframe(); afx_msg void OnRenderFlat(); afx_msg void OnRenderGouraud(); afx_msg void OnUpdateRenderFlat(CCmdUI* pCmdUI); afx_msg void OnUpdateRenderGouraud(CCmdUI* pCmdUI); afx_msg void OnUpdateRenderWireframe(CCmdUI* pCmdUI); //}}AFX_MSG DECLARE_MESSAGE_MAP() private: LPDIRECT3DRMMESHBUILDER meshbuilder; };В классе SpaceStationWin объявлены две открытые функции: конструктор и функция CreateScene(). Конструктор обнуляет переменную класса meshbuilder:
SpaceStationWin::SpaceStationWin() { meshbuilder=0; }Шесть защищенных функций предоставляют поддержку меню Render приложения SpaceStation. Закрытый указатель meshbuilder используется защищенными функциями для изменения параметров сетки.
Приложение Spotlight использует класс RMWin в качестве базового для своего собственного класса SpotlightWin:
class SpotlightWin : public RMWin { public: SpotlightWin(); BOOL CreateScene(); protected: //{{AFX_MSG(SpotlightWin) afx_msg void OnRenderWireframe(); afx_msg void OnRenderFlat(); afx_msg void OnRenderGouraud(); afx_msg void OnUpdateRenderFlat(CCmdUI* pCmdUI); afx_msg void OnUpdateRenderGouraud(CCmdUI* pCmdUI); afx_msg void OnUpdateRenderWireframe(CCmdUI* pCmdUI); afx_msg void OnBeamNormal(); afx_msg void OnBeamNarrow(); afx_msg void OnBeamWide(); afx_msg void OnUpdateBeamNormal(CCmdUI* pCmdUI); afx_msg void OnUpdateBeamNarrow(CCmdUI* pCmdUI); afx_msg void OnUpdateBeamWide(CCmdUI* pCmdUI); //}}AFX_MSG DECLARE_MESSAGE_MAP() private: static void MoveLight(LPDIRECT3DRMFRAME frame, void* arg, D3DVALUE delta); private: LPDIRECT3DRMMESH mesh1, mesh2, mesh3; LPDIRECT3DRMLIGHT spotlight; int beamwidth; };Определение класса SpotlightWin слегка отличается от определений классов других демонстрационных программ, рассмотренных в этой главе. Первым и основным отличием является большее число защищенных функций. Шесть защищенных функций подобны тем, которые используются в других демонстрационных программах, и предназначены для реализации меню Render. Новые функции, которые мы рассмотрим чуть позже, предназначены для реализации меню Beam.
Другим отличием является объявление функции MoveLight(), являющейся функцией обратного вызова, используемой для анимации прожектора.
Члены данных класса также отличаются. В предыдущих демонстрационных программах использовался единственный указатель на интерфейс Direct3DRMMeshBuilder. В классе SpotlightWin объявлены три указателя на интерфейс Direct3DRMMesh. Эти три указателя применяются для доступа к трем, используемым в приложении сферическим сеткам.
В классе также объявлен указатель на интерфейс Direct3DRMLight. Этот указатель используется функциями, обрабатывающими команды меню Beam. Кроме того, эти функции используют переменную класса beamwidth для хранения текущих параметров прожектора.
Прежде чем говорить о различных типах источников света, необходимо рассмотреть различия между методом визуализации и цветовой моделью, и обсудить, как они влияют на источники света.
Метод визуализации (каркасный, плоский, Гуро и т.д.) — это техника, используемая для формирования конечного результата работы программы. Методы визуализации иногда называют методами затенения или освещения.
Цветовая модель (RGB или Ramp) не зависят от метода визуализации. Цветовая модель RGB поддерживает цветные источники света, а модель Ramp — нет. Модель Ramp обычно дает выигрыш в быстродействии по сравнению с моделью RGB.
Методы визуализации и цветовые модели не зависят друг от друга; любой метод визуализации может использоваться совместно с любой цветовой моделью. Однако и методы визуализации и цветовые модели влияют на то, как будут восприниматься источники света. Давайте рассмотрим различия:
Цветовая модель Ramp не поддерживает цветные источники света. Это не означает, что цветные источники света будут игнорироваться, просто в цветовой модели Ramp цвет источника света преобразуется в оттенки серого. Источники света не оказывают никакого эффекта при использовании каркасного и неосвещенного методов визуализации. В обоих этих методах используется только цвет грани и цвет наложенной на грань текстуры. Источники света игнорируются. Плоский метод визуализации и метод Гуро учитывают источники света, но делают это по-разному. Плоский метод визуализации использует расположенные в сцене источники света для определения одного цвета для каждой грани. Метод Гуро использует источники света для определения цвета для каждой вершины. Затем цвета вершин применяются при вычислении цветов, используемых для представления каждой из граней.Многие демонстрационные программы на CD-ROM предоставляют команды меню, позволяющие изменять метод визуализации во время работы приложения. Это позволяет легко увидеть различия между методами.
Большинство демонстрационных программ на CD-ROM для увеличения быстродействия используют цветовую модель Ramp. Тем не менее, в некоторых приложениях из этой главы применяется цветовая модель RGB, чтобы продемонстрировать работу с цветными источниками света.
Направленный свет является противоположностью точечного света. В то время, как точечный свет имеет местоположение, но не имеет ориентации, направленный свет имеет ориентацию, но не имеет местоположения. Источник направленного света производит параллельные световые лучи, поэтому нет единственной точки, из которой бы эти лучи выходили.
Направленный свет применяется для представления удаленных источников света, таких, как солнце. Поскольку все лучи в направленном свете параллельны, для визуализации сцен, содержащих источники направленного света, требуется меньшее количество вычислений. Направленный свет более эффективен, чем точечный, потому что все лучи света идут в одном и том же направлении.
Источник параллельно-точечного света подобен источнику направленного света, но испускает лучи в двух направлениях. На параллельно-точечный источник света оказывают влияние и его местоположение, и его ориентация. На быстродействие приложения такой источник света влияет так же как источник направленного света.
В приложении Firefly точечный источник света применяется для освещения чаши. Чаша помешена в начало координат, а источник света перемещается вокруг нее. Вместе с источником света перемещается небольшая сферическая сетка, что создает иллюзию, будто именно эта сфера (светлячок) испускает лучи и освещает чашу. Помните, что сфера добавлена только ради визуального эффекта. Вы не можете увидеть точечный источник света (как и любой другой источник света)— вы можете видеть только испускаемый им свет. Вид окна приложения Firefly показан на рис. 6.4.
Рис. 6.4. Приложение Firefly
Если вы запустите приложение Firefly, то увидите, что местоположение источника света влияет на освещение чаши. С помощью меню Render можно изменить метод визуализации. Заметьте, что при использовании каркасного режима визуализации источник света не оказывает никакого влияния на изображение сцены.
В приложении Firefly демонстрируются следующие технологии:
Использование точечного источника света. Представление источника света с помощью сетки. Использование нескольких методов визуализации в одной сцене. Выполнение анимации с помощью пустых фреймов.Рассматривая код приложения Firefly мы подробнее обсудим каждую из этих технологий.
Приложение Shadow создает сцену, в которой вилка висит над прямоугольной площадкой. Источник света освещает вилку и площадку, и вилка отбрасывает тень (или, кажется, что отбрасывает). Окно приложения Shadow показано на рис. 6.10.
Рис. 6.10. Приложение Shadow
Приложение Shadow демонстрирует следующие технологии:
Использование интерфейса Direct3DRMShadow. Использование функции обратного вызова для изменения параметров анимации во время выполнения программы. Генерация случайных векторов с помощью функции D3DRMVectorRandom().В приложении SpaceDonut параллельно-точечный источник света размещен между двумя пончиками с малиновой (а может быть черничной) глазурью. Окно приложения SpaceDonut показано на рис.6.7.
Рис. 6.7. Приложение SpaceDonut
Приложение SpaceDonut демонстрирует применение следующих технологий:
Использование параллельно-точечного источника света. Использование атрибутов вращения для анимации объектов. Использование функции SetColorRGB() интерфейса Direct3DRMMeshBuilder. Присоединение одной сетки к нескольким фреймам. Использование команд меню для изменения параметров визуализации во время работы программы.Поскольку направленный свет хорошо подходит для представления удаленных источников света, следующая демонстрация будет проведена в космосе. Приложение SpaceStation отображает космическую станцию, освещенную источником направленного света. Вид окна этого приложения показан на рис.6.5.
Рис. 6.5. Приложение SpaceStation
Приложение SpaceStation демонстрирует следующие технологии:
Использование источника направленного света. Анимация с использованием атрибутов вращения. Изменение метода визуализации сетки во время работы приложения. Изменение верхнего вектора камеры для ее наклона.В приложении Spotlight анимированный прожектор освещает три сферических сетки. Угол светового пятна и угол зоны освещенности прожектора могут быть изменены во время работы программы с помощью команд меню Beam. Окно приложения Spotlight изображено на рис. 6.9.
Рис. 6.9. Приложение Spotlight
Приложение Spotlight демонстрирует использование следующих технологий:
Применение источника зонального света (прожектора). Использование функции обратного вызова для анимации источника света. Использование интерфейса Direct3DRMMesh чтобы избежать падения быстродействия, связанного с лишней работой, выполняемой при использовании интерфейса Direct3DRMMeshBuilder. Изменение параметров визуализации объекта Direct3DRMMesh во время работы программы. Изменение во время работы программы угла светового пятна и угла зоны освещенности прожектора.Чтобы продемонстрировать альтернативный способ, отличный от использовавшегося в других демонстрационных программах, в приложении Spotlight вместо интерфейса Direct3DRMMeshBuilder используется интерфейс Direct3DRMMesh. В этом нет ничего необычного, так как приложение Spotlight требует повышенного быстродействия, что и достигается использованием интерфейса Direct3DRMMesh.
Прожектор излучает свет в форме конуса. Местоположение источника света влияет на местоположение вершины конуса, а ориентация источника света влияет на местоположение основания светового конуса.
В действительности, свет, испускаемый прожектором, лучше описать в терминах двух конусов: внутреннего и внешнего. Внешний конус определяет область, освещаемую прожектором. За пределами внешнего конуса свет отсутствует. Внутренний конус определяет область с максимальной интенсивностью света. В области между этими двумя конусами освещенность постепенно уменьшается от максимальной интенсивности во внутреннем конусе до отсутствия света за пределами внешнего конуса.
Внутренний конус называется конусом светового пятна (umbra cone) и определяется углом светового пятна (umbra angle). Внешний конус называется конусом зоны освещенности (penumbra cone) и определяется углом зоны освещенности (penumbra angle). Эти конусы показаны на рис.6.8.
Рис. 6.8. Анатомия прожектора
Интерфейс Direct3DRMLight предоставляет следующие функции для настройки и контроля угла светового пятна и угла зоны освещенности:
GetPenumbra() GetUmbra() SetPenumbra() SetUmbra()Каждое из демонстрационных приложений, рассмотренных в этой главе к данному моменту, было предназначено, чтобы ознакомить вас с определенным типом источников света. Чтобы ясно показать особенности, в каждом приложении использовался только один источник света.
Это не означает, что в своих программах вы не можете использовать более одного источника света. Источники света могут применяться в любой комбинации, и, кроме того, в программе можно использовать несколько экземпляров источника света одного и того же типа.
В реальном мире рассеянный свет является отраженным светом. Рассеянный свет— это свет, который был отражен и рассеян в окружающем пространстве. Например, вечером рассеянный свет позволяет нам видеть окружающие предметы уже после того, как солнце скрылось. Лучи солнца рассеиваются в атмосфере и обеспечивают ровное, слабое освещение не имеющее направления и видимого источника.
Свет, который действительно не имеет направления и источника, в реальном мире не существует. Отраженные и рассеянные в атмосфере солнечные лучи приблизительно похожи на рассеянный свет — в действительности солнечный свет распространяется в конкретном направлении и у него есть неявный источник (солнце). В Direct3D рассеянный свет не имеет ни направления, ни источника.
С технической точки зрения рассеянный свет подобен остальным источникам света. Он представляется интерфейсом Direct3DRMLight и, чтобы стать видимым, должен быть присоединен к фрейму. Однако источник рассеянного света игнорирует местоположение и ориентацию того фрейма, к которому он подсоединен.
Данная глава называется «Источники света и тени», и мы уже обсудили пять типов источников света, поддерживаемых Direct3D, теперь настало время поговорить о тенях.
Однако перед тем, как начать, следует обратить внимание, что в Direct3D источники света и тени почти не влияют друг на друга. В Direct3D тень— это объект, который добавляется к сцене чтобы имитировать поведение реальной тени.
Точечный источник света испускает лучи света из одной точки во всех направлениях. Он подобен источникам света, к которым мы привыкли в обычной жизни, например, электрической лампочке. Привычное поведение точечного источника света делает его естественным выбором для многих ситуаций. Точечный источник света зависит от местоположения, но игнорирует ориентацию (поскольку световые лучи испускаются во всех направлениях).
К сожалению, за простоту использования и привычное поведение точечного источника света приходится платить снижением производительности. Поскольку лучи света испускаются во всех направлениях, для визуализации сцены, содержащей точечные источники света, требуется огромное количество вычислений.