DirectX для начинающих. Часть вторая

== Небольшое отступление ==
Прошёл месяц с тех пор как я написал первую часть статьи по использованию DirectX в среде Delphi. У меня накопилось ещё несколько примеров, которые, надеюсь, послужат наглядным руководством для начинающих.
Прежде, чем описывать предложенные общему вниманию программы, хочу сообщить о некоторых изменениях в их коде по сравнению с примерами первой статьи, чтобы не останавливаться впоследствии на этих мелких деталях.
Вызовы _AddRef() и _Release() больше не используются – в конце концов я посчитал это бессмысленной тратой времени при наборе кода. К тому же, как выяснилось, что вызов именно этих методов привёл к неработоспособности одного из примеров предыдущей статьи – если кто интересовался, знает, что это был пример опроса клавиатуры с использованием DirectInput. После удаления вызовов программа стал работать корректно. По-видимому, имело место некорректное взаимодействие с драйвером клавиатуры.
Выражение вида if COM-объект <> nil then COM-объект := nil переписано с использованием процедуры следующего вида:
procedure SAFE_DELETE(p: TInterfacedObject);
begin
 if p <> nil then
 p := nil;
end;
Теперь достаточно написать SAFE_DELETE( @COM-объект ) – может, это покажется и излишним, но поверьте, в более крупных программах, где надо удалить 15-20 COM-интерфейсов, это становится удобным и сокращает код. Все эти соображения навеяны под влиянием примеров из MS SDK. Кстати, может, кто-то не согласен с правильностью описанной процедуры?
Модуль basedd8.pas в проектах для DirectDraw переименован в basedd7.pas – всё-таки DirectDraw – это часть DirectX 7, в версий 8 он как таковой отсутствует.
В функции LoadFiles() добавлен вызов DeleteObject() – как известно, после работы объекты GDI надо удалять, иначе они поглощают ресурсы системы. В данном случае именно такой объект создаётся при вызове функции GDI LoadImage() – казалось бы, тип HBITMAP – это всего лишь переопределение типа LongWord, компилятор самостоятельно удалит переменную этого типа после выхода из функции. На самом деле GDI при вызове LoadImage() (и других подобных функций) создаёт ресурс GDI и резервирует для него часть системной памяти, а переменная hBmp – всего лишь идентификатор этого ресурса в общем списке ресурсов Windows. Поэтому в процессе выполнения программы будет удаляться только идентификатор, а ресурс, на который он указывает, будет «висеть» в памяти. Именно поэтому следует вызвать DeleteObject() для удаления объекта GDI. В предыдущем примере я не сделал этого по причине недосмотра.
Большая часть примеров в этой статье предназначена для работы с DirectDraw – как мне кажется, наиболее востребованному элементу DirectX (кроме, естественно, Direct3D).
Надеюсь, мой стиль написания кода программ покажется удовлетворительным – он почти во всём подобен стилю, который использовали составители DirectX SDK. Вообще, многие пишут, как курица лапой – и предлагают свои творения на всеобщее обозрение. Ещё полезно заглянуть на страницу в нашем уважаемом Королевстве - http://www.delphikingdom.com/article/tassel.htm - это классика.
== Почему я не рекомендую использовать DelphiX ==
Хочется поделиться с новичками своим мнением по поводу компонентов DelphiX и почему я не рекомендую их использовать.
С одной стороны, DelphiX - это удобно – нет необходимости выполнять утомительный набор методов DirectX и длинных, как многоступенчатая ракета, констант наподобие DDENUMSURFACES_CANBECREATED. Однако давайте посмотрим – используется что-нибудь подобное в С++? Я не могу исследовать всю Сеть в поисках совета на такой вопрос, но, думается – нет. Почему?
Такие наборы классов – это нестандартный подход. Допустим, вы потратили изрядно своего времени и досконально изучили DelphiX. Так вот, изучив всё ЭТО, вы в итоге не изучили сам DirectX. Второе – изученные классы обладают многим и позволяют писать реальные программы, но всё равно, этот подход очень негибок – вы ограничены тем, что уже сделано. На этот счёт у меня есть веский аргумент – это Direct3D. Вот тут DelphiX уж точно не даст развернуться как следует – и это характерно и для других компонент DirectX, пусть и в меньшей мере. Третье – немного, но снижается быстродействие. Четвёртое – в классах DelphiX кроме непосредственных вызовов методов интерфейсов DirectX используются ещё и собственные функции – кто даст гарантию, что в них нет ошибок?
В конце концов, именно за такие вот «примочки» Delphi не в почёте у С-программистов – они попросту надсмехаются над такими методами разработки программ. К сожалению, должен к ним присоединиться и я. Как же так, возмутятся многие. Компонентный подход – это ведь основа основ Delphi! Согласен, использование TMemo или TComboBox – это действительно удобный подход, да что там – превосходный, отличный подход! Но вот в случае с DirectX или чем-то подобным использовать такие средства разработки крайне нежелательно. Как бы вы отнеслись к компоненту TOpenGL? Или TWin32API? Вот так-то. DelphiX можно использовать как источник разных идей по реализации того или иного эффекта – перенося всё это в свою программу в виде отдельных функций или собственноручно написанных классов. Так что изучайте прямой API – для уверенности в завтрашнем дне и в собственной квалификации.
== Собственно, сами примеры ==
=== Bounds ===
В предыдущей статье я предложил вниманию только один пример для DirectDraw – простая реализация вывода спрайта поверх фона в задний буфер и перенос построенной картинки на экран. Спрайт не мог выйти за пределы экрана – и это не спроста. Если убрать досадное ограничение, то выяснится, что как только часть спрайта выходит за границы экрана (а фактически границы заднего буфера), то сразу исчезает полностью. Возвращение в прежнюю позицию восстанавливает вывод.
Когда я впервые столкнулся с этой проблемой, меня это неприятно поразило. Оказалось, что необходимо использовать интерфейс отсечения – IDirectDrawClipper. Однако он предназначен только для оконных приложений, в полноэкранном режиме от него нет никакого проку. Как уже было упомянуто, рекомендую программировать только полноэкранные приложения как наиболее быстродействующие, и забыть об оконных. Так как же быть – тупик?
К сожалению, даже в SDK нет примеров решения этого вопроса. Впрочем ответить на него не так уж сложно – нужно лишь понять, что хочет DirectDraw и как это преподнести.
Корень проблемы в том, что как только при копировании методом BltFast() часть поверхности выходит за край той поверхности, на которую она копируется (обычно задний буфер), вывод не осуществляется. В чём причина такого нелепого ограничения – думается, опять же в обеспечении наибольшего быстродействия. Например, вы планируете создать игру типа Tetris, а не скроллинговую стрелялку, и все ваши спрайты будут двигаться только в пределах экрана – но вот DirectDraw всё равно пришлось бы проверять их выход за границы, даже при отсутствии в этом необходимости. Хотя эту проблему можно было бы решить с помощью флагов при создании конкретной поверхности, но Microsoft этого не сделала. Ну что же, сделаем за неё эту работу.
Обратите внимание на четвёртый параметр метода IDirectDrawSurface7.BltFast() – это адрес структуры типа TRect. Для чего он нужен? Как известно, назначение струтуры TRect в GDI API – указание положения и размера какой либо области путём задания левого верхнего и правого нижнего угла. Так вот, эта структура позволяет указать DirectDraw о необходимости вывести не всё изображение спрайта, а лишь его часть:
Воспользуемся этой структурой и будем вместо всего спрайта выводить какую-то его область – ту, которая будет видна на экране, а невидимая будет находиться вне области, описываемой структурой TRect. Т. о. будет создаваться лишь иллюзия пребывания спрайта за пределами экрана. Например, спрайт выходит за границы экрана, как это показано на самом первом рисунке. Тогда выводимая область должна быть такой (показана красным цветом):
Теперь повоображайте и нарисуйте на бумаге, как будут выглядеть выводимые части спрайта при различных его положениях за границами экрана.
Вот код, ответственный за вывод части изображения:
// Предполагаем, что края спрайта не выходят за границы экрана
SetRect(rRect, 0, 0, SPRITE_WIDTH, SPRITE_HEIGHT);
// Проверяем выход краёв, и если такая ситуация имеет место, то корректируем
// положение области копирования на поверхности спрайта
if nX < 0 then rRect.Left := - nX ;
if nY < 0 then rRect.Top := - nY;
if nX + SPRITE_WIDTH > SCREEN_WIDTH then
 rRect.Right := SCREEN_WIDTH - nX;
if nY + SPRITE_HEIGHT > SCREEN_HEIGHT then
 rRect.Bottom := SCREEN_HEIGHT - nY;
Где nX и nY – координаты левого верхнего угла спрайта. При выводе надо не забыть скорректировать их:
 nX + rRect.Left, nY + rRect.Top
Вот и всё. Запустите проект на выполнение – вы увидите, что теперь свободно отображается даже часть спрайта. Выведите его полностью за пределы экрана – начнёт жужжать встроенный динамик компьютера – эта возможность введена для проверки правильности алгоритма.
Кстати, если необходимо вывести всю поверхность изображения, вместо адреса структуры следует передать nil – как это сделано для фона.
=== Scale ===
Иногда при выводе может понадобится растянуть или сжать объект по осям или просто увеличить или уменьшить его – для подобных эффектов DirectDraw предоставляет метод IDirectDrawSurface.Blt(). Он является хотя и более медленным, чем BltFast() – однако при этом более функционален. Так вот, мы снова будем указывать с помощью структуры TRect область вывода изображения – но уже на поверхности-приёмнике данных. Изменяя её размеры, можно добиться пропорционального или непропорционального изменения масштаба изображения по осям X и Y.
Думаю, нет надобности описывать действия которые происходят в процедуре OnDraw(). Замечу лишь, что на современных видеокартах с полной аппаратной поддержкой DirectDraw эффект масштабирования выглядит гораздо привлекательнее, чем на «ветеранах».
=== Transparent ===
При выводе изображения на экран нередко встаёт проблема затирания заднего фона, а говоря языком профессионалов – проблема вывода нерегулярных спрайтов. Нерегулярный спрайт – это обычный прямоугольный спрайт, который содержит маску для указания прозрачных участков изображения. Пиксели, принадлежащие этим участкам, при выводе игнорируются – создаётся иллюзия прозрачности.
В GDI нет прямого способа вывести нерегулярный спрайт – для этого необходимо подготовить изображение и отдельно маску. В чём недостатки такого подхода? Их много. Во-первых, требуется иметь два изображения – это увеличивает объём данных в памяти и на диске. Во-вторых, скорость вывода данного изображения падает вдвое, а ведь GDI и без этого не славится своей скоростью. В третьих, это дополнительная забота того, кто готовит изображение в графическом редакторе. В своё время автор даже написал небольшую утилиту, которая создавала маску для выбранного изображения и записывала её в отдельный файл. Но теперь об этом можно забыть.
DirectDraw предоставляет удобный инструмент для задания маски прозрачности. Цвета пикселей, которые игнорируются, называются «цветовыми ключами». Каждая поверхность может иметь свои цветовые ключи, причём их может быть несколько.
Следующий фрагмент кода создаёт и присоединяет к поверхности «цветовой ключ», цвет которого – чёрный.
var ddck: TDDCOLORKEY;
ddck.dwColorSpaceLowValue := 0;
ddck.dwColorSpaceHighValue := ddck.dwColorSpaceLowValue;
pSprite.SetColorKey(DDCKEY_SRCBLT, @ddck);
Для указания прозрачного цвета, как видно, используется структура TDDCOLORKEY. В её двух полях необходимо указать нижнюю и верхнюю границу диапазона «прозрачных» цветов. Замечу, что использование диапазона цветов возможно только в случае, если такая возможность поддерживается аппаратно. Поэтому лучше ограничиться каким-либо одним цветом, как это сделано выше. После заполнения структуры TDDCOLORKEY необходимо вызвать метод IDirectDrawSurface7.SetColorKey(), где первый параметр – один из возможных флагов, второй - адрес структуры TDDCOLORKEY. Обычно используется флаг DDCKEY_SRCBLT, который указывает, что при копировании изображения будет использоваться цветовой ключ поверхности-источника. Другие флаги можно узнать из справочной службы DirectX SDK.
Теперь о главном. В приведённом выше фрагменте кода в качестве маски задаются пиксели чёрного цвета. Как известно, нулевое значение обозначает отсутствие цвета во всех графических режимах – 16 цветов, 256, 65535 и т.д.
Поэтому можно смело присваивать 0 для чёрной маски в любом режиме. Однако, предположим, нам надо задать цветовой ключ в виде чистого синего цвета. Для 24- и 32-битного режима это можно сделать с помощью макроса (функции) из модуля windows.pas:
function RGB(r, g, b: Byte): COLORREF;
begin
 Result := (r or (g shl 8) or (b shl 16));
end;
Зарезервированное слово shl относится к сдвиговым операциям и сдвигает содержимое на указанное значение влево.
Т.к. в этих графических режимах каждый из трёх цветов (красный, синий и зелёный) кодируется одним байтом, то значение каждого параметра функции должно лежать в пределах от 0 до 255. Вот как это можно представить графически:
Так, для задания цветового ключа в виде чистого синего цвета необходимо написать так:
ddck.dwColorSpaceLowValue := RGB(0, 0, 255);
ddck.dwColorSpaceHighValue := ddck.dwColorSpaceLowValue;
Ну и в том же духе, в полном соответствии с теорией цвета.
А теперь попробуйте задать цветовой ключ для 16-битового режима. Ничего не получится. Почему? Дело в том, что цвет пикселя хранится в ячейке длиной в 16 бит, а цветовых составляющих – 3, появляется лишний бит, который чаще отдаётся зелёному цвету, а иногда просто не используется. Формат, где теряется лишний бит, обозначается 5-5-5 (на каждую цветовую составляющую по пять бит, а не одному байту), другой формат обозначается 5-6-5 (на зелёную составляющую выделяется 6 бит ). Понятно, что задание цвета с помощью функции RGB() для таких форматов ни к чему ни приведёт.
В своё время я довольно долго промучился с этой проблемой, тем более что в имеющейся литературе ничего об этом не сказано. В конце концов решил, что необходимо написать аналогичную к RGB() функцию, но об этом немного позже.
Давайте сначала выясним, какой же формат использует установленная на нашем компьютере видеокарта. DirectDraw позволяет узнать это с помощью функции IDirectDrawSurface7.GetPixelFormat(). Единственным параметром необходимо передать адрес структуры TDDPIXELFORMAT. Вот фрагмент соответствующего кода:
var ddpf: TDDPixelFormat;
ZeroMemory(@ddpf, SizeOf(TDDPixelFormat));
ddpf.dwSize := SizeOf(TDDPixelFormat);
pSprite.GetPixelFormat(ddpf);
Формат цветовых составляющих описывается в полях dwRBitMask, dwGBitMask и dwBBitMask структуры TDDPixelFormat – но только в том случае, если битовое поле dwFlags содержит флаг DDPF_RGB – признак того, что поверхность создана в RGB-режиме. Значения полей dwRBitMask, dwGBitMask и dwBBitMask для режимов с разной глубиной палитры описываются в разделе dwRBitMask, dwGBitMask и dwBBitMask справочной службы DirectX SDK:
 DDPF_RGB 16 R: 0x0000F800 
 G: 0x000007E0 
 B: 0x0000001F 
 A: 0x00000000
 
 DDPF_RGB 16 R: 0x0000001F 
 G: 0x000007E0 
 B: 0x0000F800 
 A: 0x00000000
 
 DDPF_RGB 16 R: 0x00007C00 
 G: 0x000003E0 
 B: 0x0000001F 
 A: 0x00000000
Запустите готовое приложение GetPixFormat из каталога DXCommon – и посмотрите, какой формат поверхности использует ваша карта в 16-битовом режиме. Скажу, что на компьютере с видеоакселератором GeForce2 MX420 получались значения из самой верхней ячейки – и это соответствует формату 5-6-5. По-моему, именно такой формат принят во всех современных видеокартах (заметьте, что во второй ячейке таблицы составляющие R и B переставлены местами). А вот, например, дедушка S3 Trio 3D/2X использует формат, описанный в нижней ячейке – опытным путём установлено, что это 5-5-5.
Вот как должен быть переписан макрос для формата 5-6-5:
function RGB565(r, g, b: Byte): COLORREF;
begin 
 Result := ((r shl 11) or (g shl 5) or b); 
end; 
Графически битовая маска может быть представлена так:
А вот как должен выглядеть макрос для формата 5-5-5:
function RGB555(r, g, b: Byte): COLORREF;
begin 
 Result := ((r shl 10) or (g shl 5) or b); 
end;
Графически битовая маска может быть представлена так:
Как видно, последний бит не используется. Обратите внимание, что группа битов, отвечающих за красную и синюю составляющую, в 16-битовом режиме поменялись старшинством. А формат, описанный во второй ячейке таблицы, наоборот, по старшинству схож с 24- и 32-битовым режимами. Подозреваю, что используется этот формат довольно редко.
Естественно, что теперь максимальное значение, передаваемое в макросы (функции) RGB565() и RGB555(), соответствует значению 31, а для задания чистого зелёного цвета в режиме 5-6-5 необходимо указать RGB565(0, 63, 0), т. к. битов 6.
Для того, чтобы наша DirectDraw-программа без проблем работала в обоих форматах, необходимо проверить текущий формат, запомнить его и при задании цветового ключа для поверхности вызвать соответствующий макрос. Всё это и делается в приложении Transparent – надеюсь при его разборе у вас не возникнет проблем. Не забудьте при копировании методом BltFast() указать флаг DDBLTFAST_SRCCOLORKEY.
=== FPS ===
Ещё один полезный пример – вывод текста на поверхность DirectDraw – в виде значения fps. Сама компонента не обладает такими средствами – DirectDraw изначально создавался лишь для максимально быстрого копирования одного изображения на другое. Для вывода текста необходимо использовать GDI.
Как неоднократно упоминалось, GDI очень медленнен, и вывод текста – одна из функций, которая серьёзно может «притормозить» DirectDraw-программу. Поэтому необходимо пользоваться этой функцией как можно реже. Для взаимодействия DirectDraw c GDI введён простой метод IDirectDrawSurface7.GetDC(). Получив контекст, можно спокойно чертить в нём всеми мыслимыми функциями GDI. Метод IDirectDrawSurface7.ReleaseDC() переносит содержимое контекста в область памяти, занятую поверхностью DirectDraw и удаляет контекст.
Откройте файл проекта fps.dpr. Т. к. функция TextOut() уже занята, функцию, отвечающую за вывод текста, пришлось назвать менее звучно – OutText(). Я не буду подробно описывать её, надеюсь, всё понятно. Для ускорения работы программы я поступил так: для вывода текста используется отдельная поверхность – именно на неё и выводится текст средствами GDI. Затем всё время поверхность просто копируется на задний буфер – это осуществляется гораздо быстрее, чем постоянный вывод текста на задний буфер, а когда появляется необходимость изменить текст – он снова выводится на нашу отдельную поверхность. Потребность изменить текст появляется лишь раз в секунду.
Для вызова OutText() я использовал мультимедиа-таймер Windows. Значение fps наращивается при каждом построении кадра и обнуляется после вызова OutText().
И последнее. По-видимому, в операционной системе Windows 2000 функции GDI должны работать быстрее, т. к. эта ОС полностью 32-х разрядная. Но всё же рекомендую пользоваться описанным выше подходом.
=== Text ===
Ещё один пример вывода текста – но уже на задний буфер. Добавлен мною для полноты темы. Текст заданных размеров постоянно выводится на задний буфер без поверхности-посредника. При выводе текста я столкнулся с одной проблемой – это сглаживание краёв символов. В модуле windows.pas описана константа ANTIALIASED_QUALITY, но её задание в параметре fdwQuality функции CreateFont() ни к чему ни привело. Может быть, в Windows 9x и МЕ это значение не используется? Во всяком случае, константы ANTIALIASED_QUALITY и NONANTIALIASED_QUALITY в справке Delphi Help не описаны.
=== Sound ===
Эта программа – прямое продолжение моего первого примера по использованию DirectSound. Введено ряд усовершенствований:
# Файл lowfunc.pas теперь полностью закончен и является практически прямым переводом файла wavread.cpp. Выражаю благодарность Max Morozov и iXania, которые помогли мне перевести некоторые сложные конструкции с языка C++ на Object Pascal, т. к. самому мне для этого не хватило квалификации. Теперь нет необходимости использовать отдельную динамическую библиотеку – весь код располагается в exe-файле. Всем спасибо.
# Я решил написать небольшой класс TWave – он сам заботится об открытии звукового файла, чтении данных из него в звуковой буфер и проигрывании их. Функциональность класса не полная – это лишь пример. Благодаря ООП главный модуль main.pas серьёзно уменьшился, теперь для воспроизведения wav-файла средствами DirectSound
достаточно написать:
var sound: TWave;
begin
 sound := TWave.Create();
 sound.OpenWaveFile( ‘wavefile.wav’ );
 sound.Play();
end;
Правда, просто?
=== GetDXVer и GetDXVerSetup ===
Я решил заглянуть в некоторые области DirectX, до которых руки многих авторов книг по DirectX попросту «не доходят».
Например, написание программы для определения текущей версии DirectX. Иногда это может быть очень полезно.
Первый пример, который я предлагаю вашему вниманию – это GetDXVer. Это аналог из DirectX SDK для Visual C++. Функция GetDXVersion() ответственна за получение номера текущей версии DirectX. Каким образом она действует? Механизм прост, но достаточно громоздок. Сначала загружается нужная динамическая библиотека из комплекса DirectX, например DDRAW.DLL или DINPUT.DLL. Затем получают адреса функций, которые экспортируют эти библиотеки – это «создающие» функции наподобие DirectDrawCreate() или DirectInputCreateA(). Затем при помощи этих функций и создаются нужные интерфейсы вроде IDirectDraw и т.п. Если на каком-то шаге происходит сбой, это означает, что данная функция или интерфейс не поддерживаются. Зная, в какой версии появился тот или иной интерфейс, можно выяснить текущую версию DirectX.
Ещё одна функция, GetDXRegVersion(), извлекает полный порядковый номер из реестра Windows. Кстати, эта функция может в принципе читать любой строковый параметр из реестра и делает ненужным использование класса TRegistry, что очень важно, если мы хотим получить маленький по размерам исходный модуль.
Пример имеет два недостатка:
# Работает с некоторой задержкой. Для создания всех интерфейсов требуется некоторое время. Особо медленно создаётся интерфейс IDirectMusic.
# Программа не способна определить номер версии, если он выше 8 – это принципиальный барьер.
Ещё один пример – GetDXVerSetup – использует специальную функцию DirectXSetupGetVersion() из библиотеки dsetup.dll. эта библиотека не входит в стандартный run-time DirectX, а поставляется только с setup-программами установки DirectX на компьютер пользователя. При написании этого примера я столкнулся с двумя проблемами:
В Help-службе DirectX SDK 7 указаны такие возможные значения, которые могут быть помещены в переменную pdwVersion при вызове функции DirectXSetupGetVersion():
DirectX version	Value pointed to by pdwVersion
DirectX 1 	0x00040001
DirectX 2 	0x00040002
DirectX 3 	0x00040003
DirectX 5.0 	0x00040005
DirectX 6.0 	0x00040006
DirectX 7.0 	0x00040007
А вот в Help-службе DirectX SDK 8 указаны такие:
DirectX version	Value pointed to by pdwVersion
DirectX 1 	0x00000000
DirectX 2 	0x00000000
DirectX 3 	0x00000000
DirectX 5.0 	0x00040005
DirectX 6.0 	0x00040006
DirectX 7.0 	0x00040007
DirectX 8.0 	0x00040008
Зачем понадобилось обозначить версии 1, 2 и 3 как отсутствие DirectX – непонятно. Может быть, Microsoft посчитала, что эти ранние версии уже слишком устарели и не обеспечивают пользователя нужными мультимедиа-средствами? А раз так, то может быть лучше вообще известить об отсутствии DirectX? Может статься, что это просто ошибка в файле справки. Второй вариант более правдоподобен, первый – забавен.
Используя файл directsetup.pas, мне не удалось экспортировать функцию DirectXSetupGetVersion() из библиотеки dsetup.dll. Дело в том, что она ищется в каталоге, по-видимому, указанном в ключе реестра HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Uninstall\DirectXDrivers, но я не обнаружил такой ключ в своём реестре. Так что воспользоваться этим файлом не представилось возможным. К тому же структуры TDirectXRegisterAppW2 не существует – приехали!
Я самостоятельно перевёл файл dsetup.h из SDK 8 в файл dsetup.pas, пытаясь максимально точно соблюдать синтаксис структур и параметро функций. Может быть, кто-то им воспользуется.
Недостаток приведенного метода в том, что вам придётся постоянно «таскать» библиотеку dsetup.dll вместе с исходной программой.
== Заключение ==
Написание подобных статей – хороший стимул к тщательному изучению DirectX. Скажу, что чем больше работаю с этим комплексом, тем больше разных нюансов всплывает на поверхность.
Надеюсь, что мои усилия хоть как-то помогут остальным желающим освоить этого игрового «монстра». Я надеюсь продолжить изучение DirectX и как только получится создать что-то стоящее, попробую поделиться сделанным с остальными.
Напоследок хочу выразить особую благодарность Антону Ржешевскому за его дельные советы в освоении DirectX и не только.
Скачать примеры: Samples2.Zip (106 K)
Author: Виктор Кода
Date: 14-05-2002 16:48
Source: http://www.delphikingdom.com
Category:Графика
Category:DirectX