Сергей Ваткин - DirectX 8. Начинаем работу с DirectX Graphics
nVirtKey = GetKeyState(VK_SHIFT);
if (nVirtKey & 0x8000) {/*Shifted*/}
но он мне кажется ничуть не лучше, чем использованный в Каркасе, с этого момента все ссылки на каркас графического приложения, который используется в этом цикле статей, будут происходить по этому слову). Ну а теперь, приступим к теме этой статьи. Начнем с интерфейса.
Интерфейс — это достаточно простой элемент приложения (во всяком случае, с точки зрения программиста). Наверное, все знают как это делается, но мы все равно скажем несколько слов по этому поводу. Элемент интерфейса рисуется как прямоугольник, который задаются в экранных координатах (его вершины уже трансформированы) и текстурируется той или иной текстурой. Практически никаких специальных знаний не требуется. При рисовании более сложных элементов (например, плоских игр), может потребоваться информация о положении элемента по глубине и, возможно, сортировка массивов, а также послойный вывод информации. Нужно помнить несколько моментов. Первое - интерфейс необходимо оптимизировать, второе — интерфейс это такая же часть сцены, поэтому мы только в редких случаях можем позволить себе не выгружать элементы интерфейса, которые в данный момент не используются. Фактически все.
Рассмотрим оптимизацию подробнее. Во-первых, если для каждой кнопочки вы будете использовать отдельный VB, то это будет не только медленно (необходимо менять VB для каждого элемента :( ), но и нерационально (все помнят о том, что буфер вершин - это обычная область в какой-либо памяти (видео, AGP, системная), подобно которой занимает текстура, поэтому получается 2000+ байт лишней используемой памяти для каждого буфера). Поэтому я предлагаю создавать "системный" буфер для интерфейса, в который инициализировать все экранные элементы, кроме него можно использовать буфер индексов (хотя это, наверное, необязательно). Так сделано практически во всех "интерфейсных" классах, которые добавились в этой версии приложения. Кроме того, мне кажутся разумными попытки кэшировать все текстуры интерфейса сцены в одну и рисование из нее (при этом возникают проблемы с изменением текстурных координат при смене состояния, например, кнопки), но этот вариант возможен при относительно компактном и уже определенном интерфейсе.
Мне кажется, проблем с разбором классов возникнуть не должно. Только не нужно забывать про сдвиг положения элемента интерфейса на 0.5 относительно его реального положения (чтобы центры текселей текстуры совпадали с пикселями экрана при текстурировании, подробнее можно посмотреть в DirectX SDK, топик "Directly Mapping Texels to Pixels"). Это устраняет замыливание интерфейсных элементов при фильтрации, и, кроме того, позволяет отображать интерфейс вообще без фильтрации, что сильно улучшает четкость изображения.
Список и краткое назначение классов:
Static — это класс для отображения статичной плоской графической информации. Практически все его предназначение - это рисование плоского экранного спрайта в заданных экранных координатах.
Button — это класс для отображения кнопки. Он перекрывает функциональность Static'а, кроме того он отслеживает положение курсора и состояние левой кнопки мыши, определяет момент щелчка по кнопке и запускает на выполнения функцию, которая поставлена в соответствие этому экземпляру класса. Кроме того отображается в различных состояниях:
- в состоянии NORMAL — это обычно состояние (курсор не находится над кнопкой),
- в состоянии FOCUS — курсор наведен,
- в состоянии PRESSED — курсор наведен и нажата левая кнопка,
- в состоянии DISABLED — кнопка отключена.
Animate — используется для отображения анимированной информации. В примере мы выводим огонь. Но в реальном приложении это может быть анимированная кнопка. Класс потребует некоторого расширения, но останется в принципе тем же.
И последнее,
Edit — это аналог CEdit из библиотеки MFC. Перехватывает "фокус", позволяет вводить информацию, удалять BackSpace'ом и Delete'ом. В общем, это тоже класс, который требует доработки, а также, может быть некоторой специализации. В исходнике на русском языке кратко изложено, что я думаю по этому поводу. Например, можно совместить классы Edit и CConfigFile и сделать, чтобы строчка из Edit могла быть обработана CConfigFile'ом (это очень легкая задача). Тогда можно будет легко написать консоль приложения.
Вообще, все классы, которые приведены — они как бы классы примеров, то есть не введен класс CheckBox, RadioButton, AnimateButton или ProgressBar, но не потому, что их реализация сложна, а как раз потому, что она очень похожа на реализации уже приведенных классов. Какие конкретно классы будут реализованы зависит, в основном, от необходимости и требований конкретного приложения.
Переходим к ландшафту.
В сети очень много информации по рисованию ландшафта, здесь мы отметим только принципиальные моменты, которые следует учитывать при проектировании ландшафта.
Прежде всего, нужно ориентироваться на блочную структуру, организации сцены. То есть нужно стараться, чтобы у вас были готовые небольшие локальные кусочки поверхности, которые вы можете рисовать отдельно. При этом, конечно, не стоит забывать, что по скорости рисовать два фрагмента по 400 треугольников ничуть не медленнее, чем рисовать два фрагмента по 200 треугольников. Поэтому размер фрагмента должен быть достаточно большим. При этом достаточно важно накладываемое требование на локальность кусочков. Наилучший вариант - это квадрат, но, к сожалению, квадратная организация фрагментов неоптимальна. Вы должны решить, что для вас в данный момент важнее, вывод поверхности максимально быстро или у вас есть какие-нибудь другие приоритеты (например, организовать квадратную фрагментацию гораздо проще). Дело в том, что вы будете использовать кэш видеокарты только при короткой длине линии (до восьми вершин). Вы должны захватить как можно больше вершин в кэше при обратном проходе (за счет этого можно практически вдвое увеличить скорость вывода ландшафта). Никакой практической сложности при выводе ландшафта нет. Только нужно не забывать, что линии нужно соединять без лишних треугольников, поэтому между переходами приходится вставлять вырожденные треугольники (этот механизм используется для увеличения длины линии заданной через TriangleStrip). Такие вырожденные треугольники практически бесплатны, ведь трансформированные вершины в момент использования обычно уже находятся в кэше видеокарты, и даже если их там нет, то они будут выбраны из кэша при обратном проходе, а поскольку они вырождены, то рисуются они только одной линией. То есть нам получается значительно выгоднее нарисовать лишние треугольники, но не разрывать цепочку (несколько вызовов DIP c маленьким количеством треугольников обрабатываемом при каждом вызове). Естественно, что каждому фрагменту нужно ставить в соответствие две переменные — расстояние до центра и флаг видимости (которые можно вычислять любым способом, главное — чтобы было правильно :) ). Обычно вычисляют процессором, но, например, можно для этого использовать функцию ProcessVertices (которая для этого и была создана, она, правда тоже считает процессором, но, вроде как, использует SSE или 3DNow!).
Мы реализовали поверхность с некоторыми, иногда очень важными ограничениями. Например, текстурные координаты вершин общие для всех смежных треугольников, что не всегда удобно (можно, конечно, заставить поработать художников и тогда это не проблема :) ). Зато, это позволяет использовать обычные редакторы двумерной графики для редактирования ландшафта (карты высот), что, в общем, не очень важно, но что значительно важнее — это позволяет уменьшить объем файла ресурса (для сети это очень важно).
Обычно задают минимум два набора текстурных координат, один для текстуры, второй — либо для карты освещенности (затененности), либо для Detail Map. Затененность и освещенность поверхности можно задавать в цвете конкретной вершины (в ней же можно задать цветовые пятна, которые могут повысить качество выводимой картинки). Этот способ экономит целый слой текстурирования при смешивании (это либо дополнительный проход, если мы превосходим количество слоев мультитекстрирования, либо нерациональное использование ресурсов видеокарты (введь в этот слой вместо карты освещенности можно было записать Detail Map)). Но этот способ плох в одном случае — если нас не устраивает качество затенения на поверхности (квадраты слишком большие, чтобы создавать плавные тени).
Тени могут быть заранее рассчитанными, либо считаться в реальном времени. Хороший вариант предложил Mircea Marghidanu в своей статье "Fast Computation of Terrain Shadow Maps" доступной по ссылке www.gamedev.net/reference/articles/article1817.asp. В своем классе я использовал именно подобный метод и, как он и просил, помещаю на него ссылку. Метод хорошо описан по-английски и я не вижу смысла повторять, то же что там уже сказано. Конечно, там приведена OGL реализация, но это не очень важно, как можно сделать под D3D вы можете посмотреть в исходниках. Ссылка на них есть в начале статьи. Конечно, для использования в реальном приложении метод нужно разделять (то есть обсчитывать в каждом кадре только небольшое количество точек, таким образом распределяя нагрузку на процессор). Кроме того, в реализации создается текстура для проективного текстурирования на объекты (которые, я надеюсь, появятся в следующей статье). Конечно, класс оставлять в том виде в котором он есть мы не можем. Поэтому к следующей статье нам необходим простенький редактор, позволяющий редактировать ландшафт в трехмерном виде. Он должен позволить задавать текстурные координаты треугольникам (а в случае Height-map, правильнее говорить "вершинам"), а также ставить в соответствие текстуры, которыми мы будем текстурировать конкретные треугольники. Свободное текстурирование ландшафта, так же как отсечение невидимых фрагментов ландшафта и вывод фрагментов в порядке Front-to-Back мы тоже рассмотрим в следующей статье.