Примеры кода находятся здесь
Эта статья является фрагментом книги, повещенной программированию трёхмерной
графики с использованием DirectX for NET 2.0 (окончательное название книги пока
не определено). В качестве среды программирования используется Visual Studio
2005 и язык C#.
Книга рассчитана на начинающего читателя, имеющего базовые
навыки работы с Visual Studio 2005. Предполагается, что читатель вполне в
состоянии самостоятельно создать приложение, использующее GDI+, вроде
простейшего графического редактора. Никаких начальных знаний в области
трёхмерной графики не требуется.
Основной акцент сделан на как можно более
понятное изложение материала. Другая особенность книги – активное использование
шейдеров (начиная со второй главы) вместо с которые
начинают задействоваться начиная олеее требуется, однакофиксированного
конвейера.
Все примеры книги написаны с использованием Visual
C#
2005 Express и DirectX 9 SDK December
2005, который содержит Beta-версию DirectX for
.NET 2.0. К моменту отправки книги в печать уже выйдут
Release-версии этих продуктов. Чтобы не переписывать по несколько раз
текст книги, я стараюсь не обращать внимания на различные проблемы бета версий,
которые будут исправлены в следующих версиях. Если же у вас возникнут
непредвиденные трудности, напишете мне на email (gsaf@sura.ru) и я обязательно постараюсь вам помочь.
В настоящее время DirectX for .NET 2.0
поставляется без какой-либо документации. Большая часть информации о
DirectX for .NET 2.0 была получена путём
дизассемблирования сборки Microsoft.DirectX.dll [2.0.0.0] с
использованием ILDASM и .NET Reflector. Поэтому я не могу
гарантировать отсутствие ошибок и неточностей в текущем варианте книги. Заранее
спасибо за любые отзывы, комментарии и пожелания.
Особенно интересует мнение по следующим вопросам:
1. Нужна ли в книге аналогия
между FSAA и обработкой аудиосигналов? Или её лучше убрать?
2. Следует ли оставить в книге
описание панелей управление ATI и NVIDIA?
3. Корректность описания
SSAA и MSAA.
4. Понятность описания процесса
создания формы в конструкторе форм Visual Studio.
5. Возникают ли проблемы при
запуске примеров книги на компьютерах различных конфигураций.
6. Корректность перевода
различных терминов и т.д.
7. Понятность изложения
материала. Имеет ли смысл переписать некоторые фрагменты книги более простым
языком?
Примечание
Над этим фрагментом книги ещё не работали редакторы и корректоры.
Поэтому в нём наверняка есть множество стилистических ошибок и “очепяток”.
Все примеры к книге тестировались на компьютере следующей конфигурации:
Pentium 4-2.8C, 1GB RAM, Windows XP Eng Service Pack 2.
Использовались видеокарты: ATI Radeon 9700 Pro (с
отключенным AGP), ATI Radeon 9800 XT и
NVIDIA GeForce FX 5900 Ultra с
последними WHQL-версиями драйверов. Часть примеров тестировалась на
компьютерах с видеокартами NVIDIA GeForce2 MX,
NVIDIA GeForce 6600 GT и ATI Radeon x700 Pro.
Примечание
Были обнаружены эксцессы при выполнении некоторых примеров
(Ex14, Ex15, Ex25, Ex26, Ex27, Ex28,
Ex29) на компьютерах с видеокартой GeForce2 MX. Проблема
заключается в исчезновении изображения при увеличении размеров окна. Причины
такого странного поведения программ мне пока не понятны.
Автор выражает благодарность:
q
Корпорации ATI, которая предоставила видеокарты ATI
Radeon 9700 Pro и ATI Radeon 9800
XT для тестирования примеров от книги.
q
Корпорации NVIDIA, предоставившей видеокарту NVIDIA
GeForce 5900 Ultra.
q
Корпорации Microsoft, предоставившей Microsoft Visual
Studio .NET 2003 Professional и Microsoft Visual Studio 2005 Beta2, а так же Microsoft Windows 2003 Server Standard.
q
Издательству BHV, которое предоставило англоязычную
литературу по 3D графике.
Так же автор благодарит Игоря Рыбинского (BHV), Юрия Уральского
(NVIDIA), Филиппа Герасимова (NVIDIA), Андрея Крючкова
(Microsoft), Александра Ложечкина (Microsoft) за консультации и
полезные советы. Отдельная благодарность выражается Геннадию Ригеру из
ATI Technologies и Алексею Кряжеву (Codemasters Software),
которые оказали неоценимую помощь при написании книги.
1.4. Полноэкранное сглаживание (FSAA)
Запустите на выполнение приложение Ex15 из раздела 1.3.1 (визуализация
узора Серпинского) и внимательно рассмотрите изображение. Думаю, вы быстро
заметите, что оно выглядит как-то странно – одни фрагменты узора почему-то ярче,
другие – бледнее. Чтобы понять, с чем это связано необходимо внимательно
рассмотреть увеличенный фрагмент узора (рисунок 1.46). Нетрудно заметить, что
видеокарта визуализирует изображение попиксельно. Так как пиксели имеют
квадратную форму, на краях линии образуются ярко выраженные ступеньки. Чем
меньше линия, тем заметнее эти ступеньки. Когда же треугольника приближается к
размеру пикселя, происходит катастрофа – размер ступенек становится соизмеримым
с размером треугольника, в результате чего они начинают существенно искажать его
форму (появляются несуществующие зубцы и т.д.). Это явление получило название
ступенчатости изображения (aliasing). Для борьбы с ним используются
различные технологии полноэкранного сглаживания (FSAA – Full Scene Antialiasing).
Рисунок 1.46. Увеличенный узор Серпинского
Чем же обусловлено появление этих ступенек? В процессе растеризации
графического примитива (треугольника, линии или точки) графический ускоритель
определяет цвет каждого пикселя на основе выборки из центра этого пикселя
(рисунок 1.47). Если примитив проходит через центр пикселя, то пиксель
закрашивается цветом выборки в центре пикселя, в противном случае цвет пикселя
остаётся неизменным. Иными словами, изображение формируется на основе выборок в
центрах пикселей, с шагом в один пиксель.
Рисунок 1.47. Растеризация отрезка шириной около одного пикселя.
Каждый квадратик соответствует одному пикселю экрана. Круглые точки в центре
квадратиков – выборки, на основе которых определяется цвет пикселя.
Растеризация изображения очень похожа на восстановление аудиосигнала на
основе дискретных выборок (называемых так же отсчётами). На рисунке 1.48
приведено изображение синусоиды, восстановленное на основе дискретных выборок.
Как видно, если выборки расположены достаточно часто, сигнал восстанавливается
точно. При уменьшении частоты отсчётов наступает момент, когда сигнал
начинается восстанавливаться не корректно – вместо синусоиды получается
совершенно другой, более низкочастотный, сигнал (рисунок 1.49).
Рисунок 1.48. Синусоида,
восстановленная по дискретным выборкам. Рисунок взят из [20]
Рисунок 1.49. Некорректно восстановленный сигнал при низкой частоте
выборки. Рисунок взят из [20]
Согласно теореме отсчётов сигнал может быть абсолютно точно восстановлен по
своим отсчётам, взятым с частотой, не меньше удвоенной частоты данного
сигнала.
Это условие необходимое, но не достаточное. Если частота отсчётов меньше
удвоенной частоты сигналов, то сигнал уже не удастся точно восстановить. Однако
если частота отчётов равна удвоенной частоте или больше её, это ещё не
гарантирует, что сигнал будет точно восстановлен. В качестве примера на рисунке
1.50 приведён пример выборки с удвоенной частотой сигнала, когда по чистой
случайности отсчёты выборок попали на нулевые значения функции. Полученные
выборки не содержат никакой информации об амплитуде сигнала, поэтому сигнал не
может быть восстановлен.
Рисунок 1.50. Частота отсчётов в два раза больше частоты сигнала.
Однако сигнал не удастся восстановить, так как отсчёты попали на нулевые
значения. Рисунок взят из [20]
Примечание
Именно по этой причине большинство широко распространённых аудио
форматов используют частоту выборки 44.1 кГц. Человеческое ухо воспринимает
звуки с частотой не более 20 кГц, поэтому чтобы сигнал в последствии можно было
корректно восстановить, выборки аудио сигнала необходимо брать с не
менее чем удвоенной частотой (40 кГц). Добавив “на всякий случай” ещё 4.1
кГц, получим искомые 44.1 кГц. Однако выборки с удвоенной частоты не всегда
достаточно для абсолютно точного восстановления сигнала (ещё раз обращаю ваше
внимание на выделенное курсивов слово “не менее”). В результате в последнее
время получили распространение аудио форматы с повышенной частотой выборки (до
192 кГц). При этом искушённые меломаны достаточно легко различают на слух
аудиозаписи, сделанные с частотой выборки 44, 96 и 192 кГц [29].
Однако давайте вернёмся к нашему узору Серпинского. Узор строится с
использованием треугольников, визуализируемых в каркасном режиме, т.е. с
использованием обычных линий. Толщина каждой линии равна одному пикселю.
Треугольники рекурсивно делятся до тех пор, пока размер треугольника (и длина
линий, из которых он состоит) не станет соизмерим с пикселем. Проведя грубую
аналогию с обработкой аудио сигнала можно предположить, что для корректной
растеризации изображения необходимо выбирать информацию о цвете пикселей, по
крайней мере, с два раза более высокой частотой, чем частота расположения
пикселей. В противном случае изображение не будет корректно восстановлено
(визуализировано), что и наблюдается в нашей программе, когда цвет пикселей
определяется на основе выборок в центрах пикселей (то есть частота выборки равна
частоте пикселей).
Теоретически этого эффекта можно достичь путём увеличения разрешения экрана
не менее чем в два раза. К сожалению, современные мониторы позволяют менять
разрешение экрана в очень узком диапазоне, поэтому такой лобовой метод не
пригоден для современных аппаратных средств.
Но мы можем пойти другим путём – увеличить число дискретных выборок на
пиксель при неизменном разрешении экрана. Эта технология получила название
суперсэмплинг
(supersampling), или сокращённо SSAA (SuperSampling AntiAliasing). Суперсэмплинг с N
выборками на каждый
пиксель обычно обозначается SSAA Nx. Так, SSAA 4x означает
суперсэмплинг с четырьмя выборками на пиксель.
На рисунке 1.51 приведён пример определения цвета
пикселей по четырём выборкам для каждого пикселя. Выборка производится с
удвоенной частотой по осям x и y. Полученные выборки
заносятся в видеопамять. В процессе визуализации изображения видеокарта хранит в
видеопамяти памяти все выборки. Соответственно, при использовании SSAA Nx
размер вспомогательного экранного буфера (Back Buffer) увеличивается в N раз –
так при использовании видеорежима 1280x1024 с SSAA 4x экранный буфер
будет иметь такой же размер, как и при разрешении 2560x2048 без SSAA. Пиксели этого
“большого” экранного буфера часто называют субпикселями, так как каждому пикселю
итогового изображения, показываемого на монитор, соответствует несколько
субпикселей.
Рисунок 1.51. Растеризация отрезка с использованием SSAA
4x (на фоне большого белого полигона)
Если растеризуемый примитив проходит через выборку, результаты выборки
заносятся в соответствующий субпиксель. Если выборка “не попадает” на объект,
результаты выборки игнорируются, и содержимое субпикселя не изменяется.
Технические подробности
Графический процессор, как правило, производит вычисление выборок
блоками 2x2.[36]. Каждый такой блок называется квадом.
Все выборки в пределах одного квада вычисляют с использованием одного и того же
выражения. Если блок 2x2 попадает на край полигона, ускоритель всё равно
вычисляет все выборки квада по одной и той же формуле, полагая, что все выборки
попали на полигон. В результате, если выборка в действительности не попала на
полигон, её цвет вычисляется некорректно. Чтобы не “запортить” цвет субпикселей
за пределами примитива, ускоритель предварительно вычисляет для каждого пикселя
так называемую битовую маску покрытия (Coverage
Mask),
которая показывает, какие выборки покрывает примитив. Если выборка попадает на
растеризуемый полигон, соответствующему биту в маске покрытия присваивается
true, в противном случае – false. В кадровый буфер заносятся
только те выборки, у которых соответствующие биты в маске покрытия равны
true. В частности, в случае, изображённом на рисунке 1.52, две нижние
выборки будут проигнорированы, поэтому два соответствующих субпикселя сохранят
изначальный белый цвет.
Рисунок 1.52. Увеличенный фрагмент рисунка 1.51 (второй пиксель
слева во второй строке сверху).
По окончанию визуализации изображения (то есть, после вызова метода Device.Present),
итоговый цвет пикселя определяется на основе среднего арифметического значения
всех выборок внутри пикселя. При расположении сэмплов аналогичном рисунку 1.51,
цвет каждого пикселя будет определяться по формуле , где color0, color1, color2 и color3 – цвета соответствующих
выборок. Эту формулу нетрудно обобщить на произвольное количество пикселей:
(1.22)
где
u – цвет текущего пикселя.
u – количество выборок на пиксель.
u sampleColori– цвет
-й выборки.
Реальное расположение выборок внутри пикселя – тайна за семью печатями. Эта
информация является интеллектуальной собственностью разработчиков видеокарт и не
подлежит разглашению. В общем случае взаимное расположение выборок может быть
абсолютно любым
(рисунок 1.53). Если выборки расположены асимметрично относительно центра
пикселя, то логично предположить, что наиболее сильное влияние на цвет пикселя
должны оказывать выборки в центре пикселя, а наиболее слабое – выборки на
периферии пикселя. Для реализации этого принципа можно назначить каждой выборке
свой весовой коэффициент. Чем ближе расположена выборка к центру пикселя, тем
больше коэффициент. В этом случае формула для вычисления цвета пикселя примет
следующий вид:
(1.23)
где
u – коэффициент для i–й выборки. Сумма всех коэффициентов
равна 1:
(1.24)
Как и расположение выборок, значения коэффициентов является интеллектуальной
собственностью производителей видеокарт и не подлежат разглашению.
Рисунок 1.53. Некоторые варианты расположения выборок внутри пикселя
(FSAA 4x). Рисунок взят из [22]
Использование SSAA позволяет значительно поднять качество
изображения. Однако у этой технологии есть и обратная сторона медали – очень
высокие требования к ресурсам компьютера. Например, использование SSAA с четырьмя
выборками на пиксель равносильно удваиванию разрешения экрана, что в некоторых
случаях может привести к значительному падению производительности (иногда в 3-4
раза).
В результате была разработана оптимизированная версия технологии SSAA – MSAA (MultiSampling
AntiAliasing). Главная идея технологии MSAA – замена нескольких выборок на пиксель
одной выборкой, общей для всех субпикселей. К примеру, если при использовании
MSAA с четырьмя выборками
на пиксель примитив покрывает все субпиксели пикселя (рисунок 1.54), то
результаты одной выборки (s1) заносятся во все четыре субпикселя
(p1, p2, p3 и p4). Этот случай
демонстрируется на рисунке 1.55. Если же примитив покрывает только два пикселя
(рисунок 1.52), то и результаты выборки заносятся только
в соответствующие два субпикселя (рисунок 1.56).
Рисунок 1.54. Отрезок чёрного цвета покрывает все четыре выборки.
Изображение является увеличенным фрагментом рисунка 1.51 (третий пиксель слева
во второй строке сверху).
Рисунок 1.55. Формирования цвета субпикселей при использовании
MSAA 4x. Примитив проходит через все выборки.
Рисунок 1.56. Формирование цвета субпикселей при использовании
MSAA 4x. Примитив проходит через две верхних выборки.
Примечание
Для обозначения MSAA с N субпикселями на
пиксель обычно используется сокращение MSAA Nx. К примеру,
MSAA 4x означает мультисэмплинг с четырьмя субпикселями на
пиксель. В литературе субпиксели MSAA часто называют выборками
(samples). Так, фраза “MSAA с четырьмя сэмплами
на пиксель” в действительности подразумевает “MSAA
c четырьмя субпикселями на пиксель”. Подобные фразы обычно не вызывают
путаницы, так как при MSAA всегда используется одна выборка на
пиксель. Соответственно, если в тексте говорится о нескольких выборках, это
означает что речь идёт либо о SSAA, либо под словам “выборки”
подразумеваются субпиксели.
Нетрудно заметить, что при растеризации одноцветных примитивов, например,
нашего узора Серпинского MSAA полностью аналогичен SSAA.
Если растеризуется треугольник с разноцветными вершинами,
то при использовании MSAA качество пикселей в центре примитива
будет полностью аналогично изображению, визуализированному с выключенным FSAA
(во все субпиксели пикселя заносится один и тот же цвет). Качество сглаживания
пикселей на краях примитивов будет практически аналогично SSAA. К
примеру, если на примитив не попадает ни одна из выборок, он будет полностью
прозрачным. Если при использовании MSAA 4x примитив проходит
через одну выборку, этот примитив будет вносить 25-ти процентный вклад в цвет
пикселя. Аналогично, для двух, трёх и четырёх выборок при MSAA 4x
мы получим соответственно 50-ти, 75-ти и 100 процентный вклад примитива в цвет
пикселя. В результате граница примитива будет такой же размытой,
как и при использовании SSAA 4x. Качество самого
примитива будет несколько ниже, чем у SSAA, так как во все
субпиксели заносится одна и та же выборка. Что, впрочем, не критично: главная
задача технологии MSAA – это сглаживание
краёв изображения, и с ней она справляется на 5 балов.
А как обстоят дела со скоростью MSAA? Как известно, при
растеризации изображения основное время занимает вычисление значений выборок.
Соответственно при переходе от SSAA Nx к MSAA
Nx, мы получим почти N-кратный выигрыш в
производительности. Полностью N-кратного прироста производительности в
принципе не возможно достичь, так как результаты выборки всё равно записываются
во все N субпикселей, что значительно повышает нагрузку на пропускную
способность оперативной памяти. Кроме того, по окончанию визуализации необходимо
понижать разрешение кадрового буфера путём усреднения субпикселей, что тоже
занимает некоторое время. Тем не менее, на видеокартах с быстрой видеопамятью
использование MSAA 2x или MSAA 4x
может быть практически “бесплатным” в плане производительности.
В заключение раздела, мы бегло пробежимся по проблеме расположения выборки
внутри пикселя. При использовании SSAA, мы определяем цвет
пикселя на основе множества выборок, поэтому SSAA не критичен к расположению выборок. С
MSAA всё несколько сложнее,
так как цвет субпикселей определяется на основе единственной выборки. Если всё
время делать выборку, к примеру, в центре пикселя, то при растеризации краёв
объекта вполне вероятен неприятный эксцесс – выборка может оказаться за
пределами объекта (рисунок 1.54, левое изображение), что
привёт к искажению цвета пикселя. Поэтому видеокарты смещают выборку таким
образом, чтобы она всё время оказывалась внутри полигона (рисунок 1.57, правое изображение).
Рисунок 1.57. Варианты расположения выборки для определения цвета
пикселя: в центре пикселя и внутри полигона. Рисунок взят из [22].
Теперь, когда вы уже имеете базовые представления об основе реализации SSAA и
MSAA, мы можем приступать к
практическому использованию MSAA. Существует два способа включения
полноэкранного сглаживания в приложении:
1. При
помощи панели управления драйверами видеокарты.
2. Из самого
приложения. Разумеется, для этого, разработчики (то есть мы) должны добавить
приложение соответствующую функциональность.
В следующих разделах мы подробно рассмотрим каждый из этих способов.
1.4.1. Включение полноэкранного сглаживания средствами драйвера
Драйвера современных видеокарт предоставляют пользователю развитые средства
по управлению различными аспектами работы графических приложений, в частности, и
полноэкранным сглаживанием. В этом разделе вы научитесь активировать в
приложениях различные режимы полноэкранного сглаживания средствами драйверов
видеокарт, использующих графические процессоры корпораций ATI и NVIDIA. Если вы уже
включать FSAA
средствами драйвера, можете пропустить этот раздел. Начнём с видеокарт,
основанных на графических процессорах производства ATI.
ATI Catalyst Control Center
Видеокарты семейства ATI
Radeon используют унифицированные драйвера под названием Catalyst. Эти драйвера
подходят для всех видеокарт, использующих графические процессоры R1xx
(Radeon
7xxx), R2xx (Radeon
8xxx), R3xx (Radeon
9xxx), R4xx (Radeon
Xxxx) и R5xx (Radeon
X1xxx), иными словами, для всех видеокарт на чипах ATI, выпущенных после
2000 года. Корпорация ATI регулярно обновляет
драйвера Catalyst один
раз в месяц, поэтому номера версий Catalyst имеют вид
Y.M, где Y – последняя цифра номера года, а M –
номер месяца. Например, название Catalyst 5.12 означает, что этот драйвер
был выпущен в декабре 2005 года.
Конфигурирование настроек драйвера осуществляется с использованием утилиты
Catalyst Control
Center. Для запуска Catalyst Control Center
щёлкните правой кнопкой мыши на рабочем столе Windows, и выберете в
контекстном меню пункт ATI Catalyst (R) Control
Center. По умолчанию интерфейс Catalyst Control Center
находится в режиме Standard
View, в котором от неопытного пользователя скрыты различные
потенциально опасные опции, в том числе и управление полноэкранным сглаживанием.
Для переключения расширенный режим щёлкните на кнопке View
в
верхней левой части диалогового окна и выберите в появившемся меню пункт Advanced View (рисунок 1.58).
Рисунок 1.58. Панель управления ATI Catalyst Control
Center
Рисунок 1.59. Catalyst Control Center – вкладка управления опциями полноэкранного сглаживания
Чтобы открыть вкладку управления параметрами полноэкранного сглаживания,
щелкните на узел 3D | Anti
Aliasing во вкладке Graphics Setting, расположенной в левой
части экрана. В правой части экрана откроется вкладка Anti-Aliasing. Чтобы разрешить драйверу
вмешиваться в работу приложения, снимите флажок Let
the application decide. Ползунок, расположенный внизу
диалогового окна используется для задания числа субпикселей на один пиксель при
мультисэмплинге (суперсэмплинг не поддерживается).
При этом внесённые изменения автоматически отражаются на панели предварительного
просмотра (3D
Preview), расположенной чуть выше элементов управления.
Примечание
Для переключения панели 3D Preview в
полноэкранный режим просто два раза щёлкните правой кнопкой мыши на этой панели.
Нажатие клавиши Esc вернёт панель 3D
Preview в нормальное состояние.
В частности, из рисунка 1.59 видно, видеокарта Radeon
9800XT поддерживает мультисэмплинг с использованием 2-х, 4-х или
6-ти субпикселей на пиксель. Однако собственно расположение субпикселей внутри
пикселя – тайна за семью печатями, так как эта информация является
интеллектуальной собственностью корпорации ATI и не подлежит
разглашению. В общем случае взаимное расположение может быть абсолютно любым
(рисунок 1.53). Более того, при установке флажка Temporal anti-aliasing,
расположение выборок внутри пикселей постоянно меняется от кадра к кадру, что
позволяет повысить качество сглаживания в анимированных сценах.
Чтобы применить изменения нажмите кнопку Ok или Apply. Однако не забывайте о том, что
изменения в настройках MSAA не затрагивают открытые приложения.
Поэтому, для того, чтобы изменения повлияли на уже работающее приложение, его
необходимо закрыть и запустить заново.
Попробуйте поэкспериментировать с настройками MSAA на примере вышеупомянутого приложения
Ex15. Думаю, вы очень скоро заметите следующую закономерность: качество
изображения будет заметно улучшаться до тех пор, пока количество субпикселей на
пиксель не достигнет 4-х. При дальнейшем увеличении количества субпикселей на
пиксель изменения в качестве изображения будут малозаметными.
Для выключения принудительного форсирования MSAA достаточно снять
флажок Let the application
decide.
NVIDIA ForceWare Control Panel
Включение FSAA средствами драйверов
NVIDIA во многом аналогично
драйверам ATI. Однако есть и
некоторые существенные отличия.
Видеокарты на чипах NVIDIA начиная с NV4
(RivaTNT) используют
унифицированные драйвера под кодовым названием ForceWare. В отличие от
корпорации ATI, NVIDIA
использует классическую систему нумерации версий драйверов: номер версии
формируется по схеме NN.XX (например, ForceWare 81.95), где NN –
номер версии, а XX – номер подверсии. Если
NVIDIA вносит в драйвер
какие-то существенные изменения, то номер версии драйвера увеличивается до
ближайшего числа кратного пяти и происходит так называемый выпуск нового релиза
(Release). Релизы нумеруются
целыми числами, кратными пяти. Таким образом, обозначение “ForceWare Release 75”
подразумевает любой драйвер ForceWare, номер которого
лежит в диапазоне 75.00 – 79.99.
Примечание
До Release 50 драйвера NVIDIA имели
обозначение Detonator.
Настройка параметров визуализации осуществляется с использованием панели
управления драйверами NVIDIA. Чтобы открыть эту панель, щёлкните правой
кнопкой мыши на рабочем столе и выберите в контекстном меню пункт Properties (Свойства). В
открывшемся диалоговом окне Display Properties (Свойства: Экран)
выберите вкладку Setting и щёлкните на кнопке Advanced (Дополнительно).
Появится ещё одно диалоговое окно, содержащее вкладку с названием вашей
видеокарты, например, GeForce FX 5900 Ultra.
Щелкните на эту вкладку, и вы попадёте в панель управления драйверами NVIDIA
(рисунок 1.60).
Рисунок 1.60. Панель управления драйверами NVIDIA
Переключение между различными вкладками панели осуществляется с
использованием меню, расположенном слева от диалогового окна. Чтобы открыть
вкладку, отвечающую за настройку параметров визуализации, выберите пункт меню
Performance & Quality
Setting (Производительность и качество). Для
настройки параметров найдите группу Global driver settings (Глобальные установки
драйвера) и выберите в списке элемент Antialiasing setting (Параметры сглаживания),
после чего внизу окна появятся элементы управления сглаживанием (рисунок 1.60).
Чтобы разрешить драйверу вмешиваться в работу приложения, снимите флажок
Application-Controlled (Управляемые
приложением). Установка параметров полноэкранного сглаживания
осуществляется с использованием ползунка в нижней части диалогового окна. Как
видно, из рисунка 1.60, видеокарта GeForce FX 5900 Ultra
поддерживает четыре режима сглаживания, перечисленные в порядке улучшения
качества: 2x, 2xQ,
4x и 8xS:
q 2x –
обычный мультисэмплинг с двумя субпикселями на каждый пиксель
q 2xQ –
особый режим мультисэмплинга под названием Quincunx. Результаты выборки заносятся в
два субпикселя,
расположенные в левом верхнем углу и центре пикселя (рисунок
1.60). При вычислении цвета пикселя используется пять субпикселей: четыре
субпикселя по краям пикселя и один субпиксель в центре пикселя. Таким образом,
мы получаем качество близкое к MSAA 4x при нагрузке на пропускную
способность видеопамяти как у MSAA 2x.
q 4x –
классический мультисэмплинг с четырьмя выборками на пиксель.
q 8xS –
“мичуринский гибрид” SSAA 2x и MSAA
4x
(рисунок 1.62). При растеризации границ используется две
выборки на пиксель (s1 и s2). Результаты каждой выборки заносятся
соответствующие в четыре субпикселя. Это демонстрируется на рисунке 1.59,
где результаты выборки s1 заносятся в субпиксели p1, p2, p3 и p4, а результаты
выборки s2 – в
субпиксели p5,
p6, p7 и p8. Таким образом, при
вычислении цвета каждого пикселя используется восьми субпикселей.
В результате этот режим с одной стороны похож на SSAA 2x (две выборки на
пиксель), а с другой на MSAA 4x (каждая выборка
заносится в четыре субпикселя). Режим 8xS обладает превосходным
качеством визуализации. Обратная сторона медали – катастрофическое падение
производительности. Падение производительности обусловлено двумя причинами.
Во-первых, для каждого пикселя экрана необходимо вычислять две выборки, что уже
само по себе снижает производительность почти в два раза. Во-вторых, огромной
нагрузкой на пропускную способность видеопамять из-за восьмикратного увеличения
размера экранного буфера.
Примечание
Использование SSAA ощутимо улучает качество визуализации
текстурированных объектов.
Однако, при визуализации однотонных объектов вроде узора Серпинского SSAA
является пустой тратой ресурсов.
Рисунок 1.61. Растеризация треугольника (на фоне большого белого
полигона) с использованием NVIDIA Quincunx MSSA.
Показаны только выборки для определения прозрачности пикселя.
Рисунок 1.62. Растеризация треугольника с использованием гибридного
режима 8xS (SSAA 2x + MSAA 4x)
Попробуйте поэкспериментировать с настройками MSAA на примере вышеупомянутого приложения
Ex15. Как и на видеокартах ATI, качество изображения
будет заметно улучшаться пока количество субпикселей на пиксель не достигнет
4-х. Дальнейшее увеличение числа выборок практически не окажет влияния на
качество изображения.
Для выключения принудительного форсирования MSAA достаточно снять
флажок Let the application
decide.
1.4.2. Включение MSAA из приложения
В предыдущем разделе (1.4.1) вы научились включать
полноэкранное сглаживание средствами драйвера. Это позволило нам значительно
повысить качество визуализации узора Серпинского (пример Ex15). Тем не
менее, у этого способа есть три существенных недостатка:
1. Далеко не
все пользователи умеют пользоваться панелью управления драйвера. Не исключена
вероятность того, что многие часть пользователей будут использовать ваше
приложение без FSAA, списывая ступенчатость краёв объектов на “глючность”
вашего приложения.
2. Многие
приложения требуют индивидуальных настроек FSAA. К примеру, на видеокарте Radeon
9800XT при использовании MSAA 6x наше приложение
Ex15 демонстрирует вполне нормальную производительность. Однако, запустив
на этом же режиме (MSAA 6x) знаменитый
Doom3, мы получим самое настоящее слайд-шоу – частота кадров упадёт ниже
10 FPS. Поэтому если пользователь захочет использовать наше приложение
для исследования узора Серпинского, периодически поигрывая в перерывах в
Doom3, то ему придётся постоянно корректировать настройки в панели
управления драйвером, от чего он вряд ли будет в восторге.
3. Драйвер
не всегда может корректно включить FSAA, особенно если в
приложении используются различные продвинутые технологии визуализации. Вы
наверняка не раз сталкивались с играми, не реагирующими на изменение настроек
FSAA в панели управления
драйвера.
Примечание
Драйвера NVIDIA ForceWare позволяют
сопоставлять каждому приложению индивидуальные настройки. Однако далеко не все
пользователи знают об этой возможности. Кроме того, при перестановке драйверов
информация об индивидуальных настройках теряется, что очень неудобно.
Вывод из всего вышесказанного простой – хотя панели управления большинства
драйверов и позволяют включать FSAA в приложении, не стоит
особо рассчитывать на эту функциональность. Более того, возможность управления
FSAA из панели управления
драйверов изначально позиционировалась как средство борьбы с приложениями, в
которые разработчики поленились встроить возможность управления FSAA.
То есть это скорее вынужденная мера, чем штатный режим работы приложения.
Соответственно любой уважающий себя разработчик должен предусмотреть в своем
приложении возможность включения FSAA средствами самого
приложения. Тем более что это не такая уж и сложная задача.
Microsoft
DirectX поддерживает только полноэкранное сглаживание с
использованием мультисэмплинга – корпорация Microsoft, как и ATI,
считает, что суперсэмплинг значительно уступает мультисэмплингу в качестве при
той же производительности, и поэтому его нет смысла поддерживать.
Примечание
Драйвера NVIDIA предоставляют
программисту лазейку, позволяющую приложению использовать SSAA. На видеокартах ATI тоже можно
программно реализовать SSAA с использованием шейдеров.
Использование SSAA оправдано только при визуализации
текстурированных объектов, поэтому реализация SSAA в наших текущих
приложениях приведёт только к потере производительности и усложнению кода
программы. Поэтому мы рассмотрим эту тему в следующих главах.
Включение MSAA осуществляется путём
присваивания необходимого значения полю MultiSampleType структуры Direct3D.PresentParameters:
public MultiSampleType MultiSampleType { get; set; }
Тип MultiSampleType объявлен следующим образом:
public enum MultiSampleType
{
// Мультисэмплинг отключен
None = 0,
// Немаскируемый режим MSAA. Параметры MSAA задаются с использованием свойства
// PresentParams.MultiSampleQuality
NonMaskable = 1,
// Далее идут маскируемые режимы MSAA. Название констант однозначно определяют режим MSAA.
// Обратите внимание - значения констант совпадают числом выборок (субпикселей) на пиксель.
// MSAA 2x
TwoSamples = 2,
// MSAA 3x
ThreeSamples = 3,
// MSAA 4x
FourSamples = 4,
FiveSamples = 5,
SixSamples = 6,
SevenSamples = 7,
EightSamples = 8,
NineSamples = 9,
TenSamples = 10,
ElevenSamples = 11,
TwelveSamples = 12,
ThirteenSamples = 13,
FourteenSamples = 14,
FifteenSamples = 15,
SixteenSamples = 16,
}
По умолчанию полю PresentParameters.MultiSampleType
присвоено значение MultiSampleType.None, то есть
мультисэмплинг отключен. В DirectX существует
две большие группы режимов MSAA:
1.
Маскируемые
(Maskable) режимы MSAA, задающиеся
количеством выборок (субпикселей) на пиксель.
2.
Немаскируемые (Non Maskable) режимы MSAA,
которые нумеруются целыми числами, начиная с 2.
Каждый способ имеет свои достоинства и недостатки. Достоинством первого
способа является прозрачность задания качества MSAA, которое хорошо
коррелирует с количеством выборок на пиксель. Для включения, к примеру,
MSAA 4x нам необходимо просто присвоить полю PresentParameters.MultiSampleType
значение MultiSampleType.FourSamples:
presentParams = new PresentParameters();
presentParams.IsWindowed = true;
presentParams.BackBufferCount = 1;
presentParams.BackBufferWidth = ClientSize.Width;
presentParams.BackBufferHeight = ClientSize.Height;
presentParams.SwapEffect = SwapEffect.Discard;
// Включаем MSAA 4x
presentParams.MultiSampleType = MultiSampleType.FourSamples;
device = new Device(0, DeviceType.Hardware, this.Handle,
CreateFlags.HardwareVertexProcessing, presentParams);
Примечание
Настройки параметров MSAA в панели управления драйвера имеют
приоритет перед установками программы. Например, если в драйвере включен режим
MSAA 2x, а приложение пытается создать устройство,
использующее MSAA 4x, то в итоге будет создано устройство,
использующее режим MSAA 2x.
Как видно всё очень просто, за исключением одного нюанса: если видеокарта не
поддерживает MSAA 4x, то при создании устройства командой
new Device приложение аварийно завершит работу с
исключением Direct3D.InvalidCallException. Для решения этой проблемы можно
поместить оператор создания устройства в блок try…catch,
предусмотрев обработчик исключения Direct3D.InvalidCallException, создающий
устройство с отключенным MSAA:
...
presentParams.MultiSampleType = MultiSampleType.FourSamples;
try
{
device = new Device(0, DeviceType.Hardware, this.Handle,
CreateFlags.HardwareVertexProcessing, presentParams);
}
catch (InvalidCallException)
{
// Если не удалось создать устройство с MSAA 4x, отключаем MSAA
presentParams.MultiSampleType = MultiSampleType.None;
device = new Device(0, DeviceType.Hardware, this.Handle,
CreateFlags.HardwareVertexProcessing, presentParams);
}
Правда, такое решение трудно назвать удовлетворительным, так как данный код
всегда будет отключать MSAA на любой видеокарте, не поддерживающей
MSAA 4x. Иными словами, программа будет работать по принципу либо
сразу всё, либо ничего. А ведь вполне может оказаться, что видеокарта, не
поддерживающая режим MSAA 4x, поддерживает MSAA 2x
или MSAA 6x, или вообще какой-нибудь экзотический режим
вроде MSAA 3x.
Так как наша программа не требовательна к ресурсам видеоподсистемы, мы можем
избрать следующую простую стратегию выбора режима MSAA – программа
планомерно перебирает все режимы MSAA от MSAA 16x до
MSAA 2x, пытаясь создать устройство. Если же все попытки создания
устройства потерпят крах, программа создаст устройство, не использующее
MSAA. Таким образом, программа всегда будет использовать самый
высококачественный режим MSAA. Этот подход демонстрируется в примере
Ex25 (листинг 1.26).
private void MainForm_Load(object sender, EventArgs e)
{
SetStyle(ControlStyles.Opaque | ControlStyles.ResizeRedraw, true);
MinimumSize = new System.Drawing.Size(Width - ClientSize.Width + 1,
Height - ClientSize.Height + 1);
presentParams = new PresentParameters();
presentParams.IsWindowed = true;
presentParams.BackBufferCount = 1;
presentParams.BackBufferWidth = ClientSize.Width;
presentParams.BackBufferHeight = ClientSize.Height;
presentParams.SwapEffect = SwapEffect.Discard;
// Перебираем все режимы MSAA от MSAA 16x до MSAA 2x
for (presentParams.MultiSampleType = MultiSampleType.SixteenSamples;
presentParams.MultiSampleType >= MultiSampleType.TwoSamples;
presentParams.MultiSampleType--)
{
try
{
// Пытаемся создать устройство с использованием текущего режима MSAA
device = new Device(0, DeviceType.Hardware, this.Handle,
CreateFlags.HardwareVertexProcessing, presentParams);
// Добавляем в заголовок окна информацию о выбранном типе MSAA
Text = String.Format("{0}: MSAA {1}x", Text,
(int) presentParams.MultiSampleType);
// Если удалось создать устройство, прерываем цикл
break;
}
catch (InvalidCallException)
{
}
}
// Если устройство всё же создать не удалось
if (device == null)
{
// Выключаем MSAA
presentParams.MultiSampleType = MultiSampleType.None;
// Создаём устройство
device = new Device(0, DeviceType.Hardware, this.Handle,
CreateFlags.HardwareVertexProcessing, presentParams);
// Добавляем в заголовок окна информацию об отключении MSAA
Text = String.Format("{0}: No MSAA", Text);
}
MainForm_Resize(this, null);
}
Хотя пример Ex25 и работает корректно, множество попыток создания
контекста устройства с постоянным перехватом исключений InvalidCallException
выглядят не особенно красиво. Поэтому разработчики DirectX
заботливо предусмотрели класс Manager, содержащий множество методов,
предоставляющих программисту детальную информацию о возможностях видеосистемы
компьютера. Функциональные возможности класса Manager во многом аналогичны полю Capabilities класса
Device, за
исключением одной важной особенности – для использования класса Manager
нет необходимости создавать экземпляр класс устройства. Таким образом,
класс Manager идеально подходит для получения информации о
возможностях видеоподсистемы перед созданием устройства.
Для получения информации о поддерживаемых режимах MSAA в классе Managed имеется статический метод CheckDeviceMultiSampleType, объявленный
следующим образом:
ResultCode CheckDeviceMultiSampleType(int adapterOrdinal, DeviceType
deviceType, Format surfaceFormat, bool isWindowed, MultiSampleType
sampleType);
где
u adapterOrdinal – номер
видеокарты (аналогичен одноимённому параметру конструктора класса Device)
u DeviceType – тип
устройства (аналогичен одноимённому параметру конструктора класса
Device)
u Format surfaceFormat –
формат кадрового буфера (см. ниже)
u isWindowed – является
ли приложение оконным (аналогичен одноимённому параметру конструктора класса
Device)
u sampleType – тип
MSAA, поддержку которого необходимо проверить
Если требуемый режим MSAA поддерживается, метод возвращает
true, в противном
случае false.
Большинство параметров метода CheckDeviceMultiSampleType аналогичны
одноимённым параметрам конструктора класса Device. Единственный новый
параметр, surfaceFormat, задаёт формат кадрового
буфера, используемого для хранения изображения. Формат кадрового буфера
определяет, сколько бит отводится для хранения каждого пикселя экрана, в
частности, какое количество из тратится на хранение информации о каждом цветовом
канале пикселя (красный, синий и зелёный) и как эти каналы расположены
относительно друг-друга. Формат кадрового буфера задаётся с использованием
перечислимого типа Format, названия большинства членов которого имеют
следующий вид:
{C1}{L1}{C2}{L2}{C3}{L3}…
где
u C1, C2,
C3 и т.д. – идентификаторы цветовых каналов, перечисленные в порядке
физического расположения внутри пикселя. В качестве идентификаторов канала
обычно используются первые буквы названия каналов: R – красный
(Red), G – зелёный (Green), B –голубой
(Blue), A – альфа канал
(Alpha), X – неиспользуемые биты.
u L1, L2,
L3 – количество бит, отводимых для хранения каждого цветового канала.
В этой главе мы будем
использовать широко распространенный формат Format.X8R8G8B8, известный большинству
пользователей как “32-х битный цвет”. Из названия формата Format.X8R8G8B8 можно
сделать следующие выводы о внутреннем устройстве этого формата:
1. Для
хранения каждого пикселя отводится 32-бита (8+8+8+8=32)
2. Для
хранения каждого цветового канала отводится 8 бит (один байт). Следовательно,
этот формат позволяет использовать 28=256 градаций яркости красного,
зелёного и синего цвета. Суммарное количество цветовых отсеков равно
28·28·28=224=16777216.
3. Эти 32
бита распределяются следующим образом: биты с 0-го по 7-й не используются, с
биты 8-го по 15-й используются для хранения яркости красного цвета, с 16-го по
23-й бит – яркости зелёного цвета, и, наконец, с 24-го по 31-й бит – яркости
синего цвета. Использование дополнительных 8-ми бит (с 0-го по 7-й) позволяет
увеличить размер пикселя с 24-х до 32-х бит, что положительно сказывается на
производительности видеоподсистемы. Дело в том, что современные процессоры
обрабатывают информацию порциями по 8, 16, 32 или 64 бит. В результате запись в
оперативную память порции данных размером 24 бита требует две операции записи
(сначала запись 16-ти бит, а затем 8-ми), в то время как запись 32-х бит
происходит за один присест.
Примечание
В DirectX имеется и 24-х битный Format.R8G8B8, не
использующий дополнительные биты. Однако большинство видеокарт
его не поддерживают из-за более низкой производительности, по сравнению с
Format.X8R8G8B8.
Использование метода CheckDeviceMultiSampleType позволит нам
легко переписать код примера Ex25 без использования блока try…catch
(Ex26):
presentParams = new PresentParameters();
presentParams.IsWindowed = true;
presentParams.BackBufferCount = 1;
presentParams.BackBufferWidth = ClientSize.Width;
presentParams.BackBufferHeight = ClientSize.Height;
// Задаём формат экранного буфера
presentParams.BackBufferFormat = Format.X8R8G8B8;
presentParams.SwapEffect = SwapEffect.Discard;
presentParams.MultiSampleType = MultiSampleType.None;
// Перебираем все типы MSAA с MSAA 16x до MSAA 2x
for (MultiSampleType mt = MultiSampleType.SixteenSamples; mt >= MultiSampleType.TwoSamples; mt--)
{
// Если текущий типа MSAA поддерживает MSAA
if (Manager.CheckDeviceMultiSampleType(0, DeviceType.Hardware, Format.X8R8G8B8, true,
mt))
{
// Устанавливает этот режим
presentParams.MultiSampleType = mt;
// Изменяем заголовок окна
Text = String.Format("{0}: MSAA {1}x", Text, (int) mt);
break;
}
}
// Если видеокарта не поддерживает не один из режимов MSAA, изменяем заголовок
// соответствующим образом
if (presentParams.MultiSampleType == MultiSampleType.None)
Text = String.Format("{0}: No MSAA", Text);
// Создаём устройство
device = new Device(0, DeviceType.Hardware, this.Handle,
CreateFlags.HardwareVertexProcessing, presentParams);
Обратите внимание на то явное задание формата вспомогательного буфера (в
котором собственно и рисуется изображение):
presentParams.BackBufferFormat =
Format.X8R8G8B8;
По умолчанию свойству PresentParameters.BackBufferFormat присвоено значение Format.Unknown, то есть DirectX самостоятельно выбирает формат пикселей, наиболее оптимальный с его точки
зрения. С другой стороны информация о поддерживаемых режимах MSAA,
полученная нами с использованием метода верна только для формата
Format.X8R8G8B8. Вполне вероятно, что для других форматов вроде Format.X8B8G8R8, Format.R8G8B8 или Format.A8R8G8B8 список
поддерживаемых режимов MSAA будет несколько иным. Поэтому мы
обязаны явно задавать формат кадрового буфера – оставив значение Format.Unknown мы
рискуем однажды получить исключение Direct3D.InvalidCallException.
Перейдём ко второму способу задания режимов MSAA – так называемым
немаскируемым режимам. Если присвоить свойству PresentParameters.MultiSampleType значение
MultiSampleType.NonMaskable, то режим
MSAA будет определяться значением поля PresentParameters.MultiSampleQuality:
public int MultiSampleQuality { get; set; }
Данное поле может принимать целые значения от нуля и выше, причём, чем
большее значение присвоено этому полю, тем лучше качество MSAA. Нуль
соответствует наихудшему качеству FSAA. В общем случае, “уровни качества”
MultiSampleQuality являются абстрактным
понятием, привязанным к конкретной видеокарте. Единственное, что можно наверняка
сказать об уровне качества с номером N, это то, что этот уровень качества
лучше N-1, однако при этом уступает по качеству уровню качеству
N+1. Как сильно различаются между собой уровни качества N-1,
N и N+1 и какое количество сколько сэмплов на пиксель они
используют – тайна за семью печатями. Например, на видеокарте ATI
Radeon 9800XT второй уровень качества
соответствует
MSAA 6x, а на видеокарте NVIDIA GeForce FX 5900 Ultra – MSAA 4x.
Однако нумерация немаскируемых режимов MSAA
по уровням качества
имеет и существенный плюс перед прямым заданием количества выборок на пиксель –
в отличие от маскируемых режимов MSAA, она позволяет использовать
различные нестандартные режимы MSAA вроде NVIDIA
Quincunx (см. раздел NVIDIA ForceWare Control
Panel). Более того, на многих старых видеокартах, не поддерживающих
канонический MSAA (например, NVIDIA GeForce2),
использование немаскируемых режимов MSAA является единственным способом
включения FSAA. Кроме того, режим MultiSampleType.NonMaskable позволяет
приложению легко изменять качество MSAA, не задумываясь о количестве
выборок на пиксель: для увеличения качества MSAA
достаточно
увеличить значение поля PresentParameters.MultiSampleQuality на
единицу, а для понижения качества – уменьшить на единицу.
Определение максимального поддерживаемого уровня качества MSAA
осуществляется с использованием перегруженного метода Manager.CheckDeviceMultiSampleType, с
дополнительным параметром:
public static bool CheckDeviceMultiSampleType(int adapterOrdinal,
DeviceType deviceType, Format surfaceFormat, bool isWindowed, MultiSampleType
sampleType, out int qualityLevels);
где
int
qualityLevels – возвращает количество поддерживаемых уровней
качества MSAA. Для маскируемых режимов значение этого параметра всегда
равно 1.
Примечание
При использовании маскируемых режимов MSAA
свойство
PresentParameters.MultiSampleQuality всегда должно быть равно нулю. Если
вы измените значение этого свойства на ненулевое значение, то при создании
устройства, использующее маскируемый режим MSAA, скорее всего получите
исключение Direct3D.InvalidCallException.
В листинге 1.28 приведён исходный код примера Ex27, использующего
максимальное качество немаскируемого MSAA, поддерживаемое
видеокартой:
presentParams = new PresentParameters();
presentParams.IsWindowed = true;
presentParams.BackBufferCount = 1;
presentParams.BackBufferWidth = ClientSize.Width;
presentParams.BackBufferHeight = ClientSize.Height;
presentParams.BackBufferFormat = Format.X8R8G8B8;
presentParams.SwapEffect = SwapEffect.Discard;
int qualityLevels;
// Проверяем поддержку NonMaskable MSAA, и попутно получаем информацию о количестве уровней
// качества MSAA
if (Manager.CheckDeviceMultiSampleType(0, DeviceType.Hardware, Format.X8R8G8B8, true,
MultiSampleType.NonMaskable, out qualityLevels))
{
// Если видеокарта поддерживает немаскируемый режим MSAA
// то задаём соответствующий тип MSAA
presentParams.MultiSampleType = MultiSampleType.NonMaskable;
// Устанавливаем максимальное качество MSAA
presentParams.MultiSampleQuality = qualityLevels - 1;
// Выводим в заголовке окна информацию об уровне качества MSAA
Text += ": MSAA Quality Level - " + presentParams.MultiSampleQuality;
}
else
{
// Выключаем MSAA
presentParams.MultiSampleType = MultiSampleType.None;
// Выводим соответствующую информацию в заголовке устройства
Text += ": no MSAA";
}
device = new Device(0, DeviceType.Hardware, this.Handle, CreateFlags.HardwareVertexProcessing, presentParams);
Как видно, исходный код пример Ex27, использующего немаскируемый режим
MSAA, получился более компактным, чем код примера Ex26
(маскируемый MSAA). Кроме того, у примера Ex27 есть одно важное
преимущество – в некоторых случаях он может демонстрировать более высокое
качество MSAA по сравнению с примером Ex26. Так, например, на
GeForce2 пример Ex26 запустится с отключенным MSAA, в то
время как пример Ex27 будет использовать немаскируемый режим MSAA
1-го уровня качества.
Практическое упражнение №9
“Научите” программу из практического упражнения №8
(построение фигуры Листажу) автоматически использовать наилучшее качество
MSAA, поддерживаемое текущей видеокартой.
1.4.3. Создание диалогового окна для выбора режима MSAA
В прошлом разделе мы создали несколько приложений, автоматически использующих
MSAA максимального качества. В идеальном мире такой подход вполне
оправдан, но, к сожалению, наш мир далёк от идеального, и агрессивное
использование высококачественных режимов чревато рядом потенциальных
неприятностей:
q Многие видеокарты класса
Low End существенно теряют производительность
при использовании высококачественных режимов MSAA.
q В редких случаях
видеокарта (и/или драйвер) могут “подглючивать” при использовании некоторых
режимов MSAA.
Так как вы вряд ли сможете даже приблизительно предсказать, на каких
компьютерах будут использовать ваше приложение через пять лет, было бы разумным
предоставить пользователю полную свободу выбора режима MSAA. Что мы и
сделаем.
Для начала нам надо определиться, каким образом пользователь будет задавать
режим MSAA. В разделе 1.4.2 говорилось, что DirectX позволяет задавать режим MSAA
двумя способами: с
использованием маскируемого и не маскируемого MSAA. Каждый способ имеет
свои достоинства и недостатки: маскируемые режимы MSAA
позволяют
естественно задавать качество MSAA, в то время как немаскируемые режимы
позволяют использовать нестандартные режимы MSAA. Не мудрствуя лукаво, мы
встроим в пример Ex15 (узор Серпинского) поддержку обоих режимов,
предоставив пользователю свободу выбора.
Для хранения информации о настройках приложения мы создадим свой собственный
класс Config. Для этого щёлкните правой кнопкой мыши на название проекта
в панели Solution Explore, и выберите контекстом меню пункт
Add | New Item…
(рисунок 1.63). В открывшемся диалоговом окне (рисунок 1.64) выберите значок Class и введите название нашего класса
(Config). Нажмите
Ok, после чего Visual Studio создаст
заготовку класса. Добавьте в класс два поля для хранения параметров MSAA
(листинг 1.29)
namespace ApplicationConfig
{
public class Config
{
// Тип MSAA
public MultiSampleType multiSampleType = MultiSampleType.None;
// Качество MSAA
public int multiSampleQuality = 0;
}
}
Рисунок 1.63. Добавление в проект нового класса
Рисунок 1.64. Диалоговое окно Add New Item…
После этого добавьте в класс MainForm (главная форма приложения) поле
config, в котором
будет храниться информация о приложении (листинг 1.30).
...
using ApplicationConfig;
public partial class MainForm : Form
{
// Объявляем поле для хранения информации о настройках приложения
Config config;
...
private void MainForm_Load(object sender, EventArgs e)
{
...
// Создаём экземпляр класса
config = new Config();
presentParams = new PresentParameters();
presentParams.IsWindowed = true;
presentParams.BackBufferCount = 1;
presentParams.BackBufferWidth = ClientSize.Width;
presentParams.BackBufferHeight = ClientSize.Height;
presentParams.SwapEffect = SwapEffect.Discard;
// Задаём параметры устройства на основе поля config
presentParams.MultiSampleType = config.multiSampleType;
presentParams.MultiSampleQuality = config.multiSampleQuality;
device = new Device(0, DeviceType.Hardware, this.Handle,
CreateFlags.HardwareVertexProcessing, presentParams);
...
}
}
Теперь мы можем приступать к созданию диалогового окна выбора режима MSAA.
Щелкните правой кнопкой мыши на названии проекта в панели Solution
Explore, и выберите контекстом меню пункт Add | New Item… . В
открывшемся диалоговом окне выберите значок Windows
Form и введите название нашего класса формы (SettingsForm). Нажмите кнопку Ok
и Visual Studio создаст заготовку новой
формы.
Чтобы вы могли получить представление о конечном результате, на рисунках 1.65 и 1.66 приведён внешний
вид формы, которую мы сейчас создадим. Я советую вам запустить пример
Ex28 и попробовать “поиграться” с различными параметрами MSAA.
Рисунок 1.65. Диалоговое окно «Параметры
визуализации». Настройки Maskable MSAA
Рисунок 1.66. Диалоговое окно «Параметры
визуализации». Настройки Non Maskable MSAA
Тип MSAA задается с использованием переключателей в верхней
части диалогового окна. Переключатель (RadioButton) «Количество сэмплов на пиксель»
соответствует маскируемому MSAA, а «Производительность/Качество» –
немаскируемому MSAA. В нижней части диалогового окна расположены элементы
управления для выбора настроек MSAA – выпадающий список (ComboBox)
поддерживаемых режимов MSAA при использовании Maskable
MSAA (рисунок 1.61) и ползунок (TrackBar) «Скорость/Качество» при
использовании Non Maskable MSAA (рисунок
1.62). Для применения настроек, необходимо нажать кнопку Применить, в противном
случае – Отмена.
Приступим к созданию формы. Для начала установите свойство формы Start Position в
значение CenterParent, а FormatBorderStyle – в значение FixedToolWindow.
Добавьте на форму два компонента GroupBox с заголовками «Параметры
MSAA» и «Задание MSAA с использованием». Поместите вовнутрь
второй группы два переключателя и назовите их maskedMsaaRadioButton (надпись «Количество сэмплов на
пиксель») и nonMaskedMsaaRadioButton (надпись «Производительность/Качество»).
Разместите чуть ниже группы с заголовком «Задание MSAA с использованием» компонент
Panel и назовите
его maskedMsaaPanel. Поместите на него
компонент ComboBox и переименуйте его msaaComboBox. Свойству
DropDownStyle
выпадающего списка msaaComboBox присвойте значение DropDownList.
Рисунок 1.67. Диалоговое окно «Параметры визуализации» в режиме
конструктора. Выделена панель maskedMsaaPanel.
Поместите на форму поверх (не на сам компонент!) компонента maskedMsaaPanel ещё
один компонент Panel, и назовите его nonMaskedMsaaPanel.
Разместите на этом компоненте компонент TrackBar и переименуйте его в msaaQualityLevelTrackBar. Присвойте
свойству LargeChange этого компонента значение 1, а
свойству Maximum
– значение 0.
Примечание
Для быстрого переключения между перекрывающимися maskedMsaaPanel и
nonMaskedMsaaPanel можно воспользоваться пунктами контекстного меню Bring
to Front и Bring to Back
Наконец, поместите в нижней части формы кнопки okButton (Применить) и cancelButton (Отмена).
Свойству DialogResult кнопки cancelButton присвойте
значение Cancel.
Теперь мы можем приступать к программированию логики формы. Для начала мы
реализуем следующую простую функциональность. При нажатии переключателя maskedMsaaRadioButton
должна отобразиться панель maskedMsaaPanel, а панель nonMaskedMsaaPanel –
скрыться. При нажатии nonMaskedMsaaRadioButton всё должно
происходить с точностью наоборот. Если же не нажата ни одна из радио кнопок –
обе панели должны исчезнуть.
Выберите переключатель maskedMsaaRadioButton и создайте обработчик
события CheckedChanged (листинг 1.31). После чего
выделите переключатель nonMaskedMsaaRadioButton и назначьте в
качестве обработчика события нажатия CheckedChanged аналогичный обработчик
переключателя maskedMsaaRadioButton (функцию maskedMsaaRadioButton_CheckedChanged).
private void maskedMsaaRadioButton_CheckedChanged(object sender, EventArgs e)
{
// Если нажата радио кнопка "Количество сэмплов на пиксель"
if (maskedMsaaRadioButton.Checked == true)
{
maskedMsaaPanel.Visible = true;
nonMaskedMsaaPanel.Visible = false;
}
Else
// Если нажата радио кнопка "Производительность/Качество"
if (nonMaskedMsaaRadioButton.Checked == true)
{
maskedMsaaPanel.Visible = false;
nonMaskedMsaaPanel.Visible = true;
}
else
{
// Если не нажата не одна из радио кнопок
maskedMsaaPanel.Visible = false;
nonMaskedMsaaPanel.Visible = false;
}
}
Следующий этап – написание конструктора формы (листинг
1.32):
// Ссылка на экземпляр класса Config, передаваемый конструкту формы. Модификация полей
// объекта автоматически затрагивает и оригинал
Config config;
// Конструктор. Принимает в качестве параметра экземпляр класса Config
public SettingForm(Config config)
{
InitializeComponent();
// Сохраняем ссылку на класс config в поле объекта
this.config = config;
// Формируем список поддерживаемых режимов Maskable MSAA
msaaComboBox.Items.Add("1 (MSAA Отключен)");
// Перебриваем режимы маскируемые режимы от MSAA 2x до MSAA 16x
for (MultiSampleType m=MultiSampleType.TwoSamples; m <= MultiSampleType.SixteenSamples; m++)
// Если текущий режим MSAA поддерживается
if (Manager.CheckDeviceMultiSampleType(0, DeviceType.Hardware, Format.X8R8G8B8,
true, m))
// Добавляем его в список. Обратите внимание на приведение переменной m к типу int. Если
// этого не сделать, то, к примеру, для значения MultiSampleType.TwoSamples метод ToString()
// возвратит строку "TwoSamples" вместо "2"
msaaComboBox.Items.Add(((int)m).ToString());
// Запрашиваем количество поддерживаемых уровней качества Non Maskable MSAA
int qualityLevels;
if (Manager.CheckDeviceMultiSampleType(0, DeviceType.Hardware, Format.X8R8G8B8, true,
MultiSampleType.NonMaskable, out qualityLevels))
{
// Задаём максимальное значение полосы прокрутки.
msaaQualityLevelTrackBar.Maximum = qualityLevels;
}
// Далее настраиваем интерфейс формы исходя из текущих значений полей ссылки на config
// Если используется режим MSAA
if (config.multiSampleType == MultiSampleType.NonMaskable)
{
// Включаем переключатель "Производительность/Качество". Переключатель "Количество сэмплов на
// пиксель" автоматически "отожмётся"
nonMaskedMsaaRadioButton.Checked = true;
// Если config.multiSampleQuality находится в допустимом диапазоне
if ((config.multiSampleQuality >= 0) &&
(config.multiSampleQuality < msaaQualityLevelTrackBar.Maximum))
{
// Задаём положение ползунка
msaaQualityLevelTrackBar.Value = config.multiSampleQuality + 1;
}
else
// Если значение config.multiSampleQuality является некорректным, сбрасываем оба
// переключателя
nonMaskedMsaaRadioButton.Checked = false;
}
}
else
{
// Включаем переключатель "Количество сэмплов на пиксель"
maskedMsaaRadioButton.Checked = true;
// Если MSAA отключен
if (config.multiSampleType == MultiSampleType.None)
// Выбираем первый элемент выпадающего списка ("1 (MSAA Отключен)")
msaaComboBox.SelectedIndex = 0;
else
// Выбираем элемент исходя из его значения
msaaComboBox.SelectedItem = ((int)config.multiSampleType).ToString();
}
}
Как видно, конструктор формы является довольно тривиальным. Первым делом,
конструктор сохраняет ссылку на экземпляр класса Config, которая используется для возврата
результата диалогового окна. Затем программа определяет перечень поддерживаем
режимов MSAA и заносит их в комбинированный список msaaComboBox и
устанавливает диапазон, в которых может перемещаться ползунок msaaQualityLevelTrackBar. Далее программа
выставляет положение начальные значения всех элементов управления диалогового
окна в соответствии с параметром config, проверяя при этом корректность
значений полей параметра config.
Заключительный этап создания диалогового окна «Параметры визуализации» – определение
обработчика события нажатия кнопки «Применить» (листинг 1.33).
private void okButton_Click(object sender, EventArgs e)
{
// Если включен переключатель "Количество сэмплов на пиксель"
if (maskedMsaaRadioButton.Checked == true)
{
// Если не выбран не один из пунктов выпадающего списка, выводим сообщение об ошибке
if (msaaComboBox.SelectedIndex == -1)
{
MessageBox.Show("Укажите режим MSAA", "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
try
{
// Преобразовать выбранное значение из выпадающего списка к MultiSampleType
config.multiSampleType = (MultiSampleType)Convert.ToInt32(msaaComboBox.SelectedItem);
}
catch (FormatException)
{
// Если не удалось, значит выбрано значение "1 (MSAA Отключен)"
config.multiSampleType = MultiSampleType.None;
}
// Присваиваем значению качеству MSAA нулевое значение
config.multiSampleQuality = 0;
}
else
// Если нажат переключатель "Производительность/Качество"
if (nonMaskedMsaaRadioButton.Checked == true)
{
// Если ползунок находится в крайнем левом положении
if (msaaQualityLevelTrackBar.Value == 0)
{
// Значит MSAA отключен
config.multiSampleType = MultiSampleType.None;
config.multiSampleQuality = 0;
}
else
{
// Выставляем настройки для Non Maskable MSAA
config.multiSampleType = MultiSampleType.NonMaskable;
config.multiSampleQuality = msaaQualityLevelTrackBar.Value-1;
}
}
else
{
// Если не включен ни один из переключателей, значит что-то не так
MessageBox.Show("Укажите режим MSAA", "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
// Возвращаем значение DialogResult.OK
this.DialogResult = DialogResult.OK;
}
После создания диалогового окна «Параметры визуализации» мы можем приступать
к интеграции его с приложением. Для начала поместите на форму компонент MenuStrip и создайте
меню «Файл» с
двумя вложенными пунктами «Параметры визуализации» и «Выход».
В листинге 1.34 приведён текст обработчика пункта меню «Параметры
визуализации». Как видно, при выборе пункта меню «Параметры визуализации»
программа выводит на экран пункт диалоговое окно «Параметры визуализации». Если пользователь
подтверждает выбранные настройки нажатием кнопки «Применить», приложение обновляет структуру
presentParams с учётом поля config и сбрасывает устройство с
использованием обновлённой структуры presentParams. Новые установки MSAA
вступают в силу сразу же после сброса устройства.
private void settingsToolStripMenuItem_Click(object sender, EventArgs e)
{
// Создаём новое диалоговое окно. По завершению работы обработчика диалоговое окно удаляется
using (SettingForm settingForm = new SettingForm(config))
{
// Выводим на экран диалоговое окно
if (settingForm.ShowDialog() == DialogResult.OK)
{
// Если в диалоговом окне была нажата кнопка "Применить"
// Обновляем структуру presentParams
presentParams.MultiSampleType = config.multiSampleType;
presentParams.MultiSampleQuality = config.multiSampleQuality;
// Сбрасываем устройство
device.Reset(presentParams);
// Перерисовываем содержимое формы
Invalidate();
}
}
}
В заключении создайте тривиальный обработчик выхода из программы для пункта
«Выход»:
private void exitToolStripMenuItem_Click(object sender, EventArgs e)
{
Close();
}
На этом приложение можно считать законченным (Ex28). Однако его
пользовательский интерфейс пока ещё далёк от совершенства – после каждого
запуска приложения параметры MSAA приходится задавать заново. Не
думаю, что пользователи будут этого в восторге перспективы открывать диалоговое
окно «Параметры
визуализации».
Поэтому обучим наше приложение сохранять конфигурацию, автоматически
восстанавливать настройки MSAA при следующем запуске. Приложения
для операционной системы Windows, в основном, используют один из четырёх
способов сохранения текущей конфигурации:
q Использование файлов
собственного формата. Плюсы: высокая гибкость, малый размер файла. Минусы:
большая трудоёмкость реализации, файл не возможно редактировать средствами
стандартных редакторов.
q Использование
.INI файлов. Плюсы: файл можно легко редактировать в текстовом
редакторе. Минусы: жесткие ограничения на структуру файла.
q Хранение настроек
приложения в реестре. Плюсы – относительная простота реализации и гибкость.
Минусы: невозможность переноса настроек на другой компьютер, при некорректной
деинсталляции приложения настройки могут остаться храниться в реестре мёртвым
грузом.
q Использование XML
файлов. Плюсы: простота реализации, можно редактировать в любом текстовом
редакторе, высокая гибкость формата. Минусы – большой объём из-за высокой
избыточности данных.
Давайте попробуем сформулировать требования к конфигурационному файлу нашего
приложения:
q Файл должен быть
текстовым – это позволит нам легко проверить содержимое файла в любом текстовом
редакторе, что значительно облегчит отладку приложения.
q Простота реализации – в
современном быстроменяющемся мире скорость разработки решает всё.
q Простота переноса
настроек – очень полезная возможность, к примеру, при переустановке операционной
системы.
Сопоставив эти требования с плюсами и минусами вышеприведённых файловых
форматов нетрудно заметить, что данным требованиям наиболее полно удовлетворяет
формат XML. Его мы и будем использовать.
Для начала мы добавим в класс Config метод Save, сохраняющий содержимое объекта этого
класса в XML файл (листинг 1.36).
public void Save(string filename)
{
// Создаём файловый поток для записи в файл с именем filename. Если файл уже существует, он
// перезаписывается. По завершению работы кода в блоке using, файл автоматически закрывается.
using (FileStream fs = new StreamWriter(filename))
{
// Создаём объект класса XmlSerializer для работы с объектами класса Config
XmlSerializer xmlSerializer = new XmlSerializer(typeof(Config));
// Выполняем сериализацию объекта в файл fs
xmlSerializer.Serialize(fs, this);
}
}
Как видно, в .NET Framework перевод содержимого
класса в XML файл осуществляется очень просто: программа
открывает файл для записи, выполняет сериализацию текущего экземпляра класса
Config в этот
файл и закрывает файл. Создаваемый XML-файл имеет примерно следующий вид
(MSAA 4x):
<?xml version="1.0" encoding="utf-8"?>
<Config
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<multiSampleType>FourSamples</multiSampleType>
<multiSampleQuality>0</multiSampleQuality>
</Config>
Думаю, вы без труда самостоятельно разберётесь в структуре этого файла.
Восстановление содержимого объекта класса Config из файла формата XML не
намного сложнее:
// Статический метод, возвращающий ссылку на экземпляр класса, созданного на основе XML-файла
public static Config CreateFromFile(string filename)
{
Config config;
// Открываем файл для чтения
using (FileStream fs = new FileStream(filename, FileMode.Open))
{
// Создаём объект класса XmlSerializer для работы с объектами класса Config
XmlSerializer xmlSerilizer = new XmlSerializer(typeof(Config));
// Создаем экземпляр класса Config на основе XML-файла
config = (Config)xmlSerilizer.Deserialize(fs);
}
// Возвращаем ссылку на созданный XML-файл
return config;
}
Информацию о работе с файлами в .NET Framework можно найти в [33], основы работы форматом XML хорошо описаны в
[31] и [32].
Располагая классом Config, умеющим сохранять и восстанавливать
себя из XML-файл, мы можем легко модифицировать обработчики событий Load и Close (листинг
1.38):
private void MainForm_Load(object sender, EventArgs e)
{
SetStyle(ControlStyles.Opaque | ControlStyles.ResizeRedraw, true);
MinimumSize = new System.Drawing.Size(Width - ClientSize.Width + 1,
Height - ClientSize.Height + 1);
try
{
// Пытаемся загрузить экземпляр класса config из файла
config = Config.CreateFromFile("config.xml");
}
// Если файл не был найден
catch (System.IO.FileNotFoundException)
{
// Создаём новый класс config с настройками по умолчанию
config = new Config();
// Выводим диалоговое окно "Настройки визуализации", чтобы пользователь смог подстроить
// настройки под себя
using (SettingForm settingForm = new SettingForm(config))
settingForm.ShowDialog();
}
// Если XML файл имеет некорректный формат (скорее всего повреждён)
catch (InvalidOperationException)
{
// Выводим соответствующее предупреждение
MessageBox.Show("Некорректный формат файла config.xml. Возможно файл повреждён.",
"Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Stop);
// Выводим диалоговое окно, чтобы пользователь смог подправить параметры
config = new Config();
using (SettingForm settingForm = new SettingForm(config))
settingForm.ShowDialog();
}
// Заполняем структуру presentParams необходимыми параметрами
presentParams = new PresentParameters();
presentParams.IsWindowed = true;
presentParams.BackBufferCount = 1;
presentParams.BackBufferWidth = ClientSize.Width;
presentParams.BackBufferHeight = ClientSize.Height;
presentParams.SwapEffect = SwapEffect.Discard;
// Выполняем цикл до тех пор, пока не получится создать устройство
do
{
try
{
// Заполняем структуру presentParams на основе объекта config
presentParams.MultiSampleType = config.multiSampleType;
presentParams.MultiSampleQuality = config.multiSampleQuality;
// Пытаемся создать новое устройство
device = new Device(0, DeviceType.Hardware, this.Handle,
CreateFlags.HardwareVertexProcessing, presentParams);
}
// Если устройство создать не удалось.
catch (InvalidCallException)
{
// Выводим диалоговое окно с предупреждением
MessageBox.Show("Не могу создать устройство с заданными настройками. Попробуйте изменить" +
" настройки", "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Error);
// Открываем диалоговое окно "Параметры визуализации", чтобы пользователь исправил настройки
using (SettingForm settingForm = new SettingForm(config))
// Если пользователь нажал кнопку отмена - выходим из программы
if (settingForm.ShowDialog() == DialogResult.Cancel)
{
Close();
return;
}
}
} while (device == null);
// Выполняем обработчик события Resize, в котором расположен
MainForm_Resize(this, null);
}
private void MainForm_FormClosed(object sender, FormClosedEventArgs e)
{
// Сохраняем настройки в файл приложения
config.Save("config.xml");
// Удаляем устройство
device.Dispose();
device = null;
}
Обратите внимание на код обработчика события Load, выполняющий обработку некоторых
исключительных ситуаций, которые могут произойти при загрузке настроек
приложения из файла:
q Конфигурационный файл
может отсутствовать. Например, стёрт пользователем.
q Конфигурационный файл
может иметь некорректный формат. Например, после неудачной попытки ручной правки
в текстовом редакторе.
q Конфигурационный файл
может содержать устаревшие данные. Допустим, пользователь ATI
Radeon 9800 XT, использующий режим MSAA 6x,
обновляет видеокарту на NVIDIA GeForce 6600 GT, не
поддерживающую этот режим MSAA. После смены видеокарты на GeForce
наше приложение считает настройки из файла config.xml и попытается создать устройство,
использующее MSAA 6x, что, разумеется, вызовет исключение Direct3D.InvalidCallException. Если его не
перехватить, программа аварийно завершит работу.
Обработка подобных исключительных ситуаций (так называемая
“дуракоустойчивость”) является признаком качественного приложения, которое
никогда не “вылетает в Windows” из-за пустяков. Полученное приложение
можно найти на CD диске с книгой в каталоге
Ch01\Ex29.
Примечание
По умолчанию при запуске приложения Visual
Studio 2005 делает текущим каталог с exe-файлом приложения
(например, ...\Ex29\ bin\Debug или ...\Ex29\ bin\Release).
В результате Release и Debug версии будут использовать разные
файлы config.xml. Чтобы заставить Debug и
Release версии использовать и тот же текущий каталог
(соответственно, и общий файл config.xml) необходимо в окне
Solution Explorer щёлкнуть два раза левой кнопкой мыши на
узле Properties, выбрать в открывшемся окне вкладку Debug
и
ввести в поле Working Directory путь к общему
текущему каталогу для Debug и Release версий
(рисунок 1.68).
Рисунок 1.68. Задание текущего каталога.
Практическое упражнение №10
В приложение из практического упражнения №8 (Ex24), визуализирующее
фигуру Листажу, добавьте возможность выбора режимов MSAA
с
использованием диалогового окна.
Подсказка
Можно значительно упростить себе работу, если воспользоваться
классом Config и диалоговым оконном «Параметры визуализации» из
примера Ex29. Для подключения класса Config к своему
проекту щёлкните правой кнопкой мыши на названии проекта в окне Solution
Explorer и выберите в контекстом меню Add | Existing Item. В открывшемся диалоговом окне выберите файл
config.cs и нажмите кнопку Add, после чего
Visual Studio автоматически скопирует этот файл в
каталог с вашем проектом и подключит. Для подключения к проекту диалогового окна
«Параметры визуализации» повторите эти действия для файла
SettingForm.cs.
Список использованной литературы
1. Фень
Юань. Программирование графики для Windows
2.
Константин Максимов. Совершенство графики в GDI+. Программист №2,
2002
3. Том
Миллер. Managed DirectX. Программирование графики и игр
4. Станислав
Горнаков. DirectX 9. Уроки программирования на C++
5. Н.
Секунов. Обработка звука на PC
6. Стив
Тейксейра, Ксавье Пачеко. Borland Delphi 6. Руководство
разработчика.
7. Ксавье
Пачеко. Delphi for .NET. Руководство
разработчика.
8. Джон
Роббинс. Отладка приложений для Microsoft .NET и
Microsoft Windows
9.
Брайан Джонсон, Крейг Скибо, Марк Янг. Основы Microsoft Visual
Studio .NET 2003
10. Михаил
Краснов. DirectX. Графика в проектах Delphi
11. Т.А,
Блинова, В.Н. Пореев. Компьютерная графика
12. Джеффри
Рихтер. Программирование на платформе .NET Framework
13. Владислав
Чистяков. Нововведения в C# 2.0. RSDN №6, 2003
14. И.И.
Кантор. Высокоскоростные железнодорожные магистрали
15. Мейсон Ву и
др. OpenGL. Официальное руководство программиста
16. Эдвард
Эйнджел. Интерактивная компьютерная графика. Вводный курс на базе
OpenGL.
17. М.Я.
Выгодский. Справочник по высшей математике для ВУЗов и ВТУЗов.
18. Френсис
Хилл. OpenGL. Программирование трёхмерной графики.
19.
Adobe Photoshop CS. Официальный
учебный курс
20.
Andrew Flavell. MIP-MAP Filtering в процессе
выполнения приложения. (www.ixbt.com)
21. Андрей
Воробьев. Александр Медведев. Обзор NVIDIA GeForce3. (www.ixbt.com)
22.
DirectX Documentation for C++ (DirectX SDK)
23.
DirectX Documentation for Managed Language (DirectX
SDK)
24. Александр
Медведев. NVIDIA GeForce FX или «Начало киносеанса». (www.ixbt.com)
25.
Андрей Воробьев. Александр Медведев. NVIDIA
GeForce FX 5800 Ultra 128MB. (www.ixbt.com)
26.
ForceWare Graphics Driver User Guide (www.nvidia.com)
27.
Н.И.Мусхелишвили. Курс Аналитической геометрии.
28. А.И. Плис,
Н.А. Сливина. MathCAD. Математический практикум.
29. Евгений
Козловский. Затоваренная звукотара. Компьютера №44, 2003
30. Эндрю
Троелсен. C# и платформа .NET
31. Андрей
Корявченко. Конфигурирование .NET-приложений (www.rsdn.ru)
32. Krassilchik Lucy. XmlSerializer - и как с ним
бороться (www.gotdotnet.ru)
33. Чарльз
Петцольд. Программирование для Microsoft Windows на С#.
34. Джон
Коннелл. Разработка элементов управления Microsoft .NET
на
Microsoft Visual Basic .NET
35.
Dave Barron. Multi-Sampling Anti-Aliasing
Explained (http://www.firingsquad.com)
36. Александр Медведев. DX Current. Настоящее аппаратного
ускорения графики (www.ixbt.com)
37. Дейв
Шрайнер. OpenGL. Официальный справочник.