netlib.narod.ru | < Назад | Оглавление | Далее > |
Первое, что нам следует сделать — наложить текстуру на объект. Наша стратегия состоит в следующем: возьмем демонстрационную программу, аналогичную проекту, который создает по умолчанию мастер Direct3D AppWizard, и наложим текстуру на объект, отображаемый демонстрационным приложением.
Демонстрационное приложение Jade отображает сетку, имеющую форму букв D3D и накладывает на эту сетку нефритовую текстуру. Сетка анимирована, чтобы зритель мог рассмотреть ее с разных сторон. Окно приложения Jade показано на рис. 5.3.
Рис. 5.3. Приложение Jade
В приложении Jade для наложения текстуры на сетку используется плоское покрытие. Это означает, что текстура накладывается на сетку прямолинейно, без изгибов или деформирования. Поэтому, когда направление взгляда на сетку совпадает с направлением наложения текстуры, последняя выглядит точно так же, как в графическом редакторе. Текстура, используемая в приложении Jade показана на рис. 5.4.
Рис. 5.4. Текстура, используемая в приложении Jade
Приложение Jade демонстрирует следующие технологии:
Большинство функциональных возможностей приложения Jade реализовано в классе JadeWin. Класс JadeWin наследует базовую функциональность Direct3D от класса RMWin, который был подробно описан в главе 4. Определение класса JadeWin выглядит следующим образом:
class JadeWin : public RMWin { public: JadeWin(); BOOL CreateScene(); static void MoveFrame(LPDIRECT3DRMFRAME frame, void*, D3DVALUE); protected: //{{AFX_MSG(JadeWin) 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; };
Здесь объявлены три открытые функции: конструктор, функция CreateScene() и функция обратного вызова с именем MoveFrame(). Конструктор применяется для инициализации единственной переменной класса:
JadeWin::JadeWin() { meshbuilder=0; }
Функция CreateScene() создает используемые в сцене объекты и настраивает их параметры. Функция MoveFrame() объявлена как статическая, чтобы функция CreateScene() могла установить ее в качестве функции обратного вызова. MoveFrame() используется для изменения ориентации фрейма к которому в демонстрационном приложении присоединена сетка. Это заставляет сетку менять ориентацию при каждом обновлении экрана.
Шесть защищенных функций являются обработчиками событий и предназначены для поддержки функционирования меню Render приложения Jade.
Код функции CreateScene() приложения Jade приведен в листинге 5.1.
Листинг 5.1. Функция JadeWin::CreateScene() |
BOOL JadeWin::CreateScene() { HRESULT r; //------- ФОН -------- scene->SetSceneBackgroundRGB(D3DVALUE(.2), D3DVALUE(.2), D3DVALUE(.2)); //-------- СЕТКА -------- D3DRMLOADRESOURCE resinfo; resinfo.hModule = NULL; resinfo.lpName = MAKEINTRESOURCE(IDR_D3DMESH); resinfo.lpType = "MESH"; d3drm->CreateMeshBuilder(&meshbuilder); meshbuilder->SetPerspective(TRUE); r = meshbuilder->Load(&resinfo, NULL, D3DRMLOAD_FROMRESOURCE, NULL, NULL); if (r != D3DRM_OK) { TRACE("meshbuilder->Load() failed\n"); return FALSE; } ScaleMesh(meshbuilder, D3DVALUE(35)); //-------- ТЕКСТУРА -------- LPDIRECT3DRMTEXTURE texture; HRSRC texture_id = FindResource(NULL, MAKEINTRESOURCE(IDR_JADETEXTURE), "TEXTURE"); r = d3drm->LoadTextureFromResource(texture_id, &texture); if (r != D3DRM_OK) { TRACE("d3drm->LoadTextureFromResource() failed\n"); return FALSE; } 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(0.0), D3DVALUE(1.0), // ось z наложения D3DVALUE(0.0), D3DVALUE(1.0), D3DVALUE(0.0), // ось y наложения D3DVALUE(0.5), D3DVALUE(0.5), // начало координат текстуры D3DDivide(1,w), D3DDivide(1,h), // масштаб текстуры &wrap); wrap->Apply(meshbuilder); wrap->Release(); wrap = 0; //------- ФРЕЙМ СЕТКИ ---------- LPDIRECT3DRMFRAME meshframe; d3drm->CreateFrame(scene, &meshframe); meshframe->AddVisual(meshbuilder); meshframe->AddMoveCallback(MoveFrame, NULL); meshframe->Release(); //---------- ОСВЕЩЕНИЕ ----------- LPDIRECT3DRMLIGHT light; d3drm->CreateLightRGB(D3DRMLIGHT_AMBIENT, D3DVALUE(1),D3DVALUE(1), D3DVALUE(1), &light); scene->AddLight(light); light->Release(); light = 0; //---------- КАМЕРА ----------- d3drm->CreateFrame(scene, &camera); camera->SetPosition(scene, D3DVALUE(0.0), D3DVALUE(0.0), D3DVALUE(-50.0)); d3drm->CreateViewport( device, camera, 0, 0, device->GetWidth(), device->GetHeight(), &viewport); return TRUE; } |
Функция CreateScene() выполняет следующие действия:
Давайте рассмотрим каждый из этих этапов по отдельности. Первое, что делает функция CreateScene() — это изменение цвета фона сцены с помощью функции SetSceneBackgroundRGB():
scene->SetSceneBackgroundRGB(D3DVALUE(.2), D3DVALUE(.2), D3DVALUE(.2));
Функция SetSceneBackgroundRGB() получает три аргумента, определяющие красную, зеленую и синюю составляющие нового цвета фона сцены. Для нашего примера мы выбрали темно-серый цвет.
Затем для загрузки сетки и настройки ее параметров используется интерфейс Direct3DRMMeshBuilder:
D3DRMLOADRESOURCE resinfo; resinfo.hModule = NULL; resinfo.lpName = MAKEINTRESOURCE(IDR_D3DMESH); resinfo.lpType = "MESH"; d3drm->CreateMeshBuilder(&meshbuilder); meshbuilder->SetPerspective(TRUE); r = meshbuilder->Load(&resinfo, NULL, D3DRMLOAD_FROMRESOURCE, NULL, NULL); if (r != D3DRM_OK) { TRACE("meshbuilder->Load() failed\n"); return FALSE; } ScaleMesh(meshbuilder, D3DVALUE(35));
Для загрузки сетки из ресурсов программы необходимо подготовить структуру D3DRMLOADRESOURCE, содержащую три поля: hModule, lpName и lpType. Поле hModule идентифицирует модуль, содержащий требуемый ресурс. Это полезно, если ресурсы загружаются из другого исполняемого модуля, но поскольку в нашем случае требуемый ресурс относится к той же программе, которая вызывает функцию, мы укажем NULL. Поле lpName хранит значение, идентифицирующее требуемый ресурс. Поле lpType задает тип ресурса.
СОВЕТ |
Тип ресурса MESH. Нет ничего необычного в типе ресурса MESH (Visual C++ ничего не знает о сетках Direct3D). Приложения из этой книги хранят сетки в группе ресурсов с именем MESH, чтобы отделить ресурсы сеток от других ресурсов. |
После того, как подготовлена структура D3DRMLOADRESOURCE, функция CreateMeshBuilder() интерфейса Direct3DRM инициализирует указатель meshbuilder. Функция SetPerspective() разрешает коррекцию перспективы для новой сетки. Коррекция перспективы предотвращает смещение текстуры во время анимации (чтобы увидеть этот эффект, закоменнтируйте данную строку кода и откомпилируйте пример заново).
Затем вызывается функция Load(), которой в качестве первого аргумента передается указатель на подготовленную структуру D3DRMLOADRESOURCE. Третий аргумент функции Load() (флаг D3DRMLOAD_FROMRESOURCE) указывает Direct3D, что загрузка происходит из ресурсов программы, а не из файла на диске. Возвращаемое функцией Load() значение, говорит о том, успешно ли завершилась функция. Если при выполнении функции Load() произошла ошибка, CreateScene() возвращает FALSE.
СОВЕТ |
Проверка возвращаемых значений. Большинство функций Direct3D возвращают значение типа HRESULT, указывающее на состояние функции. Как правило, не требуется проверять возвращаемое значение для каждой функции, но рекомендуется проверять возвращаемые значения для тех функций, которые могут завершиться неудачно из-за внешних причин. Например, функции загрузки файла часто завершаются неудачно, из-за того, что не могут найти требуемый файл. В данном сучае неудачное завершение функции Load() маловероятно, поскольку сетка является частью EXE-файла программы. |
Затем используется функция ScaleMesh() для указания идеального размера сетки. Первый аргумент ScaleMesh() — Direct3DRMMeshBuilder представляет масштабируемую сетку. Второй аргумент — это желаемый размер сетки.
На следующем (третьем) этапе выполняется загрузка текстуры. Загрузка тексуры из ресурсов программы слегка отличается от загрузки сеток. Для загрузки сеток применяется функция Load(), независимо от того, где находится сетка: в файле на диске или в ресурсах программы. Для загрузки текстуры из ресурсов и из файла применяются различные функции. Мы воспользуемся функцией LoadTextureFromResource() (вместо функции LoadTexture()):
LPDIRECT3DRMTEXTURE texture; HRSRC texture_id = FindResource(NULL, MAKEINTRESOURCE(IDR_JADETEXTURE), "TEXTURE"); r = d3drm->LoadTextureFromResource(texture_id, &texture); if (r != D3DRM_OK) { TRACE("d3drm->LoadTextureFromResource() failed\n"); return FALSE; } meshbuilder->SetTexture(texture); texture->Release(); texture = 0;
Функция LoadTextureFromResource() получает два аргумента: экземпляр структуры HRSRC и адрес указателя на интерфейс Direct3DRMTexture. HRSRC — это структура Win32, подобная структуре D3DRMLOADRESOURCE используемой функцией Load() интерфейса конструктора сеток. Структура HRSRC инициализируется функцией FindResource(). Три аргумента FindResource() идентифицируют загружаемый ресурс. Первый аргумент — это дескриптор модуля. Поскольку текстура загружается из того же исполняемого файла в котором содержится функция CreateScene(), мы используем значение NULL. Второе значение — это идентификатор загружаемого ресурса. Третий аргумент указывает тип ресурса.
После инициализации структуры HRSRC вызывается функция LoadTextureFromResource(). Если функция LoadTextureFromResource() завершается с ошибкой, CreateScene() возвращает FALSE (после того, как макрос TRACE выведет сообщение). Если функция завершилась успешно, то текстура присоединяется к ранее загруженной сетке с помощью функции SetTexture() интерфейса Direct3DRMMeshBuilder. Затем указатель texture освобождается, поскольку он больше нам не потребуется. После того, как указатель освобожден, ему присваивается нулевое значение.
СОВЕТ |
Остерегайтесь висящих указателей. Я рекомендую вам избегать висящих указателей, присваивая им нулевые значения после освобождения. Эта привычка спасет вас от проблем, которые могут возникнуть, если вы нечаянно используете освобожденный указатель. |
Функция SetTexture() не указывает, как должна быть наложена текстура, она только сообщает, что текстура накладывается на сетку. Чтобы определить способ покрытия текстурой необходимо использовать наложение текстуры. На этапе 4 мы создадим и применим плоское наложение текстуры чтобы текстура не изгибалась и не деформировалась, а прямо накладывалась на сетку. Нам потребуется масштабировать текстуру, чтобы она была такого же размера, как и сетка. Чтобы сделать это, нам необходимо узнать размер сетки. Приведенный ниже код расположен в функции CreateScene() перед созданием наложения текстуры:
D3DRMBOX box; meshbuilder->GetBox(&box); D3DVALUE w = box.max.x - box.min.x; D3DVALUE h = box.max.y - box.min.y;
Функция GetBox() применяется для заполнения структуры D3DRMBOX, содержащей координаты крайних точек сетки. Мы используем эти значения для вычисления длины и высоты сетки. Обратите внимание, что мы вычисляем длину и высоту сетки, но не глубину. Поскольку текстура является двумерной, третье измерение сетки нас не интересует.
Теперь мы можем создать и применить наложение текстуры:
LPDIRECT3DRMWRAP wrap; d3drm->CreateWrap(D3DRMWRAP_FLAT, scene, D3DVALUE(0.0), D3DVALUE(0.0), D3DVALUE(0.0), // начало координат наложения D3DVALUE(0.0), D3DVALUE(0.0), D3DVALUE(1.0), // ось z D3DVALUE(0.0), D3DVALUE(1.0), D3DVALUE(0.0), // ось y D3DVALUE(0.5), D3DVALUE(0.5), // начало координат текстуры D3DDivide(1,w), D3DDivide(1,h), // масштаб текстуры &wrap); wrap->Apply(meshbuilder); wrap->Release(); wrap = 0;
Для создания наложения мы используем функцию CreateWrap() интерфейса Direct3DRM. Функция CreateWrap() получает 16 аргументов. Первый аргумент определяет способ наложения. Мы используем константу D3DRMWRAP_FLAT чтобы указать на использование плоского наложения текстуры. Второй аргумент задает базовый фрейм. Мы укажем здесь корневой фрейм сцены, чтобы значения остальных аргументов воспринимались как абсолютные координаты. Если мы укажем другой фрейм, то оставшиеся аргументы будут интерпретироваться как координаты относительно указанного фрейма.
СОВЕТ |
Задание абсолютных значений. Существует два способа указать Direct3D, что заданные значения являются абсолютными, а не относительными. Первый способ — указать в качестве базового корневой фрейм сцены (как сделано в рассматриваемом коде). Второй — передать вместо указателя на базовый фрейм NULL. |
Следующие девять аргументов CreateWrap() определяют местоположение и ориентацию наложения. Используемые в функции CreateScene() значения представлены в таблице 5.1.
Таблица 5.1.Параметры наложения для приложения Jade
Параметр | Значение |
Начало координат наложения | <0.0, 0.0, 0.0> |
Ось Z наложения | <0.0, 0.0, 1.0> |
Ось Y наложения | <0.0, 1.0, 0.0> |
Первые три аргумента определяют начало координат наложения. Используя значения <0.0, 0.0, 0.0>, мы указываем, что наложение размещается на локальных осях сетки (локальное начало координат для сетки обычно совпадает с ее центром).
Следующие три аргумента определяют вектор, задающий направление наложения. В данном примере мы используем вектор, совпадающий с осью Z и направленный от зрителя. Третий набор из трех аргументов задает вектор, определяющий направление вверх для покрытия. Мы используем направленный вверх вектор, который совпадает с осью Y.
Следующие четыре аргумента CreateWrap() задают начало координат и масштаб для текстуры. Параметры, используемые в функции CreateScene() представлены в таблице 5.2.
Таблица 5.2.Параметры текстуры для приложения Jade
Параметр | Значение |
Начало координат текстуры | <0.5, 0.5> |
Масштаб текстуры | <D3DDivide(1,w), D3DDivide(1,h)> |
Начало координат текстуры определяет точку текстуры, которая будет совпадать с началом координат наложения. Мы используем значение 0.5 и для координаты X и для координаты Y текстуры, чтобы начало координат наложения совпадало с центром текстуры (значение 0.5 означает середину текстуры, 0.0 означает верхний или левый край текстуры, а 1.0 означает правый или нижний край текстуры).
Масштаб текстуры определяет размер текстуры в пропорции к наложению. Вспомните, что мы собирались масштабировать текстуру так, чтобы она точно соответствовала размерам сетки. Мы уже отцентрировали текстуру, задав значения начала ее координат равными 0.5. Теперь, используя значения ширины и высоты сетки, мы масштабируем текстуру, чтобы она соответствовала размерам сетки. Для удобства Direct3D предоставляет макрос D3DDivide. Этот макрос приводит свои аргументы к типу D3DVALUE и делит первый аргумент на второй. Благодаря этому мы получаем коэффициент масштабирования, необходимый чтобы размеры текстуры после масштабирования соответствовали размерам сетки.
Последний аргумент CreateWrap() — это адрес указателя, который после выполненой функцией инициализации будет указывать на интерфейс Direct3DRMWrap. После того, как наложение создано, оно может быть применено к сетке с помощью функции Apply() интерфейса Direct3DRMWrap:
wrap->Apply(meshbuilder);
Функция Apply() вычисляет метод, которым текстура будет накладываться, для каждой из граней сетки. После вызова функции Apply() указатель wrap освобождается.
На пятом этапе создается фрейм для сетки:
LPDIRECT3DRMFRAME meshframe; d3drm->CreateFrame(scene, &meshframe); meshframe->AddVisual(meshbuilder); meshframe->AddMoveCallback(MoveFrame, NULL); meshframe->Release(); meshframe = 0;
Функция CreateFrame() интерфейса Direct3DRM применяется для создания фрейма с именем meshframe. Конструктор сеток присоединяется к новому фрейму функцией AddVisual(). Затем с помощью функции AddMoveCallback() устанавливается функция обратного вызова MoveFrame(). После описаных действий указатель meshframe освобождается.
Шестой этап — это создание источника рассеянного света:
LPDIRECT3DRMLIGHT light; d3drm->CreateLightRGB(D3DRMLIGHT_AMBIENT, D3DVALUE(1),D3DVALUE(1), D3DVALUE(1), &light); scene->AddLight(light); light->Release(); light = 0;
Источник рассеянного света применяется в нашем примере по причине простоты использования. Более подробно об источниках света мы поговорим в главе 6.
Седьмой и последний этап выполнения функции CreateScene() — создание порта просмотра:
d3drm->CreateFrame(scene, &camera); camera->SetPosition(scene, D3DVALUE(0.0), D3DVALUE(0.0), D3DVALUE(-50.0)); d3drm->CreateViewport(device, camera, 0, 0, device->GetWidth(), device->GetHeight(), &viewport);
Переменные camera и viewport наследуются от класса RMWin и должны инициализироваться в функции CreateScene(). Переменная camera представляет собой указатель на интерфейс Direct3DRMFrame, инициализируемый функцией CreateFrame() интерфейса Direct3DRM. С помощью функции SetPosition() новый фрейм располагается на 50 единиц дальше начала координат. После этого функция CreateViewport() интерфейса Direct3DRM инициализирует указатель viewport.
Чтобы сообщить, что сцена успешно создана, функция CreateScene() возвращает TRUE. Если функция CreateScene() возвращает FALSE, выполнение приложения прекращается и выводится соответствующее сообщение.
MoveFrame() — это функция обратного вызова, управляющая анимацией сетки. Текст функции приведен ниже:
void JadeWin::MoveFrame(LPDIRECT3DRMFRAME frame, void*, D3DVALUE) { static int x; static int y; static int xi = 7; static int yi = 13; if (x < -31 || x > 31) xi = -xi; if (y < -35 || y > 35) yi = -yi; x += xi; y += yi; frame->SetOrientation(NULL, D3DVALUE(x), D3DVALUE(y), D3DVALUE(50), D3DVALUE(0), D3DVALUE(1), D3DVALUE(0)); }
Функция MoveFrame() использует простой алгоритм «подскакивающего мяча» для изменения ориентации фрейма к которому прикреплена сетка при каждом обновлении экрана. Статические целочисленные переменные используются для запоминания текущей позиции фрейма и для вычисления новой. После того, как новая позиция вычислена, она назначается фрейму с помощью функции SetOrientation() интерфейса Direct3DRMFrame.
Приложение Jade (как и большинство демонстрационных программ на CD-ROM) позволяет во время работы программы изменять метод визуализации сетки. Эту возможность обеспечивают функции OnRenderWireframe(), OnRenderFlat() и OnRenderGouraud(), вызываемые MFC при выборе пользователем команд из меню Render. Эти три функции выглядят следующим образом:
void JadeWin::OnRenderWireframe() { if (meshbuilder) meshbuilder->SetQuality(D3DRMRENDER_WIREFRAME); } void JadeWin::OnRenderFlat() { if (meshbuilder) meshbuilder->SetQuality(D3DRMRENDER_FLAT); } void JadeWin::OnRenderGouraud() { if (meshbuilder) meshbuilder->SetQuality(D3DRMRENDER_GOURAUD); }
В каждой функции для задания метода визуализации применяется метод SetQuality() интерфейса Direct3DRMMeshBuilder. Еще три функции используются для отображения метки слева от используемого в данный момент метода визуализации в меню Render. Текст функций OnUpdateRenderFlat(), OnUpdateRenderGouraud() и OnUpdateRenderWireframe() приведен ниже:
void JadeWin::OnUpdateRenderWireframe(CCmdUI* pCmdUI) { if (meshbuilder) { D3DRMRENDERQUALITY meshquality = meshbuilder->GetQuality(); pCmdUI->SetCheck(meshquality == D3DRMRENDER_WIREFRAME); } } void JadeWin::OnUpdateRenderFlat(CCmdUI* pCmdUI) { if (meshbuilder) { D3DRMRENDERQUALITY meshquality = meshbuilder->GetQuality(); pCmdUI->SetCheck(meshquality == D3DRMRENDER_FLAT); } } void JadeWin::OnUpdateRenderGouraud(CCmdUI* pCmdUI) { if (meshbuilder) { D3DRMRENDERQUALITY meshquality = meshbuilder->GetQuality(); pCmdUI->SetCheck(meshquality == D3DRMRENDER_GOURAUD); } }
MFC вызывает каждую из этих трех функций при выводе на экран меню Render. Каждая из функций проверяет текущие параметры визуализации сетки и указывает MFC необходимо ли ставить флажок около данного пункта меню.
Почти все демонстрационные программы на CD-ROM предоставляют меню, подобные меню Render. Функции поддержки команд меню, подобные рассмотренным выше, могут быть добавлены в программу или удалены из нее с помощью Visual C++ ClassWizard.
netlib.narod.ru | < Назад | Оглавление | Далее > |