Вывод текста в Direct3D
Дата создания: 2009-12-31 12:08:10
Последний раз редактировалось: 2012-03-01 01:03:50
Последний раз мы выводили текст в консоли - в Морском бое. Кстати, если вы ещё не разобрались с морским боем, то настоятельно рекомендую это сделать, так как в следующем уроке будет упражнение, в котором необходимо портировать морской бой из консоли в Direct3D.
D3DX
D3DX - это дополнительный модуль к Direct3D (от Direct3D Extension; extension - расширенный). D3DX содержит вспомогательные интерфейсы, функции и структуры, которые облегчают работу с графикой в DirectX. Например, в D3DX содержится огромное количество математических функций, в том числе и матрица вращения вокруг произвольной оси. В состав D3DX входит также множество структур: D3DXMATRIX - матрица размером 4x4, D3DXVECTOR4 - четырёхмерный вектор. Если вы смотрели справку DirectX, то возможно заметили. что в разделе Direct3D Reference → Structures есть структуры и для вектора (D3DVECTOR) и для матрицы (D3DMATRIX - помните мы её использовали?). Но если сравнить D3DVECTOR И D3DXVECTOR4, то можно увидеть, что структура D3DXVECTOR4 включает в себя не только компоненты вектора, но и несколько перегруженных операторов.
Описание D3DX в документации расположено в разделе: DirectX Graphics → Direct3D 9 → Reference → D3DX Reference.
Если вы скачивали DirectX с сайта Microsoft, то справка находится в папке куда был установлен DirectX \Documentation\DirectX9\directx_sdk.chm. Или вы можете скачать справку к DirectX из раздела Листинги и программы на сайте.
Документацию по D3DX также можно найти и в MSDN, по адресу: http://msdn.microso ft.com/en-us/library/ee418036(VS.85).aspx.
Все интерфейсы D3DX начинаются с префикса ID3DX.
Интерфейс ID3DXFont и GDI.
В DirectX не существует встроенных средств для вывода текста на экран. Есть два варианта: создать свою систему вывода текста (возможны очень разные решения) или использовать GDI. Сегодня мы рассмотрим второй вариант.
GDI (от Graphical Device Interface - графический интерфейс устройства) - система вывода графики (в том числе и текста) в WinAPI. Всё что мы видим на экране монитора при запуске операционной системы, нарисовано с помощью GDI.
Как и многое другое в Windows, GDI не самая быстрая вещь на свете.
Самый простой способ вывести текст в DirectX - использование интерфейса ID3DXFont (font - шрифт). Этот интерфейс как раз и использует GDI для вывода текста.
Прежде всего необходимо подключить к проекту библиотеку d3dx9.lib и включить заголовочный файл d3dx9core.h. Кстати, важное отличие Direct3D от D3DX: в Direct3D экземпляры всего лишь одного интефрейса можно получить через функцию, а вот в D3DX большинство интерфейсов создаётся через функции.
Функции D3DXCreateFont и D3DXCreateFontIndirect
Для получения экземпляра интерфейса ID3DXFont можно воспользоваться двумя функциями: D3DXCreateFont и D3DXCreateFontIndirect. Сначала рассмотрим заголовок первой:
HRESULT D3DXCreateFont( LPDIRECT3DDEVICE9 pDevice, // указатель на устройство IDirect3DDevice9 INT Height, // высота шрифта UINT Width, // ширина шрифта; если передать 0, то установится автоматически UINT Weight, // толшина шрифта: от нуля, до тысячи UINT MipLevels, // уровень MIP BOOL Italic, // наклонный шрифт DWORD CharSet, // кодировка DWORD OutputPrecision, // точность вывода DWORD Quality, // качество (сглаженный шрифт, ClearType...) DWORD PitchAndFamily, // шаг и семейство шрифта LPCTSTR pFacename, // имя шрифта (Arial, Times New Roman...) LPD3DXFONT * ppFont // указатель на ID3DXFont );
Большинство аргументов этой функции интуитивно понятны, рассмотрим только некоторые:
MipLevels - уровень MIP:
MIP - сокращение от латинского multum in parvo, что переводится как многое в малом. MIP используется не только для шрифтов, но и при выводе текстур. При этом для текстуры (картинки) создаётся несколько картинок разного размера, но содержащих одно и то же. Делается это для увеличения быстродействия: чем дальше объект, на который натянута текстура, расположен от камеры, тем меньшего размера (менее качественная) выводится текстура. MIP и связанные вещи (сглаживание, фильтрация) мы ещё будем обсуждать более подробно.
CharSet - кодировка (char - символ, set - набор, множество):
При создании проекта в Visual C++ 2008 по умолчанию используется кодировка unicode и если вы не меняли настройки проекта, то аргумент CharSet не будет иметь значения - можно установить любой. Вот возможные значения этого аргумента: ANSI_CHARSET (английская кодировка ANSI), RUSSIAN_CHARSET (русская кодировка), DEFAULT_CHARSET (кодировка установленная в операционной системе по умолчанию) и другие.
OutputPrecision - точность вывода:
Данный аргумент определяет насколько точно будут соблюдены другие параметры функции при выводе: размер, тип, шаг шрифта...
PitchAndFamily - шаг и семейство шрифта:
Аргумент задаётся двумя константами. Шаг шрифта - ширина между буквами (забыл как правильно это называется). Возможные значения шага: FIXED_PITCH , VARIABLE_PITCH и другие. Возможные значения семейства: FF_ROMAN, FF_SCRIPT, FF_DONTCARE (не важно какой) и другие.
Теперь функция D3DXCreateFontIndirect:
HRESULT D3DXCreateFontIndirect( LPDIRECT3DDEVICE9 pDevice, CONST D3DXFONT_DESC * pDesc, LPD3DXFONT * ppFont );
Данная функция создаёт шрифт косвенно (indirect - косвенно, не напрямую). Это значит, что все свойства шрифта задаются не через саму функцию (как в D3DXCreateFont), а через дополнительную структуру D3DXFONT_DESC (DESC от description - описание):
typedef struct D3DXFONT_DESC { INT Height; UINT Width; UINT Weight; UINT MipLevels; BOOL Italic; BYTE CharSet; BYTE OutputPrecision; BYTE Quality; BYTE PitchAndFamily; TCHAR FaceName[LF_FACESIZE]; } D3DXFONT_DESC, *LPD3DXFONT_DESC;
Здесь те же самые параметры, что и в функции D3DXCreateFont, только они собраны в отдельную структуру.
Какую функцию использовать для создания шрифта (получения экземпляра ID3DXFONT), выбирайте сами, они делают одно и то же.
Более подробное описание параметров (и их возможных значений) шрифта можно найти в MSDN, на странице справки по структуре LOGFONT (от Logical font - логический шрифт; LOGFONT использовался раньше вместо D3DXFONT_DESC, но был заменён для увеличения скорости).
Рассмотри пример создания шрифта каждой из функций:
D3DXFONT_DESC fontDesc; ZeroMemory(&fontDesc,sizeof(D3DXFONT_DESC)); fontDesc.Height = 18; fontDesc.Width = 0; // ширина задаётся автоматически, в зависмости от высоты fontDesc.Weight = 400; // 400 - стандартная толщина шрифта fontDesc.MipLevels = 0; fontDesc.Italic = 0; fontDesc.CharSet = RUSSIAN_CHARSET; fontDesc.OutputPrecision = OUT_DEFAULT_PRECIS; fontDesc.Quality = ANTIALIASED_QUALITY; // сглаженный шрифт fontDesc.PitchAndFamily = DEFAULT_PITCH | FF_DONTCARE; wchar_t FaceName[] = L"Arial"; // можно подставить Times New Roman, Serif или любой др. memcpy(&fontDesc.FaceName,&FaceName,sizeof(FaceName)); ID3DXFont* font; D3DXCreateFontIndirect(videocard,&fontDesc,&font); D3DXCreateFont(videocard,18,0,400,0,0,RUSSIAN_CHARSET, OUT_DEFAULT_PRECIS,ANTIALIASED_QUALITY, DEFAULT_PITCH | FF_DONTCARE,L"Arial",&font);
Обратите внимание на последние два оператора: нужно вызвать только одну из этих функций на выбор. Т.е. один из вызовов необходимо закомментировать. В данном примере обе функции создадут одинаковый шрифт.
Метод ID3DXFont::DrawText
Для вывода текста нужно воспользоваться методом ID3DXFont:
INT DrawText( LPD3DXSPRITE pSprite, // спрайт LPCTSTR pString, // строка символов INT Count, // количество символов LPRECT pRect, // указатель на структуру RECT DWORD Format, // формат (выравнивание текста) D3DCOLOR Color // цвет );
pSrpite:
Аргумент позволяет ускорить вывод текста. Пока что мы будем передавать NULL.
pRect:
Указатель на RECT. Этот параметр задаёт местоположение текста в окне программы.
Format:
Аргумент позволяет с помощью набора флагов задать форматирование текста. Мы будем выравнивать текст по левому/верхнему краю прямоугольника RECT: DT_TOP | DT_LEFT. Другие возможные значения данного параметра вы можете найти на странице справки по методу ID3DXFont::DrawText.
Ну а теперь вывод:
RECT rect; rect.top = 10; rect.left = 10; rect.bottom = 35; rect.right = 200; wchar_t str[11] = L"fps: 0000"; font->DrawText(NULL,str,10,&rect,DT_TOP|DT_LEFT,0xff000000);
Здесь хочу обратить ваше внимание на следующий момент: для вывода строки используется тип wchar_t. Этот тип занимает два байта, а не один как char. Это позволяет хранить в wchar_t символы Unicode. Перед константой "fps: 0000" стоит буква L. Это значит, что текстовая константа приводится к типу wchar_t.
И последнее. Метод ID3DXFont::DrawText нужно рисовать уже после построения сцены - после методов BeginScene и EndScene, но до метода Present. Т.е. в фоновом буфере уже содержится картинка, а мы просто сверху рисуем текст. По сути, здесь происходит то же самое, что и в программе Спрайты. Часть первая - копирование картинки в фоновый буфер. Только там мы получали доступ к фоновому буферу напрямую, а здесь - через интерфейс ID3DXFont.
Заключение
В сегодняшних упражнениях, помимо самих упражнений, содержится много полезной и важной информации, обязательно прочитайте.
- Упражнения
-
В первом упражнении вам нужно создать счётчик кадров. Прежде всего вам понадобится прочитать урок про таймеры (доступен на главной странице).
В программе необходимо выводить количество кадров в секунду (fps - frames per second).
Думаю, количество fps вы легко подсчитаете. Самым сложным будет вывод значения на экран. Для понимания того, как правильно сохранить значение типа int (или любого другого числового значения) в строке текста, нужно вспомнить, что такое строки в C++. А строки в C++ - это массив чисел, каждое из которых представляет символ (символы закодированы с помощью одной из кодировок: ANSI, Unicode...). Рассмотрим пример:int fps; // количество кадров в секунду wchar_t str[] = L"fps: 0000"; // строка // начало основного цикла // увеличение fps if (dt > 1000) { // копирование значения fps в str fps = 0; t1 = /* получение текущего времени*/; }
Каждый кадр происходит увеличение fps. Далее происходит проверка dt (как вычисляется dt рассказывается в уроке про таймеры). Если прошло больше секунды, то значение fps копируется в str, а fps обнуляется. То есть, значение на экране будет обновляться раз в секунду. Это конечно же не самый оптимальный вариант, но зато самый простой.
Теперь осталось понять как заполнить последние несколько символов строки. В своём примере (я, кстати, воспользовался программой Камера и рекомендую писать счётчик кадров именно в ней) я использовал цикл do while (мы его рассмотрели, но ни разу ещё не использовали). Полный код я вам не дам. Только небольшую подсказку. Отдельные цифры можно вытащить из числа следующим образом:
1. Берётся остаток деления числа на десять и сохраняется в отдельную переменную - это младший разряд.
2. Далее из числа отнимается младший разряд и результат делится на десять.
3. Первые два пункта повторяются, пока число не станет равным нулю.
Если будут проблемы/вопросы при выполнении упражнения, не стесняйтесь - спрашивайте. -
После того, как вы напишете счётчик кадров и запустите программу, вы увидите, что счётчик показывает небольшое число. У меня было число шестьдесят. Это число - частота мерцания монитора.
Очень важный момент: количество итераций основного цикла зависит от частоты мерцания экрана монитора. Т.е. за одну секунду код в основном цикле выполнится не большее количество раз, чем обновится картинка на экране монитора.
Давайте разберёмся, почему так происходит. Когда вызывается метод ID3DDevice9::Present, содержимое фонового и основного буфера меняются местами. Но чтобы картинка фонового буфера попала в основной, необходимо чтобы основной буфер был свободен. Чем же так занят основной буфер, что в него нельзя скопировать данные? Ответ очевиден: его содержимое выводится на экран. Сколько времени требуется, чтобы полностью вывести содержимое основного буфера на экран? 1/60 секунды при частоте монитора в 60 герц.
Здесь мы видим пример так называемого горлышка бутылки: программа не может выполняться быстрее чем её самая медленная часть. Но процессоры и компьютера, и видеокарты могут работать в сотни (тысячи) раз быстрее чем 60 герц.
Для того чтобы ускорить программу и позволить CPU (процессор компьютера, Computer Processor Unit - компьютерный вычислительный модуль) и GPU (процессор видеокарты, Graphical Processor Unit - графический вычислительный модуль) работать с максимальной скоростью, нужно ограничить количество вызовов ID3DDevice9::Present.
Теперь упражнение: в зависимости от частоты вашего монитора, ограничьте количество вызовов метода Present. Т.е. метод Present должен вызываться не каждую итерацию цикла, а раз в 1/60 секунды (для 60 герц). Код, который вам понадобится написать, практически полностью был приведён в начале урока о таймерах.
Вот что получилось у меня:
Число в левом верхнем углу - скорость выполнения программы. Т.е. за одну секунду на моём компьютере основной цикл выполняется 6878 раз. Все следующие программы будут содержать счётчик итераций цикла. Это позволит нам отслеживать скорость выполнения программы и видеть, с какой скоростью работает программа и в какие моменты происходит падение скорости.
Так как основной цикл стал выполняться более чем в сто раз быстрее, нужно ограничить в скорости некоторые части программы. Прежде всего ввод пользователя - сейчас перемещение в пространстве происходит очень быстро. Но это тема другого урока.
На сегодня всё. До скорой встречи.